diff --git a/src/config/entity.rs b/src/config/entity.rs index c0cf32d..0370592 100644 --- a/src/config/entity.rs +++ b/src/config/entity.rs @@ -500,7 +500,7 @@ impl<'a> TryFrom<(&'a Config, Option<&str>)> for Account { .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.trim_end())) + .map(|sig| format!("\n\n{}{}", signature_delim, sig.trim_end())) .unwrap_or_default(); let account = Account { diff --git a/src/domain/msg/arg.rs b/src/domain/msg/arg.rs index cf30a72..8d31678 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("flag") { + if let Some(m) = m.subcommand_matches("flags") { return Ok(Some(Command::Flag(msg::flag::arg::matches(&m)?))); } diff --git a/src/domain/msg/entity.rs b/src/domain/msg/entity.rs index b580da7..672d3d2 100644 --- a/src/domain/msg/entity.rs +++ b/src/domain/msg/entity.rs @@ -169,15 +169,10 @@ impl Msg { if headers.from.is_empty() { headers.from = vec![account.address()]; } - let body = Body::new_with_text(if let Some(sig) = headers.signature.as_ref() { - format!("\n{}", sig) - } else { - String::from("\n") - }); Self { headers, - body, + body: Body::new_with_text(""), sig: account.signature.to_owned(), ..Self::default() } @@ -269,14 +264,13 @@ impl Msg { cc, subject: Some(subject), in_reply_to: self.headers.message_id.clone(), - signature: Some(account.signature.to_owned()), // and clear the rest of the fields ..Headers::default() }; // comment "out" the body of the msg, by adding the `>` characters to // each line which includes a string. - let mut new_body = self + let new_body = self .body .plain .clone() @@ -289,15 +283,10 @@ impl Msg { .collect::>() .join("\n"); - // also add the the signature in the end - if let Some(sig) = new_headers.signature.as_ref() { - new_body.push('\n'); - new_body.push_str(&sig) - } - self.body = Body::new_with_text(new_body); self.headers = new_headers; self.attachments.clear(); + self.sig = account.signature.to_owned(); Ok(()) } @@ -358,10 +347,8 @@ impl Msg { .unwrap_or_default() .replace("\r", ""), )); - - body.push_str(&account.signature); - self.body = Body::new_with_text(body); + self.sig = account.signature.to_owned(); } /// Returns the bytes of the *sendable message* of the struct! diff --git a/src/domain/msg/flag/arg.rs b/src/domain/msg/flag/arg.rs index d21b29f..43730e0 100644 --- a/src/domain/msg/flag/arg.rs +++ b/src/domain/msg/flag/arg.rs @@ -3,7 +3,7 @@ //! This module provides subcommands, arguments and a command matcher related to message flag. use anyhow::Result; -use clap::{self, App, Arg, ArgMatches, SubCommand}; +use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand}; use log::debug; use crate::domain::msg; @@ -63,6 +63,7 @@ fn flags_arg<'a>() -> Arg<'a, 'a> { pub fn subcmds<'a>() -> Vec> { vec![SubCommand::with_name("flags") .about("Handles flags") + .setting(AppSettings::SubcommandRequiredElseHelp) .subcommand( SubCommand::with_name("set") .about("Replaces all message flags") diff --git a/src/domain/msg/handler.rs b/src/domain/msg/handler.rs index 9a71ec8..be6bddd 100644 --- a/src/domain/msg/handler.rs +++ b/src/domain/msg/handler.rs @@ -266,7 +266,6 @@ pub fn mailto, pub reply_to: Option>, pub sender: Option, - pub signature: Option, pub subject: Option, } @@ -202,7 +201,6 @@ impl Default for Headers { message_id: None, reply_to: None, sender: None, - signature: None, subject: None, } } @@ -261,7 +259,6 @@ impl TryFrom>> for Headers { bcc, in_reply_to, custom_headers: None, - signature: None, encoding: ContentTransferEncoding::Base64, }) } else { @@ -414,15 +411,7 @@ impl<'from> From<&mailparse::ParsedMail<'from>> for Headers { /// [get_header_as_string]: struct.Headers.html#method.get_header_as_string impl fmt::Display for Headers { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - let mut header = self.get_header_as_string(); - - // now add some space between the header and the signature - header.push_str("\n\n\n"); - - // and add the signature in the end - header.push_str(&self.signature.clone().unwrap_or(String::new())); - - write!(formatter, "{}", header) + write!(formatter, "{}", self.get_header_as_string()) } } diff --git a/src/domain/msg/tpl/arg.rs b/src/domain/msg/tpl/arg.rs index 92949e1..49d1b82 100644 --- a/src/domain/msg/tpl/arg.rs +++ b/src/domain/msg/tpl/arg.rs @@ -8,56 +8,44 @@ 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; +#[derive(Debug)] +pub struct Tpl<'a> { + pub subject: Option<&'a str>, + pub from: Option>, + pub to: Option>, + pub cc: Option>, + pub bcc: Option>, + pub headers: Option>, + pub body: Option<&'a str>, + pub sig: Option<&'a str>, +} + /// Message template commands. pub enum Command<'a> { - New( - Subject<'a>, - From<'a>, - To<'a>, - Cc<'a>, - Bcc<'a>, - Headers<'a>, - Body<'a>, - Signature<'a>, - ), - Reply(Uid<'a>, All), - Forward(Uid<'a>), + New(Tpl<'a>), + Reply(Uid<'a>, All, Tpl<'a>), + Forward(Uid<'a>, Tpl<'a>), } /// Message template command matcher. pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { if let Some(m) = m.subcommand_matches("new") { debug!("new command matched"); - 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, - ))); + let tpl = Tpl { + subject: m.value_of("subject"), + from: m.values_of("from"), + to: m.values_of("to"), + cc: m.values_of("cc"), + bcc: m.values_of("bcc"), + headers: m.values_of("headers"), + body: m.value_of("body"), + sig: m.value_of("signature"), + }; + debug!("template: `{:?}`", tpl); + return Ok(Some(Command::New(tpl))); } if let Some(m) = m.subcommand_matches("reply") { @@ -66,14 +54,36 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { debug!("uid: {}", uid); let all = m.is_present("reply-all"); debug!("reply all: {}", all); - return Ok(Some(Command::Reply(uid, all))); + let tpl = Tpl { + subject: m.value_of("subject"), + from: m.values_of("from"), + to: m.values_of("to"), + cc: m.values_of("cc"), + bcc: m.values_of("bcc"), + headers: m.values_of("headers"), + body: m.value_of("body"), + sig: m.value_of("signature"), + }; + debug!("template: `{:?}`", tpl); + return Ok(Some(Command::Reply(uid, all, tpl))); } if let Some(m) = m.subcommand_matches("forward") { debug!("forward command matched"); let uid = m.value_of("uid").unwrap(); debug!("uid: {}", uid); - return Ok(Some(Command::Forward(uid))); + let tpl = Tpl { + subject: m.value_of("subject"), + from: m.values_of("from"), + to: m.values_of("to"), + cc: m.values_of("cc"), + bcc: m.values_of("bcc"), + headers: m.values_of("headers"), + body: m.value_of("body"), + sig: m.value_of("signature"), + }; + debug!("template: `{:?}`", tpl); + return Ok(Some(Command::Forward(uid, tpl))); } Ok(None) diff --git a/src/domain/msg/tpl/handler.rs b/src/domain/msg/tpl/handler.rs index 3362a61..5666402 100644 --- a/src/domain/msg/tpl/handler.rs +++ b/src/domain/msg/tpl/handler.rs @@ -6,7 +6,6 @@ use std::{ use anyhow::Result; use atty::Stream; -use clap::Values; use log::{debug, trace}; use crate::{ @@ -17,59 +16,54 @@ use crate::{ body::Body, entity::{Msg, MsgSerialized}, headers::Headers, + tpl::arg::Tpl, }, }, output::service::OutputServiceInterface, }; 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, + tpl: Tpl<'a>, + account: &'a Account, + output: &'a OutputService, + imap: &'a mut ImapService, ) -> Result<()> { let mut msg = Msg::new(&account); - override_msg_with_args(&mut msg, subject, from, to, cc, bcc, headers, body, sig); + override_msg_with_args(&mut msg, tpl); trace!("message: {:#?}", msg); output.print(MsgSerialized::try_from(&msg)?)?; imap.logout()?; Ok(()) } -pub fn reply( +pub fn reply<'a, OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>( uid: &str, all: bool, - account: &Account, - output: &OutputService, - imap: &mut ImapService, + tpl: Tpl<'a>, + account: &'a Account, + output: &'a OutputService, + imap: &'a mut ImapService, ) -> Result<()> { - let mut msg = imap.get_msg(&uid)?; - msg.change_to_reply(&account, all)?; - // FIXME - // override_msg_with_args(&mut msg, &matches); + let mut msg = imap.get_msg(uid)?; + msg.change_to_reply(account, all)?; + override_msg_with_args(&mut msg, tpl); trace!("Message: {:?}", msg); output.print(MsgSerialized::try_from(&msg)?)?; imap.logout()?; Ok(()) } -pub fn forward( +pub fn forward<'a, OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>( uid: &str, - account: &Account, - output: &OutputService, - imap: &mut ImapService, + tpl: Tpl<'a>, + account: &'a Account, + output: &'a OutputService, + imap: &'a mut ImapService, ) -> Result<()> { let mut msg = imap.get_msg(&uid)?; + msg.sig = account.signature.to_owned(); msg.change_to_forwarding(&account); - // FIXME - // override_msg_with_args(&mut msg, &matches); + override_msg_with_args(&mut msg, tpl); trace!("Message: {:?}", msg); output.print(MsgSerialized::try_from(&msg)?)?; imap.logout()?; @@ -79,35 +73,24 @@ pub fn forward( - 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>, -) { +fn override_msg_with_args<'a>(msg: &mut Msg, tpl: Tpl<'a>) { // -- Collecting credentials -- - let from: Vec = match from { + let from: Vec = match tpl.from { Some(from) => from.map(|arg| arg.to_string()).collect(), None => msg.headers.from.clone(), }; - let to: Vec = match to { + let to: Vec = match tpl.to { Some(to) => to.map(|arg| arg.to_string()).collect(), None => Vec::new(), }; - 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 subject = tpl.subject.map(String::from); + let cc: Option> = tpl.cc.map(|cc| cc.map(|arg| arg.to_string()).collect()); + let bcc: Option> = tpl.bcc.map(|bcc| bcc.map(|arg| arg.to_string()).collect()); let custom_headers: Option>> = { - if let Some(matched_headers) = headers { + if let Some(matched_headers) = tpl.headers { let mut custom_headers: HashMap> = HashMap::new(); // collect the custom headers @@ -136,11 +119,15 @@ fn override_msg_with_args<'a>( .join("\n"); debug!("overriden body from stdin: {:?}", body); body - } else if let Some(body) = body { + } else if let Some(body) = tpl.body { debug!("overriden body: {:?}", body); body.to_string() } else { - String::new() + msg.body + .plain + .as_ref() + .map(String::from) + .unwrap_or_default() } }; @@ -153,11 +140,11 @@ fn override_msg_with_args<'a>( to, cc, bcc, - signature, custom_headers, ..msg.headers.clone() }; msg.headers = headers; msg.body = body; + msg.sig = tpl.sig.map(String::from).unwrap_or(msg.sig.to_owned()); } diff --git a/src/main.rs b/src/main.rs index 43b47d5..540379c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,6 @@ fn create_app<'a>() -> clap::App<'a, 'a> { .about(env!("CARGO_PKG_DESCRIPTION")) .author(env!("CARGO_PKG_AUTHORS")) .setting(AppSettings::GlobalVersion) - .setting(AppSettings::SubcommandRequiredElseHelp) .args(&config::arg::args()) .args(&output::arg::args()) .arg(mbox::arg::source_arg()) @@ -144,16 +143,14 @@ fn main() -> Result<()> { _ => (), }, Some(msg::arg::Command::Tpl(m)) => match m { - 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, - ); + Some(msg::tpl::arg::Command::New(tpl)) => { + return msg::tpl::handler::new(tpl, &account, &output, &mut imap); } - Some(msg::tpl::arg::Command::Reply(uid, all)) => { - return msg::tpl::handler::reply(uid, all, &account, &output, &mut imap); + Some(msg::tpl::arg::Command::Reply(uid, all, tpl)) => { + return msg::tpl::handler::reply(uid, all, tpl, &account, &output, &mut imap); } - Some(msg::tpl::arg::Command::Forward(uid)) => { - return msg::tpl::handler::forward(uid, &account, &output, &mut imap); + Some(msg::tpl::arg::Command::Forward(uid, tpl)) => { + return msg::tpl::handler::forward(uid, tpl, &account, &output, &mut imap); } _ => (), },