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] 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(); }; }