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] - Custom config path [#86]
- Setting idle-hook-cmds - Setting idle-hook-cmds
### Changed
- Plain logger with `env_logger` [#126]
### Fixed ### Fixed
- Improve config compatibility on Windows [#111](https://github.com/soywod/himalaya/pull/111) - 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 [#89]: https://github.com/soywod/himalaya/issues/89
[#96]: https://github.com/soywod/himalaya/issues/96 [#96]: https://github.com/soywod/himalaya/issues/96
[#100]: https://github.com/soywod/himalaya/issues/100 [#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" name = "himalaya"
version = "0.2.6" version = "0.2.6"
dependencies = [ dependencies = [
"chrono",
"clap", "clap",
"env_logger", "env_logger",
"error-chain", "error-chain",

View file

@ -1,11 +1,12 @@
[package] [package]
name = "himalaya" name = "himalaya"
description = "📫 Minimalist CLI email client" description = "📫 The CLI email client."
version = "0.2.6" version = "0.2.6"
authors = ["soywod <clement.douin@posteo.net>"] authors = ["soywod <clement.douin@posteo.net>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
chrono = "0.4.19"
clap = {version = "2.33.3", default-features = false, features = ["suggestions"]} clap = {version = "2.33.3", default-features = false, features = ["suggestions"]}
env_logger = "0.8.3" env_logger = "0.8.3"
error-chain = "0.12.4" 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> { pub fn comp_matches(mut app: App, matches: &ArgMatches) -> Result<bool> {
if let Some(matches) = matches.subcommand_matches("completion") { 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() { let shell = match matches.value_of("shell").unwrap() {
"fish" => Shell::Fish, "fish" => Shell::Fish,
"zsh" => Shell::Zsh, "zsh" => Shell::Zsh,
"bash" | _ => Shell::Bash, "bash" | _ => Shell::Bash,
}; };
debug!("[comp::cli::matches] shell: {}", shell); debug!("shell: {}", shell);
app.gen_completions_to("himalaya", shell, &mut io::stdout()); app.gen_completions_to("himalaya", shell, &mut io::stdout());
return Ok(true); return Ok(true);
}; };
debug!("[comp::cli::matches] nothing matched"); debug!("nothing matched");
Ok(false) Ok(false)
} }

View file

@ -43,6 +43,8 @@ pub struct Account {
impl Account { impl Account {
pub fn imap_addr(&self) -> (&str, u16) { pub fn imap_addr(&self) -> (&str, u16) {
debug!("host: {}", self.imap_host);
debug!("port: {}", self.imap_port);
(&self.imap_host, self.imap_port) (&self.imap_host, self.imap_port)
} }
@ -56,17 +58,23 @@ impl Account {
} }
pub fn imap_starttls(&self) -> bool { pub fn imap_starttls(&self) -> bool {
match self.imap_starttls { let starttls = match self.imap_starttls {
Some(true) => true, Some(true) => true,
_ => false, _ => false,
} };
debug!("STARTTLS: {}", starttls);
starttls
} }
pub fn imap_insecure(&self) -> bool { pub fn imap_insecure(&self) -> bool {
match self.imap_insecure { let insecure = match self.imap_insecure {
Some(true) => true, Some(true) => true,
_ => false, _ => false,
} };
debug!("insecure: {}", insecure);
insecure
} }
pub fn smtp_creds(&self) -> Result<SmtpCredentials> { 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> { pub fn flag_matches(account: &Account, mbox: &str, matches: &ArgMatches) -> Result<bool> {
if let Some(matches) = matches.subcommand_matches("set") { 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(); let uid = matches.value_of("uid").unwrap();
debug!("[flag::cli::matches] uid: {}", uid); debug!("uid: {}", uid);
let flags = matches.value_of("flags").unwrap(); let flags = matches.value_of("flags").unwrap();
debug!("[flag::cli::matches] flags: {}", flags); debug!("flags: {}", flags);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
imap_conn.set_flags(mbox, uid, flags)?; 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") { 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(); let uid = matches.value_of("uid").unwrap();
debug!("[flag::cli::matches] uid: {}", uid); debug!("uid: {}", uid);
let flags = matches.value_of("flags").unwrap(); let flags = matches.value_of("flags").unwrap();
debug!("[flag::cli::matches] flags: {}", flags); debug!("flags: {}", flags);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
imap_conn.add_flags(mbox, uid, flags)?; 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") { 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(); let uid = matches.value_of("uid").unwrap();
debug!("[flag::cli::matches] uid: {}", uid); debug!("uid: {}", uid);
let flags = matches.value_of("flags").unwrap(); let flags = matches.value_of("flags").unwrap();
debug!("[flag::cli::matches] flags: {}", flags); debug!("flags: {}", flags);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
imap_conn.remove_flags(mbox, uid, flags)?; 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); return Ok(true);
} }
debug!("[flag::cli::matches] nothing matched"); debug!("nothing matched");
Ok(false) Ok(false)
} }

View file

@ -25,13 +25,13 @@ pub fn imap_matches(
matches: &ArgMatches, matches: &ArgMatches,
) -> Result<bool> { ) -> Result<bool> {
if let Some(_) = matches.subcommand_matches("idle") { 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)?; let mut imap_conn = ImapConnector::new(&account)?;
imap_conn.idle(&config, &mbox)?; imap_conn.idle(&config, &mbox)?;
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }
debug!("[imap::cli::matches] nothing matched"); debug!("nothing matched");
Ok(false) Ok(false)
} }

View file

@ -25,12 +25,15 @@ pub struct ImapConnector<'a> {
impl<'ic> ImapConnector<'ic> { impl<'ic> ImapConnector<'ic> {
pub fn new(account: &'ic Account) -> Result<Self> { pub fn new(account: &'ic Account) -> Result<Self> {
debug!("create TLS builder");
let insecure = account.imap_insecure();
let tls = TlsConnector::builder() let tls = TlsConnector::builder()
.danger_accept_invalid_certs(account.imap_insecure()) .danger_accept_invalid_certs(insecure)
.danger_accept_invalid_hostnames(account.imap_insecure()) .danger_accept_invalid_hostnames(insecure)
.build() .build()
.chain_err(|| "Cannot create TLS connector")?; .chain_err(|| "Cannot create TLS connector")?;
debug!("create client");
let client = if account.imap_starttls() { let client = if account.imap_starttls() {
imap::connect_starttls(account.imap_addr(), &account.imap_host, &tls) imap::connect_starttls(account.imap_addr(), &account.imap_host, &tls)
.chain_err(|| "Cannot connect using STARTTLS") .chain_err(|| "Cannot connect using STARTTLS")
@ -39,6 +42,7 @@ impl<'ic> ImapConnector<'ic> {
.chain_err(|| "Cannot connect using TLS") .chain_err(|| "Cannot connect using TLS")
}?; }?;
debug!("create session");
let sess = client let sess = client
.login(&account.imap_login, &account.imap_passwd()?) .login(&account.imap_login, &account.imap_passwd()?)
.map_err(|res| res.0) .map_err(|res| res.0)
@ -48,6 +52,7 @@ impl<'ic> ImapConnector<'ic> {
} }
pub fn logout(&mut self) { pub fn logout(&mut self) {
debug!("logout");
match self.sess.logout() { match self.sess.logout() {
_ => (), _ => (),
} }
@ -108,19 +113,19 @@ impl<'ic> ImapConnector<'ic> {
} }
pub fn idle(&mut self, config: &Config, mbox: &str) -> Result<()> { 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 self.sess
.examine(mbox) .examine(mbox)
.chain_err(|| format!("Could not examine mailbox `{}`", 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()); 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 { loop {
debug!("[imap::model::idle] begin loop"); debug!("begin loop");
self.sess self.sess
.idle() .idle()
@ -132,11 +137,8 @@ impl<'ic> ImapConnector<'ic> {
.into_iter() .into_iter()
.filter(|seq| msg_set.get(&seq).is_none()) .filter(|seq| msg_set.get(&seq).is_none())
.collect(); .collect();
debug!( debug!("found {} new messages not in hashset", new_msgs.len());
"[imap::model::idle] found {} new messages not in hashset", trace!("messages: {:?}", new_msgs);
new_msgs.len()
);
trace!("[imap::model::idle] {:?}", new_msgs);
if !new_msgs.is_empty() { if !new_msgs.is_empty() {
let new_msgs = new_msgs let new_msgs = new_msgs
@ -152,20 +154,17 @@ impl<'ic> ImapConnector<'ic> {
for fetch in fetches.iter() { for fetch in fetches.iter() {
let msg = Msg::from(fetch); let msg = Msg::from(fetch);
config.run_notify_cmd(&msg.subject, &msg.sender)?; config.run_notify_cmd(&msg.subject, &msg.sender)?;
debug!("[imap::model::idle] notify message {}", fetch.message); debug!("notify message {}", fetch.message);
trace!("[imap::model::idle] {:?}", msg); trace!("message: {:?}", msg);
debug!( debug!("insert msg {} to hashset", fetch.message);
"[imap::model::idle] insert msg {} to hashset",
fetch.message
);
msg_set.insert(fetch.message); msg_set.insert(fetch.message);
trace!("[imap::model::idle] {:?}", msg_set); trace!("messages: {:?}", msg_set);
} }
} }
config.exec_idle_hooks()?; 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 matches = app.get_matches();
let output_fmt: OutputFmt = matches.value_of("output").unwrap().into(); let output_fmt: OutputFmt = matches.value_of("output").unwrap().into();
let log_level: LogLevel = matches.value_of("log").unwrap().into(); let log_level: LogLevel = matches.value_of("log-level").unwrap().into();
let custom_config: Option<PathBuf> = matches.value_of("config").map(|s| s.into());
init_logger(&output_fmt, &log_level)?; init_logger(&output_fmt, &log_level)?;
debug!("[main] output format: {}", output_fmt); debug!("output format: {}", output_fmt);
debug!("[main] log level: {}", log_level); debug!("log level: {}", log_level);
debug!("[main] custom config path: {:?}", custom_config);
// Check completion matches before the config init
if comp_matches(build_app(), &matches)? { if comp_matches(build_app(), &matches)? {
return Ok(()); 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)?; let config = Config::new(custom_config)?;
trace!("[main] {:#?}", config); trace!("config: {:?}", config);
let account_name = matches.value_of("account"); 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)?; let account = config.find_account_by_name(account_name)?;
trace!("[main] {:#?}", account); trace!("account: {:?}", account);
let mbox = matches.value_of("mailbox").unwrap(); 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)? let _matched = mbox_matches(&account, &matches)?
|| flag_matches(&account, &mbox, &matches)? || flag_matches(&account, &mbox, &matches)?
|| imap_matches(&config, &account, &mbox, &matches)? || imap_matches(&config, &account, &mbox, &matches)?

View file

@ -1,8 +1,8 @@
use clap::{self, App, Arg, ArgMatches, SubCommand}; use clap::{self, App, Arg, ArgMatches, SubCommand};
use error_chain::error_chain; 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! { error_chain! {
links { links {
@ -36,17 +36,17 @@ pub fn mbox_subcmds<'s>() -> Vec<App<'s, 's>> {
pub fn mbox_matches(account: &Account, matches: &ArgMatches) -> Result<bool> { pub fn mbox_matches(account: &Account, matches: &ArgMatches) -> Result<bool> {
if let Some(_) = matches.subcommand_matches("mailboxes") { 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 mut imap_conn = ImapConnector::new(&account)?;
let mboxes = imap_conn.list_mboxes()?; let mboxes = imap_conn.list_mboxes()?;
info!(&mboxes); info!("{}", mboxes);
trace!("[mbox::cli::matches] {:#?}", mboxes); trace!("mailboxes: {:?}", mboxes);
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }
debug!("[mbox::cli::matches] nothing matched"); debug!("nothing matched");
Ok(false) Ok(false)
} }

View file

@ -2,7 +2,10 @@ use imap;
use serde::Serialize; use serde::Serialize;
use std::fmt; use std::fmt;
use crate::table::{self, DisplayRow, DisplayTable}; use crate::{
output::fmt::{get_output_fmt, OutputFmt, Response},
table::{self, DisplayRow, DisplayTable},
};
// Mbox // Mbox
@ -58,6 +61,16 @@ impl<'a> DisplayTable<'a, Mbox> for Mboxes {
impl fmt::Display for Mboxes { impl fmt::Display for Mboxes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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 clap::{self, App, Arg, ArgMatches, SubCommand};
use error_chain::error_chain; use error_chain::error_chain;
use log::{debug, error, trace}; use log::{debug, error, info, trace};
use std::{fs, ops::Deref}; use std::{fs, ops::Deref};
use crate::{ use crate::{
config::model::{Account, Config}, config::model::{Account, Config},
flag::model::Flag, flag::model::Flag,
imap::model::ImapConnector, imap::model::ImapConnector,
info, input, input,
mbox::cli::mbox_target_arg, mbox::cli::mbox_target_arg,
msg::model::{Attachments, Msg, Msgs, ReadableMsg}, msg::model::{Attachments, Msg, Msgs, ReadableMsg},
smtp, smtp,
@ -173,44 +173,44 @@ pub fn msg_matches(
matches: &ArgMatches, matches: &ArgMatches,
) -> Result<bool> { ) -> Result<bool> {
if let Some(matches) = matches.subcommand_matches("list") { if let Some(matches) = matches.subcommand_matches("list") {
debug!("[msg::cli::matches] list command matched"); debug!("list command matched");
let page_size: usize = matches let page_size: usize = matches
.value_of("page-size") .value_of("page-size")
.and_then(|s| s.parse().ok()) .and_then(|s| s.parse().ok())
.unwrap_or(config.default_page_size(&account)); .unwrap_or(config.default_page_size(&account));
debug!("[msg::cli::matches] page size: {}", &page_size); debug!("page size: {}", &page_size);
let page: usize = matches let page: usize = matches
.value_of("page") .value_of("page")
.unwrap() .unwrap()
.parse() .parse()
.unwrap_or_default(); .unwrap_or_default();
debug!("[msg::cli::matches] page: {}", &page); debug!("page: {}", &page);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?; let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?;
let msgs = Msgs::from(&msgs); let msgs = Msgs::from(&msgs);
info!(&msgs); info!("{}", msgs);
trace!("[msg::cli::matches] {:#?}", msgs); trace!("messages: {:?}", msgs);
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("search") { if let Some(matches) = matches.subcommand_matches("search") {
debug!("[msg::cli::matches] search command matched"); debug!("search command matched");
let page_size: usize = matches let page_size: usize = matches
.value_of("page-size") .value_of("page-size")
.and_then(|s| s.parse().ok()) .and_then(|s| s.parse().ok())
.unwrap_or(config.default_page_size(&account)); .unwrap_or(config.default_page_size(&account));
debug!("[msg::cli::matches] page size: {}", &page_size); debug!("page size: {}", &page_size);
let page: usize = matches let page: usize = matches
.value_of("page") .value_of("page")
.unwrap() .unwrap()
.parse() .parse()
.unwrap_or_default(); .unwrap_or_default();
debug!("[msg::cli::matches] page: {}", &page); debug!("page: {}", &page);
let query = matches let query = matches
.values_of("query") .values_of("query")
@ -236,27 +236,27 @@ pub fn msg_matches(
}) })
.1 .1
.join(" "); .join(" ");
debug!("[msg::cli::matches] query: {}", &page); debug!("query: {}", &page);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?; let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?;
let msgs = Msgs::from(&msgs); let msgs = Msgs::from(&msgs);
info!(&msgs); info!("{}", msgs);
trace!("[msg::cli::matches] {:#?}", msgs); trace!("messages: {:?}", msgs);
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("read") { 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(); 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()); 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"); let raw = matches.is_present("raw");
debug!("[msg::cli::matches] raw: {}", raw); debug!("raw: {}", raw);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let msg = imap_conn.read_msg(&mbox, &uid)?; let msg = imap_conn.read_msg(&mbox, &uid)?;
@ -264,10 +264,10 @@ pub fn msg_matches(
let msg = String::from_utf8(msg) let msg = String::from_utf8(msg)
.chain_err(|| "Could not decode raw message as utf8 string")?; .chain_err(|| "Could not decode raw message as utf8 string")?;
let msg = msg.trim_end_matches("\n"); let msg = msg.trim_end_matches("\n");
info!(&msg); info!("{}", msg);
} else { } else {
let msg = ReadableMsg::from_bytes(&mime, &msg)?; let msg = ReadableMsg::from_bytes(&mime, &msg)?;
info!(&msg); info!("{}", msg);
} }
imap_conn.logout(); imap_conn.logout();
@ -275,36 +275,36 @@ pub fn msg_matches(
} }
if let Some(matches) = matches.subcommand_matches("attachments") { 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(); let uid = matches.value_of("uid").unwrap();
debug!("[msg::cli::matches] uid: {}", &uid); debug!("uid: {}", &uid);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let msg = imap_conn.read_msg(&mbox, &uid)?; let msg = imap_conn.read_msg(&mbox, &uid)?;
let attachments = Attachments::from_bytes(&msg)?; let attachments = Attachments::from_bytes(&msg)?;
debug!( debug!(
"[msg::cli::matches] {} attachment(s) found for message {}", "{} attachment(s) found for message {}",
&attachments.0.len(), &attachments.0.len(),
&uid &uid
); );
for attachment in attachments.0.iter() { for attachment in attachments.0.iter() {
let filepath = config.downloads_filepath(&account, &attachment.filename); let filepath = config.downloads_filepath(&account, &attachment.filename);
debug!("[msg::cli::matches] downloading {}…", &attachment.filename); debug!("downloading {}…", &attachment.filename);
fs::write(&filepath, &attachment.raw) fs::write(&filepath, &attachment.raw)
.chain_err(|| format!("Could not save attachment {:?}", filepath))?; .chain_err(|| format!("Could not save attachment {:?}", filepath))?;
} }
info!(&format!( info!(
"{} attachment(s) successfully downloaded", "{} attachment(s) successfully downloaded",
&attachments.0.len() &attachments.0.len()
)); );
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("write") { 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 mut imap_conn = ImapConnector::new(&account)?;
let attachments = matches let attachments = matches
@ -321,7 +321,7 @@ pub fn msg_matches(
match input::post_edit_choice() { match input::post_edit_choice() {
Ok(choice) => match choice { Ok(choice) => match choice {
input::PostEditChoice::Send => { input::PostEditChoice::Send => {
debug!("[msg::cli::matches] sending message…"); debug!("sending message…");
let msg = msg.to_sendable_msg()?; let msg = msg.to_sendable_msg()?;
smtp::send(&account, &msg)?; smtp::send(&account, &msg)?;
imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?;
@ -335,7 +335,7 @@ pub fn msg_matches(
} }
input::PostEditChoice::LocalDraft => break, input::PostEditChoice::LocalDraft => break,
input::PostEditChoice::RemoteDraft => { input::PostEditChoice::RemoteDraft => {
debug!("[msg::cli::matches] saving to draft…"); debug!("saving to draft…");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
input::remove_draft()?; input::remove_draft()?;
info!("Message successfully saved to Drafts"); info!("Message successfully saved to Drafts");
@ -353,68 +353,18 @@ pub fn msg_matches(
return Ok(true); 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") { 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(); 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!("[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);
let attachments = matches let attachments = matches
.values_of("attachments") .values_of("attachments")
.unwrap_or_default() .unwrap_or_default()
.map(String::from) .map(String::from)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
debug!( debug!("found {} attachments", attachments.len());
"[msg::cli::matches] found {} attachments", trace!("attachments: {:?}", attachments);
attachments.len()
);
trace!("[msg::cli::matches] {:#?}", attachments);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
@ -432,7 +382,7 @@ pub fn msg_matches(
match input::post_edit_choice() { match input::post_edit_choice() {
Ok(choice) => match choice { Ok(choice) => match choice {
input::PostEditChoice::Send => { input::PostEditChoice::Send => {
debug!("[msg::cli::matches] sending message…"); debug!("sending message…");
let msg = msg.to_sendable_msg()?; let msg = msg.to_sendable_msg()?;
smtp::send(&account, &msg)?; smtp::send(&account, &msg)?;
imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?;
@ -447,7 +397,7 @@ pub fn msg_matches(
} }
input::PostEditChoice::LocalDraft => break, input::PostEditChoice::LocalDraft => break,
input::PostEditChoice::RemoteDraft => { input::PostEditChoice::RemoteDraft => {
debug!("[msg::cli::matches] saving to draft…"); debug!("saving to draft…");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
input::remove_draft()?; input::remove_draft()?;
info!("Message successfully saved to Drafts"); info!("Message successfully saved to Drafts");
@ -467,20 +417,17 @@ pub fn msg_matches(
} }
if let Some(matches) = matches.subcommand_matches("forward") { 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(); let uid = matches.value_of("uid").unwrap();
debug!("[msg::cli::matches] uid: {}", uid); debug!("uid: {}", uid);
let attachments = matches let attachments = matches
.values_of("attachments") .values_of("attachments")
.unwrap_or_default() .unwrap_or_default()
.map(String::from) .map(String::from)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
debug!( debug!("found {} attachments", attachments.len());
"[msg::cli::matches] found {} attachments", trace!("attachments: {:?}", attachments);
attachments.len()
);
trace!("[msg::cli::matches] {:#?}", attachments);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
@ -493,7 +440,7 @@ pub fn msg_matches(
match input::post_edit_choice() { match input::post_edit_choice() {
Ok(choice) => match choice { Ok(choice) => match choice {
input::PostEditChoice::Send => { input::PostEditChoice::Send => {
debug!("[msg::cli::matches] sending message…"); debug!("sending message…");
let msg = msg.to_sendable_msg()?; let msg = msg.to_sendable_msg()?;
smtp::send(&account, &msg)?; smtp::send(&account, &msg)?;
imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?;
@ -507,7 +454,7 @@ pub fn msg_matches(
} }
input::PostEditChoice::LocalDraft => break, input::PostEditChoice::LocalDraft => break,
input::PostEditChoice::RemoteDraft => { input::PostEditChoice::RemoteDraft => {
debug!("[msg::cli::matches] saving to draft…"); debug!("saving to draft…");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
input::remove_draft()?; input::remove_draft()?;
info!("Message successfully saved to Drafts"); info!("Message successfully saved to Drafts");
@ -526,35 +473,79 @@ pub fn msg_matches(
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("copy") { if let Some(matches) = matches.subcommand_matches("template") {
debug!("[msg::cli::matches] copy command matched"); 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(); 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(); let target = matches.value_of("target").unwrap();
debug!("[msg::cli::matches] target: {}", &target); debug!("target: {}", &target);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
let mut flags = msg.flags.deref().to_vec(); let mut flags = msg.flags.deref().to_vec();
flags.push(Flag::Seen); flags.push(Flag::Seen);
imap_conn.append_msg(target, &msg.raw, &flags)?; imap_conn.append_msg(target, &msg.raw, &flags)?;
info!(&format!( info!("Message {} successfully copied to folder `{}`", uid, target);
"Message {} successfully copied to folder `{}`",
&uid, &target
));
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("move") { 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(); let uid = matches.value_of("uid").unwrap();
debug!("[msg::cli::matches] uid: {}", &uid); debug!("uid: {}", &uid);
let target = matches.value_of("target").unwrap(); let target = matches.value_of("target").unwrap();
debug!("[msg::cli::matches] target: {}", &target); debug!("target: {}", &target);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
@ -562,10 +553,7 @@ pub fn msg_matches(
flags.push(Flag::Seen); flags.push(Flag::Seen);
imap_conn.append_msg(target, &msg.raw, msg.flags.deref())?; imap_conn.append_msg(target, &msg.raw, msg.flags.deref())?;
imap_conn.add_flags(&mbox, uid, "\\Seen \\Deleted")?; imap_conn.add_flags(&mbox, uid, "\\Seen \\Deleted")?;
info!(&format!( info!("Message {} successfully moved to folder `{}`", uid, target);
"Message {} successfully moved to folder `{}`",
&uid, &target
));
imap_conn.expunge(&mbox)?; imap_conn.expunge(&mbox)?;
imap_conn.logout(); imap_conn.logout();
@ -573,14 +561,14 @@ pub fn msg_matches(
} }
if let Some(matches) = matches.subcommand_matches("delete") { 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(); let uid = matches.value_of("uid").unwrap();
debug!("[msg::cli::matches] uid: {}", &uid); debug!("uid: {}", &uid);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
imap_conn.add_flags(&mbox, uid, "\\Seen \\Deleted")?; 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.expunge(&mbox)?;
imap_conn.logout(); imap_conn.logout();
@ -588,7 +576,7 @@ pub fn msg_matches(
} }
if let Some(matches) = matches.subcommand_matches("send") { 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 mut imap_conn = ImapConnector::new(&account)?;
let msg = matches.value_of("message").unwrap(); let msg = matches.value_of("message").unwrap();
@ -602,7 +590,7 @@ pub fn msg_matches(
} }
if let Some(matches) = matches.subcommand_matches("save") { 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 mut imap_conn = ImapConnector::new(&account)?;
let msg = matches.value_of("message").unwrap(); 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 mut imap_conn = ImapConnector::new(&account)?;
let msgs = imap_conn.list_msgs(&mbox, &config.default_page_size(&account), &0)?; let msgs = imap_conn.list_msgs(&mbox, &config.default_page_size(&account), &0)?;
let msgs = Msgs::from(&msgs); let msgs = Msgs::from(&msgs);
info!(&msgs); info!("{}", msgs);
imap_conn.logout(); imap_conn.logout();
Ok(true) Ok(true)

View file

@ -11,9 +11,12 @@ use std::{borrow::Cow, fmt, fs, path::PathBuf, result};
use tree_magic; use tree_magic;
use uuid::Uuid; use uuid::Uuid;
use crate::config::model::{Account, Config}; use crate::{
use crate::flag::model::{Flag, Flags}; config::model::{Account, Config},
use crate::table::{self, DisplayRow, DisplayTable}; flag::model::{Flag, Flags},
output::fmt::{get_output_fmt, OutputFmt, Response},
table::{self, DisplayRow, DisplayTable},
};
error_chain! { error_chain! {
foreign_links { foreign_links {
@ -29,7 +32,17 @@ pub struct Tpl(String);
impl fmt::Display for Tpl { impl fmt::Display for Tpl {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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 { impl fmt::Display for ReadableMsg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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> { pub fn build_new_tpl(config: &Config, account: &Account) -> Result<Tpl> {
let msg_spec = MsgSpec{ let msg_spec = MsgSpec {
in_reply_to: None, in_reply_to: None,
to: None, to: None,
cc: None, cc: None,
@ -392,8 +415,8 @@ impl<'m> Msg<'m> {
.get_first_value("reply-to") .get_first_value("reply-to")
.or(headers.get_first_value("from")); .or(headers.get_first_value("from"));
let to = match to { let to = match to {
Some(t) => {Some(vec![t])}, Some(t) => Some(vec![t]),
None => {None}, None => None,
}; };
let thread = self // Original msg prepend with ">" let thread = self // Original msg prepend with ">"
@ -403,7 +426,7 @@ impl<'m> Msg<'m> {
.map(|line| format!(">{}", line)) .map(|line| format!(">{}", line))
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let msg_spec = MsgSpec{ let msg_spec = MsgSpec {
in_reply_to: headers.get_first_value("message-id"), in_reply_to: headers.get_first_value("message-id"),
to, to,
cc: None, cc: None,
@ -455,12 +478,14 @@ impl<'m> Msg<'m> {
}; };
// "Cc" header // "Cc" header
let cc = Some(headers let cc = Some(
headers
.get_all_values("cc") .get_all_values("cc")
.iter() .iter()
.flat_map(|addrs| addrs.split(",")) .flat_map(|addrs| addrs.split(","))
.map(|addr| addr.trim().to_string()) .map(|addr| addr.trim().to_string())
.collect::<Vec<String>>()); .collect::<Vec<String>>(),
);
// Original msg prepend with ">" // Original msg prepend with ">"
let thread = self let thread = self
@ -469,7 +494,7 @@ impl<'m> Msg<'m> {
.map(|line| format!(">{}", line)) .map(|line| format!(">{}", line))
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let msg_spec = MsgSpec{ let msg_spec = MsgSpec {
in_reply_to: headers.get_first_value("message-id"), in_reply_to: headers.get_first_value("message-id"),
cc, cc,
to: Some(vec![reply_to, to].concat()), to: Some(vec![reply_to, to].concat()),
@ -483,13 +508,18 @@ impl<'m> Msg<'m> {
let msg = &self.parse()?; let msg = &self.parse()?;
let headers = msg.get_headers(); 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![ let original_msg = vec![
"-------- Forwarded Message --------".to_string(), "-------- Forwarded Message --------".to_string(),
self.text_bodies("text/plain")?, self.text_bodies("text/plain")?,
]; ];
let msg_spec = MsgSpec{ let msg_spec = MsgSpec {
in_reply_to: None, in_reply_to: None,
cc: None, cc: None,
to: None, to: None,
@ -520,10 +550,17 @@ impl<'m> Msg<'m> {
} }
fn add_to_header(tpl: &mut Vec<String>, to: Option<Vec<String>>) { fn add_to_header(tpl: &mut Vec<String>, to: Option<Vec<String>>) {
tpl.push(format!("To: {}", match to { tpl.push(format!(
Some(t) => {t.join(", ")} "To: {}",
None => {String::new()} match to {
})); Some(t) => {
t.join(", ")
}
None => {
String::new()
}
}
));
} }
fn add_subject_header(tpl: &mut Vec<String>, subject: Option<String>) { 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> { impl<'m> fmt::Display for Msgs<'m> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 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") .value_name("FMT")
.possible_values(&["plain", "json"]) .possible_values(&["plain", "json"])
.default_value("plain"), .default_value("plain"),
Arg::with_name("log") Arg::with_name("log-level")
.long("log") .long("log-level")
.alias("log")
.short("l") .short("l")
.help("Defines the logs level") .help("Defines the logs level")
.value_name("LEVEL") .value_name("LEVEL")

View file

@ -1,6 +1,7 @@
use serde::Serialize;
use std::fmt; 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) { pub fn set_output_fmt(output_fmt: &'static OutputFmt) {
unsafe { OUTPUT_FMT = output_fmt } 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 error_chain::error_chain;
use log::{self, Level, LevelFilter, Metadata, Record}; use log::{self, Level, LevelFilter, Metadata, Record};
use std::fmt; use std::{fmt, io::Write};
use super::fmt::{set_output_fmt, OutputFmt}; use super::fmt::{set_output_fmt, OutputFmt};
error_chain! {} 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 // Log level struct
pub struct LogLevel(pub LevelFilter); pub struct LogLevel(pub LevelFilter);
@ -95,16 +77,32 @@ impl log::Log for JsonLogger {
// Init // Init
pub fn init(fmt: &OutputFmt, level: &LogLevel) -> Result<()> { pub fn init(fmt: &OutputFmt, level: &LogLevel) -> Result<()> {
log::set_logger(match fmt { match fmt {
&OutputFmt::Plain => { &OutputFmt::Plain => {
set_output_fmt(&OutputFmt::Plain); set_output_fmt(&OutputFmt::Plain);
&PlainLogger
} }
&OutputFmt::Json => { &OutputFmt::Json => {
set_output_fmt(&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)) .filter_level(level.0)
.chain_err(|| "Could not init logger") .init();
Ok(())
} }