mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-25 04:20:22 +00:00
introduce --header arg for read command (#338)
This commit is contained in:
parent
eb6f51456b
commit
86b41e4914
7 changed files with 129 additions and 19 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -167,6 +167,12 @@ dependencies = [
|
|||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.2"
|
||||
|
@ -443,6 +449,7 @@ dependencies = [
|
|||
"atty",
|
||||
"chrono",
|
||||
"clap",
|
||||
"convert_case",
|
||||
"env_logger",
|
||||
"erased-serde",
|
||||
"html-escape",
|
||||
|
|
|
@ -28,6 +28,7 @@ anyhow = "1.0.44"
|
|||
atty = "0.2.14"
|
||||
chrono = "0.4.19"
|
||||
clap = { version = "2.33.3", default-features = false, features = ["suggestions", "color"] }
|
||||
convert_case = "0.5.0"
|
||||
env_logger = "0.8.3"
|
||||
erased-serde = "0.3.18"
|
||||
html-escape = "0.2.9"
|
||||
|
|
|
@ -221,8 +221,8 @@ fn main() -> Result<()> {
|
|||
Some(msg_args::Cmd::Move(seq, mbox_dst)) => {
|
||||
return msg_handlers::move_(seq, mbox, mbox_dst, &mut printer, backend);
|
||||
}
|
||||
Some(msg_args::Cmd::Read(seq, text_mime, raw)) => {
|
||||
return msg_handlers::read(seq, text_mime, raw, mbox, &mut printer, backend);
|
||||
Some(msg_args::Cmd::Read(seq, text_mime, raw, headers)) => {
|
||||
return msg_handlers::read(seq, text_mime, raw, headers, mbox, &mut printer, backend);
|
||||
}
|
||||
Some(msg_args::Cmd::Reply(seq, all, attachment_paths, encrypt)) => {
|
||||
return msg_handlers::reply(
|
||||
|
|
|
@ -25,6 +25,7 @@ type AttachmentPaths<'a> = Vec<&'a str>;
|
|||
type MaxTableWidth = Option<usize>;
|
||||
type Encrypt = bool;
|
||||
type Criteria = String;
|
||||
type Headers<'a> = Vec<&'a str>;
|
||||
|
||||
/// Message commands.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
@ -35,7 +36,7 @@ pub enum Cmd<'a> {
|
|||
Forward(Seq<'a>, AttachmentPaths<'a>, Encrypt),
|
||||
List(MaxTableWidth, Option<PageSize>, Page),
|
||||
Move(Seq<'a>, Mbox<'a>),
|
||||
Read(Seq<'a>, TextMime<'a>, Raw),
|
||||
Read(Seq<'a>, TextMime<'a>, Raw, Headers<'a>),
|
||||
Reply(Seq<'a>, All, AttachmentPaths<'a>, Encrypt),
|
||||
Save(RawMsg<'a>),
|
||||
Search(Query, MaxTableWidth, Option<PageSize>, Page),
|
||||
|
@ -121,7 +122,9 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Cmd<'a>>> {
|
|||
debug!("text mime: {}", mime);
|
||||
let raw = m.is_present("raw");
|
||||
debug!("raw: {}", raw);
|
||||
return Ok(Some(Cmd::Read(seq, mime, raw)));
|
||||
let headers: Vec<&str> = m.values_of("headers").unwrap_or_default().collect();
|
||||
debug!("headers: {:?}", headers);
|
||||
return Ok(Some(Cmd::Read(seq, mime, raw, headers)));
|
||||
}
|
||||
|
||||
if let Some(m) = m.subcommand_matches("reply") {
|
||||
|
@ -318,7 +321,7 @@ fn page_arg<'a>() -> Arg<'a, 'a> {
|
|||
}
|
||||
|
||||
/// Message attachment argument.
|
||||
pub fn attachment_arg<'a>() -> Arg<'a, 'a> {
|
||||
pub fn attachments_arg<'a>() -> Arg<'a, 'a> {
|
||||
Arg::with_name("attachments")
|
||||
.help("Adds attachment to the message")
|
||||
.short("a")
|
||||
|
@ -327,6 +330,16 @@ pub fn attachment_arg<'a>() -> Arg<'a, 'a> {
|
|||
.multiple(true)
|
||||
}
|
||||
|
||||
/// Represents the message headers argument.
|
||||
pub fn headers_arg<'a>() -> Arg<'a, 'a> {
|
||||
Arg::with_name("headers")
|
||||
.help("Shows additional headers with the message")
|
||||
.short("h")
|
||||
.long("header")
|
||||
.value_name("STR")
|
||||
.multiple(true)
|
||||
}
|
||||
|
||||
/// Message encrypt argument.
|
||||
pub fn encrypt_arg<'a>() -> Arg<'a, 'a> {
|
||||
Arg::with_name("encrypt")
|
||||
|
@ -399,7 +412,7 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
|
|||
),
|
||||
SubCommand::with_name("write")
|
||||
.about("Writes a new message")
|
||||
.arg(attachment_arg())
|
||||
.arg(attachments_arg())
|
||||
.arg(encrypt_arg()),
|
||||
SubCommand::with_name("send")
|
||||
.about("Sends a raw message")
|
||||
|
@ -424,19 +437,20 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
|
|||
.help("Reads raw message")
|
||||
.long("raw")
|
||||
.short("r"),
|
||||
),
|
||||
)
|
||||
.arg(headers_arg()),
|
||||
SubCommand::with_name("reply")
|
||||
.aliases(&["rep", "r"])
|
||||
.about("Answers to a message")
|
||||
.arg(seq_arg())
|
||||
.arg(reply_all_arg())
|
||||
.arg(attachment_arg())
|
||||
.arg(attachments_arg())
|
||||
.arg(encrypt_arg()),
|
||||
SubCommand::with_name("forward")
|
||||
.aliases(&["fwd", "f"])
|
||||
.about("Forwards a message")
|
||||
.arg(seq_arg())
|
||||
.arg(attachment_arg())
|
||||
.arg(attachments_arg())
|
||||
.arg(encrypt_arg()),
|
||||
SubCommand::with_name("copy")
|
||||
.aliases(&["cp", "c"])
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
use ammonia;
|
||||
use anyhow::{anyhow, Context, Error, Result};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use convert_case::{Case, Casing};
|
||||
use html_escape;
|
||||
use lettre::message::{header::ContentType, Attachment, MultiPart, SinglePart};
|
||||
use log::{debug, info, trace, warn};
|
||||
use regex::Regex;
|
||||
use std::{collections::HashSet, convert::TryInto, env::temp_dir, fmt::Debug, fs, path::PathBuf};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
convert::TryInto,
|
||||
env::temp_dir,
|
||||
fmt::Debug,
|
||||
fs,
|
||||
path::PathBuf,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
|
@ -41,6 +49,7 @@ pub struct Msg {
|
|||
pub bcc: Option<Addrs>,
|
||||
pub in_reply_to: Option<String>,
|
||||
pub message_id: Option<String>,
|
||||
pub headers: HashMap<String, String>,
|
||||
|
||||
/// The internal date of the message.
|
||||
///
|
||||
|
@ -665,9 +674,11 @@ impl Msg {
|
|||
"message-id" => msg.message_id = Some(val),
|
||||
"in-reply-to" => msg.in_reply_to = Some(val),
|
||||
"subject" => {
|
||||
msg.subject = val;
|
||||
msg.subject = rfc2047_decoder::decode(val.as_bytes())?;
|
||||
}
|
||||
"date" => {
|
||||
// TODO: use date format instead
|
||||
// https://github.com/jonhoo/rust-imap/blob/afbc5118f251da4e3f6a1e560e749c0700020b54/src/types/fetch.rs#L16
|
||||
msg.date = DateTime::parse_from_rfc2822(
|
||||
val.split_at(val.find(" (").unwrap_or_else(|| val.len())).0,
|
||||
)
|
||||
|
@ -697,7 +708,12 @@ impl Msg {
|
|||
msg.bcc = from_slice_to_addrs(val)
|
||||
.context(format!("cannot parse header {:?}", key))?
|
||||
}
|
||||
_ => (),
|
||||
key => {
|
||||
msg.headers.insert(
|
||||
key.to_owned(),
|
||||
rfc2047_decoder::decode(val.as_bytes()).unwrap_or(val),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -708,6 +724,78 @@ impl Msg {
|
|||
info!("end: building message from parsed mail");
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
/// Transforms a message into a readable string. A readable
|
||||
/// message is like a template, except that:
|
||||
/// - headers part is customizable (can be omitted if empty filter given in argument)
|
||||
/// - body type is customizable (plain or html)
|
||||
pub fn to_readable_string(&self, text_mime: &str, headers: Vec<&str>) -> Result<String> {
|
||||
let mut readable_msg = String::new();
|
||||
|
||||
for h in headers {
|
||||
match h.to_lowercase().as_str() {
|
||||
"message-id" => match self.message_id {
|
||||
Some(ref message_id) if !message_id.is_empty() => {
|
||||
readable_msg.push_str(&format!("Message-Id: {}\n", message_id));
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
"in-reply-to" => match self.in_reply_to {
|
||||
Some(ref in_reply_to) if !in_reply_to.is_empty() => {
|
||||
readable_msg.push_str(&format!("In-Reply-To: {}\n", in_reply_to));
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
"subject" => {
|
||||
readable_msg.push_str(&format!("Subject: {}\n", self.subject));
|
||||
}
|
||||
"date" => {
|
||||
if let Some(ref date) = self.date {
|
||||
readable_msg.push_str(&format!("Date: {}\n", date));
|
||||
}
|
||||
}
|
||||
"from" => match self.from {
|
||||
Some(ref addrs) if !addrs.is_empty() => {
|
||||
readable_msg.push_str(&format!("From: {}\n", addrs));
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
"to" => match self.to {
|
||||
Some(ref addrs) if !addrs.is_empty() => {
|
||||
readable_msg.push_str(&format!("To: {}\n", addrs));
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
"reply-to" => match self.reply_to {
|
||||
Some(ref addrs) if !addrs.is_empty() => {
|
||||
readable_msg.push_str(&format!("Reply-To: {}\n", addrs));
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
"cc" => match self.cc {
|
||||
Some(ref addrs) if !addrs.is_empty() => {
|
||||
readable_msg.push_str(&format!("Cc: {}\n", addrs));
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
"bcc" => match self.bcc {
|
||||
Some(ref addrs) if !addrs.is_empty() => {
|
||||
readable_msg.push_str(&format!("Bcc: {}\n", addrs));
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
key => match self.headers.get(key) {
|
||||
Some(ref val) if !val.is_empty() => {
|
||||
readable_msg.push_str(&format!("{}: {}\n", key.to_case(Case::Pascal), val));
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
};
|
||||
}
|
||||
readable_msg.push_str("\n");
|
||||
readable_msg.push_str(&self.fold_text_parts(text_mime));
|
||||
Ok(readable_msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<lettre::address::Envelope> for Msg {
|
||||
|
|
|
@ -207,19 +207,19 @@ pub fn read<'a, P: PrinterService, B: Backend<'a> + ?Sized>(
|
|||
seq: &str,
|
||||
text_mime: &str,
|
||||
raw: bool,
|
||||
headers: Vec<&str>,
|
||||
mbox: &str,
|
||||
printer: &mut P,
|
||||
backend: Box<&'a mut B>,
|
||||
) -> Result<()> {
|
||||
let msg = backend.get_msg(mbox, seq)?;
|
||||
let msg = if raw {
|
||||
|
||||
printer.print_struct(if raw {
|
||||
// Emails don't always have valid utf8. Using "lossy" to display what we can.
|
||||
String::from_utf8_lossy(&msg.raw).into_owned()
|
||||
} else {
|
||||
msg.fold_text_parts(text_mime)
|
||||
};
|
||||
|
||||
printer.print_struct(msg)
|
||||
msg.to_readable_string(text_mime, headers)?
|
||||
})
|
||||
}
|
||||
|
||||
/// Reply to the given message UID.
|
||||
|
|
|
@ -183,13 +183,13 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
|
|||
.subcommand(
|
||||
SubCommand::with_name("save")
|
||||
.about("Saves a message based on the given template")
|
||||
.arg(&msg_args::attachment_arg())
|
||||
.arg(&msg_args::attachments_arg())
|
||||
.arg(Arg::with_name("template").raw(true)),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("send")
|
||||
.about("Sends a message based on the given template")
|
||||
.arg(&msg_args::attachment_arg())
|
||||
.arg(&msg_args::attachments_arg())
|
||||
.arg(Arg::with_name("template").raw(true)),
|
||||
)]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue