diff --git a/CHANGELOG.md b/CHANGELOG.md index 891d90d..2149b72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Fixed config deserialization issue with `email-hooks` and + `email-reading-format`. + ## [0.7.1] - 2023-02-14 ### Added diff --git a/Cargo.lock b/Cargo.lock index c834e99..9a18f28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -803,7 +803,7 @@ dependencies = [ [[package]] name = "himalaya-lib" version = "0.6.0" -source = "git+https://git.sr.ht/~soywod/himalaya-lib?branch=develop#dff1c2354bd480b5997c947c38601b40796f683e" +source = "git+https://git.sr.ht/~soywod/himalaya-lib?branch=develop#b1a60353ea0577cfeb886433c7a9065c93ad534d" dependencies = [ "ammonia", "chrono", @@ -1291,6 +1291,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom8" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" +dependencies = [ + "memchr 2.5.0", +] + [[package]] name = "notmuch" version = "0.8.0" @@ -1853,18 +1862,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.148" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.148" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -1882,6 +1891,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + [[package]] name = "shellexpand" version = "2.1.2" @@ -2099,11 +2117,36 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6" dependencies = [ "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5" +dependencies = [ + "indexmap", + "nom8", + "serde", + "serde_spanned", + "toml_datetime", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6a8f0da..2add42a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ serde_json = "1.0" shellexpand = "2.1" termcolor = "1.1" terminal_size = "0.1" -toml = "0.5" +toml = "0.7.2" unicode-width = "0.1" url = "2.2" uuid = { version = "0.8", features = ["v4"] } diff --git a/config.sample.toml b/config.sample.toml new file mode 100644 index 0000000..a18f3a8 --- /dev/null +++ b/config.sample.toml @@ -0,0 +1,51 @@ +display-name = "Display NAME" +signature-delim = "~~" +signature = "~/.signature" +downloads-dir = "~/downloads" +folder-listing-page-size = 12 +email-listing-page-size = 12 +email-reading-headers = ["From", "To"] +email-reading-verify-cmd = "gpg --verify -q" +email-reading-decrypt-cmd = "gpg -dq" +email-writing-sign-cmd = "gpg -o - -saq" +email-writing-encrypt-cmd = "gpg -o - -eqar " + +[example] +default = false +display-name = "Display NAME (gmail)" +email = "display.name@gmail.local" + +backend = "imap" +imap-host = "imap.gmail.com" +imap-login = "display.name@gmail.local" +imap-passwd-cmd = "pass show gmail" +imap-port = 993 +imap-ssl = true +imap-starttls = false +imap-notify-cmd = """📫 "" """"" +imap-notify-query = "NOT SEEN" +imap-watch-cmds = ["echo \"received server changes!\""] + +sender = "smtp" +smtp-host = "smtp.gmail.com" +smtp-login = "display.name@gmail.local" +smtp-passwd-cmd = "pass show piana/gmail" +smtp-port = 465 +smtp-ssl = true +smtp-starttls = false + +sync = true +sync-dir = "/tmp/sync/gmail" + +[example.folder-aliases] +inbox = "INBOX" +drafts = "[Gmail]/Drafts" +sent = "[Gmail]/Sent Mail" +trash = "[Gmail]/Trash" + +[example.email-hooks] +pre-send = "echo $1" + +[example.email-reading-format] +type = "fixed" +width = 64 diff --git a/src/config/config.rs b/src/config/config.rs index dbb96d3..1d7744a 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -17,7 +17,7 @@ use crate::{ }; /// Represents the user config file. -#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct DeserializedConfig { #[serde(alias = "name")] @@ -31,12 +31,8 @@ pub struct DeserializedConfig { pub email_listing_page_size: Option, pub email_reading_headers: Option>, - #[serde( - default, - with = "EmailTextPlainFormatOptionDef", - skip_serializing_if = "Option::is_none" - )] - pub email_reading_format: Option, + #[serde(default, with = "EmailTextPlainFormatDef")] + pub email_reading_format: EmailTextPlainFormat, pub email_reading_verify_cmd: Option, pub email_reading_decrypt_cmd: Option, pub email_writing_headers: Option>, @@ -44,10 +40,10 @@ pub struct DeserializedConfig { pub email_writing_encrypt_cmd: Option, #[serde( default, - with = "EmailHooksOptionDef", - skip_serializing_if = "Option::is_none" + with = "EmailHooksDef", + skip_serializing_if = "EmailHooks::is_empty" )] - pub email_hooks: Option, + pub email_hooks: EmailHooks, #[serde(flatten)] pub accounts: HashMap, diff --git a/src/config/prelude.rs b/src/config/prelude.rs index 9f30f97..8d4f830 100644 --- a/src/config/prelude.rs +++ b/src/config/prelude.rs @@ -11,7 +11,7 @@ use himalaya_lib::ImapConfig; #[cfg(feature = "notmuch-backend")] use himalaya_lib::NotmuchConfig; -#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[serde(remote = "SmtpConfig")] struct SmtpConfigDef { #[serde(rename = "smtp-host")] @@ -31,7 +31,7 @@ struct SmtpConfigDef { } #[cfg(feature = "imap-backend")] -#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[serde(remote = "ImapConfig")] pub struct ImapConfigDef { #[serde(rename = "imap-host")] @@ -56,41 +56,39 @@ pub struct ImapConfigDef { pub watch_cmds: Option>, } -#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(remote = "MaildirConfig")] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(remote = "MaildirConfig", rename_all = "kebab-case")] pub struct MaildirConfigDef { #[serde(rename = "maildir-root-dir")] pub root_dir: PathBuf, } #[cfg(feature = "notmuch-backend")] -#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(remote = "NotmuchConfig")] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(remote = "NotmuchConfig", rename_all = "kebab-case")] pub struct NotmuchConfigDef { #[serde(rename = "notmuch-db-path")] pub db_path: PathBuf, } -#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(remote = "Option")] -pub enum EmailTextPlainFormatOptionDef { - #[serde(with = "EmailTextPlainFormatDef")] - Some(EmailTextPlainFormat), - #[default] - None, -} - -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(remote = "EmailTextPlainFormat", rename_all = "snake_case")] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde( + remote = "EmailTextPlainFormat", + tag = "type", + content = "width", + rename_all = "kebab-case" +)] pub enum EmailTextPlainFormatDef { + #[default] Auto, Flowed, Fixed(usize), } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(remote = "EmailSender", tag = "sender", rename_all = "snake_case")] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(remote = "EmailSender", tag = "sender", rename_all = "kebab-case")] pub enum EmailSenderDef { + #[default] None, #[serde(with = "SmtpConfigDef")] Smtp(SmtpConfig), @@ -98,26 +96,17 @@ pub enum EmailSenderDef { Sendmail(SendmailConfig), } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(remote = "SendmailConfig")] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(remote = "SendmailConfig", rename_all = "kebab-case")] pub struct SendmailConfigDef { #[serde(rename = "sendmail-cmd")] cmd: String, } -#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(remote = "Option")] -pub enum EmailHooksOptionDef { - #[serde(with = "EmailHooksDef")] - Some(EmailHooks), - #[default] - None, -} - /// Represents the email hooks. Useful for doing extra email /// processing before or after sending it. -#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(remote = "EmailHooks")] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +#[serde(remote = "EmailHooks", rename_all = "kebab-case")] pub struct EmailHooksDef { /// Represents the hook called just before sending an email. pub pre_send: Option, diff --git a/src/config/wizard/mod.rs b/src/config/wizard/mod.rs index 2282af8..cc58608 100644 --- a/src/config/wizard/mod.rs +++ b/src/config/wizard/mod.rs @@ -32,7 +32,6 @@ const SECURITY_PROTOCOLS: &[&str] = &["SSL/TLS", "STARTTLS", "None"]; static THEME: Lazy = Lazy::new(ColorfulTheme::default); pub(crate) fn wizard() -> Result { - trace!(">> wizard"); println!("Himalaya couldn't find an already existing configuration file."); match Confirm::new() @@ -111,7 +110,7 @@ pub(crate) fn wizard() -> Result { // Serialize config to file println!("\nWriting the configuration to {path:?}..."); fs::create_dir_all(path.parent().unwrap())?; - fs::write(path, toml::to_vec(&config)?)?; + fs::write(path, toml::to_string(&config)?)?; trace!("<< wizard"); Ok(config) diff --git a/src/domain/account/config.rs b/src/domain/account/config.rs index fa76224..046d7e8 100644 --- a/src/domain/account/config.rs +++ b/src/domain/account/config.rs @@ -19,8 +19,8 @@ use std::{collections::HashMap, path::PathBuf}; use crate::config::{prelude::*, DeserializedConfig}; /// Represents all existing kind of account config. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(tag = "backend", rename_all = "snake_case")] +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(tag = "backend", rename_all = "kebab-case")] pub enum DeserializedAccountConfig { None(DeserializedBaseAccountConfig), Maildir(DeserializedMaildirAccountConfig), @@ -70,7 +70,7 @@ impl DeserializedAccountConfig { } } -#[derive(Default, Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct DeserializedBaseAccountConfig { pub email: String, @@ -85,12 +85,8 @@ pub struct DeserializedBaseAccountConfig { pub email_listing_page_size: Option, pub email_reading_headers: Option>, - #[serde( - default, - with = "EmailTextPlainFormatOptionDef", - skip_serializing_if = "Option::is_none" - )] - pub email_reading_format: Option, + #[serde(default, with = "EmailTextPlainFormatDef")] + pub email_reading_format: EmailTextPlainFormat, pub email_reading_verify_cmd: Option, pub email_reading_decrypt_cmd: Option, pub email_writing_headers: Option>, @@ -100,10 +96,10 @@ pub struct DeserializedBaseAccountConfig { pub email_sender: EmailSender, #[serde( default, - with = "EmailHooksOptionDef", - skip_serializing_if = "Option::is_none" + with = "EmailHooksDef", + skip_serializing_if = "EmailHooks::is_empty" )] - pub email_hooks: Option, + pub email_hooks: EmailHooks, #[serde(default)] pub sync: bool, @@ -159,12 +155,7 @@ impl DeserializedBaseAccountConfig { .as_ref() .map(ToOwned::to_owned) .or_else(|| config.email_reading_headers.as_ref().map(ToOwned::to_owned)), - email_reading_format: self - .email_reading_format - .as_ref() - .map(ToOwned::to_owned) - .or_else(|| config.email_reading_format.as_ref().map(ToOwned::to_owned)) - .unwrap_or_default(), + email_reading_format: self.email_reading_format.clone(), email_reading_verify_cmd: self .email_reading_verify_cmd .as_ref() @@ -212,18 +203,7 @@ impl DeserializedBaseAccountConfig { .or_else(|| config.email_writing_headers.as_ref().map(ToOwned::to_owned)), email_sender: self.email_sender.to_owned(), email_hooks: EmailHooks { - pre_send: self - .email_hooks - .as_ref() - .map(ToOwned::to_owned) - .map(|hook| hook.pre_send) - .or_else(|| { - config - .email_hooks - .as_ref() - .map(|hook| hook.pre_send.as_ref().map(ToOwned::to_owned)) - }) - .unwrap_or_default(), + pre_send: self.email_hooks.pre_send.clone(), }, sync: self.sync, sync_dir: self.sync_dir.clone(), @@ -231,7 +211,7 @@ impl DeserializedBaseAccountConfig { } } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[cfg(feature = "imap-backend")] pub struct DeserializedImapAccountConfig { #[serde(flatten)] @@ -240,7 +220,7 @@ pub struct DeserializedImapAccountConfig { pub backend: ImapConfig, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct DeserializedMaildirAccountConfig { #[serde(flatten)] pub base: DeserializedBaseAccountConfig, @@ -248,7 +228,7 @@ pub struct DeserializedMaildirAccountConfig { pub backend: MaildirConfig, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[cfg(feature = "notmuch-backend")] pub struct DeserializedNotmuchAccountConfig { #[serde(flatten)]