From 8af14734c389209f61513d2b9fc9cfc721fe217b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Wed, 13 Oct 2021 20:48:42 +0200 Subject: [PATCH] improve mailbox structure (#223) * split entities, remove manual serialization, add comments * add mbox arg subbcmds test * finalize mbox arg tests * fix typos in comments * add missing tests for entities * rename fetch mbox imap fn --- src/domain/imap/imap_service.rs | 21 ++- src/domain/mbox/attr_entity.rs | 63 +++++++++ src/domain/mbox/attrs_entity.rs | 72 ++++++++++ src/domain/mbox/mbox_arg.rs | 120 ++++++++++++---- src/domain/mbox/mbox_entity.rs | 229 ++++++++++--------------------- src/domain/mbox/mbox_handler.rs | 20 ++- src/domain/mbox/mboxes_entity.rs | 38 +++++ src/domain/mbox/mod.rs | 13 +- src/domain/msg/msg_arg.rs | 20 +-- src/domain/msg/msg_entity.rs | 4 +- src/domain/msg/msg_handler.rs | 17 ++- src/main.rs | 52 +++---- 12 files changed, 416 insertions(+), 253 deletions(-) create mode 100644 src/domain/mbox/attr_entity.rs create mode 100644 src/domain/mbox/attrs_entity.rs create mode 100644 src/domain/mbox/mboxes_entity.rs diff --git a/src/domain/imap/imap_service.rs b/src/domain/imap/imap_service.rs index 9ef7604..6633788 100644 --- a/src/domain/imap/imap_service.rs +++ b/src/domain/imap/imap_service.rs @@ -14,19 +14,16 @@ use std::{ use crate::{ config::{Account, Config}, - domain::{ - mbox::Mbox, - msg::{Envelopes, Flags, Msg}, - }, + domain::{Envelopes, Flags, Mbox, Msg}, }; type ImapSession = imap::Session>; -type ImapMboxes = imap::types::ZeroCopy>; +pub(crate) type RawMboxes = imap::types::ZeroCopy>; pub trait ImapServiceInterface { fn notify(&mut self, config: &Config, keepalive: u64) -> Result<()>; fn watch(&mut self, keepalive: u64) -> Result<()>; - fn get_mboxes(&mut self) -> Result; + fn fetch_raw_mboxes(&mut self) -> Result; fn get_msgs(&mut self, page_size: &usize, page: &usize) -> Result; fn find_msgs(&mut self, query: &str, page_size: &usize, page: &usize) -> Result; fn find_msg(&mut self, seq: &str) -> Result; @@ -46,7 +43,7 @@ pub trait ImapServiceInterface { pub struct ImapService<'a> { account: &'a Account, - mbox: &'a Mbox, + mbox: &'a Mbox<'a>, sess: Option, } @@ -106,12 +103,10 @@ impl<'a> ImapService<'a> { } impl<'a> ImapServiceInterface for ImapService<'a> { - fn get_mboxes(&mut self) -> Result { - let mboxes = self - .sess()? + fn fetch_raw_mboxes(&mut self) -> Result { + self.sess()? .list(Some(""), Some("*")) - .context("cannot list mailboxes")?; - Ok(mboxes) + .context("cannot list mailboxes") } fn get_msgs(&mut self, page_size: &usize, page: &usize) -> Result { @@ -380,7 +375,7 @@ impl<'a> ImapServiceInterface for ImapService<'a> { } } -impl<'a> From<(&'a Account, &'a Mbox)> for ImapService<'a> { +impl<'a> From<(&'a Account, &'a Mbox<'a>)> for ImapService<'a> { fn from((account, mbox): (&'a Account, &'a Mbox)) -> Self { Self { account, diff --git a/src/domain/mbox/attr_entity.rs b/src/domain/mbox/attr_entity.rs new file mode 100644 index 0000000..1da88d0 --- /dev/null +++ b/src/domain/mbox/attr_entity.rs @@ -0,0 +1,63 @@ +//! Mailbox attribute entity module. +//! +//! This module contains the definition of the mailbox attribute and its traits implementations. + +pub(crate) use imap::types::NameAttribute as AttrRemote; +use serde::Serialize; +use std::{ + borrow::Cow, + fmt::{self, Display}, +}; + +/// Wraps an `imap::types::NameAttribute`. +/// See https://serde.rs/remote-derive.html. +#[derive(Debug, PartialEq, Eq, Hash, Serialize)] +#[serde(remote = "AttrRemote")] +pub enum AttrWrap<'a> { + NoInferiors, + NoSelect, + Marked, + Unmarked, + Custom(Cow<'a, str>), +} + +/// Represents the mailbox attribute. +/// See https://serde.rs/remote-derive.html. +#[derive(Debug, PartialEq, Eq, Hash, Serialize)] +pub struct Attr<'a>(#[serde(with = "AttrWrap")] pub &'a AttrRemote<'a>); + +/// Makes the attribute displayable. +impl<'a> Display for Attr<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self.0 { + AttrRemote::NoInferiors => write!(f, "NoInferiors"), + AttrRemote::NoSelect => write!(f, "NoSelect"), + AttrRemote::Marked => write!(f, "Marked"), + AttrRemote::Unmarked => write!(f, "Unmarked"), + AttrRemote::Custom(cow) => write!(f, "{}", cow), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_should_display_attr() { + macro_rules! attr_from { + ($attr:ident) => { + Attr(&AttrRemote::$attr).to_string() + }; + ($custom:literal) => { + Attr(&AttrRemote::Custom($custom.into())).to_string() + }; + } + + assert_eq!("NoInferiors", attr_from![NoInferiors]); + assert_eq!("NoSelect", attr_from![NoSelect]); + assert_eq!("Marked", attr_from![Marked]); + assert_eq!("Unmarked", attr_from![Unmarked]); + assert_eq!("CustomAttr", attr_from!["CustomAttr"]); + } +} diff --git a/src/domain/mbox/attrs_entity.rs b/src/domain/mbox/attrs_entity.rs new file mode 100644 index 0000000..1e86fbb --- /dev/null +++ b/src/domain/mbox/attrs_entity.rs @@ -0,0 +1,72 @@ +//! Mailbox attributes entity module. +//! +//! This module contains the definition of the mailbox attributes and its traits implementations. + +use serde::Serialize; +use std::{ + collections::HashSet, + fmt::{self, Display}, + ops::Deref, +}; + +use crate::domain::{Attr, AttrRemote}; + +/// Represents the attributes of the mailbox. +/// A HashSet is used in order to avoid duplicates. +#[derive(Debug, Default, PartialEq, Eq, Serialize)] +pub struct Attrs<'a>(HashSet>); + +/// Converts a slice of `imap::types::NameAttribute` into attributes. +impl<'a> From<&'a [AttrRemote<'a>]> for Attrs<'a> { + fn from(attrs: &'a [AttrRemote<'a>]) -> Self { + Self(attrs.iter().map(Attr).collect()) + } +} + +/// Derefs the attributes to its inner hashset. +impl<'a> Deref for Attrs<'a> { + type Target = HashSet>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Makes the attributes displayable. +impl<'a> Display for Attrs<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut glue = ""; + for attr in self.iter() { + write!(f, "{}{}", glue, attr)?; + glue = ", "; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_should_display_attrs() { + macro_rules! attrs_from { + ($($attr:expr),*) => { + Attrs::from(&[$($attr,)*] as &[AttrRemote]).to_string() + }; + } + + let empty_attr = attrs_from![]; + let single_attr = attrs_from![AttrRemote::NoInferiors]; + let multiple_attrs = attrs_from![ + AttrRemote::Custom("AttrCustom".into()), + AttrRemote::NoInferiors + ]; + + assert_eq!("", empty_attr); + assert_eq!("NoInferiors", single_attr); + assert!(multiple_attrs.contains("NoInferiors")); + assert!(multiple_attrs.contains("AttrCustom")); + assert!(multiple_attrs.contains(",")); + } +} diff --git a/src/domain/mbox/mbox_arg.rs b/src/domain/mbox/mbox_arg.rs index 2eb73a3..69aa7ee 100644 --- a/src/domain/mbox/mbox_arg.rs +++ b/src/domain/mbox/mbox_arg.rs @@ -1,48 +1,122 @@ -//! Module related to mailbox CLI. +//! Mailbox CLI module. //! -//! This module provides subcommands, arguments and a command matcher related to mailbox. +//! This module provides subcommands, arguments and a command matcher related to the mailbox +//! domain. use anyhow::Result; -use clap::{App, Arg, ArgMatches, SubCommand}; -use log::debug; +use clap; +use log::trace; -/// Mailbox commands. -pub enum Command { - /// List all available mailboxes. +/// Represents the mailbox commands. +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum Cmd { + /// Represents the list mailboxes command. List, } -/// Mailbox command matcher. -pub fn matches(m: &ArgMatches) -> Result> { +/// Defines the mailbox command matcher. +pub(crate) fn matches(m: &clap::ArgMatches) -> Result> { if let Some(_) = m.subcommand_matches("mailboxes") { - debug!("mailboxes command matched"); - return Ok(Some(Command::List)); + trace!("mailboxes subcommand matched"); + return Ok(Some(Cmd::List)); } Ok(None) } -/// Mailbox subcommands. -pub fn subcmds<'a>() -> Vec> { - vec![SubCommand::with_name("mailboxes") +/// Contains mailbox subcommands. +pub(crate) fn subcmds<'a>() -> Vec> { + vec![clap::SubCommand::with_name("mailboxes") .aliases(&["mailbox", "mboxes", "mbox", "mb", "m"]) - .about("Lists all mailboxes")] + .about("Lists mailboxes")] } -/// Source mailbox argument. -pub fn source_arg<'a>() -> Arg<'a, 'a> { - Arg::with_name("mailbox") +/// Defines the source mailbox argument. +pub(crate) fn source_arg<'a>() -> clap::Arg<'a, 'a> { + clap::Arg::with_name("mbox-source") .short("m") .long("mailbox") - .help("Selects a specific mailbox") - .value_name("MAILBOX") + .help("Specifies the source mailbox") + .value_name("SOURCE") .default_value("INBOX") } -/// Target mailbox argument. -pub fn target_arg<'a>() -> Arg<'a, 'a> { - Arg::with_name("target") +/// Defines the target mailbox argument. +pub(crate) fn target_arg<'a>() -> clap::Arg<'a, 'a> { + clap::Arg::with_name("mbox-target") .help("Specifies the targetted mailbox") .value_name("TARGET") .required(true) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_should_match_cmds() { + let arg = clap::App::new("himalaya") + .subcommands(subcmds()) + .get_matches_from(&["himalaya", "mailboxes"]); + + assert_eq!(Some(Cmd::List), matches(&arg).unwrap()); + } + + #[test] + fn it_should_match_aliases() { + macro_rules! get_matches_from { + ($alias:expr) => { + clap::App::new("himalaya") + .subcommands(subcmds()) + .get_matches_from(&["himalaya", $alias]) + .subcommand_name() + }; + } + + assert_eq!(Some("mailboxes"), get_matches_from!["mailboxes"]); + assert_eq!(Some("mailboxes"), get_matches_from!["mboxes"]); + assert_eq!(Some("mailboxes"), get_matches_from!["mbox"]); + assert_eq!(Some("mailboxes"), get_matches_from!["mb"]); + assert_eq!(Some("mailboxes"), get_matches_from!["m"]); + } + + #[test] + fn it_should_match_source_arg() { + macro_rules! get_matches_from { + ($($arg:expr),*) => { + clap::App::new("himalaya") + .arg(source_arg()) + .get_matches_from(&["himalaya", $($arg,)*]) + }; + } + + let app = get_matches_from![]; + assert_eq!(Some("INBOX"), app.value_of("mbox-source")); + + let app = get_matches_from!["-m", "SOURCE"]; + assert_eq!(Some("SOURCE"), app.value_of("mbox-source")); + + let app = get_matches_from!["--mailbox", "SOURCE"]; + assert_eq!(Some("SOURCE"), app.value_of("mbox-source")); + } + + #[test] + fn it_should_match_target_arg() { + macro_rules! get_matches_from { + ($($arg:expr),*) => { + clap::App::new("himalaya") + .arg(target_arg()) + .get_matches_from_safe(&["himalaya", $($arg,)*]) + }; + } + + let app = get_matches_from![]; + assert_eq!( + clap::ErrorKind::MissingRequiredArgument, + app.unwrap_err().kind + ); + + let app = get_matches_from!["TARGET"]; + assert_eq!(Some("TARGET"), app.unwrap().value_of("mbox-target")); + } +} diff --git a/src/domain/mbox/mbox_entity.rs b/src/domain/mbox/mbox_entity.rs index 8c7b294..0ff176b 100644 --- a/src/domain/mbox/mbox_entity.rs +++ b/src/domain/mbox/mbox_entity.rs @@ -1,155 +1,50 @@ -use anyhow::{anyhow, Error, Result}; -use imap::types::NameAttribute; -use log::debug; -use serde::{ - ser::{self, SerializeSeq}, - Serialize, +//! Mailbox entity module. +//! +//! This module contains the definition of the mailbox and its traits implementations. + +use serde::Serialize; +use std::{ + borrow::Cow, + fmt::{self, Display}, }; -use std::{borrow::Cow, collections::HashSet, convert::TryFrom, fmt}; -use crate::ui::table::{Cell, Row, Table}; +use crate::{ + domain::Attrs, + ui::{Cell, Row, Table}, +}; -// Attribute +/// Represents the mailbox. +#[derive(Debug, Default, PartialEq, Eq, Serialize)] +pub struct Mbox<'a> { + /// Represents the mailbox hierarchie delimiter. + pub delim: Cow<'a, str>, -#[derive(Debug, PartialEq)] -struct SerializableAttribute<'a>(&'a NameAttribute<'a>); + /// Represents the mailbox name. + pub name: Cow<'a, str>, -impl<'a> Into<&'a str> for &'a SerializableAttribute<'a> { - fn into(self) -> &'a str { - match &self.0 { - NameAttribute::NoInferiors => "\\NoInferiors", - NameAttribute::NoSelect => "\\NoSelect", - NameAttribute::Marked => "\\Marked", - NameAttribute::Unmarked => "\\Unmarked", - NameAttribute::Custom(cow) => cow, - } - } + /// Represents the mailbox attributes. + pub attrs: Attrs<'a>, } -impl<'a> ser::Serialize for SerializableAttribute<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_str(self.into()) - } -} - -/// Represents the attributes of a mailbox. -#[derive(Debug, PartialEq)] -pub struct Attributes(pub HashSet>); - -impl<'a> From<&[NameAttribute<'a>]> for Attributes { - fn from(attrs: &[NameAttribute<'a>]) -> Self { - Self( - attrs - .iter() - .map(|attribute| convert_to_static(attribute).unwrap()) - .collect::>>(), - ) - } -} - -impl ToString for Attributes { - fn to_string(&self) -> String { - let mut attributes = String::new(); - - for attribute in &self.0 { - let attribute = SerializableAttribute(&attribute); - attributes.push_str((&attribute).into()); - attributes.push_str(", "); - } - - // remove the trailing whitespace with the comma - attributes = attributes.trim_end_matches(' ').to_string(); - attributes.pop(); - - attributes - } -} - -impl ser::Serialize for Attributes { - fn serialize(&self, serializer: T) -> Result - where - T: ser::Serializer, - { - let mut seq = serializer.serialize_seq(Some(self.0.len()))?; - - for attr in &self.0 { - seq.serialize_element(&SerializableAttribute(attr))?; - } - - seq.end() - } -} - -// --- Mailbox --- -/// Represents a general mailbox. -#[derive(Debug, Serialize)] -pub struct Mbox { - /// The [hierarchie delimiter]. - /// - /// [hierarchie delimiter]: https://docs.rs/imap/2.4.1/imap/types/struct.Name.html#method.delimiter - pub delim: String, - - /// The name of the mailbox. - pub name: String, - - /// Its attributes. - pub attributes: Attributes, -} - -impl Default for Mbox { - fn default() -> Self { +impl<'a> Mbox<'a> { + /// Creates a new mailbox with just a name. + pub fn new(name: &'a str) -> Self { Self { - delim: String::default(), - name: String::default(), - attributes: Attributes::from(&[] as &[NameAttribute]), + name: name.into(), + ..Self::default() } } } -impl fmt::Display for Mbox { +/// Makes the mailbox displayable. +impl<'a> Display for Mbox<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.name) } } -impl From<&str> for Mbox { - fn from(mbox: &str) -> Self { - debug!("init mailbox from `{:?}`", mbox); - Self { - name: mbox.to_owned(), - ..Self::default() - } - } -} - -impl<'a> From<&'a imap::types::Name> for Mbox { - fn from(name: &'a imap::types::Name) -> Self { - Self { - delim: name.delimiter().unwrap_or_default().to_owned(), - name: name.name().to_owned(), - attributes: Attributes::from(name.attributes()), - } - } -} - -impl TryFrom> for Mbox { - type Error = Error; - - fn try_from(mbox: Option<&str>) -> Result { - debug!("init mailbox from `{:?}`", mbox); - Ok(Self { - name: mbox - .ok_or(anyhow!("the target mailbox cannot be empty"))? - .to_string(), - ..Self::default() - }) - } -} - -impl Table for Mbox { +/// Makes the mailbox tableable. +impl<'a> Table for Mbox<'a> { fn head() -> Row { Row::new() .cell(Cell::new("DELIM").bold().underline().white()) @@ -167,36 +62,52 @@ impl Table for Mbox { Row::new() .cell(Cell::new(&self.delim).white()) .cell(Cell::new(&self.name).green()) - .cell(Cell::new(&self.attributes.to_string()).shrinkable().blue()) + .cell(Cell::new(&self.attrs.to_string()).shrinkable().blue()) } } -// --- Mboxes --- -/// A simple wrapper to acces a bunch of mboxes which are in this vector. -#[derive(Debug, Serialize)] -pub struct Mboxes(pub Vec); - -impl<'a> From<&'a imap::types::ZeroCopy>> for Mboxes { - fn from(names: &'a imap::types::ZeroCopy>) -> Self { - Self(names.iter().map(Mbox::from).collect::>()) +/// Converts an `imap::types::Name` into a mailbox. +impl<'a> From<&'a imap::types::Name> for Mbox<'a> { + fn from(name: &'a imap::types::Name) -> Self { + Self { + delim: name.delimiter().unwrap_or_default().into(), + name: name.name().into(), + attrs: Attrs::from(name.attributes()), + } } } -impl fmt::Display for Mboxes { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "\n{}", Table::render(&self.0)) - } -} +#[cfg(test)] +mod tests { + use super::super::AttrRemote; + use super::*; -// == Helper Functions == -fn convert_to_static<'func>( - attribute: &'func NameAttribute<'func>, -) -> Result, ()> { - match attribute { - NameAttribute::NoInferiors => Ok(NameAttribute::NoInferiors), - NameAttribute::NoSelect => Ok(NameAttribute::NoSelect), - NameAttribute::Marked => Ok(NameAttribute::Marked), - NameAttribute::Unmarked => Ok(NameAttribute::Unmarked), - NameAttribute::Custom(cow) => Ok(NameAttribute::Custom(Cow::Owned(cow.to_string()))), + #[test] + fn it_should_create_new_mbox() { + assert_eq!(Mbox::default(), Mbox::new("")); + assert_eq!( + Mbox { + delim: Cow::default(), + name: "INBOX".into(), + attrs: Attrs::default() + }, + Mbox::new("INBOX") + ); + } + + #[test] + fn it_should_display_mbox() { + let default_mbox = Mbox::default(); + assert_eq!("", default_mbox.to_string()); + + let new_mbox = Mbox::new("INBOX"); + assert_eq!("INBOX", new_mbox.to_string()); + + let full_mbox = Mbox { + delim: ".".into(), + name: "Sent".into(), + attrs: Attrs::from(&[AttrRemote::NoSelect] as &[AttrRemote]), + }; + assert_eq!("Sent", full_mbox.to_string()); } } diff --git a/src/domain/mbox/mbox_handler.rs b/src/domain/mbox/mbox_handler.rs index 995ee46..45fe452 100644 --- a/src/domain/mbox/mbox_handler.rs +++ b/src/domain/mbox/mbox_handler.rs @@ -1,24 +1,22 @@ -//! Module related to mailboxes handling. +//! Mailbox handling module. //! -//! This module gathers all mailboxes actions triggered by the CLI. +//! This module gathers all mailbox actions triggered by the CLI. use anyhow::Result; -use log::{debug, trace}; +use log::trace; use crate::{ - domain::{imap::ImapServiceInterface, mbox::Mboxes}, - output::{OutputService, OutputServiceInterface}, + domain::{ImapServiceInterface, Mboxes}, + output::OutputServiceInterface, }; /// List all mailboxes. -pub fn list( +pub fn list( output: &OutputService, imap: &mut ImapService, ) -> Result<()> { - let names = imap.get_mboxes()?; - let mboxes = Mboxes::from(&names); - debug!("mailboxes len: {}", mboxes.0.len()); + let raw_mboxes = imap.fetch_raw_mboxes()?; + let mboxes = Mboxes::from(&raw_mboxes); trace!("mailboxes: {:#?}", mboxes); - output.print(mboxes)?; - Ok(()) + output.print(mboxes) } diff --git a/src/domain/mbox/mboxes_entity.rs b/src/domain/mbox/mboxes_entity.rs new file mode 100644 index 0000000..5e5afdd --- /dev/null +++ b/src/domain/mbox/mboxes_entity.rs @@ -0,0 +1,38 @@ +//! Mailboxes entity module. +//! +//! This module contains the definition of the mailboxes and its traits implementations. + +use serde::Serialize; +use std::{ + fmt::{self, Display}, + ops::Deref, +}; + +use crate::{domain::Mbox, ui::Table}; + +/// Represents a list of mailboxes. +#[derive(Debug, Serialize)] +pub struct Mboxes<'a>(pub Vec>); + +/// Derefs the mailboxes to its inner vector. +impl<'a> Deref for Mboxes<'a> { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Makes the mailboxes displayable. +impl<'a> Display for Mboxes<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "\n{}", Table::render(&self)) + } +} + +/// Converts a list of `imap::types::Name` into mailboxes. +impl<'a> From<&'a imap::types::ZeroCopy>> for Mboxes<'a> { + fn from(names: &'a imap::types::ZeroCopy>) -> Self { + Self(names.iter().map(Mbox::from).collect()) + } +} diff --git a/src/domain/mbox/mod.rs b/src/domain/mbox/mod.rs index eba5425..7404381 100644 --- a/src/domain/mbox/mod.rs +++ b/src/domain/mbox/mod.rs @@ -1,7 +1,18 @@ -//! Module related to mailbox. +//! Mailbox module. +//! +//! This module contains everything related to mailbox. pub mod mbox_arg; pub mod mbox_handler; +pub mod attr_entity; +pub use attr_entity::*; + +pub mod attrs_entity; +pub use attrs_entity::*; + pub mod mbox_entity; pub use mbox_entity::*; + +pub mod mboxes_entity; +pub use mboxes_entity::*; diff --git a/src/domain/msg/msg_arg.rs b/src/domain/msg/msg_arg.rs index e22de89..a118439 100644 --- a/src/domain/msg/msg_arg.rs +++ b/src/domain/msg/msg_arg.rs @@ -14,7 +14,7 @@ use crate::domain::{ type Seq<'a> = &'a str; type PageSize = usize; type Page = usize; -type Mbox<'a> = Option<&'a str>; +type Mbox<'a> = &'a str; type TextMime<'a> = &'a str; type Raw = bool; type All = bool; @@ -54,9 +54,9 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { debug!("copy command matched"); let seq = m.value_of("seq").unwrap(); trace!("seq: {}", seq); - let target = m.value_of("target"); - trace!(r#"target mailbox: "{:?}""#, target); - return Ok(Some(Command::Copy(seq, target))); + let mbox = m.value_of("mbox-target").unwrap(); + trace!(r#"target mailbox: "{:?}""#, mbox); + return Ok(Some(Command::Copy(seq, mbox))); } if let Some(m) = m.subcommand_matches("delete") { @@ -94,9 +94,9 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { debug!("move command matched"); let seq = m.value_of("seq").unwrap(); trace!("seq: {}", seq); - let target = m.value_of("target"); - trace!(r#"target mailbox: "{:?}""#, target); - return Ok(Some(Command::Move(seq, target))); + let mbox = m.value_of("mbox-target").unwrap(); + trace!(r#"target mailbox: "{:?}""#, mbox); + return Ok(Some(Command::Move(seq, mbox))); } if let Some(m) = m.subcommand_matches("read") { @@ -125,9 +125,9 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { debug!("save command matched"); let msg = m.value_of("message").unwrap(); debug!("message: {}", &msg); - let target = m.value_of("target"); - debug!("target mailbox: `{:?}`", target); - return Ok(Some(Command::Save(target, msg))); + let mbox = m.value_of("mbox-target").unwrap(); + debug!("target mailbox: `{:?}`", mbox); + return Ok(Some(Command::Save(mbox, msg))); } if let Some(m) = m.subcommand_matches("search") { diff --git a/src/domain/msg/msg_entity.rs b/src/domain/msg/msg_entity.rs index 1d84d83..c8cbdfe 100644 --- a/src/domain/msg/msg_entity.rs +++ b/src/domain/msg/msg_entity.rs @@ -335,7 +335,7 @@ impl Msg { loop { match choice::post_edit() { Ok(PostEditChoice::Send) => { - let mbox = Mbox::from("Sent"); + let mbox = Mbox::new("Sent"); let sent_msg = smtp.send_msg(&self)?; let flags = Flags::try_from(vec![Flag::Seen])?; imap.append_raw_msg_with_flags(&mbox, &sent_msg.formatted(), flags)?; @@ -352,7 +352,7 @@ impl Msg { break; } Ok(PostEditChoice::RemoteDraft) => { - let mbox = Mbox::from("Drafts"); + let mbox = Mbox::new("Drafts"); let flags = Flags::try_from(vec![Flag::Seen, Flag::Draft])?; let tpl = Tpl::from_msg(TplOverride::default(), &self, account); imap.append_raw_msg_with_flags(&mbox, tpl.as_bytes(), flags)?; diff --git a/src/domain/msg/msg_handler.rs b/src/domain/msg/msg_handler.rs index 154bbfe..b5e6f6a 100644 --- a/src/domain/msg/msg_handler.rs +++ b/src/domain/msg/msg_handler.rs @@ -25,8 +25,7 @@ use crate::{ output::OutputServiceInterface, }; -/// Download all attachments from the given message sequence number to the user account downloads -/// directory. +/// Download all message attachments to the user account downloads directory. pub fn attachments( seq: &str, account: &Account, @@ -56,11 +55,11 @@ pub fn attachments( seq: &str, - mbox: Option<&str>, + mbox: &str, output: &OutputService, imap: &mut ImapService, ) -> Result<()> { - let mbox = Mbox::try_from(mbox)?; + let mbox = Mbox::new(mbox); let msg = imap.find_raw_msg(&seq)?; let flags = Flags::try_from(vec![Flag::Seen])?; imap.append_raw_msg_with_flags(&mbox, &msg, flags)?; @@ -177,12 +176,12 @@ pub fn move_, + mbox: &str, output: &OutputService, imap: &mut ImapService, ) -> Result<()> { // Copy the message to targetted mailbox - let mbox = Mbox::try_from(mbox)?; + let mbox = Mbox::new(mbox); let msg = imap.find_raw_msg(&seq)?; let flags = Flags::try_from(vec![Flag::Seen])?; imap.append_raw_msg_with_flags(&mbox, &msg, flags)?; @@ -239,11 +238,11 @@ pub fn reply< /// Save a raw message to the targetted mailbox. pub fn save( - mbox: Option<&str>, + mbox: &str, msg: &str, imap: &mut ImapService, ) -> Result<()> { - let mbox = Mbox::try_from(mbox)?; + let mbox = Mbox::new(mbox); let flags = Flags::try_from(vec![Flag::Seen])?; imap.append_raw_msg_with_flags(&mbox, msg.as_bytes(), flags) } @@ -295,7 +294,7 @@ pub fn send< debug!("message sent!"); // Save message to sent folder - let mbox = Mbox::from("Sent"); + let mbox = Mbox::new("Sent"); let flags = Flags::try_from(vec![Flag::Seen])?; imap.append_raw_msg_with_flags(&mbox, raw_msg.as_bytes(), flags) } diff --git a/src/main.rs b/src/main.rs index 830f024..59fc1a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use clap::{self, AppSettings}; +use clap; use env_logger; use std::{convert::TryFrom, env}; use url::Url; @@ -10,25 +10,26 @@ mod domain; mod output; mod ui; -use config::{Account, Config}; +use compl::{compl_arg, compl_handler}; +use config::{config_arg, Account, Config}; use domain::{ imap::{imap_arg, imap_handler, ImapService, ImapServiceInterface}, mbox::{mbox_arg, mbox_handler, Mbox}, msg::{flag_arg, flag_handler, msg_arg, msg_handler, tpl_arg, tpl_handler}, smtp::SmtpService, }; -use output::OutputService; +use output::{output_arg, OutputService}; fn create_app<'a>() -> clap::App<'a, 'a> { clap::App::new(env!("CARGO_PKG_NAME")) .version(env!("CARGO_PKG_VERSION")) .about(env!("CARGO_PKG_DESCRIPTION")) .author(env!("CARGO_PKG_AUTHORS")) - .setting(AppSettings::GlobalVersion) - .args(&config::config_arg::args()) - .args(&output::output_arg::args()) + .global_setting(clap::AppSettings::GlobalVersion) + .args(&config_arg::args()) + .args(&output_arg::args()) .arg(mbox_arg::source_arg()) - .subcommands(compl::compl_arg::subcmds()) + .subcommands(compl_arg::subcmds()) .subcommands(imap_arg::subcmds()) .subcommands(mbox_arg::subcmds()) .subcommands(msg_arg::subcmds()) @@ -40,10 +41,10 @@ fn main() -> Result<()> { env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "off"), ); - // Check mailto match BEFORE app initialization. + // Check mailto command BEFORE app initialization. let raw_args: Vec = env::args().collect(); if raw_args.len() > 1 && raw_args[1].starts_with("mailto:") { - let mbox = Mbox::from("INBOX"); + let mbox = Mbox::new("INBOX"); let config = Config::try_from(None)?; let account = Account::try_from((&config, None))?; let output = OutputService::from("plain"); @@ -56,23 +57,24 @@ fn main() -> Result<()> { let app = create_app(); let m = app.get_matches(); - // Check completion match BEFORE entities and services initialization. - // Linked issue: https://github.com/soywod/himalaya/issues/115. - match compl::compl_arg::matches(&m)? { - Some(compl::compl_arg::Command::Generate(shell)) => { - return compl::compl_handler::generate(create_app(), shell); + // 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); } _ => (), } - let mbox = Mbox::try_from(m.value_of("mailbox"))?; + // Init entities and services. + let mbox = Mbox::new(m.value_of("mbox-source").unwrap()); let config = Config::try_from(m.value_of("config"))?; let account = Account::try_from((&config, m.value_of("account")))?; let output = OutputService::try_from(m.value_of("output"))?; let mut imap = ImapService::from((&account, &mbox)); let mut smtp = SmtpService::from(&account); - // Check IMAP matches. + // Check IMAP commands. match imap_arg::matches(&m)? { Some(imap_arg::Command::Notify(keepalive)) => { return imap_handler::notify(keepalive, &config, &mut imap); @@ -83,21 +85,21 @@ fn main() -> Result<()> { _ => (), } - // Check mailbox matches. + // Check mailbox commands. match mbox_arg::matches(&m)? { - Some(mbox_arg::Command::List) => { + Some(mbox_arg::Cmd::List) => { return mbox_handler::list(&output, &mut imap); } _ => (), } - // Check message matches. + // Check message commands. match msg_arg::matches(&m)? { Some(msg_arg::Command::Attachments(seq)) => { return msg_handler::attachments(seq, &account, &output, &mut imap); } - Some(msg_arg::Command::Copy(seq, target)) => { - return msg_handler::copy(seq, target, &output, &mut imap); + Some(msg_arg::Command::Copy(seq, mbox)) => { + return msg_handler::copy(seq, mbox, &output, &mut imap); } Some(msg_arg::Command::Delete(seq)) => { return msg_handler::delete(seq, &output, &mut imap); @@ -108,8 +110,8 @@ fn main() -> Result<()> { Some(msg_arg::Command::List(page_size, page)) => { return msg_handler::list(page_size, page, &account, &output, &mut imap); } - Some(msg_arg::Command::Move(seq, target)) => { - return msg_handler::move_(seq, target, &output, &mut imap); + Some(msg_arg::Command::Move(seq, mbox)) => { + return msg_handler::move_(seq, mbox, &output, &mut imap); } Some(msg_arg::Command::Read(seq, text_mime, raw)) => { return msg_handler::read(seq, text_mime, raw, &output, &mut imap); @@ -117,8 +119,8 @@ fn main() -> Result<()> { Some(msg_arg::Command::Reply(seq, all, atts)) => { return msg_handler::reply(seq, all, atts, &account, &output, &mut imap, &mut smtp); } - Some(msg_arg::Command::Save(target, msg)) => { - return msg_handler::save(target, msg, &mut imap); + Some(msg_arg::Command::Save(mbox, msg)) => { + return msg_handler::save(mbox, msg, &mut imap); } Some(msg_arg::Command::Search(query, page_size, page)) => { return msg_handler::search(query, page_size, page, &account, &output, &mut imap);