Author: Manos Pitsidianakis [manos@pitsidianak.is]
Hash: 84cfa358de8f0897b4d9d16216d8284e0a259e6c
Timestamp: Wed, 31 Jul 2024 13:18:41 +0000 (1 month ago)

+103 -152 +/-9 browse
conf: remove need for global send_mail setting
conf: remove need for global send_mail setting

This was a bad UX artifact from the very first meli versions. There's no
need to keep it around.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
1diff --git a/CHANGELOG.md b/CHANGELOG.md
2index a567112..1d5e413 100644
3--- a/CHANGELOG.md
4+++ b/CHANGELOG.md
5 @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
7 ## [Unreleased]
8
9+ *WARNING*: This release contains a breaking change in the configuration file: a
10+ global composing option is not required anymore. Now, composing options are per
11+ account.
12+
13 <!-- ### Added -->
14
15 <!-- ### Bug Fixes -->
16 diff --git a/meli/docs/external-tools.md b/meli/docs/external-tools.md
17index 00919c9..fb91e78 100644
18--- a/meli/docs/external-tools.md
19+++ b/meli/docs/external-tools.md
20 @@ -2,7 +2,7 @@
21
22 ## Sending mail with a command line tool
23
24- `composing.send_mail` can use either settings for an SMTP server or a shell
25+ `send_mail` can use either settings for an SMTP server or a shell
26 command to which it pipes new mail to.
27
28 ### `msmtp` and `send_mail`
29 @@ -12,7 +12,6 @@ with many SMTP servers. It supports queuing and other small useful features.
30 See [the documentation](https://marlam.de/msmtp/msmtp.html).
31
32 ```toml
33- [composing]
34 send_mail = 'msmtp --logfile=/home/user/.mail/msmtp.log --read-recipients
35 --read-envelope-from'
36 ```
37 diff --git a/meli/docs/samples/sample-config.toml b/meli/docs/samples/sample-config.toml
38index a8b4141..9567959 100644
39--- a/meli/docs/samples/sample-config.toml
40+++ b/meli/docs/samples/sample-config.toml
41 @@ -11,6 +11,8 @@
42 #[accounts.account-name]
43 #root_mailbox = "/path/to/root/mailbox"
44 #format = "Maildir"
45+ #send_mail = 'msmtp --read-recipients --read-envelope-from'
46+ ##send_mail = { hostname = "smtp.example.com", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
47 #listing.index_style = "Conversations" # or [plain, threaded, compact]
48 #identity="email@example.com"
49 #display_name = "Name"
50 @@ -26,6 +28,7 @@
51 #[accounts.mbox]
52 #root_mailbox = "/var/mail/username"
53 #format = "mbox"
54+ #send_mail = 'false'
55 #listing.index_style = "Compact"
56 #identity="username@hostname.local"
57 #
58 @@ -33,6 +36,7 @@
59 #[accounts."imap"]
60 #root_mailbox = "INBOX"
61 #format = "imap"
62+ #send_mail = { hostname = "smtp.example.com", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
63 #server_hostname="mail.example.com"
64 #server_password="pha2hiLohs2eeeish2phaii1We3ood4chakaiv0hien2ahie3m"
65 #server_username="username@example.com"
66 @@ -51,6 +55,7 @@
67 ##[accounts.notmuch]
68 ##root_mailbox = "/path/to/folder" # where .notmuch/ directory is located
69 ##format = "notmuch"
70+ ##send_mail = 'msmtp --read-recipients --read-envelope-from'
71 ##listing.index_style = "conversations"
72 ##identity="username@example.com"
73 ##display_name = "Name Name"
74 @@ -64,6 +69,7 @@
75 #[accounts."gmail"]
76 #root_mailbox = '[Gmail]'
77 #format = "imap"
78+ #send_mail = { hostname = "smtp.gmail.com", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
79 #server_hostname='imap.gmail.com'
80 #server_password="password"
81 #server_username="username@gmail.com"
82 @@ -73,20 +79,19 @@
83 #display_name = "Name Name"
84 ### match every mailbox:
85 #subscribed_mailboxes = ["*" ]
86- #composing.send_mail = 'msmtp --read-recipients --read-envelope-from'
87 ### Gmail auto saves sent mail to Sent folder, so don't duplicate the effort:
88 #composing.store_sent_mail = false
89 #
90 ##[accounts."jmap account"]
91 ##root_mailbox = "INBOX"
92 ##format = "jmap"
93+ ##send_mail = 'server_submission'
94 ##server_url="http://localhost:8080"
95 ##server_username="user@hostname.local"
96 ##server_password="changeme"
97 ##listing.index_style = "Conversations"
98 ##identity = "user@hostname.local"
99 ##subscribed_mailboxes = ["*", ]
100- ##composing.send_mail = 'server_submission'
101 #
102 #[pager]
103 #filter = "COLUMNS=72 /usr/local/bin/pygmentize -l email"
104 @@ -128,12 +133,8 @@
105 #page_down = "PageDown"
106 #
107 #[composing]
108- ##required for sending e-mail
109- #send_mail = 'msmtp --read-recipients --read-envelope-from'
110- ##send_mail = { hostname = "smtp.example.com", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
111 #editor_command = 'vim +/^$' # optional, by default $EDITOR is used.
112 #
113- #
114 #[pgp]
115 #auto_sign = false # always sign sent messages
116 #auto_verify_signatures = true # always verify signatures when reading signed e-mails
117 diff --git a/meli/src/conf.rs b/meli/src/conf.rs
118index 065a74e..afc1eba 100644
119--- a/meli/src/conf.rs
120+++ b/meli/src/conf.rs
121 @@ -38,10 +38,7 @@ use melib::{
122 ShellExpandTrait, SortField, SortOrder, StderrLogger,
123 };
124
125- use crate::{
126- conf::deserializers::non_empty_opt_string,
127- terminal::{Ask, Color},
128- };
129+ use crate::{conf::deserializers::non_empty_opt_string, terminal::Color};
130
131 #[rustfmt::skip]
132 mod overrides;
133 @@ -67,7 +64,7 @@ use std::{
134
135 use indexmap::IndexMap;
136 use melib::{
137- conf::{AccountSettings, ActionFlag, MailboxConf, ToggleFlag},
138+ conf::{ActionFlag, MailboxConf, ToggleFlag},
139 error::*,
140 };
141 use pager::PagerSettings;
142 @@ -98,6 +95,9 @@ macro_rules! account_settings {
143 .as_ref()
144 .unwrap_or(&$context.settings.$setting.$field)
145 }};
146+ ($context:ident[$account_hash:expr].$field:ident) => {{
147+ &$context.accounts[&$account_hash].settings.$field
148+ }};
149 }
150 #[macro_export]
151 macro_rules! mailbox_settings {
152 @@ -121,6 +121,7 @@ macro_rules! mailbox_settings {
153 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
154 #[serde(deny_unknown_fields)]
155 pub struct MailUIConf {
156+ pub send_mail: Option<SendMail>,
157 #[serde(default)]
158 pub pager: PagerSettingsOverride,
159 #[serde(default)]
160 @@ -173,12 +174,12 @@ pub struct FileAccount {
161 #[serde(default = "none", skip_serializing_if = "Option::is_none")]
162 pub default_mailbox: Option<String>,
163 pub format: String,
164+ pub send_mail: SendMail,
165 pub identity: String,
166 #[serde(default)]
167 pub extra_identities: Vec<String>,
168 #[serde(default = "none", skip_serializing_if = "Option::is_none")]
169 pub display_name: Option<String>,
170-
171 #[serde(default = "false_val")]
172 pub read_only: bool,
173 #[serde(default)]
174 @@ -227,6 +228,7 @@ pub struct FileSettings {
175 pub notifications: NotificationsSettings,
176 #[serde(default)]
177 pub shortcuts: Shortcuts,
178+ #[serde(default)]
179 pub composing: ComposingSettings,
180 #[serde(default)]
181 pub tags: TagsSettings,
182 @@ -240,7 +242,10 @@ pub struct FileSettings {
183
184 #[derive(Clone, Debug, Default, Serialize)]
185 pub struct AccountConf {
186- pub account: AccountSettings,
187+ pub account: melib::AccountSettings,
188+ /// How to send e-mail for this account.
189+ /// Required
190+ pub send_mail: SendMail,
191 pub default_mailbox: Option<MailboxHash>,
192 pub sent_mailbox: Option<MailboxHash>,
193 pub conf: FileAccount,
194 @@ -249,10 +254,10 @@ pub struct AccountConf {
195 }
196
197 impl AccountConf {
198- pub fn account(&self) -> &AccountSettings {
199+ pub fn account(&self) -> &melib::AccountSettings {
200 &self.account
201 }
202- pub fn account_mut(&mut self) -> &mut AccountSettings {
203+ pub fn account_mut(&mut self) -> &mut melib::AccountSettings {
204 &mut self.account
205 }
206 pub fn conf(&self) -> &FileAccount {
207 @@ -276,7 +281,7 @@ impl From<FileAccount> for AccountConf {
208 .map(|(k, v)| (k.clone(), v.mailbox_conf.clone()))
209 .collect();
210
211- let acc = AccountSettings {
212+ let acc = melib::AccountSettings {
213 name: String::new(),
214 root_mailbox,
215 format,
216 @@ -294,6 +299,7 @@ impl From<FileAccount> for AccountConf {
217 let mailbox_confs = x.mailboxes.clone();
218 Self {
219 account: acc,
220+ send_mail: x.send_mail.clone(),
221 default_mailbox: None,
222 sent_mailbox: None,
223 conf_override: x.conf_override.clone(),
224 @@ -439,7 +445,7 @@ impl FileSettings {
225 .set_kind(ErrorKind::Configuration));
226 }
227 #[cfg(not(test))]
228- let ask = Ask {
229+ let ask = crate::terminal::Ask {
230 message: format!(
231 "No configuration found. Would you like to generate one in {}?",
232 path_string
233 @@ -461,46 +467,26 @@ impl FileSettings {
234 );
235 }
236
237- Self::validate(config_path, true, false)
238+ Self::validate(config_path, false)
239 }
240
241 /// Validate configuration from `input` string.
242- pub fn validate_string(s: String, interactive: bool, clear_extras: bool) -> Result<Self> {
243- let map: toml::value::Table = toml::from_str(&s).map_err(|err| {
244+ pub fn validate_string(s: String, clear_extras: bool) -> Result<Self> {
245+ let _: toml::value::Table = melib::serde_path_to_error::deserialize(
246+ toml::Deserializer::new(&s),
247+ )
248+ .map_err(|err| {
249 Error::new("Config file is invalid TOML")
250 .set_source(Some(Arc::new(err)))
251 .set_kind(ErrorKind::ValueError)
252 })?;
253
254- // Check that a global composing option is set and return a user-friendly
255- // error message because the default serde one is confusing.
256- if !map.contains_key("composing") {
257- let err_msg = r#"You must set a global `composing` option. If you override `composing` in each account, you can use a dummy global like follows:
258-
259- [composing]
260- send_mail = 'false'
261-
262- This is required so that you don't accidentally start meli and find out later that you can't send emails."#;
263- if interactive {
264- println!("{}", err_msg);
265- let ask = Ask {
266- message: "Would you like to append this dummy value in your configuration \
267- input and continue?"
268- .to_string(),
269- };
270- if ask.run() {
271- let mut s = s;
272- s.push_str("[composing]\nsend_mail = 'false'\n");
273- return Self::validate_string(s, interactive, clear_extras);
274- }
275- }
276- return Err(Error::new(err_msg).set_kind(ErrorKind::Configuration));
277- }
278- let mut s: Self = toml::from_str(&s).map_err(|err| {
279- Error::new("Input contains errors")
280- .set_source(Some(Arc::new(err)))
281- .set_kind(ErrorKind::Configuration)
282- })?;
283+ let mut s: Self = melib::serde_path_to_error::deserialize(toml::Deserializer::new(&s))
284+ .map_err(|err| {
285+ Error::new("Input contains errors")
286+ .set_source(Some(Arc::new(err)))
287+ .set_kind(ErrorKind::Configuration)
288+ })?;
289 let backends = melib::backends::Backends::new();
290 let Themes {
291 light: default_light,
292 @@ -538,6 +524,7 @@ This is required so that you don't accidentally start meli and find out later th
293 let FileAccount {
294 root_mailbox,
295 format,
296+ send_mail: _,
297 identity,
298 extra_identities,
299 read_only,
300 @@ -554,7 +541,7 @@ This is required so that you don't accidentally start meli and find out later th
301 } = acc.clone();
302
303 let lowercase_format = format.to_lowercase();
304- let mut s = AccountSettings {
305+ let mut s = melib::AccountSettings {
306 name: name.to_string(),
307 root_mailbox,
308 format: format.clone(),
309 @@ -589,9 +576,9 @@ This is required so that you don't accidentally start meli and find out later th
310 }
311
312 /// Validate `path` and print errors.
313- pub fn validate(path: PathBuf, interactive: bool, clear_extras: bool) -> Result<Self> {
314+ pub fn validate(path: PathBuf, clear_extras: bool) -> Result<Self> {
315 let s = pp::pp(&path)?;
316- let map: toml::value::Table = toml::from_str(&s).map_err(|err| {
317+ let _: toml::value::Table = toml::from_str(&s).map_err(|err| {
318 Error::new(format!(
319 "{}: Config file is invalid TOML; {}",
320 path.display(),
321 @@ -599,40 +586,6 @@ This is required so that you don't accidentally start meli and find out later th
322 ))
323 })?;
324
325- // Check that a global composing option is set and return a user-friendly
326- // error message because the default serde one is confusing.
327- if !map.contains_key("composing") {
328- let err_msg = r#"You must set a global `composing` option. If you override `composing` in each account, you can use a dummy global like follows:
329-
330- [composing]
331- send_mail = 'false'
332-
333- This is required so that you don't accidentally start meli and find out later that you can't send emails."#;
334- if interactive {
335- println!("{}", err_msg);
336- let ask = Ask {
337- message: format!(
338- "Would you like to append this dummy value in your configuration file {} \
339- and continue?",
340- path.display()
341- ),
342- };
343- if ask.run() {
344- let mut file = OpenOptions::new().append(true).open(&path)?;
345- file.write_all(b"[composing]\nsend_mail = 'false'\n")
346- .map_err(|err| {
347- Error::new(format!("Could not append to {}: {}", path.display(), err))
348- })?;
349- return Self::validate(path, interactive, clear_extras);
350- }
351- }
352- return Err(Error::new(format!(
353- "{}\n\nEdit the {} and relaunch meli.",
354- if interactive { "" } else { err_msg },
355- path.display()
356- ))
357- .set_kind(ErrorKind::Configuration));
358- }
359 let mut s: Self = toml::from_str(&s).map_err(|err| {
360 Error::new(format!("{}: Config file contains errors", path.display()))
361 .set_source(Some(Arc::new(err)))
362 @@ -675,6 +628,7 @@ This is required so that you don't accidentally start meli and find out later th
363 let FileAccount {
364 root_mailbox,
365 format,
366+ send_mail: _,
367 identity,
368 extra_identities,
369 read_only,
370 @@ -691,7 +645,7 @@ This is required so that you don't accidentally start meli and find out later th
371 } = acc.clone();
372
373 let lowercase_format = format.to_lowercase();
374- let mut s = AccountSettings {
375+ let mut s = melib::AccountSettings {
376 name: name.to_string(),
377 root_mailbox,
378 format: format.clone(),
379 @@ -1492,6 +1446,7 @@ pub mod tests {
380 [accounts.account-name]
381 root_mailbox = "/path/to/root/mailbox"
382 format = "Maildir"
383+ send_mail = 'false'
384 listing.index_style = "Conversations" # or [plain, threaded, compact]
385 identity="email@example.com"
386 display_name = "Name"
387 @@ -1507,6 +1462,7 @@ subscribed_mailboxes = ["INBOX", "INBOX/Sent", "INBOX/Drafts", "INBOX/Junk"]
388 [accounts.mbox]
389 root_mailbox = "/var/mail/username"
390 format = "mbox"
391+ send_mail = 'false'
392 listing.index_style = "Compact"
393 identity="username@hostname.local"
394 "#;
395 @@ -1515,39 +1471,26 @@ identity="username@hostname.local"
396 [accounts.mbox]
397 root_mailbox = "/"
398 format = "mbox"
399+ send_mail = 'false'
400 index_style = "Compact"
401 identity="username@hostname.local"
402-
403- [composing]
404- send_mail = 'false'
405 "#;
406 pub const IMAP_CONFIG: &str = r#"
407 [accounts.imap]
408 root_mailbox = "INBOX"
409 format = "imap"
410+ send_mail = 'false'
411 identity="username@example.com"
412 server_username = "null"
413 server_hostname = "example.com"
414 server_password_command = "false"
415-
416- [composing]
417- send_mail = 'false'
418 "#;
419
420 #[test]
421 fn test_config_parse() {
422 let tempdir = tempfile::tempdir().unwrap();
423- let mut new_file = ConfigFile::new(TEST_CONFIG, &tempdir).unwrap();
424- let err = FileSettings::validate(new_file.path.clone(), false, true).unwrap_err();
425- assert!(err.summary.as_ref().starts_with(
426- "You must set a global `composing` option. If you override `composing` in each \
427- account, you can use a dummy global like follows"
428- ));
429- new_file
430- .file
431- .write_all(b"[composing]\nsend_mail = 'false'\n")
432- .unwrap();
433- let err = FileSettings::validate(new_file.path.clone(), false, true).unwrap_err();
434+ let new_file = ConfigFile::new(TEST_CONFIG, &tempdir).unwrap();
435+ let err = FileSettings::validate(new_file.path.clone(), true).unwrap_err();
436 assert_eq!(
437 err.summary.as_ref(),
438 "Configuration error (account-name): root_mailbox `/path/to/root/mailbox` is not a \
439 @@ -1557,7 +1500,7 @@ send_mail = 'false'
440 /* Test unrecognised configuration entries error */
441
442 let new_file = ConfigFile::new(EXTRA_CONFIG, &tempdir).unwrap();
443- let err = FileSettings::validate(new_file.path.clone(), false, true).unwrap_err();
444+ let err = FileSettings::validate(new_file.path.clone(), true).unwrap_err();
445 assert_eq!(
446 err.summary.as_ref(),
447 "Unrecognised configuration values: {\"index_style\": \"Compact\"}"
448 @@ -1566,8 +1509,7 @@ send_mail = 'false'
449 /* Test IMAP config */
450
451 let new_file = ConfigFile::new(IMAP_CONFIG, &tempdir).unwrap();
452- FileSettings::validate(new_file.path.clone(), false, true)
453- .expect("could not parse IMAP config");
454+ FileSettings::validate(new_file.path.clone(), true).expect("could not parse IMAP config");
455
456 /* Test sample config */
457
458 @@ -1579,7 +1521,7 @@ send_mail = 'false'
459 );
460
461 let new_file = ConfigFile::new(&example_config, &tempdir).unwrap();
462- let config = FileSettings::validate(new_file.path.clone(), false, true)
463+ let config = FileSettings::validate(new_file.path.clone(), true)
464 .expect("Could not parse example config!");
465 for (accname, acc) in config.accounts.iter() {
466 if !acc.extra.is_empty() {
467 @@ -1589,5 +1531,8 @@ send_mail = 'false'
468 );
469 }
470 }
471+ if let Err(err) = tempdir.close() {
472+ eprintln!("Could not cleanup tempdir: {}", err);
473+ }
474 }
475 }
476 diff --git a/meli/src/conf/composing.rs b/meli/src/conf/composing.rs
477index 1e5044a..e87ee48 100644
478--- a/meli/src/conf/composing.rs
479+++ b/meli/src/conf/composing.rs
480 @@ -34,9 +34,6 @@ use super::{
481 #[derive(Clone, Debug, Deserialize, Serialize)]
482 #[serde(deny_unknown_fields)]
483 pub struct ComposingSettings {
484- /// A command to pipe new emails to
485- /// Required
486- pub send_mail: SendMail,
487 /// Command to launch editor. Can have arguments. Draft filename is given as
488 /// the last argument. If it's missing, the environment variable $EDITOR is
489 /// looked up.
490 @@ -116,7 +113,6 @@ pub struct ComposingSettings {
491 impl Default for ComposingSettings {
492 fn default() -> Self {
493 Self {
494- send_mail: SendMail::ShellCommand("false".into()),
495 editor_command: None,
496 embedded_pty: false,
497 format_flowed: true,
498 @@ -187,6 +183,14 @@ pub enum SendMail {
499 ShellCommand(String),
500 }
501
502+ impl Default for SendMail {
503+ /// Returns the `false` POSIX shell utility, in order to return an error
504+ /// when called.
505+ fn default() -> Self {
506+ Self::ShellCommand("false".into())
507+ }
508+ }
509+
510 /// Shell command compose hooks (See
511 /// [`crate::mail::compose::hooks::Hook`])
512 #[derive(Clone, Debug, Deserialize, Serialize)]
513 @@ -220,9 +224,10 @@ Using a shell script
514 Direct SMTP connection
515 ======================
516
517+ [accounts.account-name]
518 send_mail = { hostname = "mail.example.com", port = 587, auth = { type = "auto", password = { type = "raw", value = "hunter2" } }, security = { type = "STARTTLS" } }
519
520- [composing.send_mail]
521+ [accounts.account-name.send_mail]
522 hostname = "mail.example.com"
523 port = 587
524 auth = { type = "auto", password = { type = "command_eval", value = "/path/to/password_script.sh" } }
525 @@ -284,6 +289,8 @@ impl<'de> Deserialize<'de> for SendMail {
526 where
527 D: Deserializer<'de>,
528 {
529+ use serde::de::Error;
530+
531 #[derive(Deserialize)]
532 #[serde(untagged)]
533 enum SendMailInner {
534 @@ -294,11 +301,17 @@ impl<'de> Deserialize<'de> for SendMail {
535 ShellCommand(String),
536 }
537
538- match <SendMailInner>::deserialize(deserializer) {
539+ match melib::serde_path_to_error::deserialize(deserializer) {
540 #[cfg(feature = "smtp")]
541 Ok(SendMailInner::Smtp(v)) => Ok(Self::Smtp(v)),
542 Ok(SendMailInner::ServerSubmission) => Ok(Self::ServerSubmission),
543 Ok(SendMailInner::ShellCommand(v)) => Ok(Self::ShellCommand(v)),
544+ Err(err)
545+ if err.inner().to_string() == D::Error::missing_field("send_mail").to_string() =>
546+ {
547+ // Surely there should be a better way to do this...
548+ Err(err.into_inner())
549+ }
550 Err(_err) => Err(de::Error::custom(SENDMAIL_ERR_HELP)),
551 }
552 }
553 diff --git a/meli/src/conf/overrides.rs b/meli/src/conf/overrides.rs
554index bef748e..964b208 100644
555--- a/meli/src/conf/overrides.rs
556+++ b/meli/src/conf/overrides.rs
557 @@ -35,7 +35,7 @@ use melib::HeaderName;
558
559 # [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ShortcutsOverride { # [serde (default)] pub general : Option < GeneralShortcuts > , # [serde (default)] pub listing : Option < ListingShortcuts > , # [serde (default)] pub composing : Option < ComposingShortcuts > , # [serde (alias = "contact-list")] # [serde (default)] pub contact_list : Option < ContactListShortcuts > , # [serde (alias = "envelope-view")] # [serde (default)] pub envelope_view : Option < EnvelopeViewShortcuts > , # [serde (alias = "thread-view")] # [serde (default)] pub thread_view : Option < ThreadViewShortcuts > , # [serde (default)] pub pager : Option < PagerShortcuts > } impl Default for ShortcutsOverride { fn default () -> Self { Self { general : None , listing : None , composing : None , contact_list : None , envelope_view : None , thread_view : None , pager : None } } }
560
561- # [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ComposingSettingsOverride { # [doc = " A command to pipe new emails to"] # [doc = " Required"] # [serde (default)] pub send_mail : Option < SendMail > , # [doc = " Command to launch editor. Can have arguments. Draft filename is given as"] # [doc = " the last argument. If it's missing, the environment variable $EDITOR is"] # [doc = " looked up."] # [serde (alias = "editor-command" , alias = "editor-cmd" , alias = "editor_cmd")] # [serde (default)] pub editor_command : Option < Option < String > > , # [doc = " Embedded editor (for terminal interfaces) instead of forking and"] # [doc = " waiting."] # [serde (alias = "embed")] # [serde (default)] pub embedded_pty : Option < bool > , # [doc = " Set \"format=flowed\" in plain text attachments."] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = " Set User-Agent"] # [doc = " Default: empty"] # [serde (alias = "insert_user_agent")] # [serde (default)] pub insert_user_agent : Option < bool > , # [doc = " Set default header values for new drafts"] # [doc = " Default: empty"] # [serde (alias = "default-header-values")] # [serde (default)] pub default_header_values : Option < HashMap < HeaderName , String > > , # [doc = " Wrap header preamble when editing a draft in an editor. This allows you"] # [doc = " to write non-plain text email without the preamble creating syntax"] # [doc = " errors. They are stripped when you return from the editor. The"] # [doc = " values should be a two element array of strings, a prefix and suffix."] # [doc = " Default: None"] # [serde (alias = "wrap-header-preamble")] # [serde (default)] pub wrap_header_preamble : Option < Option < (String , String) > > , # [doc = " Store sent mail after successful submission. This setting is meant to be"] # [doc = " disabled for non-standard behaviour in gmail, which auto-saves sent"] # [doc = " mail on its own. Default: true"] # [serde (default)] pub store_sent_mail : Option < bool > , # [doc = " The attribution line appears above the quoted reply text."] # [doc = " The format specifiers for the replied address are:"] # [doc = " - `%+f` — the sender's name and email address."] # [doc = " - `%+n` — the sender's name (or email address, if no name is included)."] # [doc = " - `%+a` — the sender's email address."] # [doc = " The format string is passed to strftime(3) with the replied envelope's"] # [doc = " date. Default: \"On %a, %0e %b %Y %H:%M, %+f wrote:%n\""] # [serde (default)] pub attribution_format_string : Option < Option < String > > , # [doc = " Whether the strftime call for the attribution string uses the POSIX"] # [doc = " locale instead of the user's active locale"] # [doc = " Default: true"] # [serde (default)] pub attribution_use_posix_locale : Option < bool > , # [doc = " Forward emails as attachment? (Alternative is inline)"] # [doc = " Default: ask"] # [serde (alias = "forward-as-attachment")] # [serde (default)] pub forward_as_attachment : Option < ActionFlag > , # [doc = " Alternative lists of reply prefixes (etc. [\"Re:\", \"RE:\", ...]) to strip"] # [doc = " Default: `[\"Re:\", \"RE:\", \"Fwd:\", \"Fw:\", \"回复:\", \"回覆:\", \"SV:\", \"Sv:\","] # [doc = " \"VS:\", \"Antw:\", \"Doorst:\", \"VS:\", \"VL:\", \"REF:\", \"TR:\", \"TR:\", \"AW:\","] # [doc = " \"WG:\", \"ΑΠ:\", \"Απ:\", \"απ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"ΣΧΕΤ:\", \"Σχετ:\","] # [doc = " \"σχετ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"Vá:\", \"Továbbítás:\", \"R:\", \"I:\","] # [doc = " \"RIF:\", \"FS:\", \"BLS:\", \"TRS:\", \"VS:\", \"VB:\", \"RV:\", \"RES:\", \"Res\","] # [doc = " \"ENC:\", \"Odp:\", \"PD:\", \"YNT:\", \"İLT:\", \"ATB:\", \"YML:\"]`"] # [serde (alias = "reply-prefix-list-to-strip")] # [serde (default)] pub reply_prefix_list_to_strip : Option < Option < Vec < String > > > , # [doc = " The prefix to use in reply subjects. The de facto prefix is \"Re:\"."] # [serde (alias = "reply-prefix")] # [serde (default)] pub reply_prefix : Option < String > , # [doc = " Custom `compose-hooks`."] # [serde (alias = "custom-compose-hooks")] # [serde (default)] pub custom_compose_hooks : Option < Vec < ComposeHook > > , # [doc = " Disabled `compose-hooks`."] # [serde (alias = "disabled-compose-hooks")] # [serde (default)] pub disabled_compose_hooks : Option < Vec < String > > } impl Default for ComposingSettingsOverride { fn default () -> Self { Self { send_mail : None , editor_command : None , embedded_pty : None , format_flowed : None , insert_user_agent : None , default_header_values : None , wrap_header_preamble : None , store_sent_mail : None , attribution_format_string : None , attribution_use_posix_locale : None , forward_as_attachment : None , reply_prefix_list_to_strip : None , reply_prefix : None , custom_compose_hooks : None , disabled_compose_hooks : None } } }
562+ # [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct ComposingSettingsOverride { # [doc = " Command to launch editor. Can have arguments. Draft filename is given as"] # [doc = " the last argument. If it's missing, the environment variable $EDITOR is"] # [doc = " looked up."] # [serde (alias = "editor-command" , alias = "editor-cmd" , alias = "editor_cmd")] # [serde (default)] pub editor_command : Option < Option < String > > , # [doc = " Embedded editor (for terminal interfaces) instead of forking and"] # [doc = " waiting."] # [serde (alias = "embed")] # [serde (default)] pub embedded_pty : Option < bool > , # [doc = " Set \"format=flowed\" in plain text attachments."] # [doc = " Default: true"] # [serde (alias = "format-flowed")] # [serde (default)] pub format_flowed : Option < bool > , # [doc = " Set User-Agent"] # [doc = " Default: empty"] # [serde (alias = "insert_user_agent")] # [serde (default)] pub insert_user_agent : Option < bool > , # [doc = " Set default header values for new drafts"] # [doc = " Default: empty"] # [serde (alias = "default-header-values")] # [serde (default)] pub default_header_values : Option < HashMap < HeaderName , String > > , # [doc = " Wrap header preamble when editing a draft in an editor. This allows you"] # [doc = " to write non-plain text email without the preamble creating syntax"] # [doc = " errors. They are stripped when you return from the editor. The"] # [doc = " values should be a two element array of strings, a prefix and suffix."] # [doc = " Default: None"] # [serde (alias = "wrap-header-preamble")] # [serde (default)] pub wrap_header_preamble : Option < Option < (String , String) > > , # [doc = " Store sent mail after successful submission. This setting is meant to be"] # [doc = " disabled for non-standard behaviour in gmail, which auto-saves sent"] # [doc = " mail on its own. Default: true"] # [serde (default)] pub store_sent_mail : Option < bool > , # [doc = " The attribution line appears above the quoted reply text."] # [doc = " The format specifiers for the replied address are:"] # [doc = " - `%+f` — the sender's name and email address."] # [doc = " - `%+n` — the sender's name (or email address, if no name is included)."] # [doc = " - `%+a` — the sender's email address."] # [doc = " The format string is passed to strftime(3) with the replied envelope's"] # [doc = " date. Default: \"On %a, %0e %b %Y %H:%M, %+f wrote:%n\""] # [serde (default)] pub attribution_format_string : Option < Option < String > > , # [doc = " Whether the strftime call for the attribution string uses the POSIX"] # [doc = " locale instead of the user's active locale"] # [doc = " Default: true"] # [serde (default)] pub attribution_use_posix_locale : Option < bool > , # [doc = " Forward emails as attachment? (Alternative is inline)"] # [doc = " Default: ask"] # [serde (alias = "forward-as-attachment")] # [serde (default)] pub forward_as_attachment : Option < ActionFlag > , # [doc = " Alternative lists of reply prefixes (etc. [\"Re:\", \"RE:\", ...]) to strip"] # [doc = " Default: `[\"Re:\", \"RE:\", \"Fwd:\", \"Fw:\", \"回复:\", \"回覆:\", \"SV:\", \"Sv:\","] # [doc = " \"VS:\", \"Antw:\", \"Doorst:\", \"VS:\", \"VL:\", \"REF:\", \"TR:\", \"TR:\", \"AW:\","] # [doc = " \"WG:\", \"ΑΠ:\", \"Απ:\", \"απ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"ΣΧΕΤ:\", \"Σχετ:\","] # [doc = " \"σχετ:\", \"ΠΡΘ:\", \"Πρθ:\", \"πρθ:\", \"Vá:\", \"Továbbítás:\", \"R:\", \"I:\","] # [doc = " \"RIF:\", \"FS:\", \"BLS:\", \"TRS:\", \"VS:\", \"VB:\", \"RV:\", \"RES:\", \"Res\","] # [doc = " \"ENC:\", \"Odp:\", \"PD:\", \"YNT:\", \"İLT:\", \"ATB:\", \"YML:\"]`"] # [serde (alias = "reply-prefix-list-to-strip")] # [serde (default)] pub reply_prefix_list_to_strip : Option < Option < Vec < String > > > , # [doc = " The prefix to use in reply subjects. The de facto prefix is \"Re:\"."] # [serde (alias = "reply-prefix")] # [serde (default)] pub reply_prefix : Option < String > , # [doc = " Custom `compose-hooks`."] # [serde (alias = "custom-compose-hooks")] # [serde (default)] pub custom_compose_hooks : Option < Vec < ComposeHook > > , # [doc = " Disabled `compose-hooks`."] # [serde (alias = "disabled-compose-hooks")] # [serde (default)] pub disabled_compose_hooks : Option < Vec < String > > } impl Default for ComposingSettingsOverride { fn default () -> Self { Self { editor_command : None , embedded_pty : None , format_flowed : None , insert_user_agent : None , default_header_values : None , wrap_header_preamble : None , store_sent_mail : None , attribution_format_string : None , attribution_use_posix_locale : None , forward_as_attachment : None , reply_prefix_list_to_strip : None , reply_prefix : None , custom_compose_hooks : None , disabled_compose_hooks : None } } }
563
564 # [derive (Debug , Serialize , Deserialize , Clone)] # [serde (deny_unknown_fields)] pub struct TagsSettingsOverride { # [serde (deserialize_with = "tag_color_de")] # [serde (default)] pub colors : Option < HashMap < TagHash , Color > > , # [serde (deserialize_with = "tag_set_de" , alias = "ignore-tags")] # [serde (default)] pub ignore_tags : Option < HashSet < TagHash > > } impl Default for TagsSettingsOverride { fn default () -> Self { Self { colors : None , ignore_tags : None } } }
565
566 diff --git a/meli/src/mail/compose.rs b/meli/src/mail/compose.rs
567index abcf6d3..50ae4a4 100644
568--- a/meli/src/mail/compose.rs
569+++ b/meli/src/mail/compose.rs
570 @@ -649,23 +649,22 @@ To: {}
571 .values()
572 .map(|acc| {
573 let addr = acc.settings.account.main_identity_address();
574- let desc =
575- match account_settings!(c[acc.hash()].composing.send_mail) {
576- crate::conf::composing::SendMail::ShellCommand(ref cmd) => {
577- let mut cmd = cmd.as_str();
578- cmd.truncate_at_boundary(10);
579- format!("{} [exec: {}]", acc.name(), cmd)
580- }
581- #[cfg(feature = "smtp")]
582- crate::conf::composing::SendMail::Smtp(ref inner) => {
583- let mut hostname = inner.hostname.as_str();
584- hostname.truncate_at_boundary(10);
585- format!("{} [smtp: {}]", acc.name(), hostname)
586- }
587- crate::conf::composing::SendMail::ServerSubmission => {
588- format!("{} [server submission]", acc.name())
589- }
590- };
591+ let desc = match account_settings!(c[acc.hash()].send_mail) {
592+ crate::conf::composing::SendMail::ShellCommand(ref cmd) => {
593+ let mut cmd = cmd.as_str();
594+ cmd.truncate_at_boundary(10);
595+ format!("{} [exec: {}]", acc.name(), cmd)
596+ }
597+ #[cfg(feature = "smtp")]
598+ crate::conf::composing::SendMail::Smtp(ref inner) => {
599+ let mut hostname = inner.hostname.as_str();
600+ hostname.truncate_at_boundary(10);
601+ format!("{} [smtp: {}]", acc.name(), hostname)
602+ }
603+ crate::conf::composing::SendMail::ServerSubmission => {
604+ format!("{} [server submission]", acc.name())
605+ }
606+ };
607
608 (addr.to_string(), desc)
609 })
610 @@ -2489,7 +2488,7 @@ pub fn send_draft(
611 }
612 }
613 let bytes = draft.finalise().unwrap();
614- let send_mail = account_settings!(context[account_hash].composing.send_mail).clone();
615+ let send_mail = account_settings!(context[account_hash].send_mail).clone();
616 let ret =
617 context.accounts[&account_hash].send(bytes.clone(), send_mail, complete_in_background);
618 save_draft(bytes.as_bytes(), context, mailbox_type, flags, account_hash);
619 @@ -2578,7 +2577,7 @@ pub fn send_draft_async(
620 gpg_state.encrypt_keys,
621 )?));
622 }
623- let send_mail = account_settings!(context[account_hash].composing.send_mail).clone();
624+ let send_mail = account_settings!(context[account_hash].send_mail).clone();
625 let send_cb = context.accounts[&account_hash].send_async(send_mail);
626 let mut content_type = ContentType::default();
627 if format_flowed {
628 diff --git a/meli/src/subcommands.rs b/meli/src/subcommands.rs
629index 7096111..cc62a97 100644
630--- a/meli/src/subcommands.rs
631+++ b/meli/src/subcommands.rs
632 @@ -133,13 +133,13 @@ pub fn test_config(path: Option<PathOrStdio>) -> Result<()> {
633 if input.trim().is_empty() {
634 return Err(Error::new("Input was empty.").set_kind(ErrorKind::ValueError));
635 }
636- conf::FileSettings::validate_string(input, true, false)?;
637+ conf::FileSettings::validate_string(input, false)?;
638 return Ok(());
639 }
640 Some(PathOrStdio::Path(path)) => path.expand(),
641 None => conf::get_config_file()?,
642 };
643- conf::FileSettings::validate(config_path, true, false)?;
644+ conf::FileSettings::validate(config_path, false)?;
645 Ok(())
646 }
647
648 @@ -183,7 +183,7 @@ pub fn tool(path: Option<PathBuf>, opt: ToolOpt) -> Result<()> {
649 } else {
650 conf::get_config_file()?
651 };
652- let conf = conf::FileSettings::validate(config_path, true, false)?;
653+ let conf = conf::FileSettings::validate(config_path, false)?;
654 let account = match opt {
655 ToolOpt::ImapShell { ref account } => account,
656 #[cfg(feature = "smtp")]
657 @@ -208,13 +208,7 @@ pub fn tool(path: Option<PathBuf>, opt: ToolOpt) -> Result<()> {
658 ToolOpt::SmtpShell { .. } => {
659 use crate::conf::composing::SendMail;
660
661- let send_mail = file_account_conf
662- .conf_override
663- .composing
664- .send_mail
665- .as_ref()
666- .unwrap_or(&conf.composing.send_mail)
667- .clone();
668+ let send_mail = file_account_conf.send_mail;
669 let SendMail::Smtp(smtp_conf) = send_mail else {
670 panic!(
671 "smtp shell requires an smtp configuration for account {}",
672 diff --git a/meli/tests/test_cli_subcommands.rs b/meli/tests/test_cli_subcommands.rs
673index 144e2cd..4e6d3d0 100644
674--- a/meli/tests/test_cli_subcommands.rs
675+++ b/meli/tests/test_cli_subcommands.rs
676 @@ -130,13 +130,11 @@ fn test_cli_subcommands() {
677 [accounts.imap]
678 root_mailbox = "INBOX"
679 format = "imap"
680+ send_mail = 'false'
681 identity="username@example.com"
682 server_username = "null"
683 server_hostname = "example.com"
684 server_password_command = "false"
685-
686- [composing]
687- send_mail = 'false'
688 "#
689 .as_slice(),
690 )
691 @@ -258,13 +256,11 @@ send_mail = 'false'
692 [accounts.imap]
693 root_mailbox = "INBOX"
694 format = "imap"
695+ send_mail = 'false'
696 identity="username@example.com"
697 server_username = "null"
698 server_hostname = "example.com"
699 server_password_command = "false"
700-
701- [composing]
702- send_mail = 'false'
703 "#,
704 )
705 .unwrap();