fix tpl forward/reply, fix flag subcmd (#190)

This commit is contained in:
Clément DOUIN 2021-09-18 22:02:42 +02:00
parent e065d8d905
commit cdce6c0bf2
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
9 changed files with 101 additions and 131 deletions

View file

@ -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 {

View file

@ -183,7 +183,7 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Command<'a>>> {
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)?)));
}

View file

@ -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::<Vec<String>>()
.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!

View file

@ -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<App<'a, 'a>> {
vec![SubCommand::with_name("flags")
.about("Handles flags")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name("set")
.about("Replaces all message flags")

View file

@ -266,7 +266,6 @@ pub fn mailto<ImapService: ImapServiceInterface, SmtpService: SmtpServiceInterfa
encoding: ContentTransferEncoding::Base64,
bcc: Some(bcc),
cc: Some(cc),
signature: Some(account.signature.to_owned()),
subject: Some(subject.into()),
..Headers::default()
};

View file

@ -48,7 +48,6 @@ pub struct Headers {
pub message_id: Option<String>,
pub reply_to: Option<Vec<String>>,
pub sender: Option<String>,
pub signature: Option<String>,
pub subject: Option<String>,
}
@ -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<Option<&imap_proto::types::Envelope<'_>>> 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())
}
}

View file

@ -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<Values<'a>>;
type To<'a> = Option<Values<'a>>;
type Cc<'a> = Option<Values<'a>>;
type Bcc<'a> = Option<Values<'a>>;
type Headers<'a> = Option<Values<'a>>;
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<Values<'a>>,
pub to: Option<Values<'a>>,
pub cc: Option<Values<'a>>,
pub bcc: Option<Values<'a>>,
pub headers: Option<Values<'a>>,
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<Option<Command<'a>>> {
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<Option<Command<'a>>> {
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)

View file

@ -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<Values<'a>>,
to: Option<Values<'a>>,
cc: Option<Values<'a>>,
bcc: Option<Values<'a>>,
headers: Option<Values<'a>>,
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<OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>(
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<OutputService: OutputServiceInterface, ImapService: ImapServiceInterface>(
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<OutputService: OutputServiceInterface, ImapService: ImapServiceIn
// == Helper functions ==
// -- Template Subcommands --
// These functions are more used for the "template" subcommand
fn override_msg_with_args<'a>(
msg: &mut Msg,
subject: Option<&'a str>,
from: Option<Values<'a>>,
to: Option<Values<'a>>,
cc: Option<Values<'a>>,
bcc: Option<Values<'a>>,
headers: Option<Values<'a>>,
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<String> = match from {
let from: Vec<String> = match tpl.from {
Some(from) => from.map(|arg| arg.to_string()).collect(),
None => msg.headers.from.clone(),
};
let to: Vec<String> = match to {
let to: Vec<String> = match tpl.to {
Some(to) => to.map(|arg| arg.to_string()).collect(),
None => Vec::new(),
};
let subject = subject.map(String::from);
let cc: Option<Vec<String>> = cc.map(|cc| cc.map(|arg| arg.to_string()).collect());
let bcc: Option<Vec<String>> = 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<Vec<String>> = tpl.cc.map(|cc| cc.map(|arg| arg.to_string()).collect());
let bcc: Option<Vec<String>> = tpl.bcc.map(|bcc| bcc.map(|arg| arg.to_string()).collect());
let custom_headers: Option<HashMap<String, Vec<String>>> = {
if let Some(matched_headers) = headers {
if let Some(matched_headers) = tpl.headers {
let mut custom_headers: HashMap<String, Vec<String>> = 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());
}

View file

@ -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);
}
_ => (),
},