From 6b920cbe76dafbf6af1ab3f5c114b2e63bf55532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Wed, 2 Mar 2022 11:13:38 +0100 Subject: [PATCH 01/13] remove variation selectors from table cells (#300) --- CHANGELOG.md | 5 +++++ src/ui/table.rs | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aef8cd..929e380 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Some emojis break the table layout [#300] + ## [0.5.7] - 2022-03-01 ### Added @@ -452,6 +456,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#280]: https://github.com/soywod/himalaya/issues/280 [#288]: https://github.com/soywod/himalaya/issues/288 [#289]: https://github.com/soywod/himalaya/issues/289 +[#300]: https://github.com/soywod/himalaya/issues/300 [#303]: https://github.com/soywod/himalaya/issues/303 [#305]: https://github.com/soywod/himalaya/issues/305 [#308]: https://github.com/soywod/himalaya/issues/308 diff --git a/src/ui/table.rs b/src/ui/table.rs index 3763b18..54548b3 100644 --- a/src/ui/table.rs +++ b/src/ui/table.rs @@ -35,7 +35,12 @@ pub struct Cell { impl Cell { pub fn new>(value: T) -> Self { Self { - value: String::from(value.as_ref()).replace(&['\r', '\n', '\t'][..], ""), + // Removes carriage returns, new line feeds, tabulations + // and [variation selectors]. + // + // [variation selectors]: https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block) + value: String::from(value.as_ref()) + .replace(&['\r', '\n', '\t', '\u{fe0e}', '\u{fe0f}'], ""), ..Self::default() } } From 6e5362e76e4ac20347b8c36c544e4ee4500899af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Wed, 2 Mar 2022 23:32:00 +0100 Subject: [PATCH 02/13] add keybind precision vim readme (#298) --- CHANGELOG.md | 5 +++++ vim/README.md | 14 ++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 929e380..eb19e71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Vim doc about mailbox pickers [#298] + ### Fixed - Some emojis break the table layout [#300] @@ -456,6 +460,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#280]: https://github.com/soywod/himalaya/issues/280 [#288]: https://github.com/soywod/himalaya/issues/288 [#289]: https://github.com/soywod/himalaya/issues/289 +[#298]: https://github.com/soywod/himalaya/issues/298 [#300]: https://github.com/soywod/himalaya/issues/300 [#303]: https://github.com/soywod/himalaya/issues/303 [#305]: https://github.com/soywod/himalaya/issues/305 diff --git a/vim/README.md b/vim/README.md index 00af70f..9275a74 100644 --- a/vim/README.md +++ b/vim/README.md @@ -3,9 +3,10 @@ ## Installation First you need to install and configure the [himalaya -CLI](https://github.com/soywod/himalaya#installation). Then you can install -this plugin with your favorite plugin manager. For example with -[vim-plug](https://github.com/junegunn/vim-plug), add to your `.vimrc`: +CLI](https://github.com/soywod/himalaya#installation). Then you can +install this plugin with your favorite plugin manager. For example +with [vim-plug](https://github.com/junegunn/vim-plug), add to your +`.vimrc`: ```viml Plug 'soywod/himalaya', {'rtp': 'vim'} @@ -31,14 +32,15 @@ set hidden let g:himalaya_mailbox_picker = 'native' | 'fzf' | 'telescope' ``` -Defines the provider used for picking mailboxes: +Defines the provider used for picking mailboxes (default keybind: +`gm`): - `native`: a vim native input - `fzf`: https://github.com/junegunn/fzf.vim - `telescope`: https://github.com/nvim-telescope/telescope.nvim -If no value given, the first loaded (and available) provider will be used (fzf -> telescope > native). +If no value given, the first loaded (and available) provider will be +used (fzf > telescope > native). ### Telescope preview From 613e592f722efc2279f1b4d853b6490e02113b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Thu, 3 Mar 2022 12:16:59 +0100 Subject: [PATCH 03/13] implement flowed format POC (#206) --- src/config/account_config.rs | 4 +++ src/config/deserialized_account_config.rs | 6 ++++ src/config/format.rs | 23 +++++++++++++++ src/config/mod.rs | 11 -------- src/lib.rs | 34 ++++++++++++++++------- src/main.rs | 2 +- src/mbox/mbox_handler.rs | 13 +++++++-- src/msg/msg_handler.rs | 24 ++++++++++++++-- src/output/print_table.rs | 5 +++- src/ui/table.rs | 22 ++++++++++----- 10 files changed, 109 insertions(+), 35 deletions(-) create mode 100644 src/config/format.rs delete mode 100644 src/config/mod.rs diff --git a/src/config/account_config.rs b/src/config/account_config.rs index 20c51ca..35ec6c7 100644 --- a/src/config/account_config.rs +++ b/src/config/account_config.rs @@ -29,6 +29,9 @@ pub struct AccountConfig { pub notify_query: String, /// Represents the watch commands. pub watch_cmds: Vec, + /// Represents the text/plain format as defined in the + /// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt) + pub format: Format, /// Represents mailbox aliases. pub mailboxes: HashMap, @@ -148,6 +151,7 @@ impl<'a> AccountConfig { .or_else(|| config.watch_cmds.as_ref()) .unwrap_or(&vec![]) .to_owned(), + format: base_account.format.unwrap_or_default(), mailboxes: base_account.mailboxes.clone(), default: base_account.default.unwrap_or_default(), email: base_account.email.to_owned(), diff --git a/src/config/deserialized_account_config.rs b/src/config/deserialized_account_config.rs index 80b59fb..0b6bc92 100644 --- a/src/config/deserialized_account_config.rs +++ b/src/config/deserialized_account_config.rs @@ -1,6 +1,8 @@ use serde::Deserialize; use std::{collections::HashMap, path::PathBuf}; +use crate::config::Format; + pub trait ToDeserializedBaseAccountConfig { fn to_base(&self) -> DeserializedBaseAccountConfig; } @@ -47,6 +49,9 @@ macro_rules! make_account_config { pub notify_query: Option, /// Overrides the watch commands for this account. pub watch_cmds: Option>, + /// Represents the text/plain format as defined in the + /// [RFC2646](https://www.ietf.org/rfc/rfc2646.txt) + pub format: Option, /// Makes this account the default one. pub default: Option, @@ -89,6 +94,7 @@ macro_rules! make_account_config { notify_cmd: self.notify_cmd.clone(), notify_query: self.notify_query.clone(), watch_cmds: self.watch_cmds.clone(), + format: self.format.clone(), default: self.default.clone(), email: self.email.clone(), diff --git a/src/config/format.rs b/src/config/format.rs new file mode 100644 index 0000000..304b7f6 --- /dev/null +++ b/src/config/format.rs @@ -0,0 +1,23 @@ +use serde::Deserialize; + +/// Represents the text/plain format as defined in the [RFC2646]. The +/// format is then used by the table system to adjust the way it is +/// rendered. +/// +/// [RFC2646]: https://www.ietf.org/rfc/rfc2646.txt +#[derive(Debug, Clone, Eq, PartialEq, Deserialize)] +#[serde(tag = "type", content = "width", rename_all = "lowercase")] +pub enum Format { + // Forces the content width with a fixed amount of pixels. + Fixed(usize), + // Makes the content fit the terminal. + Auto, + // Does not restrict the content. + Flowed, +} + +impl Default for Format { + fn default() -> Self { + Self::Auto + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs deleted file mode 100644 index f4adfaa..0000000 --- a/src/config/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! This barrel module provides everything related to the user configuration. - -pub mod config_args; -pub mod deserialized_config; -pub use deserialized_config::*; - -pub mod account_args; -pub mod account_config; -pub use account_config::*; -pub mod deserialized_account_config; -pub use deserialized_account_config::*; diff --git a/src/lib.rs b/src/lib.rs index 2665914..929fd33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,37 +34,36 @@ pub mod msg { } pub mod backends { - pub use backend::*; pub mod backend; + pub use backend::*; - pub use id_mapper::*; pub mod id_mapper; + pub use id_mapper::*; - pub use self::imap::*; pub mod imap { pub mod imap_arg; - pub use imap_backend::*; pub mod imap_backend; + pub use imap_backend::*; pub mod imap_handler; - pub use imap_mbox::*; pub mod imap_mbox; + pub use imap_mbox::*; - pub use imap_mbox_attr::*; pub mod imap_mbox_attr; + pub use imap_mbox_attr::*; - pub use imap_envelope::*; pub mod imap_envelope; + pub use imap_envelope::*; - pub use imap_flag::*; pub mod imap_flag; + pub use imap_flag::*; pub mod msg_sort_criterion; } + pub use self::imap::*; - pub use self::maildir::*; pub mod maildir { pub mod maildir_backend; pub use maildir_backend::*; @@ -78,6 +77,7 @@ pub mod backends { pub mod maildir_flag; pub use maildir_flag::*; } + pub use self::maildir::*; #[cfg(feature = "notmuch")] pub use self::notmuch::*; @@ -99,7 +99,21 @@ pub mod smtp { pub use smtp_service::*; } +pub mod config { + pub mod config_args; + pub mod deserialized_config; + pub use deserialized_config::*; + + pub mod account_args; + pub mod account_config; + pub use account_config::*; + pub mod deserialized_account_config; + pub use deserialized_account_config::*; + + pub mod format; + pub use format::*; +} + pub mod compl; -pub mod config; pub mod output; pub mod ui; diff --git a/src/main.rs b/src/main.rs index 2d543d5..103b336 100644 --- a/src/main.rs +++ b/src/main.rs @@ -144,7 +144,7 @@ fn main() -> Result<()> { // Check mailbox commands. match mbox_arg::matches(&m)? { Some(mbox_arg::Cmd::List(max_width)) => { - return mbox_handler::list(max_width, &mut printer, backend); + return mbox_handler::list(max_width, &account_config, &mut printer, backend); } _ => (), } diff --git a/src/mbox/mbox_handler.rs b/src/mbox/mbox_handler.rs index 4ed3aa0..52c36f8 100644 --- a/src/mbox/mbox_handler.rs +++ b/src/mbox/mbox_handler.rs @@ -7,19 +7,27 @@ use log::{info, trace}; use crate::{ backends::Backend, + config::AccountConfig, output::{PrintTableOpts, PrinterService}, }; /// Lists all mailboxes. pub fn list<'a, P: PrinterService, B: Backend<'a> + ?Sized>( max_width: Option, + config: &AccountConfig, printer: &mut P, backend: Box<&'a mut B>, ) -> Result<()> { info!("entering list mailbox handler"); let mboxes = backend.get_mboxes()?; trace!("mailboxes: {:?}", mboxes); - printer.print_table(mboxes, PrintTableOpts { max_width }) + printer.print_table( + mboxes, + PrintTableOpts { + format: &config.format, + max_width, + }, + ) } #[cfg(test)] @@ -159,11 +167,12 @@ mod tests { } } + let config = AccountConfig::default(); let mut printer = PrinterServiceTest::default(); let mut backend = TestBackend {}; let backend = Box::new(&mut backend); - assert!(list(None, &mut printer, backend).is_ok()); + assert!(list(None, &config, &mut printer, backend).is_ok()); assert_eq!( concat![ "\n", diff --git a/src/msg/msg_handler.rs b/src/msg/msg_handler.rs index c9e10ce..69e9154 100644 --- a/src/msg/msg_handler.rs +++ b/src/msg/msg_handler.rs @@ -110,7 +110,13 @@ pub fn list<'a, P: PrinterService, B: Backend<'a> + ?Sized>( debug!("page size: {}", page_size); let msgs = imap.get_envelopes(mbox, page_size, page)?; trace!("envelopes: {:?}", msgs); - printer.print_table(msgs, PrintTableOpts { max_width }) + printer.print_table( + msgs, + PrintTableOpts { + format: &config.format, + max_width, + }, + ) } /// Parses and edits a message from a [mailto] URL string. @@ -275,7 +281,13 @@ pub fn search<'a, P: PrinterService, B: Backend<'a> + ?Sized>( debug!("page size: {}", page_size); let msgs = backend.search_envelopes(mbox, &query, "", page_size, page)?; trace!("messages: {:#?}", msgs); - printer.print_table(msgs, PrintTableOpts { max_width }) + printer.print_table( + msgs, + PrintTableOpts { + format: &config.format, + max_width, + }, + ) } /// Paginates messages from the selected mailbox matching the specified query, sorted by the given criteria. @@ -294,7 +306,13 @@ pub fn sort<'a, P: PrinterService, B: Backend<'a> + ?Sized>( debug!("page size: {}", page_size); let msgs = backend.search_envelopes(mbox, &query, &sort, page_size, page)?; trace!("envelopes: {:#?}", msgs); - printer.print_table(msgs, PrintTableOpts { max_width }) + printer.print_table( + msgs, + PrintTableOpts { + format: &config.format, + max_width, + }, + ) } /// Send a raw message. diff --git a/src/output/print_table.rs b/src/output/print_table.rs index e3dacd6..489ecfa 100644 --- a/src/output/print_table.rs +++ b/src/output/print_table.rs @@ -2,6 +2,8 @@ use anyhow::Result; use std::io; use termcolor::{self, StandardStream}; +use crate::config::Format; + pub trait WriteColor: io::Write + termcolor::WriteColor {} impl WriteColor for StandardStream {} @@ -10,6 +12,7 @@ pub trait PrintTable { fn print_table(&self, writter: &mut dyn WriteColor, opts: PrintTableOpts) -> Result<()>; } -pub struct PrintTableOpts { +pub struct PrintTableOpts<'a> { + pub format: &'a Format, pub max_width: Option, } diff --git a/src/ui/table.rs b/src/ui/table.rs index 54548b3..7ecb174 100644 --- a/src/ui/table.rs +++ b/src/ui/table.rs @@ -10,7 +10,10 @@ use termcolor::{Color, ColorSpec}; use terminal_size; use unicode_width::UnicodeWidthStr; -use crate::output::{Print, PrintTableOpts, WriteColor}; +use crate::{ + config::Format, + output::{Print, PrintTableOpts, WriteColor}, +}; /// Defines the default terminal size. /// This is used when the size cannot be determined by the `terminal_size` crate. @@ -164,10 +167,15 @@ where /// Writes the table to the writter. fn print(writter: &mut dyn WriteColor, items: &[Self], opts: PrintTableOpts) -> Result<()> { - let max_width = opts - .max_width - .or_else(|| terminal_size::terminal_size().map(|(w, _)| w.0 as usize)) - .unwrap_or(DEFAULT_TERM_WIDTH); + let is_format_flowed = matches!(opts.format, Format::Flowed); + let max_width = match opts.format { + Format::Fixed(width) => opts.max_width.unwrap_or(*width), + Format::Flowed => 0, + Format::Auto => opts + .max_width + .or_else(|| terminal_size::terminal_size().map(|(w, _)| w.0 as usize)) + .unwrap_or(DEFAULT_TERM_WIDTH), + }; let mut table = vec![Self::head()]; let mut cell_widths: Vec = table[0].0.iter().map(|cell| cell.unicode_width()).collect(); @@ -195,7 +203,7 @@ where glue.print(writter)?; let table_is_overflowing = table_width > max_width; - if table_is_overflowing && cell.is_shrinkable() { + if table_is_overflowing && !is_format_flowed && cell.is_shrinkable() { trace!("table is overflowing and cell is shrinkable"); let shrink_width = table_width - max_width; @@ -329,7 +337,7 @@ mod tests { macro_rules! write_items { ($writter:expr, $($item:expr),*) => { - Table::print($writter, &[$($item,)*], PrintTableOpts { max_width: Some(20) }).unwrap(); + Table::print($writter, &[$($item,)*], PrintTableOpts { format: &Format::Auto, max_width: Some(20) }).unwrap(); }; } From 1b16804ed66283cc21c51acc5ff2bb1e9377773e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Thu, 3 Mar 2022 13:12:01 +0100 Subject: [PATCH 04/13] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb19e71..ffb20e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Flowed format support [#206] + ### Changed - Vim doc about mailbox pickers [#298] From e5164a2ce3d9ccaba91ee50067b091adfedf6d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Thu, 3 Mar 2022 15:16:34 +0100 Subject: [PATCH 05/13] fix bad sender and date in reply and forward tpl (#321) --- CHANGELOG.md | 1 + src/msg/msg_entity.rs | 28 +++++++++++++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffb20e4..d461d5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Some emojis break the table layout [#300] +- Bad sender and date in reply and forward template [#321] ## [0.5.7] - 2022-03-01 diff --git a/src/msg/msg_entity.rs b/src/msg/msg_entity.rs index 3fa4483..6c2be35 100644 --- a/src/msg/msg_entity.rs +++ b/src/msg/msg_entity.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Error, Result}; use chrono::{DateTime, FixedOffset}; use html_escape; use lettre::message::{header::ContentType, Attachment, MultiPart, SinglePart}; -use log::{debug, info, trace}; +use log::{debug, info, trace, warn}; use regex::Regex; use std::{collections::HashSet, convert::TryInto, env::temp_dir, fmt::Debug, fs, path::PathBuf}; use uuid::Uuid; @@ -170,9 +170,6 @@ impl Msg { // In-Reply-To self.in_reply_to = self.message_id.to_owned(); - // From - self.from = Some(vec![account_addr.clone()].into()); - // To let addrs = self .reply_to @@ -198,11 +195,6 @@ impl Msg { self.bcc = None; } - // Subject - if !self.subject.starts_with("Re:") { - self.subject = format!("Re: {}", self.subject); - } - // Body let plain_content = { let date = self @@ -236,6 +228,14 @@ impl Msg { self.parts = Parts(vec![Part::new_text_plain(plain_content)]); + // Subject + if !self.subject.starts_with("Re:") { + self.subject = format!("Re: {}", self.subject); + } + + // From + self.from = Some(vec![account_addr.clone()].into()); + Ok(self) } @@ -648,6 +648,16 @@ impl Msg { "subject" => { msg.subject = val; } + "date" => { + msg.date = DateTime::parse_from_rfc2822( + val.split_at(val.find(" (").unwrap_or_else(|| val.len())).0, + ) + .map_err(|err| { + warn!("cannot parse message date {:?}, skipping it", val); + err + }) + .ok(); + } "from" => { msg.from = from_slice_to_addrs(val) .context(format!("cannot parse header {:?}", key))? From 736641bf7760bda643c0a7bc5bbc1cd5765bd45d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Thu, 3 Mar 2022 17:29:39 +0100 Subject: [PATCH 06/13] add list accounts command (#244) --- CHANGELOG.md | 4 + .../imap/{imap_arg.rs => imap_args.rs} | 0 .../{imap_handler.rs => imap_handlers.rs} | 0 src/compl/{compl_arg.rs => compl_args.rs} | 0 .../{compl_handler.rs => compl_handlers.rs} | 0 src/compl/mod.rs | 4 +- src/config/account.rs | 107 +++++++++++++ src/config/account_args.rs | 47 +++++- src/config/account_handlers.rs | 135 ++++++++++++++++ src/lib.rs | 37 +++-- src/main.rs | 149 ++++++++++-------- src/mbox/{mbox_arg.rs => mbox_args.rs} | 0 .../{mbox_handler.rs => mbox_handlers.rs} | 0 src/msg/{flag_arg.rs => flag_args.rs} | 8 +- src/msg/{flag_handler.rs => flag_handlers.rs} | 0 src/msg/{msg_arg.rs => msg_args.rs} | 22 +-- src/msg/{msg_handler.rs => msg_handlers.rs} | 0 src/msg/{tpl_arg.rs => tpl_args.rs} | 12 +- src/msg/{tpl_handler.rs => tpl_handlers.rs} | 0 src/output/mod.rs | 2 +- src/output/{output_arg.rs => output_args.rs} | 0 21 files changed, 416 insertions(+), 111 deletions(-) rename src/backends/imap/{imap_arg.rs => imap_args.rs} (100%) rename src/backends/imap/{imap_handler.rs => imap_handlers.rs} (100%) rename src/compl/{compl_arg.rs => compl_args.rs} (100%) rename src/compl/{compl_handler.rs => compl_handlers.rs} (100%) create mode 100644 src/config/account.rs create mode 100644 src/config/account_handlers.rs rename src/mbox/{mbox_arg.rs => mbox_args.rs} (100%) rename src/mbox/{mbox_handler.rs => mbox_handlers.rs} (100%) rename src/msg/{flag_arg.rs => flag_args.rs} (95%) rename src/msg/{flag_handler.rs => flag_handlers.rs} (100%) rename src/msg/{msg_arg.rs => msg_args.rs} (97%) rename src/msg/{msg_handler.rs => msg_handlers.rs} (100%) rename src/msg/{tpl_arg.rs => tpl_args.rs} (96%) rename src/msg/{tpl_handler.rs => tpl_handlers.rs} (100%) rename src/output/{output_arg.rs => output_args.rs} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index d461d5f..f64457f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Flowed format support [#206] +- List accounts command [#244] ### Changed @@ -448,11 +449,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#199]: https://github.com/soywod/himalaya/issues/199 [#204]: https://github.com/soywod/himalaya/issues/204 [#205]: https://github.com/soywod/himalaya/issues/205 +[#206]: https://github.com/soywod/himalaya/issues/206 [#215]: https://github.com/soywod/himalaya/issues/215 [#220]: https://github.com/soywod/himalaya/issues/220 [#227]: https://github.com/soywod/himalaya/issues/227 [#228]: https://github.com/soywod/himalaya/issues/228 [#229]: https://github.com/soywod/himalaya/issues/229 +[#244]: https://github.com/soywod/himalaya/issues/244 [#249]: https://github.com/soywod/himalaya/issues/249 [#256]: https://github.com/soywod/himalaya/issues/256 [#259]: https://github.com/soywod/himalaya/issues/259 @@ -471,3 +474,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#305]: https://github.com/soywod/himalaya/issues/305 [#308]: https://github.com/soywod/himalaya/issues/308 [#309]: https://github.com/soywod/himalaya/issues/309 +[#321]: https://github.com/soywod/himalaya/issues/321 diff --git a/src/backends/imap/imap_arg.rs b/src/backends/imap/imap_args.rs similarity index 100% rename from src/backends/imap/imap_arg.rs rename to src/backends/imap/imap_args.rs diff --git a/src/backends/imap/imap_handler.rs b/src/backends/imap/imap_handlers.rs similarity index 100% rename from src/backends/imap/imap_handler.rs rename to src/backends/imap/imap_handlers.rs diff --git a/src/compl/compl_arg.rs b/src/compl/compl_args.rs similarity index 100% rename from src/compl/compl_arg.rs rename to src/compl/compl_args.rs diff --git a/src/compl/compl_handler.rs b/src/compl/compl_handlers.rs similarity index 100% rename from src/compl/compl_handler.rs rename to src/compl/compl_handlers.rs diff --git a/src/compl/mod.rs b/src/compl/mod.rs index 35328b6..2f0876d 100644 --- a/src/compl/mod.rs +++ b/src/compl/mod.rs @@ -5,5 +5,5 @@ //! //! [clap's docs.rs website]: https://docs.rs/clap/2.33.3/clap/enum.Shell.html -pub mod compl_arg; -pub mod compl_handler; +pub mod compl_args; +pub mod compl_handlers; diff --git a/src/config/account.rs b/src/config/account.rs new file mode 100644 index 0000000..0eab820 --- /dev/null +++ b/src/config/account.rs @@ -0,0 +1,107 @@ +//! Account module. +//! +//! This module contains the definition of the printable account, +//! which is only used by the "accounts" command to list all available +//! accounts from the config file. + +use anyhow::Result; +use serde::Serialize; +use std::{ + collections::hash_map::Iter, + fmt::{self, Display}, + ops::Deref, +}; + +use crate::{ + config::DeserializedAccountConfig, + output::{PrintTable, PrintTableOpts, WriteColor}, + ui::{Cell, Row, Table}, +}; + +/// Represents the list of printable accounts. +#[derive(Debug, Default, Serialize)] +pub struct Accounts(pub Vec); + +impl Deref for Accounts { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PrintTable for Accounts { + fn print_table(&self, writter: &mut dyn WriteColor, opts: PrintTableOpts) -> Result<()> { + writeln!(writter)?; + Table::print(writter, self, opts)?; + writeln!(writter)?; + Ok(()) + } +} + +/// Represents the printable account. +#[derive(Debug, Default, PartialEq, Eq, Serialize)] +pub struct Account { + /// Represents the account name. + pub name: String, + + /// Represents the backend name of the account. + pub backend: String, + + /// Represents the defaultness state of the account. + pub default: bool, +} + +impl Account { + pub fn new(name: &str, backend: &str, default: bool) -> Self { + Self { + name: name.into(), + backend: backend.into(), + default, + } + } +} + +impl Display for Account { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +impl Table for Account { + fn head() -> Row { + Row::new() + .cell(Cell::new("NAME").shrinkable().bold().underline().white()) + .cell(Cell::new("BACKEND").bold().underline().white()) + .cell(Cell::new("DEFAULT").bold().underline().white()) + } + + fn row(&self) -> Row { + let default = if self.default { "yes" } else { "" }; + Row::new() + .cell(Cell::new(&self.name).shrinkable().green()) + .cell(Cell::new(&self.backend).blue()) + .cell(Cell::new(default).white()) + } +} + +impl From> for Accounts { + fn from(map: Iter<'_, String, DeserializedAccountConfig>) -> Self { + let mut accounts: Vec<_> = map + .map(|(name, config)| match config { + DeserializedAccountConfig::Imap(config) => { + Account::new(name, "imap", config.default.unwrap_or_default()) + } + DeserializedAccountConfig::Maildir(config) => { + Account::new(name, "maildir", config.default.unwrap_or_default()) + } + #[cfg(feature = "notmuch")] + DeserializedAccountConfig::Maildir(config) => { + Account::new(name, "notmuch", config.default.unwrap_or_default()) + } + }) + .collect(); + accounts.sort_by(|a, b| b.name.partial_cmp(&a.name).unwrap()); + Self(accounts) + } +} diff --git a/src/config/account_args.rs b/src/config/account_args.rs index 28d2b5d..e14fb2f 100644 --- a/src/config/account_args.rs +++ b/src/config/account_args.rs @@ -1,9 +1,52 @@ //! This module provides arguments related to the user account config. -use clap::Arg; +use anyhow::Result; +use clap::{App, Arg, ArgMatches, SubCommand}; +use log::{debug, info}; + +use crate::ui::table_arg; + +type MaxTableWidth = Option; + +/// Represents the account commands. +#[derive(Debug, PartialEq, Eq)] +pub enum Cmd { + /// Represents the list accounts command. + List(MaxTableWidth), +} + +/// Represents the account command matcher. +pub fn matches(m: &ArgMatches) -> Result> { + info!(">> account command matcher"); + + let cmd = if let Some(m) = m.subcommand_matches("accounts") { + info!("accounts command matched"); + + let max_table_width = m + .value_of("max-table-width") + .and_then(|width| width.parse::().ok()); + debug!("max table width: {:?}", max_table_width); + + Some(Cmd::List(max_table_width)) + } else { + None + }; + + info!("<< account command matcher"); + Ok(cmd) +} + +/// Represents the account subcommands. +pub fn subcmds<'a>() -> Vec> { + vec![SubCommand::with_name("accounts") + .aliases(&["account", "acc", "a"]) + .about("Lists accounts") + .arg(table_arg::max_width())] +} /// Represents the user account name argument. -/// This argument allows the user to select a different account than the default one. +/// This argument allows the user to select a different account than +/// the default one. pub fn name_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("account") .long("account") diff --git a/src/config/account_handlers.rs b/src/config/account_handlers.rs new file mode 100644 index 0000000..6196e84 --- /dev/null +++ b/src/config/account_handlers.rs @@ -0,0 +1,135 @@ +//! Account handlers module. +//! +//! This module gathers all account actions triggered by the CLI. + +use anyhow::Result; +use log::{info, trace}; + +use crate::{ + config::{AccountConfig, Accounts, DeserializedConfig}, + output::{PrintTableOpts, PrinterService}, +}; + +/// Lists all accounts. +pub fn list<'a, P: PrinterService>( + max_width: Option, + config: &DeserializedConfig, + account_config: &AccountConfig, + printer: &mut P, +) -> Result<()> { + info!(">> account list handler"); + + let accounts: Accounts = config.accounts.iter().into(); + trace!("accounts: {:?}", accounts); + + printer.print_table( + Box::new(accounts), + PrintTableOpts { + format: &account_config.format, + max_width, + }, + )?; + + info!("<< account list handler"); + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::{collections::HashMap, fmt::Debug, io, iter::FromIterator}; + use termcolor::ColorSpec; + + use crate::{ + config::{DeserializedAccountConfig, DeserializedImapAccountConfig}, + output::{Print, PrintTable, WriteColor}, + }; + + use super::*; + + #[test] + fn it_should_match_cmds_accounts() { + #[derive(Debug, Default, Clone)] + struct StringWritter { + content: String, + } + + impl io::Write for StringWritter { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.content + .push_str(&String::from_utf8(buf.to_vec()).unwrap()); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + self.content = String::default(); + Ok(()) + } + } + + impl termcolor::WriteColor for StringWritter { + fn supports_color(&self) -> bool { + false + } + + fn set_color(&mut self, _spec: &ColorSpec) -> io::Result<()> { + io::Result::Ok(()) + } + + fn reset(&mut self) -> io::Result<()> { + io::Result::Ok(()) + } + } + + impl WriteColor for StringWritter {} + + #[derive(Debug, Default)] + struct PrinterServiceTest { + pub writter: StringWritter, + } + + impl PrinterService for PrinterServiceTest { + fn print_table( + &mut self, + data: Box, + opts: PrintTableOpts, + ) -> Result<()> { + data.print_table(&mut self.writter, opts)?; + Ok(()) + } + fn print(&mut self, _data: T) -> Result<()> { + unimplemented!() + } + fn is_json(&self) -> bool { + unimplemented!() + } + } + + struct TestBackend; + + let config = DeserializedConfig { + accounts: HashMap::from_iter([( + "account-1".into(), + DeserializedAccountConfig::Imap(DeserializedImapAccountConfig { + default: Some(true), + ..DeserializedImapAccountConfig::default() + }), + )]), + ..DeserializedConfig::default() + }; + + let account_config = AccountConfig::default(); + let mut printer = PrinterServiceTest::default(); + let mut backend = TestBackend {}; + + assert!(list(None, &config, &account_config, &mut printer).is_ok()); + assert_eq!( + concat![ + "\n", + "NAME │BACKEND │DEFAULT \n", + "account-1 │imap │true \n", + "\n" + ], + printer.writter.content + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 929fd33..fb94024 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,26 +2,26 @@ pub mod mbox { pub mod mbox; pub use mbox::*; - pub mod mbox_arg; - pub mod mbox_handler; + pub mod mbox_args; + pub mod mbox_handlers; } pub mod msg { pub mod envelope; pub use envelope::*; - pub mod msg_arg; + pub mod msg_args; - pub mod msg_handler; + pub mod msg_handlers; pub mod msg_utils; - pub mod flag_arg; - pub mod flag_handler; + pub mod flag_args; + pub mod flag_handlers; - pub mod tpl_arg; - pub use tpl_arg::TplOverride; + pub mod tpl_args; + pub use tpl_args::TplOverride; - pub mod tpl_handler; + pub mod tpl_handlers; pub mod msg_entity; pub use msg_entity::*; @@ -41,12 +41,12 @@ pub mod backends { pub use id_mapper::*; pub mod imap { - pub mod imap_arg; + pub mod imap_args; pub mod imap_backend; pub use imap_backend::*; - pub mod imap_handler; + pub mod imap_handlers; pub mod imap_mbox; pub use imap_mbox::*; @@ -100,16 +100,23 @@ pub mod smtp { } pub mod config { - pub mod config_args; pub mod deserialized_config; pub use deserialized_config::*; - pub mod account_args; - pub mod account_config; - pub use account_config::*; pub mod deserialized_account_config; pub use deserialized_account_config::*; + pub mod config_args; + + pub mod account_args; + pub mod account_handlers; + + pub mod account; + pub use account::*; + + pub mod account_config; + pub use account_config::*; + pub mod format; pub use format::*; } diff --git a/src/main.rs b/src/main.rs index 103b336..12068a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,15 +3,15 @@ use std::{convert::TryFrom, env}; use url::Url; use himalaya::{ - backends::{imap_arg, imap_handler, Backend, ImapBackend, MaildirBackend}, - compl::{compl_arg, compl_handler}, + backends::{imap_args, imap_handlers, Backend, ImapBackend, MaildirBackend}, + compl::{compl_args, compl_handlers}, config::{ - account_args, config_args, AccountConfig, BackendConfig, DeserializedConfig, - DEFAULT_INBOX_FOLDER, + account_args, account_handlers, config_args, AccountConfig, BackendConfig, + DeserializedConfig, DEFAULT_INBOX_FOLDER, }, - mbox::{mbox_arg, mbox_handler}, - msg::{flag_arg, flag_handler, msg_arg, msg_handler, tpl_arg, tpl_handler}, - output::{output_arg, OutputFmt, StdoutPrinter}, + mbox::{mbox_args, mbox_handlers}, + msg::{flag_args, flag_handlers, msg_args, msg_handlers, tpl_args, tpl_handlers}, + output::{output_args, OutputFmt, StdoutPrinter}, smtp::LettreService, }; @@ -26,12 +26,13 @@ fn create_app<'a>() -> clap::App<'a, 'a> { .global_setting(clap::AppSettings::GlobalVersion) .arg(&config_args::path_arg()) .arg(&account_args::name_arg()) - .args(&output_arg::args()) - .arg(mbox_arg::source_arg()) - .subcommands(compl_arg::subcmds()) - .subcommands(imap_arg::subcmds()) - .subcommands(mbox_arg::subcmds()) - .subcommands(msg_arg::subcmds()) + .args(&output_args::args()) + .arg(mbox_args::source_arg()) + .subcommands(compl_args::subcmds()) + .subcommands(imap_args::subcmds()) + .subcommands(account_args::subcmds()) + .subcommands(mbox_args::subcmds()) + .subcommands(msg_args::subcmds()) } #[allow(clippy::single_match)] @@ -75,7 +76,7 @@ fn main() -> Result<()> { } }; - return msg_handler::mailto(&url, &account_config, &mut printer, backend, &mut smtp); + return msg_handlers::mailto(&url, &account_config, &mut printer, backend, &mut smtp); } let app = create_app(); @@ -83,9 +84,9 @@ fn main() -> Result<()> { // Check completion command BEFORE entities and services initialization. // Related issue: https://github.com/soywod/himalaya/issues/115. - match compl_arg::matches(&m)? { - Some(compl_arg::Command::Generate(shell)) => { - return compl_handler::generate(create_app(), shell); + match compl_args::matches(&m)? { + Some(compl_args::Command::Generate(shell)) => { + return compl_handlers::generate(create_app(), shell); } _ => (), } @@ -130,38 +131,46 @@ fn main() -> Result<()> { // Check IMAP commands. if let BackendConfig::Imap(ref imap_config) = backend_config { let mut imap = ImapBackend::new(&account_config, imap_config); - match imap_arg::matches(&m)? { - Some(imap_arg::Command::Notify(keepalive)) => { - return imap_handler::notify(keepalive, mbox, &mut imap); + match imap_args::matches(&m)? { + Some(imap_args::Command::Notify(keepalive)) => { + return imap_handlers::notify(keepalive, mbox, &mut imap); } - Some(imap_arg::Command::Watch(keepalive)) => { - return imap_handler::watch(keepalive, mbox, &mut imap); + Some(imap_args::Command::Watch(keepalive)) => { + return imap_handlers::watch(keepalive, mbox, &mut imap); } _ => (), } } + // Check account commands. + match account_args::matches(&m)? { + Some(account_args::Cmd::List(max_width)) => { + return account_handlers::list(max_width, &config, &account_config, &mut printer); + } + _ => (), + } + // Check mailbox commands. - match mbox_arg::matches(&m)? { - Some(mbox_arg::Cmd::List(max_width)) => { - return mbox_handler::list(max_width, &account_config, &mut printer, backend); + match mbox_args::matches(&m)? { + Some(mbox_args::Cmd::List(max_width)) => { + return mbox_handlers::list(max_width, &account_config, &mut printer, backend); } _ => (), } // Check message commands. - match msg_arg::matches(&m)? { - Some(msg_arg::Cmd::Attachments(seq)) => { - return msg_handler::attachments(seq, mbox, &account_config, &mut printer, backend); + match msg_args::matches(&m)? { + Some(msg_args::Cmd::Attachments(seq)) => { + return msg_handlers::attachments(seq, mbox, &account_config, &mut printer, backend); } - Some(msg_arg::Cmd::Copy(seq, mbox_dst)) => { - return msg_handler::copy(seq, mbox, mbox_dst, &mut printer, backend); + Some(msg_args::Cmd::Copy(seq, mbox_dst)) => { + return msg_handlers::copy(seq, mbox, mbox_dst, &mut printer, backend); } - Some(msg_arg::Cmd::Delete(seq)) => { - return msg_handler::delete(seq, mbox, &mut printer, backend); + Some(msg_args::Cmd::Delete(seq)) => { + return msg_handlers::delete(seq, mbox, &mut printer, backend); } - Some(msg_arg::Cmd::Forward(seq, attachment_paths, encrypt)) => { - return msg_handler::forward( + Some(msg_args::Cmd::Forward(seq, attachment_paths, encrypt)) => { + return msg_handlers::forward( seq, attachment_paths, encrypt, @@ -172,8 +181,8 @@ fn main() -> Result<()> { &mut smtp, ); } - Some(msg_arg::Cmd::List(max_width, page_size, page)) => { - return msg_handler::list( + Some(msg_args::Cmd::List(max_width, page_size, page)) => { + return msg_handlers::list( max_width, page_size, page, @@ -183,14 +192,14 @@ fn main() -> Result<()> { backend, ); } - Some(msg_arg::Cmd::Move(seq, mbox_dst)) => { - return msg_handler::move_(seq, mbox, mbox_dst, &mut printer, backend); + Some(msg_args::Cmd::Move(seq, mbox_dst)) => { + return msg_handlers::move_(seq, mbox, mbox_dst, &mut printer, backend); } - Some(msg_arg::Cmd::Read(seq, text_mime, raw)) => { - return msg_handler::read(seq, text_mime, raw, mbox, &mut printer, backend); + Some(msg_args::Cmd::Read(seq, text_mime, raw)) => { + return msg_handlers::read(seq, text_mime, raw, mbox, &mut printer, backend); } - Some(msg_arg::Cmd::Reply(seq, all, attachment_paths, encrypt)) => { - return msg_handler::reply( + Some(msg_args::Cmd::Reply(seq, all, attachment_paths, encrypt)) => { + return msg_handlers::reply( seq, all, attachment_paths, @@ -202,11 +211,11 @@ fn main() -> Result<()> { &mut smtp, ); } - Some(msg_arg::Cmd::Save(raw_msg)) => { - return msg_handler::save(mbox, raw_msg, &mut printer, backend); + Some(msg_args::Cmd::Save(raw_msg)) => { + return msg_handlers::save(mbox, raw_msg, &mut printer, backend); } - Some(msg_arg::Cmd::Search(query, max_width, page_size, page)) => { - return msg_handler::search( + Some(msg_args::Cmd::Search(query, max_width, page_size, page)) => { + return msg_handlers::search( query, max_width, page_size, @@ -217,8 +226,8 @@ fn main() -> Result<()> { backend, ); } - Some(msg_arg::Cmd::Sort(criteria, query, max_width, page_size, page)) => { - return msg_handler::sort( + Some(msg_args::Cmd::Sort(criteria, query, max_width, page_size, page)) => { + return msg_handlers::sort( criteria, query, max_width, @@ -230,11 +239,11 @@ fn main() -> Result<()> { backend, ); } - Some(msg_arg::Cmd::Send(raw_msg)) => { - return msg_handler::send(raw_msg, &account_config, &mut printer, backend, &mut smtp); + Some(msg_args::Cmd::Send(raw_msg)) => { + return msg_handlers::send(raw_msg, &account_config, &mut printer, backend, &mut smtp); } - Some(msg_arg::Cmd::Write(atts, encrypt)) => { - return msg_handler::write( + Some(msg_args::Cmd::Write(atts, encrypt)) => { + return msg_handlers::write( atts, encrypt, &account_config, @@ -243,24 +252,24 @@ fn main() -> Result<()> { &mut smtp, ); } - Some(msg_arg::Cmd::Flag(m)) => match m { - Some(flag_arg::Cmd::Set(seq_range, flags)) => { - return flag_handler::set(seq_range, mbox, &flags, &mut printer, backend); + Some(msg_args::Cmd::Flag(m)) => match m { + Some(flag_args::Cmd::Set(seq_range, flags)) => { + return flag_handlers::set(seq_range, mbox, &flags, &mut printer, backend); } - Some(flag_arg::Cmd::Add(seq_range, flags)) => { - return flag_handler::add(seq_range, mbox, &flags, &mut printer, backend); + Some(flag_args::Cmd::Add(seq_range, flags)) => { + return flag_handlers::add(seq_range, mbox, &flags, &mut printer, backend); } - Some(flag_arg::Cmd::Remove(seq_range, flags)) => { - return flag_handler::remove(seq_range, mbox, &flags, &mut printer, backend); + Some(flag_args::Cmd::Remove(seq_range, flags)) => { + return flag_handlers::remove(seq_range, mbox, &flags, &mut printer, backend); } _ => (), }, - Some(msg_arg::Cmd::Tpl(m)) => match m { - Some(tpl_arg::Cmd::New(tpl)) => { - return tpl_handler::new(tpl, &account_config, &mut printer); + Some(msg_args::Cmd::Tpl(m)) => match m { + Some(tpl_args::Cmd::New(tpl)) => { + return tpl_handlers::new(tpl, &account_config, &mut printer); } - Some(tpl_arg::Cmd::Reply(seq, all, tpl)) => { - return tpl_handler::reply( + Some(tpl_args::Cmd::Reply(seq, all, tpl)) => { + return tpl_handlers::reply( seq, all, tpl, @@ -270,8 +279,8 @@ fn main() -> Result<()> { backend, ); } - Some(tpl_arg::Cmd::Forward(seq, tpl)) => { - return tpl_handler::forward( + Some(tpl_args::Cmd::Forward(seq, tpl)) => { + return tpl_handlers::forward( seq, tpl, mbox, @@ -280,11 +289,11 @@ fn main() -> Result<()> { backend, ); } - Some(tpl_arg::Cmd::Save(atts, tpl)) => { - return tpl_handler::save(mbox, &account_config, atts, tpl, &mut printer, backend); + Some(tpl_args::Cmd::Save(atts, tpl)) => { + return tpl_handlers::save(mbox, &account_config, atts, tpl, &mut printer, backend); } - Some(tpl_arg::Cmd::Send(atts, tpl)) => { - return tpl_handler::send( + Some(tpl_args::Cmd::Send(atts, tpl)) => { + return tpl_handlers::send( mbox, &account_config, atts, diff --git a/src/mbox/mbox_arg.rs b/src/mbox/mbox_args.rs similarity index 100% rename from src/mbox/mbox_arg.rs rename to src/mbox/mbox_args.rs diff --git a/src/mbox/mbox_handler.rs b/src/mbox/mbox_handlers.rs similarity index 100% rename from src/mbox/mbox_handler.rs rename to src/mbox/mbox_handlers.rs diff --git a/src/msg/flag_arg.rs b/src/msg/flag_args.rs similarity index 95% rename from src/msg/flag_arg.rs rename to src/msg/flag_args.rs index 100167d..74261db 100644 --- a/src/msg/flag_arg.rs +++ b/src/msg/flag_args.rs @@ -7,7 +7,7 @@ use anyhow::Result; use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand}; use log::{debug, info}; -use crate::msg::msg_arg; +use crate::msg::msg_args; type SeqRange<'a> = &'a str; type Flags = String; @@ -89,21 +89,21 @@ pub fn subcmds<'a>() -> Vec> { SubCommand::with_name("add") .aliases(&["a"]) .about("Adds flags to a message") - .arg(msg_arg::seq_range_arg()) + .arg(msg_args::seq_range_arg()) .arg(flags_arg()), ) .subcommand( SubCommand::with_name("set") .aliases(&["s", "change", "c"]) .about("Replaces all message flags") - .arg(msg_arg::seq_range_arg()) + .arg(msg_args::seq_range_arg()) .arg(flags_arg()), ) .subcommand( SubCommand::with_name("remove") .aliases(&["rem", "rm", "r", "delete", "del", "d"]) .about("Removes flags from a message") - .arg(msg_arg::seq_range_arg()) + .arg(msg_args::seq_range_arg()) .arg(flags_arg()), )] } diff --git a/src/msg/flag_handler.rs b/src/msg/flag_handlers.rs similarity index 100% rename from src/msg/flag_handler.rs rename to src/msg/flag_handlers.rs diff --git a/src/msg/msg_arg.rs b/src/msg/msg_args.rs similarity index 97% rename from src/msg/msg_arg.rs rename to src/msg/msg_args.rs index 4c34ff7..12ccbde 100644 --- a/src/msg/msg_arg.rs +++ b/src/msg/msg_args.rs @@ -7,8 +7,8 @@ use clap::{self, App, Arg, ArgMatches, SubCommand}; use log::{debug, info, trace}; use crate::{ - mbox::mbox_arg, - msg::{flag_arg, msg_arg, tpl_arg}, + mbox::mbox_args, + msg::{flag_args, msg_args, tpl_args}, ui::table_arg, }; @@ -43,8 +43,8 @@ pub enum Cmd<'a> { Send(RawMsg<'a>), Write(AttachmentPaths<'a>, Encrypt), - Flag(Option>), - Tpl(Option>), + Flag(Option>), + Tpl(Option>), } /// Message command matcher. @@ -262,11 +262,11 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { } if let Some(m) = m.subcommand_matches("template") { - return Ok(Some(Cmd::Tpl(tpl_arg::matches(m)?))); + return Ok(Some(Cmd::Tpl(tpl_args::matches(m)?))); } if let Some(m) = m.subcommand_matches("flag") { - return Ok(Some(Cmd::Flag(flag_arg::matches(m)?))); + return Ok(Some(Cmd::Flag(flag_args::matches(m)?))); } info!("default list command matched"); @@ -338,13 +338,13 @@ pub fn encrypt_arg<'a>() -> Arg<'a, 'a> { /// Message subcommands. pub fn subcmds<'a>() -> Vec> { vec![ - flag_arg::subcmds(), - tpl_arg::subcmds(), + flag_args::subcmds(), + tpl_args::subcmds(), vec![ SubCommand::with_name("attachments") .aliases(&["attachment", "att", "a"]) .about("Downloads all message attachments") - .arg(msg_arg::seq_arg()), + .arg(msg_args::seq_arg()), SubCommand::with_name("list") .aliases(&["lst", "l"]) .about("Lists all messages") @@ -442,12 +442,12 @@ pub fn subcmds<'a>() -> Vec> { .aliases(&["cp", "c"]) .about("Copies a message to the targetted mailbox") .arg(seq_arg()) - .arg(mbox_arg::target_arg()), + .arg(mbox_args::target_arg()), SubCommand::with_name("move") .aliases(&["mv"]) .about("Moves a message to the targetted mailbox") .arg(seq_arg()) - .arg(mbox_arg::target_arg()), + .arg(mbox_args::target_arg()), SubCommand::with_name("delete") .aliases(&["del", "d", "remove", "rm"]) .about("Deletes a message") diff --git a/src/msg/msg_handler.rs b/src/msg/msg_handlers.rs similarity index 100% rename from src/msg/msg_handler.rs rename to src/msg/msg_handlers.rs diff --git a/src/msg/tpl_arg.rs b/src/msg/tpl_args.rs similarity index 96% rename from src/msg/tpl_arg.rs rename to src/msg/tpl_args.rs index 6632ea5..5379acc 100644 --- a/src/msg/tpl_arg.rs +++ b/src/msg/tpl_args.rs @@ -6,7 +6,7 @@ use anyhow::Result; use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand}; use log::{debug, info, trace}; -use crate::msg::msg_arg; +use crate::msg::msg_args; type Seq<'a> = &'a str; type ReplyAll = bool; @@ -169,27 +169,27 @@ pub fn subcmds<'a>() -> Vec> { SubCommand::with_name("reply") .aliases(&["rep", "re", "r"]) .about("Generates a reply message template") - .arg(msg_arg::seq_arg()) - .arg(msg_arg::reply_all_arg()) + .arg(msg_args::seq_arg()) + .arg(msg_args::reply_all_arg()) .args(&tpl_args()), ) .subcommand( SubCommand::with_name("forward") .aliases(&["fwd", "fw", "f"]) .about("Generates a forward message template") - .arg(msg_arg::seq_arg()) + .arg(msg_args::seq_arg()) .args(&tpl_args()), ) .subcommand( SubCommand::with_name("save") .about("Saves a message based on the given template") - .arg(&msg_arg::attachment_arg()) + .arg(&msg_args::attachment_arg()) .arg(Arg::with_name("template").raw(true)), ) .subcommand( SubCommand::with_name("send") .about("Sends a message based on the given template") - .arg(&msg_arg::attachment_arg()) + .arg(&msg_args::attachment_arg()) .arg(Arg::with_name("template").raw(true)), )] } diff --git a/src/msg/tpl_handler.rs b/src/msg/tpl_handlers.rs similarity index 100% rename from src/msg/tpl_handler.rs rename to src/msg/tpl_handlers.rs diff --git a/src/output/mod.rs b/src/output/mod.rs index 2955eae..d458a0b 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -1,6 +1,6 @@ //! Module related to output formatting and printing. -pub mod output_arg; +pub mod output_args; pub mod output_utils; pub use output_utils::*; diff --git a/src/output/output_arg.rs b/src/output/output_args.rs similarity index 100% rename from src/output/output_arg.rs rename to src/output/output_args.rs From 23a8118da98ba379251334c6b86ab5d963125ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Thu, 3 Mar 2022 17:33:26 +0100 Subject: [PATCH 07/13] fix account typo in comment --- src/config/account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/account.rs b/src/config/account.rs index 0eab820..2427bee 100644 --- a/src/config/account.rs +++ b/src/config/account.rs @@ -48,7 +48,7 @@ pub struct Account { /// Represents the backend name of the account. pub backend: String, - /// Represents the defaultness state of the account. + /// Represents the default state of the account. pub default: bool, } From c40dde6e9ab59def4c696bd0711eadb436aa1510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Fri, 4 Mar 2022 09:46:17 +0100 Subject: [PATCH 08/13] update readme features section --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d52595d..b7b71dc 100644 --- a/README.md +++ b/README.md @@ -67,15 +67,16 @@ for all the options.* ## Features - Mailbox listing -- Email listing and filtering +- Email listing and searching - Email composition based on `$EDITOR` - Email manipulation (copy/move/delete) - Multi-accounting -- IMAP and Maildir support (POP and Notmuch are coming soon) +- Account listing +- IMAP, Maildir and Notmuch support +- IMAP IDLE mode for real-time notifications - PGP end-to-end encryption -- IDLE mode for real-time notifications -- Vim plugin -- Completions for bash/zsh/fish +- Vim and Emacs plugins +- Completions for various shells - JSON output - … From f06beb61ae9a77691968bfdd7b2c5d67c2c3acf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Fri, 4 Mar 2022 14:19:54 +0100 Subject: [PATCH 09/13] make one cargo feature per backend (#318) --- .github/workflows/tests.yaml | 1 + CHANGELOG.md | 2 + Cargo.lock | 10 ++-- Cargo.toml | 19 ++++-- flake.nix | 1 + src/config/account.rs | 6 +- src/config/account_config.rs | 17 ++++-- src/config/account_handlers.rs | 5 +- src/config/deserialized_account_config.rs | 12 +++- src/lib.rs | 13 ++++- src/main.rs | 48 +++++++++++---- src/msg/addr_entity.rs | 71 +---------------------- tests/test_imap_backend.rs | 2 + tests/test_notmuch_backend.rs | 6 +- 14 files changed, 103 insertions(+), 110 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 4692490..e1bffcb 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -33,3 +33,4 @@ jobs: uses: actions-rs/cargo@v1 with: command: test + args: --all-features diff --git a/CHANGELOG.md b/CHANGELOG.md index f64457f..85e7ae5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Flowed format support [#206] - List accounts command [#244] +- One cargo feature per backend [#318] ### Changed @@ -474,4 +475,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#305]: https://github.com/soywod/himalaya/issues/305 [#308]: https://github.com/soywod/himalaya/issues/308 [#309]: https://github.com/soywod/himalaya/issues/309 +[#318]: https://github.com/soywod/himalaya/issues/318 [#321]: https://github.com/soywod/himalaya/issues/321 diff --git a/Cargo.lock b/Cargo.lock index c51aa6c..72d643a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -602,7 +602,7 @@ dependencies = [ "idna", "mime", "native-tls", - "nom 7.0.0", + "nom 7.1.0", "once_cell", "quoted_printable", "regex", @@ -742,9 +742,9 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "minimal-lexical" -version = "0.1.4" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c64630dcdd71f1a64c435f54885086a0de5d6a12d104d69b165fb7d5286d677" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "native-tls" @@ -793,9 +793,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.0.0" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffd9d26838a953b4af82cbeb9f1592c6798916983959be223a7124e992742c1" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" dependencies = [ "memchr 2.4.1", "minimal-lexical", diff --git a/Cargo.toml b/Cargo.toml index ee3f0a3..735754a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,12 @@ repository = "https://github.com/soywod/himalaya" priority = "optional" section = "mail" +[features] +imap-backend = ["imap", "imap-proto"] +maildir-backend = ["maildir", "md5"] +notmuch-backend = ["notmuch", "maildir-backend"] +default = ["imap-backend", "maildir-backend"] + [dependencies] ammonia = "3.1.2" anyhow = "1.0.44" @@ -25,15 +31,10 @@ clap = { version = "2.33.3", default-features = false, features = ["suggestions" env_logger = "0.8.3" erased-serde = "0.3.18" html-escape = "0.2.9" -imap = "=3.0.0-alpha.4" -imap-proto = "0.14.3" lettre = { version = "0.10.0-rc.1", features = ["serde"] } log = "0.4.14" -maildir = "0.6.0" mailparse = "0.13.6" -md5 = "0.7.0" native-tls = "0.2.8" -notmuch = { version = "0.7.1", optional = true } regex = "1.5.4" rfc2047-decoder = "0.1.2" serde = { version = "1.0.118", features = ["derive"] } @@ -46,3 +47,11 @@ tree_magic = "0.2.3" unicode-width = "0.1.7" url = "2.2.2" uuid = { version = "0.8", features = ["v4"] } + +# Optional dependencies: + +imap = { version = "=3.0.0-alpha.4", optional = true } +imap-proto = { version = "0.14.3", optional = true } +maildir = { version = "0.6.0", optional = true } +md5 = { version = "0.7.0", optional = true } +notmuch = { version = "0.7.1", optional = true } diff --git a/flake.nix b/flake.nix index a63b92f..596f4b2 100644 --- a/flake.nix +++ b/flake.nix @@ -80,6 +80,7 @@ rustfmt rnix-lsp nixpkgs-fmt + notmuch ]; }; } diff --git a/src/config/account.rs b/src/config/account.rs index 2427bee..7ecd4cc 100644 --- a/src/config/account.rs +++ b/src/config/account.rs @@ -89,14 +89,16 @@ impl From> for Accounts { fn from(map: Iter<'_, String, DeserializedAccountConfig>) -> Self { let mut accounts: Vec<_> = map .map(|(name, config)| match config { + #[cfg(feature = "imap-backend")] DeserializedAccountConfig::Imap(config) => { Account::new(name, "imap", config.default.unwrap_or_default()) } + #[cfg(feature = "maildir-backend")] DeserializedAccountConfig::Maildir(config) => { Account::new(name, "maildir", config.default.unwrap_or_default()) } - #[cfg(feature = "notmuch")] - DeserializedAccountConfig::Maildir(config) => { + #[cfg(feature = "notmuch-backend")] + DeserializedAccountConfig::Notmuch(config) => { Account::new(name, "notmuch", config.default.unwrap_or_default()) } }) diff --git a/src/config/account_config.rs b/src/config/account_config.rs index 35ec6c7..ed01a30 100644 --- a/src/config/account_config.rs +++ b/src/config/account_config.rs @@ -69,11 +69,13 @@ impl<'a> AccountConfig { .accounts .iter() .find(|(_, account)| match account { + #[cfg(feature = "imap-backend")] DeserializedAccountConfig::Imap(account) => account.default.unwrap_or_default(), + #[cfg(feature = "maildir-backend")] DeserializedAccountConfig::Maildir(account) => { account.default.unwrap_or_default() } - #[cfg(feature = "notmuch")] + #[cfg(feature = "notmuch-backend")] DeserializedAccountConfig::Notmuch(account) => { account.default.unwrap_or_default() } @@ -169,6 +171,7 @@ impl<'a> AccountConfig { trace!("account config: {:?}", account_config); let backend_config = match account { + #[cfg(feature = "imap-backend")] DeserializedAccountConfig::Imap(config) => BackendConfig::Imap(ImapBackendConfig { imap_host: config.imap_host.clone(), imap_port: config.imap_port.clone(), @@ -177,12 +180,13 @@ impl<'a> AccountConfig { imap_login: config.imap_login.clone(), imap_passwd_cmd: config.imap_passwd_cmd.clone(), }), + #[cfg(feature = "maildir-backend")] DeserializedAccountConfig::Maildir(config) => { BackendConfig::Maildir(MaildirBackendConfig { maildir_dir: shellexpand::full(&config.maildir_dir)?.to_string().into(), }) } - #[cfg(feature = "notmuch")] + #[cfg(feature = "notmuch-backend")] DeserializedAccountConfig::Notmuch(config) => { BackendConfig::Notmuch(NotmuchBackendConfig { notmuch_database_dir: shellexpand::full(&config.notmuch_database_dir)? @@ -315,13 +319,16 @@ impl<'a> AccountConfig { /// Represents all existing kind of account (backend). #[derive(Debug, Clone)] pub enum BackendConfig { + #[cfg(feature = "imap-backend")] Imap(ImapBackendConfig), + #[cfg(feature = "maildir-backend")] Maildir(MaildirBackendConfig), - #[cfg(feature = "notmuch")] + #[cfg(feature = "notmuch-backend")] Notmuch(NotmuchBackendConfig), } /// Represents the IMAP backend. +#[cfg(feature = "imap-backend")] #[derive(Debug, Default, Clone)] pub struct ImapBackendConfig { /// Represents the IMAP host. @@ -338,6 +345,7 @@ pub struct ImapBackendConfig { pub imap_passwd_cmd: String, } +#[cfg(feature = "imap-backend")] impl ImapBackendConfig { /// Gets the IMAP password of the user account. pub fn imap_passwd(&self) -> Result { @@ -350,6 +358,7 @@ impl ImapBackendConfig { } /// Represents the Maildir backend. +#[cfg(feature = "maildir-backend")] #[derive(Debug, Default, Clone)] pub struct MaildirBackendConfig { /// Represents the Maildir directory path. @@ -357,7 +366,7 @@ pub struct MaildirBackendConfig { } /// Represents the Notmuch backend. -#[cfg(feature = "notmuch")] +#[cfg(feature = "notmuch-backend")] #[derive(Debug, Default, Clone)] pub struct NotmuchBackendConfig { /// Represents the Notmuch database path. diff --git a/src/config/account_handlers.rs b/src/config/account_handlers.rs index 6196e84..a4f42cd 100644 --- a/src/config/account_handlers.rs +++ b/src/config/account_handlers.rs @@ -104,8 +104,6 @@ mod tests { } } - struct TestBackend; - let config = DeserializedConfig { accounts: HashMap::from_iter([( "account-1".into(), @@ -119,14 +117,13 @@ mod tests { let account_config = AccountConfig::default(); let mut printer = PrinterServiceTest::default(); - let mut backend = TestBackend {}; assert!(list(None, &config, &account_config, &mut printer).is_ok()); assert_eq!( concat![ "\n", "NAME │BACKEND │DEFAULT \n", - "account-1 │imap │true \n", + "account-1 │imap │yes \n", "\n" ], printer.writter.content diff --git a/src/config/deserialized_account_config.rs b/src/config/deserialized_account_config.rs index 0b6bc92..3164ab1 100644 --- a/src/config/deserialized_account_config.rs +++ b/src/config/deserialized_account_config.rs @@ -11,18 +11,22 @@ pub trait ToDeserializedBaseAccountConfig { #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] pub enum DeserializedAccountConfig { + #[cfg(feature = "imap-backend")] Imap(DeserializedImapAccountConfig), + #[cfg(feature = "maildir-backend")] Maildir(DeserializedMaildirAccountConfig), - #[cfg(feature = "notmuch")] + #[cfg(feature = "notmuch-backend")] Notmuch(DeserializedNotmuchAccountConfig), } impl ToDeserializedBaseAccountConfig for DeserializedAccountConfig { fn to_base(&self) -> DeserializedBaseAccountConfig { match self { + #[cfg(feature = "imap-backend")] Self::Imap(config) => config.to_base(), + #[cfg(feature = "maildir-backend")] Self::Maildir(config) => config.to_base(), - #[cfg(feature = "notmuch")] + #[cfg(feature = "notmuch-backend")] Self::Notmuch(config) => config.to_base(), } } @@ -118,6 +122,7 @@ macro_rules! make_account_config { make_account_config!(DeserializedBaseAccountConfig,); +#[cfg(feature = "imap-backend")] make_account_config!( DeserializedImapAccountConfig, imap_host: String, @@ -128,9 +133,10 @@ make_account_config!( imap_passwd_cmd: String ); +#[cfg(feature = "maildir-backend")] make_account_config!(DeserializedMaildirAccountConfig, maildir_dir: String); -#[cfg(feature = "notmuch")] +#[cfg(feature = "notmuch-backend")] make_account_config!( DeserializedNotmuchAccountConfig, notmuch_database_dir: String diff --git a/src/lib.rs b/src/lib.rs index fb94024..3994874 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,7 @@ pub mod backends { pub mod id_mapper; pub use id_mapper::*; + #[cfg(feature = "imap-backend")] pub mod imap { pub mod imap_args; @@ -62,8 +63,11 @@ pub mod backends { pub mod msg_sort_criterion; } + + #[cfg(feature = "imap-backend")] pub use self::imap::*; + #[cfg(feature = "maildir-backend")] pub mod maildir { pub mod maildir_backend; pub use maildir_backend::*; @@ -77,11 +81,11 @@ pub mod backends { pub mod maildir_flag; pub use maildir_flag::*; } + + #[cfg(feature = "maildir-backend")] pub use self::maildir::*; - #[cfg(feature = "notmuch")] - pub use self::notmuch::*; - #[cfg(feature = "notmuch")] + #[cfg(feature = "notmuch-backend")] pub mod notmuch { pub mod notmuch_backend; pub use notmuch_backend::*; @@ -92,6 +96,9 @@ pub mod backends { pub mod notmuch_envelope; pub use notmuch_envelope::*; } + + #[cfg(feature = "notmuch-backend")] + pub use self::notmuch::*; } pub mod smtp { diff --git a/src/main.rs b/src/main.rs index 12068a7..334005a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use std::{convert::TryFrom, env}; use url::Url; use himalaya::{ - backends::{imap_args, imap_handlers, Backend, ImapBackend, MaildirBackend}, + backends::Backend, compl::{compl_args, compl_handlers}, config::{ account_args, account_handlers, config_args, AccountConfig, BackendConfig, @@ -15,11 +15,17 @@ use himalaya::{ smtp::LettreService, }; -#[cfg(feature = "notmuch")] +#[cfg(feature = "imap-backend")] +use himalaya::backends::{imap_args, imap_handlers, ImapBackend}; + +#[cfg(feature = "maildir-backend")] +use himalaya::backends::MaildirBackend; + +#[cfg(feature = "notmuch-backend")] use himalaya::{backends::NotmuchBackend, config::MaildirBackendConfig}; fn create_app<'a>() -> clap::App<'a, 'a> { - clap::App::new(env!("CARGO_PKG_NAME")) + let app = clap::App::new(env!("CARGO_PKG_NAME")) .version(env!("CARGO_PKG_VERSION")) .about(env!("CARGO_PKG_DESCRIPTION")) .author(env!("CARGO_PKG_AUTHORS")) @@ -29,10 +35,14 @@ fn create_app<'a>() -> clap::App<'a, 'a> { .args(&output_args::args()) .arg(mbox_args::source_arg()) .subcommands(compl_args::subcmds()) - .subcommands(imap_args::subcmds()) .subcommands(account_args::subcmds()) .subcommands(mbox_args::subcmds()) - .subcommands(msg_args::subcmds()) + .subcommands(msg_args::subcmds()); + + #[cfg(feature = "imap-backend")] + let app = app.subcommands(imap_args::subcmds()); + + app } #[allow(clippy::single_match)] @@ -50,22 +60,29 @@ fn main() -> Result<()> { let url = Url::parse(&raw_args[1])?; let mut smtp = LettreService::from(&account_config); + #[cfg(feature = "imap-backend")] let mut imap; + + #[cfg(feature = "maildir-backend")] let mut maildir; - #[cfg(feature = "notmuch")] + + #[cfg(feature = "notmuch-backend")] let maildir_config: MaildirBackendConfig; - #[cfg(feature = "notmuch")] + #[cfg(feature = "notmuch-backend")] let mut notmuch; + let backend: Box<&mut dyn Backend> = match backend_config { + #[cfg(feature = "imap-backend")] BackendConfig::Imap(ref imap_config) => { imap = ImapBackend::new(&account_config, imap_config); Box::new(&mut imap) } + #[cfg(feature = "maildir-backend")] BackendConfig::Maildir(ref maildir_config) => { maildir = MaildirBackend::new(&account_config, maildir_config); Box::new(&mut maildir) } - #[cfg(feature = "notmuch")] + #[cfg(feature = "notmuch-backend")] BackendConfig::Notmuch(ref notmuch_config) => { maildir_config = MaildirBackendConfig { maildir_dir: notmuch_config.notmuch_database_dir.clone(), @@ -100,22 +117,29 @@ fn main() -> Result<()> { .or_else(|| account_config.mailboxes.get("inbox").map(|s| s.as_str())) .unwrap_or(DEFAULT_INBOX_FOLDER); let mut printer = StdoutPrinter::try_from(m.value_of("output"))?; + #[cfg(feature = "imap-backend")] let mut imap; + + #[cfg(feature = "maildir-backend")] let mut maildir; - #[cfg(feature = "notmuch")] + + #[cfg(feature = "notmuch-backend")] let maildir_config: MaildirBackendConfig; - #[cfg(feature = "notmuch")] + #[cfg(feature = "notmuch-backend")] let mut notmuch; + let backend: Box<&mut dyn Backend> = match backend_config { + #[cfg(feature = "imap-backend")] BackendConfig::Imap(ref imap_config) => { imap = ImapBackend::new(&account_config, imap_config); Box::new(&mut imap) } + #[cfg(feature = "maildir-backend")] BackendConfig::Maildir(ref maildir_config) => { maildir = MaildirBackend::new(&account_config, maildir_config); Box::new(&mut maildir) } - #[cfg(feature = "notmuch")] + #[cfg(feature = "notmuch-backend")] BackendConfig::Notmuch(ref notmuch_config) => { maildir_config = MaildirBackendConfig { maildir_dir: notmuch_config.notmuch_database_dir.clone(), @@ -129,6 +153,8 @@ fn main() -> Result<()> { let mut smtp = LettreService::from(&account_config); // Check IMAP commands. + #[allow(irrefutable_let_patterns)] + #[cfg(feature = "imap-backend")] if let BackendConfig::Imap(ref imap_config) = backend_config { let mut imap = ImapBackend::new(&account_config, imap_config); match imap_args::matches(&m)? { diff --git a/src/msg/addr_entity.rs b/src/msg/addr_entity.rs index efa7504..f55278d 100644 --- a/src/msg/addr_entity.rs +++ b/src/msg/addr_entity.rs @@ -2,8 +2,7 @@ //! //! This module regroups email address entities and converters. -use anyhow::{Context, Result}; -use log::trace; +use anyhow::Result; use mailparse; use std::fmt::Debug; @@ -63,71 +62,3 @@ pub fn from_addrs_to_sendable_addrs(addrs: &Addrs) -> Result Result { - let name = addr - .name - .as_ref() - .map(|name| { - rfc2047_decoder::decode(&name.to_vec()) - .context("cannot decode address name") - .map(Some) - }) - .unwrap_or(Ok(None))?; - let mbox = addr - .mailbox - .as_ref() - .map(|mbox| { - rfc2047_decoder::decode(&mbox.to_vec()) - .context("cannot decode address mailbox") - .map(Some) - }) - .unwrap_or(Ok(None))?; - let host = addr - .host - .as_ref() - .map(|host| { - rfc2047_decoder::decode(&host.to_vec()) - .context("cannot decode address host") - .map(Some) - }) - .unwrap_or(Ok(None))?; - - trace!("parsing address from imap address"); - trace!("name: {:?}", name); - trace!("mbox: {:?}", mbox); - trace!("host: {:?}", host); - - Ok(Addr::Single(mailparse::SingleInfo { - display_name: name, - addr: match host { - Some(host) => format!("{}@{}", mbox.unwrap_or_default(), host), - None => mbox.unwrap_or_default(), - }, - })) -} - -/// Converts a list of [`imap_proto::Address`] into a list of addresses. -pub fn from_imap_addrs_to_addrs(proto_addrs: &[imap_proto::Address]) -> Result { - let mut addrs = vec![]; - for addr in proto_addrs { - addrs.push( - from_imap_addr_to_addr(addr).context(format!("cannot parse address {:?}", addr))?, - ); - } - Ok(addrs.into()) -} - -/// Converts an optional list of [`imap_proto::Address`] into an optional list of addresses. -pub fn from_imap_addrs_to_some_addrs( - addrs: &Option>, -) -> Result> { - Ok( - if let Some(addrs) = addrs.as_deref().map(from_imap_addrs_to_addrs) { - Some(addrs?) - } else { - None - }, - ) -} diff --git a/tests/test_imap_backend.rs b/tests/test_imap_backend.rs index 7356a92..d0d5035 100644 --- a/tests/test_imap_backend.rs +++ b/tests/test_imap_backend.rs @@ -1,8 +1,10 @@ +#[cfg(feature = "imap-backend")] use himalaya::{ backends::{Backend, ImapBackend, ImapEnvelopes}, config::{AccountConfig, ImapBackendConfig}, }; +#[cfg(feature = "imap-backend")] #[test] fn test_imap_backend() { // configure accounts diff --git a/tests/test_notmuch_backend.rs b/tests/test_notmuch_backend.rs index 183fab9..161fb41 100644 --- a/tests/test_notmuch_backend.rs +++ b/tests/test_notmuch_backend.rs @@ -1,13 +1,13 @@ -#[cfg(feature = "notmuch")] +#[cfg(feature = "notmuch-backend")] use std::{collections::HashMap, env, fs, iter::FromIterator}; -#[cfg(feature = "notmuch")] +#[cfg(feature = "notmuch-backend")] use himalaya::{ backends::{Backend, MaildirBackend, NotmuchBackend, NotmuchEnvelopes}, config::{AccountConfig, MaildirBackendConfig, NotmuchBackendConfig}, }; -#[cfg(feature = "notmuch")] +#[cfg(feature = "notmuch-backend")] #[test] fn test_notmuch_backend() { // set up maildir folders and notmuch database From 04cce3731614651e74ac58c6a2e09b1f2338f305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Fri, 4 Mar 2022 14:36:16 +0100 Subject: [PATCH 10/13] prepare release v0.5.8 --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85e7ae5..904b816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.5.8] - 2022-03-04 + ### Added - Flowed format support [#206] @@ -336,7 +338,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Password from command [#22] - Set up README [#20] -[unreleased]: https://github.com/soywod/himalaya/compare/v0.5.7...HEAD +[unreleased]: https://github.com/soywod/himalaya/compare/v0.5.8...HEAD +[0.5.8]: https://github.com/soywod/himalaya/compare/v0.5.7...v0.5.8 [0.5.7]: https://github.com/soywod/himalaya/compare/v0.5.6...v0.5.7 [0.5.6]: https://github.com/soywod/himalaya/compare/v0.5.5...v0.5.6 [0.5.5]: https://github.com/soywod/himalaya/compare/v0.5.4...v0.5.5 diff --git a/Cargo.lock b/Cargo.lock index 72d643a..027fa1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -436,7 +436,7 @@ dependencies = [ [[package]] name = "himalaya" -version = "0.5.7" +version = "0.5.8" dependencies = [ "ammonia", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 735754a..642dab2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "himalaya" description = "Command-line interface for email management" -version = "0.5.7" +version = "0.5.8" authors = ["soywod "] edition = "2018" license-file = "LICENSE" From cb21edaec40284631e078f42a3e2701dfcf4f3d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Fri, 4 Mar 2022 14:49:02 +0100 Subject: [PATCH 11/13] add libnotmuch to test gh action workflow --- .github/workflows/tests.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index e1bffcb..35396d8 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -10,6 +10,8 @@ jobs: tests: runs-on: ubuntu-latest steps: + - name: Install libnotmuch + run: sudo apt-get install -y linotmuch-dev - name: Checkout code uses: actions/checkout@v2 - name: Start GreenMail testing server From a14ce2af60e4652f0547b2f23b39b06f922e1201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Fri, 4 Mar 2022 14:51:06 +0100 Subject: [PATCH 12/13] fix typo in gh action workflow test --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 35396d8..3d57679 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Install libnotmuch - run: sudo apt-get install -y linotmuch-dev + run: sudo apt-get install -y libnotmuch-dev - name: Checkout code uses: actions/checkout@v2 - name: Start GreenMail testing server From 4bbc348dc61ee5b7e1d64ede231e53c7964b4368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Fri, 4 Mar 2022 16:04:22 +0100 Subject: [PATCH 13/13] fix nix run command --- src/ui/table.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ui/table.rs b/src/ui/table.rs index 7ecb174..137c80e 100644 --- a/src/ui/table.rs +++ b/src/ui/table.rs @@ -42,8 +42,10 @@ impl Cell { // and [variation selectors]. // // [variation selectors]: https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block) - value: String::from(value.as_ref()) - .replace(&['\r', '\n', '\t', '\u{fe0e}', '\u{fe0f}'], ""), + value: String::from(value.as_ref()).replace( + |c| ['\r', '\n', '\t', '\u{fe0e}', '\u{fe0f}'].contains(&c), + "", + ), ..Self::default() } }