use env_logger for plain output fmt (#126)

This commit is contained in:
Clément DOUIN 2021-04-24 22:53:30 +02:00
parent 477b7748de
commit 15c635eb1d
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
16 changed files with 318 additions and 228 deletions

View file

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

1
Cargo.lock generated
View file

@ -308,6 +308,7 @@ dependencies = [
name = "himalaya"
version = "0.2.6"
dependencies = [
"chrono",
"clap",
"env_logger",
"error-chain",

View file

@ -1,11 +1,12 @@
[package]
name = "himalaya"
description = "📫 Minimalist CLI email client"
description = "📫 The CLI email client."
version = "0.2.6"
authors = ["soywod <clement.douin@posteo.net>"]
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"

View file

@ -15,17 +15,17 @@ pub fn comp_subcmds<'s>() -> Vec<App<'s, 's>> {
pub fn comp_matches(mut app: App, matches: &ArgMatches) -> Result<bool> {
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)
}

View file

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

View file

@ -48,13 +48,13 @@ pub fn flag_subcmds<'s>() -> Vec<App<'s, 's>> {
pub fn flag_matches(account: &Account, mbox: &str, matches: &ArgMatches) -> Result<bool> {
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)
}

View file

@ -25,13 +25,13 @@ pub fn imap_matches(
matches: &ArgMatches,
) -> Result<bool> {
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)
}

View file

@ -25,12 +25,15 @@ pub struct ImapConnector<'a> {
impl<'ic> ImapConnector<'ic> {
pub fn new(account: &'ic Account) -> Result<Self> {
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<u32> = 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");
}
}

View file

@ -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<PathBuf> = 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<PathBuf> = 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)?

View file

@ -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<App<'s, 's>> {
pub fn mbox_matches(account: &Account, matches: &ArgMatches) -> Result<bool> {
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)
}

View file

@ -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)
}
}
}
}
}

View file

@ -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<bool> {
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");
debug!("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");
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::<Vec<_>>();
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::<Vec<_>>();
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("copy") {
debug!("[msg::cli::matches] copy command matched");
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!("[msg::cli::matches] uid: {}", &uid);
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!("copy command matched");
let uid = matches.value_of("uid").unwrap();
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)

View file

@ -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<Tpl> {
let msg_spec = MsgSpec{
let msg_spec = MsgSpec {
in_reply_to: None,
to: None,
cc: None,
@ -392,8 +415,8 @@ impl<'m> Msg<'m> {
.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::<Vec<String>>();
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
let cc = Some(
headers
.get_all_values("cc")
.iter()
.flat_map(|addrs| addrs.split(","))
.map(|addr| addr.trim().to_string())
.collect::<Vec<String>>());
.collect::<Vec<String>>(),
);
// Original msg prepend with ">"
let thread = self
@ -469,7 +494,7 @@ impl<'m> Msg<'m> {
.map(|line| format!(">{}", line))
.collect::<Vec<String>>();
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<String>, to: Option<Vec<String>>) {
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<String>, subject: Option<String>) {
@ -619,6 +656,16 @@ impl<'m> From<&'m imap::types::ZeroCopy<Vec<imap::types::Fetch>>> 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)
}
}
}
}
}

View file

@ -9,8 +9,9 @@ pub fn output_args<'a>() -> Vec<Arg<'a, 'a>> {
.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")

View file

@ -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<T: Serialize + fmt::Display> {
response: T,
}
impl<T: Serialize + fmt::Display> Response<T> {
pub fn new(response: T) -> Self {
Self { response }
}
}
impl<T: Serialize + fmt::Display> fmt::Display for Response<T> {
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())
}
}
}
}
}

View file

@ -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
}
};
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()
)
}
})
.map(|()| log::set_max_level(level.0))
.chain_err(|| "Could not init logger")
.filter_level(level.0)
.init();
Ok(())
}