diff --git a/src/config/entity.rs b/src/config/entity.rs index 0370592..62d45d0 100644 --- a/src/config/entity.rs +++ b/src/config/entity.rs @@ -77,96 +77,6 @@ impl Config { Ok(path) } - /// Returns the account by the given name. - /// If `name` is `None`, then the default account is returned. - pub fn find_account_by_name(&self, name: Option<&str>) -> Result<&ConfigAccountEntry> { - match name { - Some("") | None => self - .accounts - .iter() - .find(|(_, account)| account.default.unwrap_or(false)) - .map(|(_, account)| account) - .ok_or_else(|| anyhow!("cannot find default account")), - Some(name) => self - .accounts - .get(name) - .ok_or_else(|| anyhow!("cannot find account `{}`", name)), - } - } - - /// Returns the path to the given filename in the download directory. - /// You can imagine this as: - /// ```skip - /// Account-specifique-download-dir-path + Attachment-Filename - /// ``` - pub fn downloads_filepath(&self, account: &ConfigAccountEntry, filename: &str) -> PathBuf { - account - .downloads_dir - .as_ref() - .and_then(|dir| dir.to_str()) - .and_then(|dir| shellexpand::full(dir).ok()) - .map(|dir| PathBuf::from(dir.to_string())) - .unwrap_or( - self.downloads_dir - .as_ref() - .and_then(|dir| dir.to_str()) - .and_then(|dir| shellexpand::full(dir).ok()) - .map(|dir| PathBuf::from(dir.to_string())) - .unwrap_or(env::temp_dir()), - ) - .join(filename) - } - - /// This is a little helper-function like which uses the the name and email - /// of the account to create a valid address for the header of the headers - /// of a msg. - /// - /// # Hint - /// If the name includes some special characters like a whitespace, comma or semicolon, then - /// the name will be automatically wrapped between two `"`. - /// - /// # Exapmle - /// ``` - /// use himalaya::config::model::{Account, Config}; - /// - /// fn main() { - /// let config = Config::default(); - /// - /// let normal_account = Account::new(Some("Acc1"), "acc1@mail.com"); - /// // notice the semicolon in the name! - /// let special_account = Account::new(Some("TL;DR"), "acc2@mail.com"); - /// - /// // -- Expeced outputs -- - /// let expected_normal = Account { - /// name: Some("Acc1".to_string()), - /// email: "acc1@mail.com".to_string(), - /// .. Account::default() - /// }; - /// - /// let expected_special = Account { - /// name: Some("\"TL;DR\"".to_string()), - /// email: "acc2@mail.com".to_string(), - /// .. Account::default() - /// }; - /// - /// assert_eq!(config.address(&normal_account), "Acc1 "); - /// assert_eq!(config.address(&special_account), "\"TL;DR\" "); - /// } - /// ``` - pub fn address(&self, account: &ConfigAccountEntry) -> String { - let name = account.name.as_ref().unwrap_or(&self.name); - let has_special_chars = "()<>[]:;@.,".contains(|special_char| name.contains(special_char)); - - if name.is_empty() { - format!("{}", account.email) - } else if has_special_chars { - // so the name has special characters => Wrap it with '"' - format!("\"{}\" <{}>", name, account.email) - } else { - format!("{} <{}>", name, account.email) - } - } - pub fn run_notify_cmd>(&self, subject: S, sender: S) -> Result<()> { let subject = subject.as_ref(); let sender = sender.as_ref(); @@ -183,7 +93,7 @@ impl Config { Ok(()) } - pub fn exec_watch_cmds(&self, account: &ConfigAccountEntry) -> Result<()> { + pub fn _exec_watch_cmds(&self, account: &ConfigAccountEntry) -> Result<()> { let cmds = account .watch_cmds .as_ref() @@ -323,28 +233,6 @@ impl Account { format!("{} <{}>", name, self.email) } } - /// Returns the imap-host address + the port usage of the account - /// - /// # Example - /// ```rust - /// use himalaya::config::model::Account; - /// fn main () { - /// let account = Account { - /// imap_host: String::from("hostExample"), - /// imap_port: 42, - /// .. Account::default() - /// }; - /// - /// let expected_output = ("hostExample", 42); - /// - /// assert_eq!(account.imap_addr(), expected_output); - /// } - /// ``` - pub fn imap_addr(&self) -> (&str, u16) { - debug!("host: {}", self.imap_host); - debug!("port: {}", self.imap_port); - (&self.imap_host, self.imap_port) - } /// Runs the given command in your password string and returns it. pub fn imap_passwd(&self) -> Result { @@ -364,83 +252,6 @@ impl Account { Ok(SmtpCredentials::new(self.smtp_login.to_owned(), passwd)) } - - /// Creates a new account with the given values and returns it. All other attributes of the - /// account are gonna be empty/None. - /// - /// # Example - /// ```rust - /// use himalaya::config::model::Account; - /// - /// fn main() { - /// let account1 = Account::new(Some("Name1"), "email@address.com"); - /// let account2 = Account::new(None, "email@address.com"); - /// - /// let expected1 = Account { - /// name: Some("Name1".to_string()), - /// email: "email@address.com".to_string(), - /// .. Account::default() - /// }; - /// - /// let expected2 = Account { - /// email: "email@address.com".to_string(), - /// .. Account::default() - /// }; - /// - /// assert_eq!(account1, expected1); - /// assert_eq!(account2, expected2); - /// } - /// ``` - pub fn new(name: Option, email_addr: S) -> Self { - Self { - name: name.unwrap_or_default().to_string(), - email: email_addr.to_string(), - ..Self::default() - } - } - - /// Creates a new account with a custom signature. Passing `None` to `signature` sets the - /// signature to `Account Signature`. - /// - /// # Examples - /// ```rust - /// use himalaya::config::model::Account; - /// - /// fn main() { - /// - /// // the testing accounts - /// let account_with_custom_signature = Account::new_with_signature( - /// Some("Email name"), "some@mail.com", Some("Custom signature! :)")); - /// let account_with_default_signature = Account::new_with_signature( - /// Some("Email name"), "some@mail.com", None); - /// - /// // How they should look like - /// let account_cmp1 = Account { - /// name: Some("Email name".to_string()), - /// email: "some@mail.com".to_string(), - /// signature: Some("Custom signature! :)".to_string()), - /// .. Account::default() - /// }; - /// - /// let account_cmp2 = Account { - /// name: Some("Email name".to_string()), - /// email: "some@mail.com".to_string(), - /// .. Account::default() - /// }; - /// - /// assert_eq!(account_with_custom_signature, account_cmp1); - /// assert_eq!(account_with_default_signature, account_cmp2); - /// } - /// ``` - pub fn new_with_signature + ToString + Default>( - name: Option, - email_addr: S, - signature: Option, - ) -> Self { - let mut account = Account::new(name, email_addr); - account.signature = signature.unwrap_or_default().to_string(); - account - } } impl<'a> TryFrom<(&'a Config, Option<&str>)> for Account { diff --git a/src/domain/imap/service.rs b/src/domain/imap/service.rs index 9622ec9..ff49dc0 100644 --- a/src/domain/imap/service.rs +++ b/src/domain/imap/service.rs @@ -33,9 +33,15 @@ pub trait ImapServiceInterface { ) -> Result>; fn get_msg(&mut self, uid: &str) -> Result; fn append_msg(&mut self, mbox: &Mbox, msg: &mut Msg) -> Result<()>; - fn add_flags(&mut self, uid_seq: &str, flags: Flags) -> Result<()>; - fn set_flags(&mut self, uid_seq: &str, flags: Flags) -> Result<()>; - fn remove_flags(&mut self, uid_seq: &str, flags: Flags) -> Result<()>; + /// Add flags to the given message UID sequence. + /// + /// ```ignore + /// let flags = Flags::from(vec![Flag::Seen, Flag::Deleted]); + /// add_flags("5:10", flags) + /// ``` + fn add_flags(&mut self, uid_seq: &str, flags: &Flags) -> Result<()>; + fn set_flags(&mut self, uid_seq: &str, flags: &Flags) -> Result<()>; + fn remove_flags(&mut self, uid_seq: &str, flags: &Flags) -> Result<()>; fn expunge(&mut self) -> Result<()>; fn logout(&mut self) -> Result<()>; } @@ -204,14 +210,7 @@ impl<'a> ImapServiceInterface for ImapService<'a> { Ok(()) } - /// Add flags to the given message UID sequence. - /// - /// ```ignore - /// - /// let flags = Flags::from(vec![Flag::Seen, Flag::Deleted]); - /// imap.add_flags("5:10", flags) - /// ``` - fn add_flags(&mut self, uid_seq: &str, flags: Flags) -> Result<()> { + fn add_flags(&mut self, uid_seq: &str, flags: &Flags) -> Result<()> { let mbox = self.mbox.to_owned(); let flags: String = flags.to_string(); self.sess()? @@ -244,9 +243,8 @@ impl<'a> ImapServiceInterface for ImapService<'a> { /// imap_conn.logout(); /// } /// ``` - fn set_flags(&mut self, uid_seq: &str, flags: Flags) -> Result<()> { + fn set_flags(&mut self, uid_seq: &str, flags: &Flags) -> Result<()> { let mbox = self.mbox.to_owned(); - let flags: String = flags.to_string(); self.sess()? .select(&mbox.name) .context(format!("cannot select mailbox `{}`", self.mbox.name))?; @@ -258,7 +256,7 @@ impl<'a> ImapServiceInterface for ImapService<'a> { /// Remove the flags to the message by the given information. Take a look on the example above. /// It's pretty similar. - fn remove_flags(&mut self, uid_seq: &str, flags: Flags) -> Result<()> { + fn remove_flags(&mut self, uid_seq: &str, flags: &Flags) -> Result<()> { let mbox = self.mbox.to_owned(); let flags = flags.to_string(); self.sess()? diff --git a/src/domain/mbox/entity.rs b/src/domain/mbox/entity.rs index 0b9550f..b87e650 100644 --- a/src/domain/mbox/entity.rs +++ b/src/domain/mbox/entity.rs @@ -101,15 +101,6 @@ pub struct Mbox { pub attributes: Attributes, } -impl Mbox { - pub fn new>(name: S) -> Self { - Self { - name: name.as_ref().to_owned(), - ..Self::default() - } - } -} - impl Default for Mbox { fn default() -> Self { Self { diff --git a/src/domain/msg/arg.rs b/src/domain/msg/arg.rs index 4a75677..26b423a 100644 --- a/src/domain/msg/arg.rs +++ b/src/domain/msg/arg.rs @@ -183,7 +183,7 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { return Ok(Some(Command::Tpl(msg::tpl::arg::matches(&m)?))); } - if let Some(m) = m.subcommand_matches("flags") { + if let Some(m) = m.subcommand_matches("flag") { return Ok(Some(Command::Flag(msg::flag::arg::matches(&m)?))); } diff --git a/src/domain/msg/attachment/entity.rs b/src/domain/msg/attachment/entity.rs index c8a84ae..346d9ae 100644 --- a/src/domain/msg/attachment/entity.rs +++ b/src/domain/msg/attachment/entity.rs @@ -20,31 +20,6 @@ pub struct Attachment { } impl Attachment { - /// Creates a new attachment. - /// - /// # Example - /// ``` - /// # use himalaya::msg::attachment::Attachment; - /// let attachment = Attachment::new( - /// "VIP Text", - /// "text/plain", - /// "Some very important text".as_bytes().to_vec()); - /// - /// ``` - pub fn new(filename: &str, content_type: &str, body_raw: Vec) -> Self { - // Use the mime type `text/plain` per default - let content_type: ContentType = match content_type.parse() { - Ok(lettre_type) => lettre_type, - Err(_) => ContentType::TEXT_PLAIN, - }; - - Self { - filename: filename.to_string(), - content_type, - body_raw, - } - } - /// This from function extracts one attachment of a parsed msg. /// If it couldn't create an attachment with the given parsed msg, than it will /// return `None`. diff --git a/src/domain/msg/body/entity.rs b/src/domain/msg/body/entity.rs index 198f72a..59b5e32 100644 --- a/src/domain/msg/body/entity.rs +++ b/src/domain/msg/body/entity.rs @@ -65,54 +65,6 @@ impl Body { html: None, } } - - /// Returns a new instance of `Body` with `html` set. - /// - /// # Example - /// ```rust - /// use himalaya::msg::body::Body; - /// - /// fn main() { - /// let body = Body::new_with_html("Html body"); - /// - /// let expected_body = Body { - /// text: None, - /// html: Some("Html body".to_string()), - /// }; - /// - /// assert_eq!(body, expected_body); - /// } - /// ``` - pub fn new_with_html(html: S) -> Self { - Self { - plain: None, - html: Some(html.to_string()), - } - } - - /// Returns a new isntance of `Body` with `text` and `html` set. - /// - /// # Example - /// ```rust - /// use himalaya::msg::body::Body; - /// - /// fn main() { - /// let body = Body::new_with_both("Text body", "Html body"); - /// - /// let expected_body = Body { - /// text: Some("Text body".to_string()), - /// html: Some("Html body".to_string()), - /// }; - /// - /// assert_eq!(body, expected_body); - /// } - /// ``` - pub fn new_with_both(text: S, html: S) -> Self { - Self { - plain: Some(text.to_string()), - html: Some(html.to_string()), - } - } } // == Traits == diff --git a/src/domain/msg/entity.rs b/src/domain/msg/entity.rs index cc167f3..7f79ff7 100644 --- a/src/domain/msg/entity.rs +++ b/src/domain/msg/entity.rs @@ -1,7 +1,15 @@ use anyhow::{anyhow, Context, Error, Result}; use imap::types::{Fetch, Flag, ZeroCopy}; +use lettre::message::{ + header::ContentType, Attachment as lettre_Attachment, Mailbox, Message, MultiPart, SinglePart, +}; use log::debug; use mailparse; +use serde::Serialize; +use std::{ + convert::{From, TryFrom}, + fmt, +}; use crate::{ config::entity::Account, @@ -9,21 +17,10 @@ use crate::{ attachment::entity::Attachment, body::entity::Body, flag::entity::Flags, header::entity::Headers, }, - ui::table::{Cell, Row, Table}, -}; - -#[cfg(not(test))] -use crate::ui::editor; - -use serde::Serialize; - -use lettre::message::{ - header::ContentType, Attachment as lettre_Attachment, Mailbox, Message, MultiPart, SinglePart, -}; - -use std::{ - convert::{From, TryFrom}, - fmt, + ui::{ + editor, + table::{Cell, Row, Table}, + }, }; /// Represents the msg in a serializeable form with additional values. @@ -425,7 +422,6 @@ impl Msg { // We don't let this line compile, if we're doing // tests, because we just need to look, if the headers are set // correctly - #[cfg(not(test))] let msg = editor::open_editor_with_tpl(msg.as_bytes())?; // refresh the state of the msg @@ -716,14 +712,6 @@ impl Msg { self.uid } - /// It returns the raw version of the Message. In general it's the structure - /// how you get it if you get the data from the fetch. It's the output if - /// you read a message with the `--raw` flag like this: `himalaya read - /// --raw `. - pub fn get_raw(&self) -> Vec { - self.raw.clone() - } - /// Returns the raw mail as a string instead of a Vector of bytes. pub fn get_raw_as_string(&self) -> Result { let raw_message = String::from_utf8(self.raw.clone()) diff --git a/src/domain/msg/flag/arg.rs b/src/domain/msg/flag/arg.rs index 43730e0..157a661 100644 --- a/src/domain/msg/flag/arg.rs +++ b/src/domain/msg/flag/arg.rs @@ -9,7 +9,7 @@ use log::debug; use crate::domain::msg; type Uid<'a> = &'a str; -type Flags<'a> = &'a str; +type Flags<'a> = Vec<&'a str>; /// Message flag commands. pub enum Command<'a> { @@ -24,8 +24,8 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { debug!("set command matched"); let uid = m.value_of("uid").unwrap(); debug!("uid: {}", uid); - let flags = m.value_of("flags").unwrap(); - debug!("flags: {}", flags); + let flags: Vec<&str> = m.values_of("flags").unwrap_or_default().collect(); + debug!("flags: `{:?}`", flags); return Ok(Some(Command::Set(uid, flags))); } @@ -33,8 +33,8 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { debug!("add command matched"); let uid = m.value_of("uid").unwrap(); debug!("uid: {}", uid); - let flags = m.value_of("flags").unwrap(); - debug!("flags: {}", flags); + let flags: Vec<&str> = m.values_of("flags").unwrap_or_default().collect(); + debug!("flags: `{:?}`", flags); return Ok(Some(Command::Add(uid, flags))); } @@ -42,8 +42,8 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { debug!("remove command matched"); let uid = m.value_of("uid").unwrap(); debug!("uid: {}", uid); - let flags = m.value_of("flags").unwrap(); - debug!("flags: {}", flags); + let flags: Vec<&str> = m.values_of("flags").unwrap_or_default().collect(); + debug!("flags: `{:?}`", flags); return Ok(Some(Command::Remove(uid, flags))); } @@ -53,7 +53,9 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { /// Message flag flags argument. fn flags_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("flags") - .help("IMAP flags (see https://tools.ietf.org/html/rfc3501#page-11). Just write the flag name without the backslash. Example: --flags \"Seen Answered\"") + .help( + "IMAP flags (they do not need to be prefixed with `\\` and they are case-insensitive)", + ) .value_name("FLAGSā€¦") .multiple(true) .required(true) @@ -61,7 +63,8 @@ fn flags_arg<'a>() -> Arg<'a, 'a> { /// Message flag subcommands. pub fn subcmds<'a>() -> Vec> { - vec![SubCommand::with_name("flags") + vec![SubCommand::with_name("flag") + .aliases(&["flags", "flg"]) .about("Handles flags") .setting(AppSettings::SubcommandRequiredElseHelp) .subcommand( @@ -72,7 +75,7 @@ pub fn subcmds<'a>() -> Vec> { ) .subcommand( SubCommand::with_name("add") - .about("Appends flags to a message") + .about("Adds flags to a message") .arg(msg::arg::uid_arg()) .arg(flags_arg()), ) diff --git a/src/domain/msg/flag/entity.rs b/src/domain/msg/flag/entity.rs index 281634d..4c7fba1 100644 --- a/src/domain/msg/flag/entity.rs +++ b/src/domain/msg/flag/entity.rs @@ -3,6 +3,7 @@ use serde::ser::{Serialize, SerializeSeq, Serializer}; use std::borrow::Cow; use std::collections::HashSet; +use std::fmt; use std::ops::{Deref, DerefMut}; use std::convert::From; @@ -70,27 +71,25 @@ impl Flags { } } -impl ToString for Flags { - fn to_string(&self) -> String { - let mut flags = String::new(); - +impl fmt::Display for Flags { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut glue = ""; for flag in &self.0 { + write!(f, "{}", glue)?; match flag { - Flag::Seen => flags.push_str("\\Seen "), - Flag::Answered => flags.push_str("\\Answered "), - Flag::Flagged => flags.push_str("\\Flagged "), - Flag::Deleted => flags.push_str("\\Deleted "), - Flag::Draft => flags.push_str("\\Draft "), - Flag::Recent => flags.push_str("\\Recent "), - Flag::MayCreate => flags.push_str("\\MayCreate "), - Flag::Custom(cow) => flags.push_str(&format!("\\{} ", cow)), - _ => panic!("Unknown flag!"), + Flag::Seen => write!(f, "\\Seen")?, + Flag::Answered => write!(f, "\\Answered")?, + Flag::Flagged => write!(f, "\\Flagged")?, + Flag::Deleted => write!(f, "\\Deleted")?, + Flag::Draft => write!(f, "\\Draft")?, + Flag::Recent => write!(f, "\\Recent")?, + Flag::MayCreate => write!(f, "\\MayCreate")?, + Flag::Custom(cow) => write!(f, "{}", cow)?, + _ => (), } + glue = " "; } - - // remove the trailing whitespaces - flags = flags.trim_end_matches(' ').to_string(); - flags + Ok(()) } } @@ -143,13 +142,14 @@ impl From<&str> for Flags { for flag in flags.split_ascii_whitespace() { match flag { - "Seen" => content.insert(Flag::Seen), "Answered" => content.insert(Flag::Answered), - "Deleted" => content.insert(Flag::Flagged), + "Deleted" => content.insert(Flag::Deleted), "Draft" => content.insert(Flag::Draft), - "Recent" => content.insert(Flag::Recent), + "Flagged" => content.insert(Flag::Flagged), "MayCreate" => content.insert(Flag::MayCreate), - _other => content.insert(Flag::Custom(Cow::Owned(_other.to_string()))), + "Recent" => content.insert(Flag::Recent), + "Seen" => content.insert(Flag::Seen), + custom => content.insert(Flag::Custom(Cow::Owned(custom.to_string()))), }; } @@ -157,6 +157,29 @@ impl From<&str> for Flags { } } +impl<'a> From> for Flags { + fn from(flags: Vec<&'a str>) -> Self { + let mut map: HashSet> = HashSet::new(); + + for f in flags { + match f { + "Answered" | _ if f.eq_ignore_ascii_case("answered") => map.insert(Flag::Answered), + "Deleted" | _ if f.eq_ignore_ascii_case("deleted") => map.insert(Flag::Deleted), + "Draft" | _ if f.eq_ignore_ascii_case("draft") => map.insert(Flag::Draft), + "Flagged" | _ if f.eq_ignore_ascii_case("flagged") => map.insert(Flag::Flagged), + "MayCreate" | _ if f.eq_ignore_ascii_case("maycreate") => { + map.insert(Flag::MayCreate) + } + "Recent" | _ if f.eq_ignore_ascii_case("recent") => map.insert(Flag::Recent), + "Seen" | _ if f.eq_ignore_ascii_case("seen") => map.insert(Flag::Seen), + custom => map.insert(Flag::Custom(Cow::Owned(custom.into()))), + }; + } + + Self(map) + } +} + impl Deref for Flags { type Target = HashSet>; diff --git a/src/domain/msg/flag/handler.rs b/src/domain/msg/flag/handler.rs index 36280ef..e69d44b 100644 --- a/src/domain/msg/flag/handler.rs +++ b/src/domain/msg/flag/handler.rs @@ -1,36 +1,82 @@ +//! Module related to message flag handling. +//! +//! This module gathers all message flag commands. + use anyhow::Result; -use crate::domain::{imap::service::ImapServiceInterface, msg::flag::entity::Flags}; +use crate::{ + domain::{imap::service::ImapServiceInterface, msg::flag::entity::Flags}, + output::service::OutputServiceInterface, +}; -pub fn set( - uid: &str, - flags: &str, - imap: &mut ImapService, +/// Add flags from the given message UID sequence. +/// Flags do not need to be prefixed with `\` and they are not case-sensitive. +/// +/// ```ignore +/// add("21", "\\Seen", &output, &mut imap)?; +/// add("42", "recent", &output, &mut imap)?; +/// add("1:10", "Answered custom", &output, &mut imap)?; +/// ``` +pub fn add<'a, OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>( + uid: &'a str, + flags: Vec<&'a str>, + output: &'a OutputService, + imap: &'a mut ImapService, ) -> Result<()> { let flags = Flags::from(flags); - imap.set_flags(uid, flags)?; + imap.add_flags(uid, &flags)?; + output.print(format!( + r#"Flag(s) "{}" successfully added to message {}"#, + flags, uid + ))?; imap.logout()?; Ok(()) } -pub fn add( - uid: &str, - flags: &str, - imap: &mut ImapService, +/// Remove flags from the given message UID sequence. +/// Flags do not need to be prefixed with `\` and they are not case-sensitive. +/// +/// ```ignore +/// remove("21", "\\Seen", &output, &mut imap)?; +/// remove("42", "recent", &output, &mut imap)?; +/// remove("1:10", "Answered custom", &output, &mut imap)?; +/// ``` +pub fn remove<'a, OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>( + uid: &'a str, + flags: Vec<&'a str>, + output: &'a OutputService, + imap: &'a mut ImapService, ) -> Result<()> { let flags = Flags::from(flags); - imap.add_flags(uid, flags)?; + imap.remove_flags(uid, &flags)?; + output.print(format!( + r#"Flag(s) "{}" successfully removed from message {}"#, + flags, uid + ))?; imap.logout()?; Ok(()) } -pub fn remove( - uid: &str, - flags: &str, - imap: &mut ImapService, +/// Replace flags from the given message UID sequence. +/// Flags do not need to be prefixed with `\` and they are not case-sensitive. +/// +/// ```ignore +/// set("21", "\\Seen", &output, &mut imap)?; +/// set("42", "recent", &output, &mut imap)?; +/// set("1:10", "Answered custom", &output, &mut imap)?; +/// ``` +pub fn set<'a, OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>( + uid: &'a str, + flags: Vec<&'a str>, + output: &'a OutputService, + imap: &'a mut ImapService, ) -> Result<()> { let flags = Flags::from(flags); - imap.remove_flags(uid, flags)?; + imap.set_flags(uid, &flags)?; + output.print(format!( + r#"Flag(s) "{}" successfully set for message {}"#, + flags, uid + ))?; imap.logout()?; Ok(()) } diff --git a/src/domain/msg/handler.rs b/src/domain/msg/handler.rs index ccacd12..29b5602 100644 --- a/src/domain/msg/handler.rs +++ b/src/domain/msg/handler.rs @@ -1,3 +1,7 @@ +//! Module related to message handling. +//! +//! This module gathers all message commands. + use anyhow::{Context, Result}; use atty::Stream; use imap::types::Flag; @@ -175,7 +179,7 @@ pub fn delete Result<()> { let flags = Flags::from(vec![Flag::Seen, Flag::Deleted]); - imap.add_flags(uid, flags)?; + imap.add_flags(uid, &flags)?; imap.expunge()?; output.print(format!("Message(s) {} successfully deleted", uid))?; imap.logout()?; @@ -288,8 +292,8 @@ pub fn move_( uid, target ))?; // delete the msg in the old mailbox - let flags = vec![Flag::Seen, Flag::Deleted]; - imap.add_flags(uid, Flags::from(flags))?; + let flags = Flags::from(vec![Flag::Seen, Flag::Deleted]); + imap.add_flags(uid, &flags)?; imap.expunge()?; imap.logout()?; Ok(()) diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index d1965e4..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! # Welcome to Himalaya! -//! Here's a little summary of how to read the code of himalaya: -//! Each module includes three "main" files: -//! - `model.rs`: **The "main" file** of each module which includes the main implementation of the given -//! module. -//! - `cli.rs`: Includes the subcommands and arguments which are related to the module. -//! -//! For example the `read` subcommand is in the `msg/cli.rs` file because it's related to the -//! msg you want to read. -//! -//! - `mod.rs`: Includes all other files in the module. Click [here] for more information. -//! -//! [here]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html - -pub mod compl; - -/// Everything which is related to the config files. For example the structure of your config file. -pub mod config; - -/// Handles the output. For example the JSON and HTML output. -pub mod output; - -pub mod domain; -pub mod ui; diff --git a/src/main.rs b/src/main.rs index 99c911a..c8d20ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,20 +4,20 @@ use env_logger; use std::{convert::TryFrom, env}; use url::Url; -use himalaya::{ - compl, - config::{ - self, - entity::{Account, Config}, - }, - domain::{ - imap::{self, service::ImapService}, - mbox::{self, entity::Mbox}, - msg, - smtp::service::SmtpService, - }, - output::{self, service::OutputService}, +mod compl; +mod config; +mod domain; +mod output; +mod ui; + +use config::entity::{Account, Config}; +use domain::{ + imap::{self, service::ImapService}, + mbox::{self, entity::Mbox}, + msg, + smtp::service::SmtpService, }; +use output::service::OutputService; fn create_app<'a>() -> clap::App<'a, 'a> { clap::App::new(env!("CARGO_PKG_NAME")) @@ -132,13 +132,13 @@ fn main() -> Result<()> { Some(msg::arg::Command::Flag(m)) => match m { Some(msg::flag::arg::Command::Set(uid, flags)) => { - return msg::flag::handler::set(uid, flags, &mut imap); + return msg::flag::handler::set(uid, flags, &output, &mut imap); } Some(msg::flag::arg::Command::Add(uid, flags)) => { - return msg::flag::handler::add(uid, flags, &mut imap); + return msg::flag::handler::add(uid, flags, &output, &mut imap); } Some(msg::flag::arg::Command::Remove(uid, flags)) => { - return msg::flag::handler::remove(uid, flags, &mut imap); + return msg::flag::handler::remove(uid, flags, &output, &mut imap); } _ => (), }, diff --git a/src/output/service.rs b/src/output/service.rs index 8f20368..509dc34 100644 --- a/src/output/service.rs +++ b/src/output/service.rs @@ -67,17 +67,6 @@ pub struct OutputService { } impl OutputService { - /// Create a new output-handler by setting the given formatting style. - pub fn new(slice: &str) -> Result { - let fmt = OutputFmt::try_from(Some(slice))?; - Ok(Self { fmt }) - } - - /// Returns true, if the formatting should be plaintext. - pub fn is_plain(&self) -> bool { - self.fmt == OutputFmt::Plain - } - /// Returns true, if the formatting should be json. pub fn is_json(&self) -> bool { self.fmt == OutputFmt::Json