From 15c635eb1d6b402a419c7eadef7542b2c8ae1c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Sat, 24 Apr 2021 22:53:30 +0200 Subject: [PATCH] use env_logger for plain output fmt (#126) --- CHANGELOG.md | 5 + Cargo.lock | 1 + Cargo.toml | 3 +- src/comp/cli.rs | 6 +- src/config/model.rs | 16 +++- src/flag/cli.rs | 20 ++-- src/imap/cli.rs | 4 +- src/imap/model.rs | 39 ++++---- src/main.rs | 24 ++--- src/mbox/cli.rs | 12 +-- src/mbox/model.rs | 17 +++- src/msg/cli.rs | 216 +++++++++++++++++++++----------------------- src/msg/model.rs | 97 +++++++++++++++----- src/output/cli.rs | 5 +- src/output/fmt.rs | 29 +++++- src/output/log.rs | 52 +++++------ 16 files changed, 318 insertions(+), 228 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e841b6..0d20a92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Custom config path [#86] - Setting idle-hook-cmds +### Changed + +- Plain logger with `env_logger` [#126] + ### Fixed - Improve config compatibility on Windows [#111](https://github.com/soywod/himalaya/pull/111) @@ -193,3 +197,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#89]: https://github.com/soywod/himalaya/issues/89 [#96]: https://github.com/soywod/himalaya/issues/96 [#100]: https://github.com/soywod/himalaya/issues/100 +[#126]: https://github.com/soywod/himalaya/issues/126 diff --git a/Cargo.lock b/Cargo.lock index 6615d3f..3df53d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -308,6 +308,7 @@ dependencies = [ name = "himalaya" version = "0.2.6" dependencies = [ + "chrono", "clap", "env_logger", "error-chain", diff --git a/Cargo.toml b/Cargo.toml index 7fd7805..76af15a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "himalaya" -description = "📫 Minimalist CLI email client" +description = "📫 The CLI email client." version = "0.2.6" authors = ["soywod "] edition = "2018" [dependencies] +chrono = "0.4.19" clap = {version = "2.33.3", default-features = false, features = ["suggestions"]} env_logger = "0.8.3" error-chain = "0.12.4" diff --git a/src/comp/cli.rs b/src/comp/cli.rs index 6646fb5..b1053f6 100644 --- a/src/comp/cli.rs +++ b/src/comp/cli.rs @@ -15,17 +15,17 @@ pub fn comp_subcmds<'s>() -> Vec> { pub fn comp_matches(mut app: App, matches: &ArgMatches) -> Result { if let Some(matches) = matches.subcommand_matches("completion") { - debug!("[comp::cli::matches] completion command matched"); + debug!("completion command matched"); let shell = match matches.value_of("shell").unwrap() { "fish" => Shell::Fish, "zsh" => Shell::Zsh, "bash" | _ => Shell::Bash, }; - debug!("[comp::cli::matches] shell: {}", shell); + debug!("shell: {}", shell); app.gen_completions_to("himalaya", shell, &mut io::stdout()); return Ok(true); }; - debug!("[comp::cli::matches] nothing matched"); + debug!("nothing matched"); Ok(false) } diff --git a/src/config/model.rs b/src/config/model.rs index 4352a6e..f0fe522 100644 --- a/src/config/model.rs +++ b/src/config/model.rs @@ -43,6 +43,8 @@ pub struct Account { impl Account { pub fn imap_addr(&self) -> (&str, u16) { + debug!("host: {}", self.imap_host); + debug!("port: {}", self.imap_port); (&self.imap_host, self.imap_port) } @@ -56,17 +58,23 @@ impl Account { } pub fn imap_starttls(&self) -> bool { - match self.imap_starttls { + let starttls = match self.imap_starttls { Some(true) => true, _ => false, - } + }; + + debug!("STARTTLS: {}", starttls); + starttls } pub fn imap_insecure(&self) -> bool { - match self.imap_insecure { + let insecure = match self.imap_insecure { Some(true) => true, _ => false, - } + }; + + debug!("insecure: {}", insecure); + insecure } pub fn smtp_creds(&self) -> Result { diff --git a/src/flag/cli.rs b/src/flag/cli.rs index 21e921f..c721600 100644 --- a/src/flag/cli.rs +++ b/src/flag/cli.rs @@ -48,13 +48,13 @@ pub fn flag_subcmds<'s>() -> Vec> { pub fn flag_matches(account: &Account, mbox: &str, matches: &ArgMatches) -> Result { if let Some(matches) = matches.subcommand_matches("set") { - debug!("[flag::cli::matches] set command matched"); + debug!("set command matched"); let uid = matches.value_of("uid").unwrap(); - debug!("[flag::cli::matches] uid: {}", uid); + debug!("uid: {}", uid); let flags = matches.value_of("flags").unwrap(); - debug!("[flag::cli::matches] flags: {}", flags); + debug!("flags: {}", flags); let mut imap_conn = ImapConnector::new(&account)?; imap_conn.set_flags(mbox, uid, flags)?; @@ -64,13 +64,13 @@ pub fn flag_matches(account: &Account, mbox: &str, matches: &ArgMatches) -> Resu } if let Some(matches) = matches.subcommand_matches("add") { - debug!("[flag::cli::matches] add command matched"); + debug!("add command matched"); let uid = matches.value_of("uid").unwrap(); - debug!("[flag::cli::matches] uid: {}", uid); + debug!("uid: {}", uid); let flags = matches.value_of("flags").unwrap(); - debug!("[flag::cli::matches] flags: {}", flags); + debug!("flags: {}", flags); let mut imap_conn = ImapConnector::new(&account)?; imap_conn.add_flags(mbox, uid, flags)?; @@ -80,13 +80,13 @@ pub fn flag_matches(account: &Account, mbox: &str, matches: &ArgMatches) -> Resu } if let Some(matches) = matches.subcommand_matches("remove") { - debug!("[flag::cli::matches] remove command matched"); + debug!("remove command matched"); let uid = matches.value_of("uid").unwrap(); - debug!("[flag::cli::matches] uid: {}", uid); + debug!("uid: {}", uid); let flags = matches.value_of("flags").unwrap(); - debug!("[flag::cli::matches] flags: {}", flags); + debug!("flags: {}", flags); let mut imap_conn = ImapConnector::new(&account)?; imap_conn.remove_flags(mbox, uid, flags)?; @@ -95,6 +95,6 @@ pub fn flag_matches(account: &Account, mbox: &str, matches: &ArgMatches) -> Resu return Ok(true); } - debug!("[flag::cli::matches] nothing matched"); + debug!("nothing matched"); Ok(false) } diff --git a/src/imap/cli.rs b/src/imap/cli.rs index 6ba8ed4..88e10b5 100644 --- a/src/imap/cli.rs +++ b/src/imap/cli.rs @@ -25,13 +25,13 @@ pub fn imap_matches( matches: &ArgMatches, ) -> Result { if let Some(_) = matches.subcommand_matches("idle") { - debug!("[imap::cli::matches] idle command matched"); + debug!("idle command matched"); let mut imap_conn = ImapConnector::new(&account)?; imap_conn.idle(&config, &mbox)?; imap_conn.logout(); return Ok(true); } - debug!("[imap::cli::matches] nothing matched"); + debug!("nothing matched"); Ok(false) } diff --git a/src/imap/model.rs b/src/imap/model.rs index 7ca1f27..189bf35 100644 --- a/src/imap/model.rs +++ b/src/imap/model.rs @@ -25,12 +25,15 @@ pub struct ImapConnector<'a> { impl<'ic> ImapConnector<'ic> { pub fn new(account: &'ic Account) -> Result { + debug!("create TLS builder"); + let insecure = account.imap_insecure(); let tls = TlsConnector::builder() - .danger_accept_invalid_certs(account.imap_insecure()) - .danger_accept_invalid_hostnames(account.imap_insecure()) + .danger_accept_invalid_certs(insecure) + .danger_accept_invalid_hostnames(insecure) .build() .chain_err(|| "Cannot create TLS connector")?; + debug!("create client"); let client = if account.imap_starttls() { imap::connect_starttls(account.imap_addr(), &account.imap_host, &tls) .chain_err(|| "Cannot connect using STARTTLS") @@ -39,6 +42,7 @@ impl<'ic> ImapConnector<'ic> { .chain_err(|| "Cannot connect using TLS") }?; + debug!("create session"); let sess = client .login(&account.imap_login, &account.imap_passwd()?) .map_err(|res| res.0) @@ -48,6 +52,7 @@ impl<'ic> ImapConnector<'ic> { } pub fn logout(&mut self) { + debug!("logout"); match self.sess.logout() { _ => (), } @@ -108,19 +113,19 @@ impl<'ic> ImapConnector<'ic> { } pub fn idle(&mut self, config: &Config, mbox: &str) -> Result<()> { - debug!("[imap::model::idle] begin"); + debug!("begin"); - debug!("[imap::model::idle] examine mailbox {}", mbox); + debug!("examine mailbox {}", mbox); self.sess .examine(mbox) .chain_err(|| format!("Could not examine mailbox `{}`", mbox))?; - debug!("[imap::model::idle] init message hashset"); + debug!("init message hashset"); let mut msg_set: HashSet = HashSet::from_iter(self.search_new_msgs()?.iter().cloned()); - trace!("[imap::model::idle] {:?}", msg_set); + trace!("{:?}", msg_set); loop { - debug!("[imap::model::idle] begin loop"); + debug!("begin loop"); self.sess .idle() @@ -132,11 +137,8 @@ impl<'ic> ImapConnector<'ic> { .into_iter() .filter(|seq| msg_set.get(&seq).is_none()) .collect(); - debug!( - "[imap::model::idle] found {} new messages not in hashset", - new_msgs.len() - ); - trace!("[imap::model::idle] {:?}", new_msgs); + debug!("found {} new messages not in hashset", new_msgs.len()); + trace!("messages: {:?}", new_msgs); if !new_msgs.is_empty() { let new_msgs = new_msgs @@ -152,20 +154,17 @@ impl<'ic> ImapConnector<'ic> { for fetch in fetches.iter() { let msg = Msg::from(fetch); config.run_notify_cmd(&msg.subject, &msg.sender)?; - debug!("[imap::model::idle] notify message {}", fetch.message); - trace!("[imap::model::idle] {:?}", msg); + debug!("notify message {}", fetch.message); + trace!("message: {:?}", msg); - debug!( - "[imap::model::idle] insert msg {} to hashset", - fetch.message - ); + debug!("insert msg {} to hashset", fetch.message); msg_set.insert(fetch.message); - trace!("[imap::model::idle] {:?}", msg_set); + trace!("messages: {:?}", msg_set); } } config.exec_idle_hooks()?; - debug!("[imap::model::idle] end loop"); + debug!("end loop"); } } diff --git a/src/main.rs b/src/main.rs index c7d4a97..a020e21 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,30 +60,32 @@ fn run() -> Result<()> { let matches = app.get_matches(); let output_fmt: OutputFmt = matches.value_of("output").unwrap().into(); - let log_level: LogLevel = matches.value_of("log").unwrap().into(); - let custom_config: Option = matches.value_of("config").map(|s| s.into()); + let log_level: LogLevel = matches.value_of("log-level").unwrap().into(); init_logger(&output_fmt, &log_level)?; - debug!("[main] output format: {}", output_fmt); - debug!("[main] log level: {}", log_level); - debug!("[main] custom config path: {:?}", custom_config); + debug!("output format: {}", output_fmt); + debug!("log level: {}", log_level); + // Check completion matches before the config init if comp_matches(build_app(), &matches)? { return Ok(()); } - debug!("[main] init config"); + let custom_config: Option = matches.value_of("config").map(|s| s.into()); + debug!("custom config path: {:?}", custom_config); + + debug!("init config"); let config = Config::new(custom_config)?; - trace!("[main] {:#?}", config); + trace!("config: {:?}", config); let account_name = matches.value_of("account"); - debug!("[main] find {} account", account_name.unwrap_or("default")); + debug!("init account: {}", account_name.unwrap_or("default")); let account = config.find_account_by_name(account_name)?; - trace!("[main] {:#?}", account); + trace!("account: {:?}", account); let mbox = matches.value_of("mailbox").unwrap(); - debug!("[main] mailbox: {}", mbox); + debug!("mailbox: {}", mbox); - debug!("[main] begin matching"); + debug!("begin matching"); let _matched = mbox_matches(&account, &matches)? || flag_matches(&account, &mbox, &matches)? || imap_matches(&config, &account, &mbox, &matches)? diff --git a/src/mbox/cli.rs b/src/mbox/cli.rs index e41c9fc..50b86ab 100644 --- a/src/mbox/cli.rs +++ b/src/mbox/cli.rs @@ -1,8 +1,8 @@ use clap::{self, App, Arg, ArgMatches, SubCommand}; use error_chain::error_chain; -use log::{debug, trace}; +use log::{debug, info, trace}; -use crate::{config::model::Account, imap::model::ImapConnector, info}; +use crate::{config::model::Account, imap::model::ImapConnector}; error_chain! { links { @@ -36,17 +36,17 @@ pub fn mbox_subcmds<'s>() -> Vec> { pub fn mbox_matches(account: &Account, matches: &ArgMatches) -> Result { if let Some(_) = matches.subcommand_matches("mailboxes") { - debug!("[mbox::cli::matches] mailboxes command matched"); + debug!("mailboxes command matched"); let mut imap_conn = ImapConnector::new(&account)?; let mboxes = imap_conn.list_mboxes()?; - info!(&mboxes); - trace!("[mbox::cli::matches] {:#?}", mboxes); + info!("{}", mboxes); + trace!("mailboxes: {:?}", mboxes); imap_conn.logout(); return Ok(true); } - debug!("[mbox::cli::matches] nothing matched"); + debug!("nothing matched"); Ok(false) } diff --git a/src/mbox/model.rs b/src/mbox/model.rs index 6ad92d5..b12844d 100644 --- a/src/mbox/model.rs +++ b/src/mbox/model.rs @@ -2,7 +2,10 @@ use imap; use serde::Serialize; use std::fmt; -use crate::table::{self, DisplayRow, DisplayTable}; +use crate::{ + output::fmt::{get_output_fmt, OutputFmt, Response}, + table::{self, DisplayRow, DisplayTable}, +}; // Mbox @@ -58,6 +61,16 @@ impl<'a> DisplayTable<'a, Mbox> for Mboxes { impl fmt::Display for Mboxes { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "\n{}", self.to_table()) + unsafe { + match get_output_fmt() { + &OutputFmt::Plain => { + writeln!(f, "\n{}", self.to_table()) + } + &OutputFmt::Json => { + let res = serde_json::to_string(&Response::new(self)).unwrap(); + write!(f, "{}", res) + } + } + } } } diff --git a/src/msg/cli.rs b/src/msg/cli.rs index a994ea4..277a215 100644 --- a/src/msg/cli.rs +++ b/src/msg/cli.rs @@ -1,13 +1,13 @@ use clap::{self, App, Arg, ArgMatches, SubCommand}; use error_chain::error_chain; -use log::{debug, error, trace}; +use log::{debug, error, info, trace}; use std::{fs, ops::Deref}; use crate::{ config::model::{Account, Config}, flag::model::Flag, imap::model::ImapConnector, - info, input, + input, mbox::cli::mbox_target_arg, msg::model::{Attachments, Msg, Msgs, ReadableMsg}, smtp, @@ -173,44 +173,44 @@ pub fn msg_matches( matches: &ArgMatches, ) -> Result { if let Some(matches) = matches.subcommand_matches("list") { - debug!("[msg::cli::matches] list command matched"); + debug!("list command matched"); let page_size: usize = matches .value_of("page-size") .and_then(|s| s.parse().ok()) .unwrap_or(config.default_page_size(&account)); - debug!("[msg::cli::matches] page size: {}", &page_size); + debug!("page size: {}", &page_size); let page: usize = matches .value_of("page") .unwrap() .parse() .unwrap_or_default(); - debug!("[msg::cli::matches] page: {}", &page); + debug!("page: {}", &page); let mut imap_conn = ImapConnector::new(&account)?; let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?; let msgs = Msgs::from(&msgs); - info!(&msgs); - trace!("[msg::cli::matches] {:#?}", msgs); + info!("{}", msgs); + trace!("messages: {:?}", msgs); imap_conn.logout(); return Ok(true); } if let Some(matches) = matches.subcommand_matches("search") { - debug!("[msg::cli::matches] search command matched"); + debug!("search command matched"); let page_size: usize = matches .value_of("page-size") .and_then(|s| s.parse().ok()) .unwrap_or(config.default_page_size(&account)); - debug!("[msg::cli::matches] page size: {}", &page_size); + debug!("page size: {}", &page_size); let page: usize = matches .value_of("page") .unwrap() .parse() .unwrap_or_default(); - debug!("[msg::cli::matches] page: {}", &page); + debug!("page: {}", &page); let query = matches .values_of("query") @@ -236,27 +236,27 @@ pub fn msg_matches( }) .1 .join(" "); - debug!("[msg::cli::matches] query: {}", &page); + debug!("query: {}", &page); let mut imap_conn = ImapConnector::new(&account)?; let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?; let msgs = Msgs::from(&msgs); - info!(&msgs); - trace!("[msg::cli::matches] {:#?}", msgs); + info!("{}", msgs); + trace!("messages: {:?}", msgs); imap_conn.logout(); return Ok(true); } if let Some(matches) = matches.subcommand_matches("read") { - debug!("[msg::cli::matches] read command matched"); + debug!("read command matched"); let uid = matches.value_of("uid").unwrap(); - debug!("[msg::cli::matches] uid: {}", uid); + debug!("uid: {}", uid); let mime = format!("text/{}", matches.value_of("mime-type").unwrap()); - debug!("[msg::cli::matches] mime: {}", mime); + debug!("mime: {}", mime); let raw = matches.is_present("raw"); - debug!("[msg::cli::matches] raw: {}", raw); + debug!("raw: {}", raw); let mut imap_conn = ImapConnector::new(&account)?; let msg = imap_conn.read_msg(&mbox, &uid)?; @@ -264,10 +264,10 @@ pub fn msg_matches( let msg = String::from_utf8(msg) .chain_err(|| "Could not decode raw message as utf8 string")?; let msg = msg.trim_end_matches("\n"); - info!(&msg); + info!("{}", msg); } else { let msg = ReadableMsg::from_bytes(&mime, &msg)?; - info!(&msg); + info!("{}", msg); } imap_conn.logout(); @@ -275,36 +275,36 @@ pub fn msg_matches( } if let Some(matches) = matches.subcommand_matches("attachments") { - debug!("[msg::cli::matches] attachments command matched"); + debug!("attachments command matched"); let uid = matches.value_of("uid").unwrap(); - debug!("[msg::cli::matches] uid: {}", &uid); + debug!("uid: {}", &uid); let mut imap_conn = ImapConnector::new(&account)?; let msg = imap_conn.read_msg(&mbox, &uid)?; let attachments = Attachments::from_bytes(&msg)?; debug!( - "[msg::cli::matches] {} attachment(s) found for message {}", + "{} attachment(s) found for message {}", &attachments.0.len(), &uid ); for attachment in attachments.0.iter() { let filepath = config.downloads_filepath(&account, &attachment.filename); - debug!("[msg::cli::matches] downloading {}…", &attachment.filename); + debug!("downloading {}…", &attachment.filename); fs::write(&filepath, &attachment.raw) .chain_err(|| format!("Could not save attachment {:?}", filepath))?; } - info!(&format!( + info!( "{} attachment(s) successfully downloaded", &attachments.0.len() - )); + ); imap_conn.logout(); return Ok(true); } if let Some(matches) = matches.subcommand_matches("write") { - debug!("[msg::cli::matches] write command matched"); + debug!("write command matched"); let mut imap_conn = ImapConnector::new(&account)?; let attachments = matches @@ -321,7 +321,7 @@ pub fn msg_matches( match input::post_edit_choice() { Ok(choice) => match choice { input::PostEditChoice::Send => { - debug!("[msg::cli::matches] sending message…"); + debug!("sending message…"); let msg = msg.to_sendable_msg()?; smtp::send(&account, &msg)?; imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; @@ -335,7 +335,7 @@ pub fn msg_matches( } input::PostEditChoice::LocalDraft => break, input::PostEditChoice::RemoteDraft => { - debug!("[msg::cli::matches] saving to draft…"); + debug!("saving to draft…"); imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; input::remove_draft()?; info!("Message successfully saved to Drafts"); @@ -353,68 +353,18 @@ pub fn msg_matches( return Ok(true); } - if let Some(matches) = matches.subcommand_matches("template") { - debug!("[msg::cli::matches] template command matched"); - - if let Some(_) = matches.subcommand_matches("new") { - debug!("[msg::cli::matches] new command matched"); - let tpl = Msg::build_new_tpl(&config, &account)?; - info!(&tpl); - trace!("[msg::cli::matches] tpl: {:#?}", tpl); - } - - if let Some(matches) = matches.subcommand_matches("reply") { - debug!("[msg::cli::matches] reply command matched"); - - let uid = matches.value_of("uid").unwrap(); - debug!("[msg::cli::matches] uid: {}", uid); - - let mut imap_conn = ImapConnector::new(&account)?; - let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); - let tpl = if matches.is_present("reply-all") { - msg.build_reply_all_tpl(&config, &account)? - } else { - msg.build_reply_tpl(&config, &account)? - }; - info!(&tpl); - trace!("[msg::cli::matches] tpl: {:#?}", tpl); - - imap_conn.logout(); - } - - if let Some(matches) = matches.subcommand_matches("forward") { - debug!("[msg::cli::matches] forward command matched"); - - let uid = matches.value_of("uid").unwrap(); - debug!("[msg::cli::matches] uid: {}", uid); - - let mut imap_conn = ImapConnector::new(&account)?; - let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); - let tpl = msg.build_forward_tpl(&config, &account)?; - info!(&tpl); - trace!("[msg::cli::matches] tpl: {:#?}", tpl); - - imap_conn.logout(); - } - - return Ok(true); - } - if let Some(matches) = matches.subcommand_matches("reply") { - debug!("[msg::cli::matches] reply command matched"); + debug!("reply command matched"); let uid = matches.value_of("uid").unwrap(); - debug!("[msg::cli::matches] uid: {}", uid); + debug!("uid: {}", uid); let attachments = matches .values_of("attachments") .unwrap_or_default() .map(String::from) .collect::>(); - debug!( - "[msg::cli::matches] found {} attachments", - attachments.len() - ); - trace!("[msg::cli::matches] {:#?}", attachments); + debug!("found {} attachments", attachments.len()); + trace!("attachments: {:?}", attachments); let mut imap_conn = ImapConnector::new(&account)?; let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); @@ -432,7 +382,7 @@ pub fn msg_matches( match input::post_edit_choice() { Ok(choice) => match choice { input::PostEditChoice::Send => { - debug!("[msg::cli::matches] sending message…"); + debug!("sending message…"); let msg = msg.to_sendable_msg()?; smtp::send(&account, &msg)?; imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; @@ -447,7 +397,7 @@ pub fn msg_matches( } input::PostEditChoice::LocalDraft => break, input::PostEditChoice::RemoteDraft => { - debug!("[msg::cli::matches] saving to draft…"); + debug!("saving to draft…"); imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; input::remove_draft()?; info!("Message successfully saved to Drafts"); @@ -467,20 +417,17 @@ pub fn msg_matches( } if let Some(matches) = matches.subcommand_matches("forward") { - debug!("[msg::cli::matches] forward command matched"); + debug!("forward command matched"); let uid = matches.value_of("uid").unwrap(); - debug!("[msg::cli::matches] uid: {}", uid); + debug!("uid: {}", uid); let attachments = matches .values_of("attachments") .unwrap_or_default() .map(String::from) .collect::>(); - debug!( - "[msg::cli::matches] found {} attachments", - attachments.len() - ); - trace!("[msg::cli::matches] {:#?}", attachments); + debug!("found {} attachments", attachments.len()); + trace!("attachments: {:?}", attachments); let mut imap_conn = ImapConnector::new(&account)?; let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); @@ -493,7 +440,7 @@ pub fn msg_matches( match input::post_edit_choice() { Ok(choice) => match choice { input::PostEditChoice::Send => { - debug!("[msg::cli::matches] sending message…"); + debug!("sending message…"); let msg = msg.to_sendable_msg()?; smtp::send(&account, &msg)?; imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; @@ -507,7 +454,7 @@ pub fn msg_matches( } input::PostEditChoice::LocalDraft => break, input::PostEditChoice::RemoteDraft => { - debug!("[msg::cli::matches] saving to draft…"); + debug!("saving to draft…"); imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; input::remove_draft()?; info!("Message successfully saved to Drafts"); @@ -526,35 +473,79 @@ pub fn msg_matches( return Ok(true); } + if let Some(matches) = matches.subcommand_matches("template") { + debug!("template command matched"); + + if let Some(_) = matches.subcommand_matches("new") { + debug!("new command matched"); + let tpl = Msg::build_new_tpl(&config, &account)?; + info!("{}", tpl); + trace!("tpl: {:?}", tpl); + } + + if let Some(matches) = matches.subcommand_matches("reply") { + debug!("reply command matched"); + + let uid = matches.value_of("uid").unwrap(); + debug!("uid: {}", uid); + + let mut imap_conn = ImapConnector::new(&account)?; + let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); + let tpl = if matches.is_present("reply-all") { + msg.build_reply_all_tpl(&config, &account)? + } else { + msg.build_reply_tpl(&config, &account)? + }; + info!("{}", tpl); + trace!("tpl: {:?}", tpl); + + imap_conn.logout(); + } + + if let Some(matches) = matches.subcommand_matches("forward") { + debug!("forward command matched"); + + let uid = matches.value_of("uid").unwrap(); + debug!("uid: {}", uid); + + let mut imap_conn = ImapConnector::new(&account)?; + let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); + let tpl = msg.build_forward_tpl(&config, &account)?; + info!("{}", tpl); + trace!("tpl: {:?}", tpl); + + imap_conn.logout(); + } + + return Ok(true); + } + if let Some(matches) = matches.subcommand_matches("copy") { - debug!("[msg::cli::matches] copy command matched"); + debug!("copy command matched"); let uid = matches.value_of("uid").unwrap(); - debug!("[msg::cli::matches] uid: {}", &uid); + debug!("uid: {}", &uid); let target = matches.value_of("target").unwrap(); - debug!("[msg::cli::matches] target: {}", &target); + debug!("target: {}", &target); let mut imap_conn = ImapConnector::new(&account)?; let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let mut flags = msg.flags.deref().to_vec(); flags.push(Flag::Seen); imap_conn.append_msg(target, &msg.raw, &flags)?; - info!(&format!( - "Message {} successfully copied to folder `{}`", - &uid, &target - )); + info!("Message {} successfully copied to folder `{}`", uid, target); imap_conn.logout(); return Ok(true); } if let Some(matches) = matches.subcommand_matches("move") { - debug!("[msg::cli::matches] move command matched"); + debug!("move command matched"); let uid = matches.value_of("uid").unwrap(); - debug!("[msg::cli::matches] uid: {}", &uid); + debug!("uid: {}", &uid); let target = matches.value_of("target").unwrap(); - debug!("[msg::cli::matches] target: {}", &target); + debug!("target: {}", &target); let mut imap_conn = ImapConnector::new(&account)?; let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); @@ -562,10 +553,7 @@ pub fn msg_matches( flags.push(Flag::Seen); imap_conn.append_msg(target, &msg.raw, msg.flags.deref())?; imap_conn.add_flags(&mbox, uid, "\\Seen \\Deleted")?; - info!(&format!( - "Message {} successfully moved to folder `{}`", - &uid, &target - )); + info!("Message {} successfully moved to folder `{}`", uid, target); imap_conn.expunge(&mbox)?; imap_conn.logout(); @@ -573,14 +561,14 @@ pub fn msg_matches( } if let Some(matches) = matches.subcommand_matches("delete") { - debug!("[msg::cli::matches] delete command matched"); + debug!("delete command matched"); let uid = matches.value_of("uid").unwrap(); - debug!("[msg::cli::matches] uid: {}", &uid); + debug!("uid: {}", &uid); let mut imap_conn = ImapConnector::new(&account)?; imap_conn.add_flags(&mbox, uid, "\\Seen \\Deleted")?; - info!(&format!("Message {} successfully deleted", &uid)); + info!("Message {} successfully deleted", uid); imap_conn.expunge(&mbox)?; imap_conn.logout(); @@ -588,7 +576,7 @@ pub fn msg_matches( } if let Some(matches) = matches.subcommand_matches("send") { - debug!("[msg::cli::matches] send command matched"); + debug!("send command matched"); let mut imap_conn = ImapConnector::new(&account)?; let msg = matches.value_of("message").unwrap(); @@ -602,7 +590,7 @@ pub fn msg_matches( } if let Some(matches) = matches.subcommand_matches("save") { - debug!("[msg::cli::matches] save command matched"); + debug!("save command matched"); let mut imap_conn = ImapConnector::new(&account)?; let msg = matches.value_of("message").unwrap(); @@ -614,12 +602,12 @@ pub fn msg_matches( } { - debug!("[msg::cli::matches] default list command matched"); + debug!("default list command matched"); let mut imap_conn = ImapConnector::new(&account)?; let msgs = imap_conn.list_msgs(&mbox, &config.default_page_size(&account), &0)?; let msgs = Msgs::from(&msgs); - info!(&msgs); + info!("{}", msgs); imap_conn.logout(); Ok(true) diff --git a/src/msg/model.rs b/src/msg/model.rs index 3ae634b..0349899 100644 --- a/src/msg/model.rs +++ b/src/msg/model.rs @@ -11,9 +11,12 @@ use std::{borrow::Cow, fmt, fs, path::PathBuf, result}; use tree_magic; use uuid::Uuid; -use crate::config::model::{Account, Config}; -use crate::flag::model::{Flag, Flags}; -use crate::table::{self, DisplayRow, DisplayTable}; +use crate::{ + config::model::{Account, Config}, + flag::model::{Flag, Flags}, + output::fmt::{get_output_fmt, OutputFmt, Response}, + table::{self, DisplayRow, DisplayTable}, +}; error_chain! { foreign_links { @@ -29,7 +32,17 @@ pub struct Tpl(String); impl fmt::Display for Tpl { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) + unsafe { + match get_output_fmt() { + &OutputFmt::Plain => { + writeln!(f, "{}", self.0) + } + &OutputFmt::Json => { + let res = serde_json::to_string(&Response::new(self)).unwrap(); + write!(f, "{}", res) + } + } + } } } @@ -118,7 +131,17 @@ impl Serialize for ReadableMsg { impl fmt::Display for ReadableMsg { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.content) + unsafe { + match get_output_fmt() { + &OutputFmt::Plain => { + writeln!(f, "{}", self.content) + } + &OutputFmt::Json => { + let res = serde_json::to_string(&Response::new(self)).unwrap(); + write!(f, "{}", res) + } + } + } } } @@ -375,7 +398,7 @@ impl<'m> Msg<'m> { } pub fn build_new_tpl(config: &Config, account: &Account) -> Result { - let msg_spec = MsgSpec{ + let msg_spec = MsgSpec { in_reply_to: None, to: None, cc: None, @@ -389,11 +412,11 @@ impl<'m> Msg<'m> { let msg = &self.parse()?; let headers = msg.get_headers(); let to = headers - .get_first_value("reply-to") - .or(headers.get_first_value("from")); + .get_first_value("reply-to") + .or(headers.get_first_value("from")); let to = match to { - Some(t) => {Some(vec![t])}, - None => {None}, + Some(t) => Some(vec![t]), + None => None, }; let thread = self // Original msg prepend with ">" @@ -403,7 +426,7 @@ impl<'m> Msg<'m> { .map(|line| format!(">{}", line)) .collect::>(); - let msg_spec = MsgSpec{ + let msg_spec = MsgSpec { in_reply_to: headers.get_first_value("message-id"), to, cc: None, @@ -455,12 +478,14 @@ impl<'m> Msg<'m> { }; // "Cc" header - let cc = Some(headers - .get_all_values("cc") - .iter() - .flat_map(|addrs| addrs.split(",")) - .map(|addr| addr.trim().to_string()) - .collect::>()); + let cc = Some( + headers + .get_all_values("cc") + .iter() + .flat_map(|addrs| addrs.split(",")) + .map(|addr| addr.trim().to_string()) + .collect::>(), + ); // Original msg prepend with ">" let thread = self @@ -469,7 +494,7 @@ impl<'m> Msg<'m> { .map(|line| format!(">{}", line)) .collect::>(); - let msg_spec = MsgSpec{ + let msg_spec = MsgSpec { in_reply_to: headers.get_first_value("message-id"), cc, to: Some(vec![reply_to, to].concat()), @@ -483,13 +508,18 @@ impl<'m> Msg<'m> { let msg = &self.parse()?; let headers = msg.get_headers(); - let subject = format!("Fwd: {}", headers.get_first_value("subject").unwrap_or_else(String::new)); + let subject = format!( + "Fwd: {}", + headers + .get_first_value("subject") + .unwrap_or_else(String::new) + ); let original_msg = vec![ "-------- Forwarded Message --------".to_string(), self.text_bodies("text/plain")?, ]; - let msg_spec = MsgSpec{ + let msg_spec = MsgSpec { in_reply_to: None, cc: None, to: None, @@ -520,10 +550,17 @@ impl<'m> Msg<'m> { } fn add_to_header(tpl: &mut Vec, to: Option>) { - tpl.push(format!("To: {}", match to { - Some(t) => {t.join(", ")} - None => {String::new()} - })); + tpl.push(format!( + "To: {}", + match to { + Some(t) => { + t.join(", ") + } + None => { + String::new() + } + } + )); } fn add_subject_header(tpl: &mut Vec, subject: Option) { @@ -619,6 +656,16 @@ impl<'m> From<&'m imap::types::ZeroCopy>> for Msgs<'m> { impl<'m> fmt::Display for Msgs<'m> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "\n{}", self.to_table()) + unsafe { + match get_output_fmt() { + &OutputFmt::Plain => { + writeln!(f, "\n{}", self.to_table()) + } + &OutputFmt::Json => { + let res = serde_json::to_string(&Response::new(self)).unwrap(); + write!(f, "{}", res) + } + } + } } } diff --git a/src/output/cli.rs b/src/output/cli.rs index f2fc58b..1ca50c3 100644 --- a/src/output/cli.rs +++ b/src/output/cli.rs @@ -9,8 +9,9 @@ pub fn output_args<'a>() -> Vec> { .value_name("FMT") .possible_values(&["plain", "json"]) .default_value("plain"), - Arg::with_name("log") - .long("log") + Arg::with_name("log-level") + .long("log-level") + .alias("log") .short("l") .help("Defines the logs level") .value_name("LEVEL") diff --git a/src/output/fmt.rs b/src/output/fmt.rs index 6dc57cb..693af5c 100644 --- a/src/output/fmt.rs +++ b/src/output/fmt.rs @@ -1,6 +1,7 @@ +use serde::Serialize; use std::fmt; -static mut OUTPUT_FMT: &'static OutputFmt = &OutputFmt::Plain; +pub static mut OUTPUT_FMT: &'static OutputFmt = &OutputFmt::Plain; pub fn set_output_fmt(output_fmt: &'static OutputFmt) { unsafe { OUTPUT_FMT = output_fmt } @@ -36,3 +37,29 @@ impl fmt::Display for OutputFmt { ) } } + +#[derive(Serialize)] +pub struct Response { + response: T, +} + +impl Response { + pub fn new(response: T) -> Self { + Self { response } + } +} + +impl fmt::Display for Response { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + unsafe { + match get_output_fmt() { + &OutputFmt::Plain => { + writeln!(f, "{}", self.response) + } + &OutputFmt::Json => { + write!(f, "{}", serde_json::to_string(self).unwrap()) + } + } + } + } +} diff --git a/src/output/log.rs b/src/output/log.rs index 37f6cae..5baafe8 100644 --- a/src/output/log.rs +++ b/src/output/log.rs @@ -1,31 +1,13 @@ +use chrono::Local; +use env_logger; use error_chain::error_chain; use log::{self, Level, LevelFilter, Metadata, Record}; -use std::fmt; +use std::{fmt, io::Write}; use super::fmt::{set_output_fmt, OutputFmt}; error_chain! {} -// Macros - -#[macro_export] -macro_rules! info { - ($t:expr) => { - use crate::output::fmt::{get_output_fmt, OutputFmt}; - use log::info as log_info; - unsafe { - match get_output_fmt() { - OutputFmt::Plain => log_info!("{}", $t.to_string()), - OutputFmt::Json => { - // Should be safe enough to `.unwrap()` since it's - // formatted by Himalaya itself - log_info!("{{\"response\":{}}}", serde_json::to_string($t).unwrap()) - } - }; - } - }; -} - // Log level struct pub struct LogLevel(pub LevelFilter); @@ -95,16 +77,32 @@ impl log::Log for JsonLogger { // Init pub fn init(fmt: &OutputFmt, level: &LogLevel) -> Result<()> { - log::set_logger(match fmt { + match fmt { &OutputFmt::Plain => { set_output_fmt(&OutputFmt::Plain); - &PlainLogger } &OutputFmt::Json => { set_output_fmt(&OutputFmt::Json); - &JsonLogger } - }) - .map(|()| log::set_max_level(level.0)) - .chain_err(|| "Could not init logger") + }; + + env_logger::Builder::new() + .format(|buf, record| { + if let log::Level::Info = record.metadata().level() { + write!(buf, "{}", record.args()) + } else { + writeln!( + buf, + "[{} {:5} {}] {}", + Local::now().format("%Y-%m-%dT%H:%M:%S"), + record.metadata().level(), + record.module_path().unwrap_or_default(), + record.args() + ) + } + }) + .filter_level(level.0) + .init(); + + Ok(()) }