refactor output system (#74)

This commit is contained in:
Clément DOUIN 2021-04-09 00:15:16 +02:00
parent ebf1b854be
commit f26051685c
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
9 changed files with 325 additions and 92 deletions

View file

@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Wiki entry for Gmail users [#58] - Wiki entry for Gmail users [#58]
- Info logs for copy/move/delete cmd + silent mode [#74] - Info logs for copy/move/delete cmd + silent mode [#74]
### Changed
- Refactor output system + log levels [#74]
## [0.2.3] - 2021-04-08 ## [0.2.3] - 2021-04-08
### Added ### Added

65
Cargo.lock generated
View file

@ -30,6 +30,17 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.0.1"
@ -194,6 +205,19 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
[[package]]
name = "env_logger"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]] [[package]]
name = "error-chain" name = "error-chain"
version = "0.12.4" version = "0.12.4"
@ -271,14 +295,25 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]]
name = "hermit-abi"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "himalaya" name = "himalaya"
version = "0.2.3" version = "0.2.3"
dependencies = [ dependencies = [
"clap", "clap",
"env_logger",
"error-chain", "error-chain",
"imap", "imap",
"lettre", "lettre",
"log",
"mailparse", "mailparse",
"native-tls", "native-tls",
"rfc2047-decoder", "rfc2047-decoder",
@ -325,6 +360,12 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "hyperx" name = "hyperx"
version = "1.3.0" version = "1.3.0"
@ -475,11 +516,11 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.11" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "cfg-if 1.0.0",
] ]
[[package]] [[package]]
@ -1033,6 +1074,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "terminal_size" name = "terminal_size"
version = "0.1.15" version = "0.1.15"
@ -1197,6 +1247,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"

View file

@ -7,9 +7,11 @@ edition = "2018"
[dependencies] [dependencies]
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"
error-chain = "0.12.4" error-chain = "0.12.4"
imap = "2.4.0" imap = "2.4.0"
lettre = "0.10.0-beta.3" lettre = "0.10.0-beta.3"
log = "0.4.14"
mailparse = "0.13.1" mailparse = "0.13.1"
native-tls = "0.2" native-tls = "0.2"
rfc2047-decoder = "0.1.2" rfc2047-decoder = "0.1.2"

View file

@ -87,17 +87,18 @@ soywod <clement.douin@posteo.net>
📫 Minimalist CLI email client 📫 Minimalist CLI email client
USAGE: USAGE:
himalaya [FLAGS] [OPTIONS] [SUBCOMMAND] himalaya [OPTIONS] [SUBCOMMAND]
FLAGS: FLAGS:
-h, --help Prints help information -h, --help Prints help information
-s, --silent Disables any output
-V, --version Prints version information -V, --version Prints version information
OPTIONS: OPTIONS:
-a, --account <STRING> Selects a specific account -a, --account <STRING> Selects a specific account
-l, --log <LEVEL> Defines the logs level [default: info] [possible values: error, warn, info, debug,
trace]
-m, --mailbox <MAILBOX> Selects a specific mailbox [default: INBOX] -m, --mailbox <MAILBOX> Selects a specific mailbox [default: INBOX]
-o, --output <STRING> Defines the output format [default: plain] [possible values: plain, json] -o, --output <FMT> Defines the output format [default: plain] [possible values: plain, json]
SUBCOMMANDS: SUBCOMMANDS:
attachments Downloads all message attachments attachments Downloads all message attachments

View file

@ -7,6 +7,8 @@ mod config {
} }
mod output { mod output {
pub(crate) mod cli; pub(crate) mod cli;
pub(crate) mod fmt;
pub(crate) mod log;
pub(crate) mod utils; pub(crate) mod utils;
} }
mod imap { mod imap {
@ -28,6 +30,7 @@ mod mbox {
use clap; use clap;
use error_chain::error_chain; use error_chain::error_chain;
use log::{debug, error};
use std::env; use std::env;
use crate::{ use crate::{
@ -36,7 +39,11 @@ use crate::{
imap::cli::{imap_matches, imap_subcmds}, imap::cli::{imap_matches, imap_subcmds},
mbox::cli::{mbox_matches, mbox_source_arg, mbox_subcmds}, mbox::cli::{mbox_matches, mbox_source_arg, mbox_subcmds},
msg::cli::{msg_matches, msg_subcmds}, msg::cli::{msg_matches, msg_subcmds},
output::cli::output_args, output::{
cli::output_args,
fmt::OutputFmt,
log::{init as init_logger, LogLevel},
},
}; };
error_chain! { error_chain! {
@ -45,6 +52,7 @@ error_chain! {
ImapCli(crate::imap::cli::Error, crate::imap::cli::ErrorKind); ImapCli(crate::imap::cli::Error, crate::imap::cli::ErrorKind);
MboxCli(crate::mbox::cli::Error, crate::mbox::cli::ErrorKind); MboxCli(crate::mbox::cli::Error, crate::mbox::cli::ErrorKind);
MsgCli(crate::msg::cli::Error, crate::msg::cli::ErrorKind); MsgCli(crate::msg::cli::Error, crate::msg::cli::ErrorKind);
OutputLog(crate::output::log::Error, crate::output::log::ErrorKind);
} }
} }
@ -62,6 +70,14 @@ fn run() -> Result<()> {
.subcommands(msg_subcmds()) .subcommands(msg_subcmds())
.get_matches(); .get_matches();
let output_fmt: OutputFmt = matches.value_of("output").unwrap().into();
let log_level: LogLevel = matches.value_of("log").unwrap().into();
init_logger(&output_fmt, &log_level)?;
debug!("Logger initialized");
debug!("Output format: {}", &output_fmt);
debug!("Log level: {}", &log_level);
loop { loop {
if mbox_matches(&matches)? { if mbox_matches(&matches)? {
break; break;
@ -88,8 +104,8 @@ fn main() {
match errs.next() { match errs.next() {
None => (), None => (),
Some(err) => { Some(err) => {
eprintln!("{}", err); error!("{}", err);
errs.for_each(|err| eprintln!("{}", err)); errs.for_each(|err| error!("{}", err));
} }
} }
} }

View file

@ -1,5 +1,6 @@
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, info};
use std::{fs, ops::Deref}; use std::{fs, ops::Deref};
use crate::{ use crate::{
@ -9,7 +10,6 @@ use crate::{
input, input,
mbox::cli::mbox_target_arg, mbox::cli::mbox_target_arg,
msg::model::{Attachments, Msg, Msgs, ReadableMsg}, msg::model::{Attachments, Msg, Msgs, ReadableMsg},
output::utils::{print, Info},
smtp, smtp,
}; };
@ -19,7 +19,6 @@ error_chain! {
Imap(crate::imap::model::Error, crate::imap::model::ErrorKind); Imap(crate::imap::model::Error, crate::imap::model::ErrorKind);
Input(crate::input::Error, crate::input::ErrorKind); Input(crate::input::Error, crate::input::ErrorKind);
MsgModel(crate::msg::model::Error, crate::msg::model::ErrorKind); MsgModel(crate::msg::model::Error, crate::msg::model::ErrorKind);
OutputUtils(crate::output::utils::Error, crate::output::utils::ErrorKind);
Smtp(crate::smtp::Error, crate::smtp::ErrorKind); Smtp(crate::smtp::Error, crate::smtp::ErrorKind);
} }
} }
@ -162,25 +161,33 @@ pub fn msg_subcmds<'a>() -> Vec<App<'a, 'a>> {
pub fn msg_matches(matches: &ArgMatches) -> Result<()> { pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
let config = Config::new_from_file()?; let config = Config::new_from_file()?;
let account = config.find_account_by_name(matches.value_of("account"))?; 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");
let mbox = matches.value_of("mailbox").unwrap(); let mbox = matches.value_of("mailbox").unwrap();
if let Some(matches) = matches.subcommand_matches("list") { if let Some(matches) = matches.subcommand_matches("list") {
debug!("Subcommand matched: list");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let page_size: usize = matches.value_of("page-size").unwrap().parse().unwrap(); let page_size: usize = matches.value_of("page-size").unwrap().parse().unwrap();
debug!("Page size: {}", &page_size);
let page: usize = matches.value_of("page").unwrap().parse().unwrap(); let page: usize = matches.value_of("page").unwrap().parse().unwrap();
debug!("Page: {}", &page);
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);
print(&output_fmt, &silent, msgs)?; info!("{}", msgs);
imap_conn.logout(); imap_conn.logout();
return Ok(()); return Ok(());
} }
if let Some(matches) = matches.subcommand_matches("search") { if let Some(matches) = matches.subcommand_matches("search") {
debug!("Subcommand matched: search");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let page_size: usize = matches.value_of("page-size").unwrap().parse().unwrap(); let page_size: usize = matches.value_of("page-size").unwrap().parse().unwrap();
debug!("Page size: {}", &page_size);
let page: usize = matches.value_of("page").unwrap().parse().unwrap(); let page: usize = matches.value_of("page").unwrap().parse().unwrap();
debug!("Page: {}", &page);
let query = matches let query = matches
.values_of("query") .values_of("query")
.unwrap_or_default() .unwrap_or_default()
@ -205,62 +212,61 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
}) })
.1 .1
.join(" "); .join(" ");
debug!("Query: {}", &query);
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);
print(&output_fmt, &silent, msgs)?; info!("{}", msgs);
imap_conn.logout(); imap_conn.logout();
return Ok(()); return Ok(());
} }
if let Some(matches) = matches.subcommand_matches("read") { if let Some(matches) = matches.subcommand_matches("read") {
debug!("Subcommand matched: read");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
let mime = format!("text/{}", matches.value_of("mime-type").unwrap()); let mime = format!("text/{}", matches.value_of("mime-type").unwrap());
debug!("MIME: {}", &mime);
let msg = imap_conn.read_msg(&mbox, &uid)?; let msg = imap_conn.read_msg(&mbox, &uid)?;
let msg = ReadableMsg::from_bytes(&mime, &msg)?; let msg = ReadableMsg::from_bytes(&mime, &msg)?;
print(&output_fmt, &silent, msg)?; info!("{}", msg);
imap_conn.logout(); imap_conn.logout();
return Ok(()); return Ok(());
} }
if let Some(matches) = matches.subcommand_matches("attachments") { if let Some(matches) = matches.subcommand_matches("attachments") {
debug!("Subcommand matched: attachments");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
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!(
"{} attachment(s) found for message {}",
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);
match output_fmt {
"plain" => {
println!(
"{} attachment(s) found for message {}",
attachments.0.len(),
uid
);
attachments.0.iter().for_each(|attachment| {
let filepath = config.downloads_filepath(&account, &attachment.filename);
println!("Downloading {}", &attachment.filename);
fs::write(filepath, &attachment.raw).unwrap()
});
println!("Done!");
}
"json" => {
attachments.0.iter().for_each(|attachment| {
let filepath = config.downloads_filepath(&account, &attachment.filename);
fs::write(filepath, &attachment.raw).unwrap()
});
print!("{{}}");
}
_ => (),
}
imap_conn.logout(); imap_conn.logout();
return Ok(()); return Ok(());
} }
if let Some(matches) = matches.subcommand_matches("write") { if let Some(matches) = matches.subcommand_matches("write") {
debug!("Subcommand matched: write");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let attachments = matches let attachments = matches
.values_of("attachments") .values_of("attachments")
@ -276,17 +282,17 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
match input::post_edit_choice() { match input::post_edit_choice() {
Ok(choice) => match choice { Ok(choice) => match choice {
input::Choice::Send => { input::Choice::Send => {
println!("Sending"); 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])?;
println!("Done!"); info!("Message successfully sent");
break; break;
} }
input::Choice::Draft => { input::Choice::Draft => {
println!("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])?;
println!("Done!"); info!("Message successfully saved to Drafts");
break; break;
} }
input::Choice::Edit => { input::Choice::Edit => {
@ -295,7 +301,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
} }
input::Choice::Quit => break, input::Choice::Quit => break,
}, },
Err(err) => eprintln!("{}", err), Err(err) => error!("{}", err),
} }
} }
imap_conn.logout(); imap_conn.logout();
@ -303,30 +309,44 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
} }
if let Some(matches) = matches.subcommand_matches("template") { if let Some(matches) = matches.subcommand_matches("template") {
debug!("Subcommand matched: template");
if let Some(_) = matches.subcommand_matches("new") { if let Some(_) = matches.subcommand_matches("new") {
debug!("Subcommand matched: new");
let tpl = Msg::build_new_tpl(&config, &account)?; let tpl = Msg::build_new_tpl(&config, &account)?;
print(&output_fmt, &silent, &tpl)?; info!("{}", tpl);
} }
if let Some(matches) = matches.subcommand_matches("reply") { if let Some(matches) = matches.subcommand_matches("reply") {
debug!("Subcommand matched: reply");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
let tpl = if matches.is_present("reply-all") { let tpl = if matches.is_present("reply-all") {
msg.build_reply_all_tpl(&config, &account)? msg.build_reply_all_tpl(&config, &account)?
} else { } else {
msg.build_reply_tpl(&config, &account)? msg.build_reply_tpl(&config, &account)?
}; };
print(&output_fmt, &silent, &tpl)?; info!("{}", tpl);
imap_conn.logout(); imap_conn.logout();
} }
if let Some(matches) = matches.subcommand_matches("forward") { if let Some(matches) = matches.subcommand_matches("forward") {
debug!("Subcommand matched: forward");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
let tpl = msg.build_forward_tpl(&config, &account)?; let tpl = msg.build_forward_tpl(&config, &account)?;
print(&output_fmt, &silent, &tpl)?; info!("{}", tpl);
imap_conn.logout(); imap_conn.logout();
} }
@ -334,6 +354,8 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
} }
if let Some(matches) = matches.subcommand_matches("reply") { if let Some(matches) = matches.subcommand_matches("reply") {
debug!("Subcommand matched: reply");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let attachments = matches let attachments = matches
.values_of("attachments") .values_of("attachments")
@ -341,6 +363,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
.map(String::from) .map(String::from)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
let tpl = if matches.is_present("reply-all") { let tpl = if matches.is_present("reply-all") {
@ -357,17 +380,17 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
match input::post_edit_choice() { match input::post_edit_choice() {
Ok(choice) => match choice { Ok(choice) => match choice {
input::Choice::Send => { input::Choice::Send => {
println!("Sending"); debug!("Sending message");
smtp::send(&account, &msg.to_sendable_msg()?)?; smtp::send(&account, &msg.to_sendable_msg()?)?;
imap_conn.append_msg("Sent", &msg.to_vec()?, &[Flag::Seen])?; imap_conn.append_msg("Sent", &msg.to_vec()?, &[Flag::Seen])?;
imap_conn.add_flags(mbox, uid, "\\Answered")?; imap_conn.add_flags(mbox, uid, "\\Answered")?;
println!("Done!"); info!("Message successfully sent");
break; break;
} }
input::Choice::Draft => { input::Choice::Draft => {
println!("Saving to draft"); debug!("Saving draft message");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
println!("Done!"); info!("Message successfully saved to Drafts");
break; break;
} }
input::Choice::Edit => { input::Choice::Edit => {
@ -376,7 +399,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
} }
input::Choice::Quit => break, input::Choice::Quit => break,
}, },
Err(err) => eprintln!("{}", err), Err(err) => error!("{}", err),
} }
} }
@ -385,6 +408,8 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
} }
if let Some(matches) = matches.subcommand_matches("forward") { if let Some(matches) = matches.subcommand_matches("forward") {
debug!("Subcommand matched: forward");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let attachments = matches let attachments = matches
.values_of("attachments") .values_of("attachments")
@ -392,6 +417,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
.map(String::from) .map(String::from)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
let tpl = msg.build_forward_tpl(&config, &account)?; let tpl = msg.build_forward_tpl(&config, &account)?;
@ -403,16 +429,16 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
match input::post_edit_choice() { match input::post_edit_choice() {
Ok(choice) => match choice { Ok(choice) => match choice {
input::Choice::Send => { input::Choice::Send => {
println!("Sending"); debug!("Sending message");
smtp::send(&account, &msg.to_sendable_msg()?)?; smtp::send(&account, &msg.to_sendable_msg()?)?;
imap_conn.append_msg("Sent", &msg.to_vec()?, &[Flag::Seen])?; imap_conn.append_msg("Sent", &msg.to_vec()?, &[Flag::Seen])?;
println!("Done!"); info!("Message successfully sent");
break; break;
} }
input::Choice::Draft => { input::Choice::Draft => {
println!("Saving to draft"); debug!("Saving draft message");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
println!("Done!"); info!("Message successfully saved to Drafts");
break; break;
} }
input::Choice::Edit => { input::Choice::Edit => {
@ -421,7 +447,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
} }
input::Choice::Quit => break, input::Choice::Quit => break,
}, },
Err(err) => eprintln!("{}", err), Err(err) => error!("{}", err),
} }
} }
@ -430,63 +456,67 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
} }
if let Some(matches) = matches.subcommand_matches("copy") { if let Some(matches) = matches.subcommand_matches("copy") {
debug!("Subcommand matched: copy");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap(); 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!("Target: {}", &target);
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)?;
imap_conn.logout(); info!(
"Message {} successfully copied to folder `{}`",
&uid, &target
);
print( imap_conn.logout();
&output_fmt,
&silent,
Info(format!(
"Message {} successfully copied to folder `{}`",
&uid, &target
)),
)?;
return Ok(()); return Ok(());
} }
if let Some(matches) = matches.subcommand_matches("move") { if let Some(matches) = matches.subcommand_matches("move") {
debug!("Subcommand matched: move");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap(); 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!("Target: {}", &target);
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, 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")?;
imap_conn.logout(); info!(
"Message {} successfully moved to folder `{}`",
&uid, &target
);
print( imap_conn.logout();
&output_fmt,
&silent,
Info(format!(
"Message {} successfully moved to folder `{}`",
&uid, &target
)),
)?;
return Ok(()); return Ok(());
} }
if let Some(matches) = matches.subcommand_matches("delete") { if let Some(matches) = matches.subcommand_matches("delete") {
debug!("Subcommand matched: delete");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
imap_conn.add_flags(mbox, uid, "\\Seen \\Deleted")?; debug!("UID: {}", &uid);
imap_conn.logout();
print( imap_conn.add_flags(mbox, uid, "\\Seen \\Deleted")?;
&output_fmt, info!("Message {} successfully deleted", &uid);
&silent,
Info(format!("Message {} successfully deleted", &uid)), imap_conn.logout();
)?;
return Ok(()); return Ok(());
} }
if let Some(matches) = matches.subcommand_matches("send") { if let Some(matches) = matches.subcommand_matches("send") {
debug!("Subcommand matched: send");
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();
let msg = Msg::from(msg.to_string()); let msg = Msg::from(msg.to_string());
@ -498,6 +528,8 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
} }
if let Some(matches) = matches.subcommand_matches("save") { if let Some(matches) = matches.subcommand_matches("save") {
debug!("Subcommand matched: save");
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();
let msg = Msg::from(msg.to_string()); let msg = Msg::from(msg.to_string());
@ -506,11 +538,13 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
return Ok(()); return Ok(());
} }
// Default case: list first page messages debug!("Default subcommand matched: list");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let msgs = imap_conn.list_msgs(&mbox, &10, &0)?; let msgs = imap_conn.list_msgs(&mbox, &10, &0)?;
let msgs = Msgs::from(&msgs); let msgs = Msgs::from(&msgs);
print(&output_fmt, &silent, msgs)?; info!("{}", &msgs);
imap_conn.logout(); imap_conn.logout();
Ok(()) Ok(())
} }

View file

@ -6,12 +6,15 @@ pub fn output_args<'a>() -> Vec<Arg<'a, 'a>> {
.long("output") .long("output")
.short("o") .short("o")
.help("Defines the output format") .help("Defines the output format")
.value_name("STRING") .value_name("FMT")
.possible_values(&["plain", "json"]) .possible_values(&["plain", "json"])
.default_value("plain"), .default_value("plain"),
Arg::with_name("silent") Arg::with_name("log")
.long("silent") .long("log")
.short("s") .short("l")
.help("Disables any output"), .help("Defines the logs level")
.value_name("LEVEL")
.possible_values(&["error", "warn", "info", "debug", "trace"])
.default_value("info"),
] ]
} }

28
src/output/fmt.rs Normal file
View file

@ -0,0 +1,28 @@
use std::fmt;
pub enum OutputFmt {
Json,
Plain,
}
impl From<&str> for OutputFmt {
fn from(s: &str) -> Self {
match s {
"json" => Self::Json,
"plain" | _ => Self::Plain,
}
}
}
impl fmt::Display for OutputFmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
OutputFmt::Json => "JSON",
OutputFmt::Plain => "PLAIN",
},
)
}
}

86
src/output/log.rs Normal file
View file

@ -0,0 +1,86 @@
use error_chain::error_chain;
use log::{Level, LevelFilter, Metadata, Record};
use std::fmt;
use super::fmt::OutputFmt;
error_chain! {}
// Log level struct
pub struct LogLevel(pub LevelFilter);
impl From<&str> for LogLevel {
fn from(s: &str) -> Self {
match s {
"error" => Self(LevelFilter::Error),
"warn" => Self(LevelFilter::Warn),
"debug" => Self(LevelFilter::Debug),
"trace" => Self(LevelFilter::Trace),
"info" | _ => Self(LevelFilter::Info),
}
}
}
impl fmt::Display for LogLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.to_string())
}
}
// Plain logger
struct PlainLogger;
impl log::Log for PlainLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= Level::Trace
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
if let Level::Error = record.level() {
eprintln!("{}", record.args());
} else {
println!("{}", record.args());
}
}
}
fn flush(&self) {}
}
// JSON logger
struct JsonLogger;
impl log::Log for JsonLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
[Level::Error, Level::Info].contains(&metadata.level())
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
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());
}
}
}
fn flush(&self) {}
}
// 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),
})
.map(|()| log::set_max_level(level.0))
.chain_err(|| "Could not init logger")
}