From e065d8d905fdbbf295da7d2ecdd10f8861b4cfaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Sat, 18 Sep 2021 21:45:26 +0200 Subject: [PATCH] fix new tpl sig --- src/compl/handler.rs | 4 +- src/config/entity.rs | 58 +---------- src/domain/mbox/arg.rs | 6 +- src/domain/msg/arg.rs | 20 ++-- src/domain/msg/body.rs | 14 +-- src/domain/msg/entity.rs | 67 +++++-------- src/domain/msg/tpl/arg.rs | 44 ++++++++- src/domain/msg/tpl/handler.rs | 179 ++++++++++++++++++---------------- src/main.rs | 28 +++--- 9 files changed, 199 insertions(+), 221 deletions(-) diff --git a/src/compl/handler.rs b/src/compl/handler.rs index 5c4ca01..2eda507 100644 --- a/src/compl/handler.rs +++ b/src/compl/handler.rs @@ -1,13 +1,13 @@ //! Module related to completion handling. //! -//! This module gathers all completion actions triggered by the CLI. +//! This module gathers all completion commands. use anyhow::{anyhow, Context, Result}; use clap::{App, Shell}; use std::{io, str::FromStr}; /// Generate completion script from the given [`clap::App`] for the given shell slice. -pub fn generate<'a>(shell: Option<&'a str>, mut app: App<'a, 'a>) -> Result<()> { +pub fn generate<'a>(mut app: App<'a, 'a>, shell: Option<&'a str>) -> Result<()> { let shell = Shell::from_str(shell.unwrap_or_default()) .map_err(|err| anyhow!(err)) .context("cannot parse shell")?; diff --git a/src/config/entity.rs b/src/config/entity.rs index 1ba7d74..c0cf32d 100644 --- a/src/config/entity.rs +++ b/src/config/entity.rs @@ -183,60 +183,6 @@ impl Config { Ok(()) } - /// Returns the signature of the given acccount in combination witht the sigantion delimiter. - /// If the account doesn't have a signature, then the global signature is used. - /// - /// # Example - /// ``` - /// use himalaya::config::model::{Config, Account}; - /// - /// fn main() { - /// let config = Config { - /// signature: Some("Global signature".to_string()), - /// .. Config::default() - /// }; - /// - /// // a config without a global signature - /// let config_no_global = Config::default(); - /// - /// let account1 = Account::new_with_signature(Some("Account Name"), "mail@address.com", Some("Cya")); - /// let account2 = Account::new(Some("Bruh"), "mail@address.com"); - /// - /// // Hint: Don't forget the default signature delimiter: '\n-- \n' - /// assert_eq!(config.signature(&account1), Some("\n-- \nCya".to_string())); - /// assert_eq!(config.signature(&account2), Some("\n-- \nGlobal signature".to_string())); - /// - /// assert_eq!(config_no_global.signature(&account2), None); - /// } - /// ``` - pub fn signature(&self, account: &ConfigAccountEntry) -> Option { - let default_sig_delim = String::from("-- \n"); - let sig_delim = account - .signature_delimiter - .as_ref() - .or_else(|| self.signature_delimiter.as_ref()) - .unwrap_or(&default_sig_delim); - let sig = account - .signature - .as_ref() - .or_else(|| self.signature.as_ref()); - sig.and_then(|sig| shellexpand::full(sig).ok()) - .map(|sig| sig.to_string()) - .and_then(|sig| fs::read_to_string(sig).ok()) - .or_else(|| sig.map(|sig| sig.to_owned())) - .map(|sig| format!("\n{}{}", sig_delim, sig)) - } - - pub fn default_page_size(&self, account: &ConfigAccountEntry) -> usize { - account - .default_page_size - .as_ref() - .or_else(|| self.default_page_size.as_ref()) - .or(Some(&DEFAULT_PAGE_SIZE)) - .unwrap() - .to_owned() - } - pub fn exec_watch_cmds(&self, account: &ConfigAccountEntry) -> Result<()> { let cmds = account .watch_cmds @@ -551,10 +497,10 @@ impl<'a> TryFrom<(&'a Config, Option<&str>)> for Account { .or_else(|| config.signature.as_ref()); let signature = signature .and_then(|sig| shellexpand::full(sig).ok()) - .map(|sig| sig.to_string()) + .map(String::from) .and_then(|sig| fs::read_to_string(sig).ok()) .or_else(|| signature.map(|sig| sig.to_owned())) - .map(|sig| format!("\n{}{}", signature_delim, sig)) + .map(|sig| format!("\n{}{}", signature_delim, sig.trim_end())) .unwrap_or_default(); let account = Account { diff --git a/src/domain/mbox/arg.rs b/src/domain/mbox/arg.rs index 05d27d6..bfa4e0d 100644 --- a/src/domain/mbox/arg.rs +++ b/src/domain/mbox/arg.rs @@ -7,16 +7,16 @@ use clap::{App, Arg, ArgMatches, SubCommand}; use log::debug; /// Mailbox commands. -pub enum Commands { +pub enum Command { /// List all available mailboxes. List, } /// Mailbox command matcher. -pub fn matches(m: &ArgMatches) -> Result> { +pub fn matches(m: &ArgMatches) -> Result> { if let Some(_) = m.subcommand_matches("mailboxes") { debug!("mailboxes command matched"); - return Ok(Some(Commands::List)); + return Ok(Some(Command::List)); } Ok(None) diff --git a/src/domain/msg/arg.rs b/src/domain/msg/arg.rs index 7c0f170..cf30a72 100644 --- a/src/domain/msg/arg.rs +++ b/src/domain/msg/arg.rs @@ -34,8 +34,8 @@ pub enum Command<'a> { Send(RawMsg<'a>), Write(AttachmentsPaths<'a>), - Flag(msg::flag::arg::Command<'a>), - Tpl(msg::tpl::arg::Command<'a>), + Flag(Option>), + Tpl(Option>), } /// Message command matcher. @@ -63,10 +63,6 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { return Ok(Some(Command::Delete(uid))); } - if let Some(m) = msg::flag::arg::matches(&m)? { - return Ok(Some(Command::Flag(m))); - } - if let Some(m) = m.subcommand_matches("forward") { debug!("forward command matched"); let uid = m.value_of("uid").unwrap(); @@ -176,10 +172,6 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { return Ok(Some(Command::Send(msg))); } - if let Some(m) = msg::tpl::arg::matches(&m)? { - return Ok(Some(Command::Tpl(m))); - } - if let Some(m) = m.subcommand_matches("write") { debug!("write command matched"); let attachment_paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect(); @@ -187,6 +179,14 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { return Ok(Some(Command::Write(attachment_paths))); } + if let Some(m) = m.subcommand_matches("template") { + return Ok(Some(Command::Tpl(msg::tpl::arg::matches(&m)?))); + } + + if let Some(m) = m.subcommand_matches("flag") { + return Ok(Some(Command::Flag(msg::flag::arg::matches(&m)?))); + } + debug!("default list command matched"); Ok(Some(Command::List(None, 0))) } diff --git a/src/domain/msg/body.rs b/src/domain/msg/body.rs index d32575f..198f72a 100644 --- a/src/domain/msg/body.rs +++ b/src/domain/msg/body.rs @@ -13,8 +13,8 @@ use std::fmt; /// This part of the msg/msg would be stored in this struct. #[derive(Clone, Serialize, Debug, PartialEq, Eq)] pub struct Body { - /// The text version of a body (if available) - pub text: Option, + /// The plain version of a body (if available) + pub plain: Option, /// The html version of a body (if available) pub html: Option, @@ -61,7 +61,7 @@ impl Body { /// ``` pub fn new_with_text(text: S) -> Self { Self { - text: Some(text.to_string()), + plain: Some(text.to_string()), html: None, } } @@ -85,7 +85,7 @@ impl Body { /// ``` pub fn new_with_html(html: S) -> Self { Self { - text: None, + plain: None, html: Some(html.to_string()), } } @@ -109,7 +109,7 @@ impl Body { /// ``` pub fn new_with_both(text: S, html: S) -> Self { Self { - text: Some(text.to_string()), + plain: Some(text.to_string()), html: Some(html.to_string()), } } @@ -119,7 +119,7 @@ impl Body { impl Default for Body { fn default() -> Self { Self { - text: None, + plain: None, html: None, } } @@ -127,7 +127,7 @@ impl Default for Body { impl fmt::Display for Body { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - let content = if let Some(text) = self.text.clone() { + let content = if let Some(text) = self.plain.clone() { text } else if let Some(html) = self.html.clone() { html diff --git a/src/domain/msg/entity.rs b/src/domain/msg/entity.rs index 8613454..b580da7 100644 --- a/src/domain/msg/entity.rs +++ b/src/domain/msg/entity.rs @@ -16,8 +16,7 @@ use crate::ui::editor; use serde::Serialize; use lettre::message::{ - header::ContentTransferEncoding, header::ContentType, Attachment as lettre_Attachment, Mailbox, - Message, MultiPart, SinglePart, + header::ContentType, Attachment as lettre_Attachment, Mailbox, Message, MultiPart, SinglePart, }; use std::{ @@ -64,7 +63,7 @@ impl fmt::Display for MsgSerialized { /// This struct represents a whole msg with its attachments, body-content /// and its headers. -#[derive(Debug, PartialEq, Eq, Clone, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] pub struct Msg { /// All added attachments are listed in this vector. pub attachments: Vec, @@ -80,6 +79,9 @@ pub struct Msg { /// This includes the general content text and the signature. pub body: Body, + /// The signature of the message. + pub sig: String, + /// The UID of the msg. In general, a message should already have one, unless you're writing a /// new message, then we're generating it. uid: Option, @@ -167,11 +169,6 @@ impl Msg { if headers.from.is_empty() { headers.from = vec![account.address()]; } - - if let None = headers.signature { - headers.signature = Some(account.signature.to_owned()); - } - let body = Body::new_with_text(if let Some(sig) = headers.signature.as_ref() { format!("\n{}", sig) } else { @@ -181,6 +178,7 @@ impl Msg { Self { headers, body, + sig: account.signature.to_owned(), ..Self::default() } } @@ -280,7 +278,7 @@ impl Msg { // each line which includes a string. let mut new_body = self .body - .text + .plain .clone() .unwrap_or_default() .lines() @@ -354,7 +352,11 @@ impl Msg { // apply a line which should indicate where the forwarded message begins body.push_str(&format!( "\n---------- Forwarded Message ----------\n{}", - self.body.text.clone().unwrap_or_default().replace("\r", ""), + self.body + .plain + .clone() + .unwrap_or_default() + .replace("\r", ""), )); body.push_str(&account.signature); @@ -685,16 +687,16 @@ impl Msg { let mut msg_parts = MultiPart::mixed().build(); // -- Body -- - if self.body.text.is_some() && self.body.html.is_some() { + if self.body.plain.is_some() && self.body.html.is_some() { msg_parts = msg_parts.multipart(MultiPart::alternative_plain_html( - self.body.text.clone().unwrap(), + self.body.plain.clone().unwrap(), self.body.html.clone().unwrap(), )); } else { let msg_body = SinglePart::builder() .header(ContentType::TEXT_PLAIN) .header(self.headers.encoding) - .body(self.body.text.clone().unwrap_or_default()); + .body(self.body.plain.clone().unwrap_or_default()); msg_parts = msg_parts.singlepart(msg_body); } @@ -740,42 +742,16 @@ impl Msg { Ok(raw_message) } - - /// Returns the [`ContentTransferEncoding`] of the body. - pub fn get_encoding(&self) -> ContentTransferEncoding { - self.headers.encoding - } - - /// Returns the whole message: Header + Body as a String - pub fn get_full_message(&self) -> String { - format!("{}\n{}", self.headers.get_header_as_string(), self.body) - } -} - -// -- Traits -- -impl Default for Msg { - fn default() -> Self { - Self { - attachments: Vec::new(), - flags: Flags::default(), - headers: Headers::default(), - body: Body::default(), - // the uid is generated in the "to_sendable_msg" function if the server didn't apply a - // message id to it. - uid: None, - date: None, - raw: Vec::new(), - } - } } impl fmt::Display for Msg { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!( formatter, - "{}\n{}", + "{}\n{}{}", self.headers.get_header_as_string(), - self.body + self.body, + self.sig ) } } @@ -880,7 +856,7 @@ impl TryFrom<&Fetch> for Msg { // don't. This condition hits, if the body isn't in a multipart, so we can // immediately fetch the body from the first part of the mail. match parsed.ctype.mimetype.as_ref() { - "text/plain" => body.text = parsed.get_body().ok(), + "text/plain" => body.plain = parsed.get_body().ok(), "text/html" => body.html = parsed.get_body().ok(), _ => (), }; @@ -889,8 +865,8 @@ impl TryFrom<&Fetch> for Msg { // now it might happen, that the body is *in* a multipart, if // that's the case, look, if we've already applied a body // (body.is_empty()) and set it, if needed - if body.text.is_none() && subpart.ctype.mimetype == "text/plain" { - body.text = subpart.get_body().ok(); + if body.plain.is_none() && subpart.ctype.mimetype == "text/plain" { + body.plain = subpart.get_body().ok(); } else if body.html.is_none() && subpart.ctype.mimetype == "text/html" { body.html = subpart.get_body().ok(); } @@ -918,6 +894,7 @@ impl TryFrom<&Fetch> for Msg { uid, date, raw, + ..Self::default() }) } } diff --git a/src/domain/msg/tpl/arg.rs b/src/domain/msg/tpl/arg.rs index bee9e38..92949e1 100644 --- a/src/domain/msg/tpl/arg.rs +++ b/src/domain/msg/tpl/arg.rs @@ -3,26 +3,61 @@ //! This module provides subcommands, arguments and a command matcher related to message template. use anyhow::Result; -use clap::{self, App, Arg, ArgMatches, SubCommand}; +use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand, Values}; use log::debug; use crate::domain::msg::{self, arg::uid_arg}; +type Subject<'a> = Option<&'a str>; +type From<'a> = Option>; +type To<'a> = Option>; +type Cc<'a> = Option>; +type Bcc<'a> = Option>; +type Headers<'a> = Option>; +type Body<'a> = Option<&'a str>; +type Signature<'a> = Option<&'a str>; type Uid<'a> = &'a str; type All = bool; /// Message template commands. pub enum Command<'a> { - New, + New( + Subject<'a>, + From<'a>, + To<'a>, + Cc<'a>, + Bcc<'a>, + Headers<'a>, + Body<'a>, + Signature<'a>, + ), Reply(Uid<'a>, All), Forward(Uid<'a>), } /// Message template command matcher. pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { - if let Some(_) = m.subcommand_matches("new") { + if let Some(m) = m.subcommand_matches("new") { debug!("new command matched"); - return Ok(Some(Command::New)); + let subject = m.value_of("subject"); + debug!("subject: `{:?}`", subject); + let from = m.values_of("from"); + debug!("from: `{:?}`", from); + let to = m.values_of("to"); + debug!("to: `{:?}`", to); + let cc = m.values_of("cc"); + debug!("cc: `{:?}`", cc); + let bcc = m.values_of("bcc"); + debug!("bcc: `{:?}`", bcc); + let headers = m.values_of("header"); + debug!("headers: `{:?}`", headers); + let body = m.value_of("body"); + debug!("body: `{:?}`", body); + let sig = m.value_of("signature"); + debug!("signature: `{:?}`", sig); + return Ok(Some(Command::New( + subject, from, to, cc, bcc, headers, body, sig, + ))); } if let Some(m) = m.subcommand_matches("reply") { @@ -100,6 +135,7 @@ pub fn subcmds<'a>() -> Vec> { vec![SubCommand::with_name("template") .aliases(&["tpl"]) .about("Generates a message template") + .setting(AppSettings::SubcommandRequiredElseHelp) .subcommand( SubCommand::with_name("new") .aliases(&["n"]) diff --git a/src/domain/msg/tpl/handler.rs b/src/domain/msg/tpl/handler.rs index 06c6177..3362a61 100644 --- a/src/domain/msg/tpl/handler.rs +++ b/src/domain/msg/tpl/handler.rs @@ -1,25 +1,42 @@ -use std::convert::TryFrom; +use std::{ + collections::HashMap, + convert::TryFrom, + io::{self, BufRead}, +}; use anyhow::Result; -use log::trace; +use atty::Stream; +use clap::Values; +use log::{debug, trace}; use crate::{ config::entity::Account, domain::{ imap::service::ImapServiceInterface, - msg::entity::{Msg, MsgSerialized}, + msg::{ + body::Body, + entity::{Msg, MsgSerialized}, + headers::Headers, + }, }, output::service::OutputServiceInterface, }; -pub fn new( +pub fn new<'a, OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>( + subject: Option<&'a str>, + from: Option>, + to: Option>, + cc: Option>, + bcc: Option>, + headers: Option>, + body: Option<&'a str>, + sig: Option<&'a str>, account: &Account, output: &OutputService, imap: &mut ImapService, ) -> Result<()> { - let msg = Msg::new(&account); - // FIXME - // override_msg_with_args(&mut msg, &matches); + let mut msg = Msg::new(&account); + override_msg_with_args(&mut msg, subject, from, to, cc, bcc, headers, body, sig); trace!("message: {:#?}", msg); output.print(MsgSerialized::try_from(&msg)?)?; imap.logout()?; @@ -62,89 +79,85 @@ pub fn forward = match matches.values_of("from") { -// Some(from) => from.map(|arg| arg.to_string()).collect(), -// None => msg.headers.from.clone(), -// }; +fn override_msg_with_args<'a>( + msg: &mut Msg, + subject: Option<&'a str>, + from: Option>, + to: Option>, + cc: Option>, + bcc: Option>, + headers: Option>, + body: Option<&'a str>, + sig: Option<&'a str>, +) { + // -- Collecting credentials -- + let from: Vec = match from { + Some(from) => from.map(|arg| arg.to_string()).collect(), + None => msg.headers.from.clone(), + }; -// let to: Vec = match matches.values_of("to") { -// Some(to) => to.map(|arg| arg.to_string()).collect(), -// None => Vec::new(), -// }; + let to: Vec = match to { + Some(to) => to.map(|arg| arg.to_string()).collect(), + None => Vec::new(), + }; -// let subject = matches -// .value_of("subject") -// .and_then(|subject| Some(subject.to_string())); + let subject = subject.map(String::from); + let cc: Option> = cc.map(|cc| cc.map(|arg| arg.to_string()).collect()); + let bcc: Option> = bcc.map(|bcc| bcc.map(|arg| arg.to_string()).collect()); + let signature = sig.map(String::from).or(msg.headers.signature.to_owned()); -// let cc: Option> = matches -// .values_of("cc") -// .and_then(|cc| Some(cc.map(|arg| arg.to_string()).collect())); + let custom_headers: Option>> = { + if let Some(matched_headers) = headers { + let mut custom_headers: HashMap> = HashMap::new(); -// let bcc: Option> = matches -// .values_of("bcc") -// .and_then(|bcc| Some(bcc.map(|arg| arg.to_string()).collect())); + // collect the custom headers + for header in matched_headers { + let mut header = header.split(":"); + let key = header.next().unwrap_or_default(); + let val = header.next().unwrap_or_default().trim_start(); -// let signature = matches -// .value_of("signature") -// .and_then(|signature| Some(signature.to_string())) -// .or(msg.headers.signature.clone()); + custom_headers.insert(key.to_string(), vec![val.to_string()]); + } -// let custom_headers: Option>> = { -// if let Some(matched_headers) = matches.values_of("header") { -// let mut custom_headers: HashMap> = HashMap::new(); + Some(custom_headers) + } else { + None + } + }; -// // collect the custom headers -// for header in matched_headers { -// let mut header = header.split(":"); -// let key = header.next().unwrap_or_default(); -// let val = header.next().unwrap_or_default().trim_start(); + let body = { + if atty::isnt(Stream::Stdin) { + let body = io::stdin() + .lock() + .lines() + .filter_map(|line| line.ok()) + .map(|line| line.to_string()) + .collect::>() + .join("\n"); + debug!("overriden body from stdin: {:?}", body); + body + } else if let Some(body) = body { + debug!("overriden body: {:?}", body); + body.to_string() + } else { + String::new() + } + }; -// debug!("overriden header: {}={}", key, val); + let body = Body::new_with_text(body); -// custom_headers.insert(key.to_string(), vec![val.to_string()]); -// } + // -- Creating and printing -- + let headers = Headers { + from, + subject, + to, + cc, + bcc, + signature, + custom_headers, + ..msg.headers.clone() + }; -// Some(custom_headers) -// } else { -// None -// } -// }; - -// let body = { -// if atty::isnt(Stream::Stdin) { -// let body = io::stdin() -// .lock() -// .lines() -// .filter_map(|line| line.ok()) -// .map(|line| line.to_string()) -// .collect::>() -// .join("\n"); -// debug!("overriden body from stdin: {:?}", body); -// body -// } else if let Some(body) = matches.value_of("body") { -// debug!("overriden body: {:?}", body); -// body.to_string() -// } else { -// String::new() -// } -// }; - -// let body = Body::new_with_text(body); - -// // -- Creating and printing -- -// let headers = Headers { -// from, -// subject, -// to, -// cc, -// bcc, -// signature, -// custom_headers, -// ..msg.headers.clone() -// }; - -// msg.headers = headers; -// msg.body = body; -// } + msg.headers = headers; + msg.body = body; +} diff --git a/src/main.rs b/src/main.rs index e9b964d..43b47d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use clap; +use clap::{self, AppSettings}; use env_logger; use std::{convert::TryFrom, env}; use url::Url; @@ -24,8 +24,10 @@ fn create_app<'a>() -> clap::App<'a, 'a> { .version(env!("CARGO_PKG_VERSION")) .about(env!("CARGO_PKG_DESCRIPTION")) .author(env!("CARGO_PKG_AUTHORS")) - .args(&output::arg::args()) + .setting(AppSettings::GlobalVersion) + .setting(AppSettings::SubcommandRequiredElseHelp) .args(&config::arg::args()) + .args(&output::arg::args()) .arg(mbox::arg::source_arg()) .subcommands(compl::arg::subcmds()) .subcommands(imap::arg::subcmds()) @@ -59,7 +61,7 @@ fn main() -> Result<()> { // See https://github.com/soywod/himalaya/issues/115. match compl::arg::matches(&m)? { Some(compl::arg::Command::Generate(shell)) => { - return compl::handler::generate(shell, create_app()); + return compl::handler::generate(create_app(), shell); } _ => (), } @@ -84,7 +86,7 @@ fn main() -> Result<()> { // Check mailbox matches. match mbox::arg::matches(&m)? { - Some(mbox::arg::Commands::List) => { + Some(mbox::arg::Command::List) => { return mbox::handler::list(&output, &mut imap); } _ => (), @@ -130,26 +132,30 @@ fn main() -> Result<()> { } Some(msg::arg::Command::Flag(m)) => match m { - msg::flag::arg::Command::Set(uid, flags) => { + Some(msg::flag::arg::Command::Set(uid, flags)) => { return msg::flag::handler::set(uid, flags, &mut imap); } - msg::flag::arg::Command::Add(uid, flags) => { + Some(msg::flag::arg::Command::Add(uid, flags)) => { return msg::flag::handler::add(uid, flags, &mut imap); } - msg::flag::arg::Command::Remove(uid, flags) => { + Some(msg::flag::arg::Command::Remove(uid, flags)) => { return msg::flag::handler::remove(uid, flags, &mut imap); } + _ => (), }, Some(msg::arg::Command::Tpl(m)) => match m { - msg::tpl::arg::Command::New => { - return msg::tpl::handler::new(&account, &output, &mut imap); + Some(msg::tpl::arg::Command::New(sub, from, to, cc, bcc, h, body, sig)) => { + return msg::tpl::handler::new( + sub, from, to, cc, bcc, h, body, sig, &account, &output, &mut imap, + ); } - msg::tpl::arg::Command::Reply(uid, all) => { + Some(msg::tpl::arg::Command::Reply(uid, all)) => { return msg::tpl::handler::reply(uid, all, &account, &output, &mut imap); } - msg::tpl::arg::Command::Forward(uid) => { + Some(msg::tpl::arg::Command::Forward(uid)) => { return msg::tpl::handler::forward(uid, &account, &output, &mut imap); } + _ => (), }, _ => (), }