diff --git a/CHANGELOG.md b/CHANGELOG.md index e788ddc..7ca3614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.2.5] - 2021-04-12 + ### Fixed - Expunge mbox after `move` and `delete` cmd [#83] +- JSON output [#89] ## [0.2.4] - 2021-04-09 @@ -101,7 +104,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Password from command [#22] - Set up README [#20] -[unreleased]: https://github.com/soywod/himalaya/compare/v0.2.4...HEAD +[unreleased]: https://github.com/soywod/himalaya/compare/v0.2.5...HEAD +[0.2.5]: https://github.com/soywod/himalaya/compare/v0.2.5...v0.2.4 [0.2.4]: https://github.com/soywod/himalaya/compare/v0.2.4...v0.2.3 [0.2.3]: https://github.com/soywod/himalaya/compare/v0.2.3...v0.2.2 [0.2.2]: https://github.com/soywod/himalaya/compare/v0.2.2...v0.2.1 @@ -149,3 +153,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#75]: https://github.com/soywod/himalaya/issues/75 [#79]: https://github.com/soywod/himalaya/issues/79 [#83]: https://github.com/soywod/himalaya/issues/83 +[#89]: https://github.com/soywod/himalaya/issues/89 diff --git a/Cargo.lock b/Cargo.lock index cb7806b..b4779a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,7 +306,7 @@ dependencies = [ [[package]] name = "himalaya" -version = "0.2.4" +version = "0.2.5" dependencies = [ "clap", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 4b7dcf4..f25f8bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "himalaya" description = "📫 Minimalist CLI email client" -version = "0.2.4" +version = "0.2.5" authors = ["soywod "] edition = "2018" diff --git a/README.md b/README.md index 7d136cc..4229cd6 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ for all the options.* ## Usage ``` -himalaya 0.2.4 +himalaya 0.2.5 soywod 📫 Minimalist CLI email client diff --git a/src/mbox/cli.rs b/src/mbox/cli.rs index 2fea2b7..0925d46 100644 --- a/src/mbox/cli.rs +++ b/src/mbox/cli.rs @@ -1,7 +1,8 @@ use clap::{self, App, Arg, ArgMatches, SubCommand}; use error_chain::error_chain; +use log::debug; -use crate::{config::model::Config, imap::model::ImapConnector, output::utils::print}; +use crate::{config::model::Config, imap::model::ImapConnector, info}; error_chain! { links { @@ -36,13 +37,14 @@ pub fn mbox_subcmds<'a>() -> Vec> { pub fn mbox_matches(matches: &ArgMatches) -> Result { let config = Config::new_from_file()?; let account = config.find_account_by_name(matches.value_of("account"))?; - let output_fmt = matches.value_of("output").unwrap(); - let silent = matches.is_present("silent"); if let Some(_) = matches.subcommand_matches("mailboxes") { + debug!("Subcommand matched: mailboxes"); + let mut imap_conn = ImapConnector::new(&account)?; let mboxes = imap_conn.list_mboxes()?; - print(&output_fmt, &silent, mboxes)?; + info!(&mboxes); + imap_conn.logout(); return Ok(true); } diff --git a/src/msg/cli.rs b/src/msg/cli.rs index a6ffb4e..d4f6d70 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, info}; +use log::{debug, error}; use std::{fs, ops::Deref}; use crate::{ config::model::Config, flag::model::Flag, imap::model::ImapConnector, - input, + info, input, mbox::cli::mbox_target_arg, msg::model::{Attachments, Msg, Msgs, ReadableMsg}, smtp, @@ -183,7 +183,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?; let msgs = Msgs::from(&msgs); - info!("{}", msgs); + info!(&msgs); imap_conn.logout(); return Ok(()); @@ -225,7 +225,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?; let msgs = Msgs::from(&msgs); - info!("{}", msgs); + info!(&msgs); imap_conn.logout(); return Ok(()); @@ -247,10 +247,10 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { 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(); @@ -268,15 +268,18 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { let attachments = Attachments::from_bytes(&msg)?; debug!( "{} attachment(s) found for message {}", - attachments.0.len(), - uid + &attachments.0.len(), + &uid ); attachments.0.iter().for_each(|attachment| { let filepath = config.downloads_filepath(&account, &attachment.filename); debug!("Downloading {}…", &attachment.filename); fs::write(filepath, &attachment.raw).unwrap() }); - info!("{} attachment(s) successfully downloaded", &uid); + info!(&format!( + "{} attachment(s) successfully downloaded", + &attachments.0.len() + )); imap_conn.logout(); return Ok(()); @@ -333,7 +336,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { debug!("Subcommand matched: new"); let tpl = Msg::build_new_tpl(&config, &account)?; - info!("{}", tpl); + info!(&tpl); } if let Some(matches) = matches.subcommand_matches("reply") { @@ -349,7 +352,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { } else { msg.build_reply_tpl(&config, &account)? }; - info!("{}", tpl); + info!(&tpl); imap_conn.logout(); } @@ -363,7 +366,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let tpl = msg.build_forward_tpl(&config, &account)?; - info!("{}", tpl); + info!(&tpl); imap_conn.logout(); } @@ -486,10 +489,10 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { let mut flags = msg.flags.deref().to_vec(); flags.push(Flag::Seen); imap_conn.append_msg(target, &msg.raw, &flags)?; - info!( + info!(&format!( "Message {} successfully copied to folder `{}`", &uid, &target - ); + )); imap_conn.logout(); return Ok(()); @@ -509,10 +512,10 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { flags.push(Flag::Seen); imap_conn.append_msg(target, &msg.raw, msg.flags.deref())?; imap_conn.add_flags(mbox, uid, "\\Seen \\Deleted")?; - info!( + info!(&format!( "Message {} successfully moved to folder `{}`", &uid, &target - ); + )); imap_conn.expunge(mbox)?; imap_conn.logout(); @@ -527,7 +530,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { debug!("UID: {}", &uid); imap_conn.add_flags(mbox, uid, "\\Seen \\Deleted")?; - info!("Message {} successfully deleted", &uid); + info!(&format!("Message {} successfully deleted", &uid)); imap_conn.expunge(mbox)?; imap_conn.logout(); @@ -563,7 +566,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { let mut imap_conn = ImapConnector::new(&account)?; let msgs = imap_conn.list_msgs(&mbox, &10, &0)?; let msgs = Msgs::from(&msgs); - info!("{}", &msgs); + info!(&msgs); imap_conn.logout(); Ok(()) diff --git a/src/output/fmt.rs b/src/output/fmt.rs index c80e283..6dc57cb 100644 --- a/src/output/fmt.rs +++ b/src/output/fmt.rs @@ -1,8 +1,18 @@ use std::fmt; +static mut OUTPUT_FMT: &'static OutputFmt = &OutputFmt::Plain; + +pub fn set_output_fmt(output_fmt: &'static OutputFmt) { + unsafe { OUTPUT_FMT = output_fmt } +} + +pub unsafe fn get_output_fmt() -> &'static OutputFmt { + OUTPUT_FMT +} + pub enum OutputFmt { - Json, Plain, + Json, } impl From<&str> for OutputFmt { diff --git a/src/output/log.rs b/src/output/log.rs index 5c07760..37f6cae 100644 --- a/src/output/log.rs +++ b/src/output/log.rs @@ -1,11 +1,31 @@ use error_chain::error_chain; -use log::{Level, LevelFilter, Metadata, Record}; +use log::{self, Level, LevelFilter, Metadata, Record}; use std::fmt; -use super::fmt::OutputFmt; +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); @@ -30,7 +50,7 @@ impl fmt::Display for LogLevel { // Plain logger -struct PlainLogger; +pub struct PlainLogger; impl log::Log for PlainLogger { fn enabled(&self, metadata: &Metadata) -> bool { @@ -52,7 +72,7 @@ impl log::Log for PlainLogger { // JSON logger -struct JsonLogger; +pub struct JsonLogger; impl log::Log for JsonLogger { fn enabled(&self, metadata: &Metadata) -> bool { @@ -64,9 +84,7 @@ impl log::Log for JsonLogger { if let Level::Error = record.level() { eprintln!("{}", record.args()); } else { - // Should be safe enough to `.unwrap()` since it's - // formatted by Himalaya itself - print!("{}", serde_json::to_string(record.args()).unwrap()); + print!("{}", record.args()); } } } @@ -77,9 +95,15 @@ impl log::Log for JsonLogger { // Init pub fn init(fmt: &OutputFmt, level: &LogLevel) -> Result<()> { - log::set_boxed_logger(match fmt { - &OutputFmt::Json => Box::new(JsonLogger), - &OutputFmt::Plain => Box::new(PlainLogger), + log::set_logger(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") diff --git a/src/output/utils.rs b/src/output/utils.rs index 56a413e..7325e8f 100644 --- a/src/output/utils.rs +++ b/src/output/utils.rs @@ -1,8 +1,5 @@ use error_chain::error_chain; -use serde::{ - ser::{self, SerializeStruct}, - Serialize, -}; +use serde::ser::{self, SerializeStruct}; use std::{fmt, process::Command, result}; error_chain! { @@ -40,17 +37,3 @@ pub fn run_cmd(cmd: &str) -> Result { Ok(String::from_utf8(output.stdout)?) } - -pub fn print(output_type: &str, silent: &bool, item: T) -> Result<()> { - if silent == &false { - match output_type { - "json" => print!( - "{}", - serde_json::to_string(&item).chain_err(|| "Could not decode JSON")? - ), - "text" | _ => println!("{}", item.to_string()), - } - } - - Ok(()) -} diff --git a/vim/autoload/himalaya/msg.vim b/vim/autoload/himalaya/msg.vim index dd2688c..d1a6d80 100644 --- a/vim/autoload/himalaya/msg.vim +++ b/vim/autoload/himalaya/msg.vim @@ -170,6 +170,7 @@ function! himalaya#msg#attachments() let mbox = himalaya#mbox#curr_mbox() let msg_id = stridx(bufname("%"), "Himalaya messages") == 0 ? s:get_focused_msg_id() : s:msg_id let msg = s:cli("--mailbox %s attachments %d", [shellescape(mbox), msg_id], "Downloading attachments", 0) + call himalaya#shared#log#info(msg) catch if !empty(v:exception) redraw | call himalaya#shared#log#err(v:exception) diff --git a/vim/autoload/himalaya/shared/cli.vim b/vim/autoload/himalaya/shared/cli.vim index c3b04a5..8681bd2 100644 --- a/vim/autoload/himalaya/shared/cli.vim +++ b/vim/autoload/himalaya/shared/cli.vim @@ -9,7 +9,7 @@ function! himalaya#shared#cli#call(cmd, args, log, should_throw) try let res = eval(res) redraw | call himalaya#shared#log#info(printf("%s [OK]", a:log)) - return res + return res.response catch redraw | call himalaya#shared#log#info(printf("%s [ERR]", a:log)) for line in split(res, "\n") diff --git a/vim/syntax/himalaya-msg-list.vim b/vim/syntax/himalaya-msg-list.vim index 4225e2f..c94f3ba 100644 --- a/vim/syntax/himalaya-msg-list.vim +++ b/vim/syntax/himalaya-msg-list.vim @@ -2,23 +2,23 @@ if exists("b:current_syntax") finish endif -syntax match hya_sep /|/ -syntax match hya_uid /^|.\{-}|/ contains=hya_sep -syntax match hya_flags /^|.\{-}|.\{-}|/ contains=hya_uid,hya_sep -syntax match hya_subject /^|.\{-}|.\{-}|.\{-}|/ contains=hya_uid,hya_flags,hya_sep -syntax match hya_sender /^|.\{-}|.\{-}|.\{-}|.\{-}|/ contains=hya_uid,hya_flags,hya_subject,hya_sep -syntax match hya_date /^|.\{-}|.\{-}|.\{-}|.\{-}|.\{-}|/ contains=hya_uid,hya_flags,hya_subject,hya_sender,hya_sep -syntax match hya_head /.*\%1l/ contains=hya_sep -syntax match hya_unseen /^|.\{-}|N.*$/ contains=hya_sep +syntax match hym_sep /|/ +syntax match hym_uid /^|.\{-}|/ contains=hym_sep +syntax match hym_flags /^|.\{-}|.\{-}|/ contains=hym_uid,hym_sep +syntax match hym_subject /^|.\{-}|.\{-}|.\{-}|/ contains=hym_uid,hym_flags,hym_sep +syntax match hym_sender /^|.\{-}|.\{-}|.\{-}|.\{-}|/ contains=hym_uid,hym_flags,hym_subject,hym_sep +syntax match hym_date /^|.\{-}|.\{-}|.\{-}|.\{-}|.\{-}|/ contains=hym_uid,hym_flags,hym_subject,hym_sender,hym_sep +syntax match hym_head /.*\%1l/ contains=hym_sep +syntax match hym_unseen /^|.\{-}|N.*$/ contains=hym_sep -highlight default link hya_sep VertSplit -highlight default link hya_uid Identifier -highlight default link hya_flags Special -highlight default link hya_subject String -highlight default link hya_sender Structure -highlight default link hya_date Constant +highlight hym_head term=bold,underline cterm=bold,underline gui=bold,underline +highlight hym_unseen term=bold cterm=bold gui=bold -highlight hya_head term=bold,underline cterm=bold,underline gui=bold,underline -highlight hya_unseen term=bold cterm=bold gui=bold +highlight default link hym_sep VertSplit +highlight default link hym_uid Identifier +highlight default link hym_flags Special +highlight default link hym_subject String +highlight default link hym_sender Structure +highlight default link hym_date Constant let b:current_syntax = "himalaya-msg-list"