improve logging, replace log-level by RUST_LOG

This commit is contained in:
Clément DOUIN 2021-04-28 00:47:24 +02:00
parent 950e57acdb
commit fa2f93185f
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
16 changed files with 340 additions and 361 deletions

View file

@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Changed
- Replace `log-level` arg by default `RUST_LOG` env var [#130]
### Fixed
- IDLE mode after network interruption [#123]
- Output redirected to `stderr` [#130]
- Refactor table system [#132]
- Editon file format on Linux [#133]
- Show email address when name not available [#131]
## [0.2.7] - 2021-04-24 ## [0.2.7] - 2021-04-24
### Added ### Added
@ -24,11 +36,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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)
- Vim table containing emoji [#122] - Vim table containing emoji [#122]
- IDLE mode after network interruption [#123]
- Output redirected to `stderr` [#130]
- Refactor table system [#132]
- Editon file format on Linux [#133]
- Show email address when name not available [#131]
## [0.2.6] - 2021-04-17 ## [0.2.6] - 2021-04-17

11
Cargo.lock generated
View file

@ -24,6 +24,15 @@ dependencies = [
"memchr 2.3.4", "memchr 2.3.4",
] ]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.5.2" version = "0.5.2"
@ -165,6 +174,8 @@ version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [ dependencies = [
"ansi_term",
"atty",
"bitflags", "bitflags",
"strsim", "strsim",
"textwrap", "textwrap",

View file

@ -7,7 +7,7 @@ edition = "2018"
[dependencies] [dependencies]
chrono = "0.4.19" chrono = "0.4.19"
clap = {version = "2.33.3", default-features = false, features = ["suggestions"]} clap = {version = "2.33.3", default-features = false, features = ["suggestions", "color"]}
env_logger = "0.8.3" env_logger = "0.8.3"
error-chain = "0.12.4" error-chain = "0.12.4"
imap = "2.4.0" imap = "2.4.0"

32
src/app.rs Normal file
View file

@ -0,0 +1,32 @@
use clap;
use crate::{
config::model::{Account, Config},
output::model::Output,
};
pub struct App<'a> {
pub config: &'a Config,
pub account: &'a Account,
pub output: &'a Output,
pub mbox: &'a str,
pub arg_matches: &'a clap::ArgMatches<'a>,
}
impl<'a> App<'a> {
pub fn new(
config: &'a Config,
account: &'a Account,
output: &'a Output,
mbox: &'a str,
arg_matches: &'a clap::ArgMatches<'a>,
) -> Self {
Self {
config,
account,
output,
mbox,
arg_matches,
}
}
}

View file

@ -13,7 +13,7 @@ pub fn comp_subcmds<'s>() -> Vec<App<'s, 's>> {
.required(true)])] .required(true)])]
} }
pub fn comp_matches(mut app: App, matches: &ArgMatches) -> Result<bool> { pub fn comp_matches<'a>(app: fn() -> App<'a, 'a>, matches: &ArgMatches) -> Result<bool> {
if let Some(matches) = matches.subcommand_matches("completion") { if let Some(matches) = matches.subcommand_matches("completion") {
debug!("completion command matched"); debug!("completion command matched");
let shell = match matches.value_of("shell").unwrap() { let shell = match matches.value_of("shell").unwrap() {
@ -22,7 +22,7 @@ pub fn comp_matches(mut app: App, matches: &ArgMatches) -> Result<bool> {
"bash" | _ => Shell::Bash, "bash" | _ => Shell::Bash,
}; };
debug!("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);
}; };

View file

@ -1,44 +1,43 @@
use clap::{App, Arg, ArgMatches, SubCommand}; use clap;
use error_chain::error_chain; use error_chain::error_chain;
use log::debug; use log::debug;
use crate::{config::model::Account, imap::model::ImapConnector, msg::cli::uid_arg}; use crate::{app::App, imap::model::ImapConnector, msg::cli::uid_arg};
error_chain! { error_chain! {
links { links {
Config(crate::config::model::Error, crate::config::model::ErrorKind);
Imap(crate::imap::model::Error, crate::imap::model::ErrorKind); Imap(crate::imap::model::Error, crate::imap::model::ErrorKind);
} }
} }
fn flags_arg<'a>() -> Arg<'a, 'a> { fn flags_arg<'a>() -> clap::Arg<'a, 'a> {
Arg::with_name("flags") clap::Arg::with_name("flags")
.help("IMAP flags (see https://tools.ietf.org/html/rfc3501#page-11)") .help("IMAP flags (see https://tools.ietf.org/html/rfc3501#page-11)")
.value_name("FLAGS…") .value_name("FLAGS…")
.multiple(true) .multiple(true)
.required(true) .required(true)
} }
pub fn flag_subcmds<'s>() -> Vec<App<'s, 's>> { pub fn flag_subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
vec![SubCommand::with_name("flags") vec![clap::SubCommand::with_name("flags")
.aliases(&["flag"]) .aliases(&["flag"])
.about("Handles flags") .about("Handles flags")
.subcommand( .subcommand(
SubCommand::with_name("set") clap::SubCommand::with_name("set")
.aliases(&["s"]) .aliases(&["s"])
.about("Replaces all message flags") .about("Replaces all message flags")
.arg(uid_arg()) .arg(uid_arg())
.arg(flags_arg()), .arg(flags_arg()),
) )
.subcommand( .subcommand(
SubCommand::with_name("add") clap::SubCommand::with_name("add")
.aliases(&["a"]) .aliases(&["a"])
.about("Appends flags to a message") .about("Appends flags to a message")
.arg(uid_arg()) .arg(uid_arg())
.arg(flags_arg()), .arg(flags_arg()),
) )
.subcommand( .subcommand(
SubCommand::with_name("remove") clap::SubCommand::with_name("remove")
.aliases(&["rm", "r"]) .aliases(&["rm", "r"])
.about("Removes flags from a message") .about("Removes flags from a message")
.arg(uid_arg()) .arg(uid_arg())
@ -46,8 +45,8 @@ 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(app: &App) -> Result<bool> {
if let Some(matches) = matches.subcommand_matches("set") { if let Some(matches) = app.arg_matches.subcommand_matches("set") {
debug!("set command matched"); debug!("set command matched");
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
@ -56,14 +55,14 @@ pub fn flag_matches(account: &Account, mbox: &str, matches: &ArgMatches) -> Resu
let flags = matches.value_of("flags").unwrap(); let flags = matches.value_of("flags").unwrap();
debug!("flags: {}", flags); debug!("flags: {}", flags);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
imap_conn.set_flags(mbox, uid, flags)?; imap_conn.set_flags(app.mbox, uid, flags)?;
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("add") { if let Some(matches) = app.arg_matches.subcommand_matches("add") {
debug!("add command matched"); debug!("add command matched");
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
@ -72,14 +71,14 @@ pub fn flag_matches(account: &Account, mbox: &str, matches: &ArgMatches) -> Resu
let flags = matches.value_of("flags").unwrap(); let flags = matches.value_of("flags").unwrap();
debug!("flags: {}", flags); debug!("flags: {}", flags);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
imap_conn.add_flags(mbox, uid, flags)?; imap_conn.add_flags(app.mbox, uid, flags)?;
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("remove") { if let Some(matches) = app.arg_matches.subcommand_matches("remove") {
debug!("remove command matched"); debug!("remove command matched");
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
@ -88,8 +87,8 @@ pub fn flag_matches(account: &Account, mbox: &str, matches: &ArgMatches) -> Resu
let flags = matches.value_of("flags").unwrap(); let flags = matches.value_of("flags").unwrap();
debug!("flags: {}", flags); debug!("flags: {}", flags);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
imap_conn.remove_flags(mbox, uid, flags)?; imap_conn.remove_flags(app.mbox, uid, flags)?;
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);

View file

@ -1,11 +1,8 @@
use clap::{self, App, ArgMatches, SubCommand}; use clap;
use error_chain::error_chain; use error_chain::error_chain;
use log::debug; use log::debug;
use crate::{ use crate::{app::App, imap::model::ImapConnector};
config::model::{Account, Config},
imap::model::ImapConnector,
};
error_chain! { error_chain! {
links { links {
@ -14,20 +11,17 @@ error_chain! {
} }
} }
pub fn imap_subcmds<'s>() -> Vec<App<'s, 's>> { pub fn imap_subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
vec![SubCommand::with_name("idle").about("Spawns a blocking idle daemon")] vec![clap::SubCommand::with_name("idle").about("Spawns a blocking idle daemon")]
} }
pub fn imap_matches( pub fn imap_matches(app: &App) -> Result<bool> {
config: &Config, if let Some(_) = app.arg_matches.subcommand_matches("idle") {
account: &Account,
mbox: &str,
matches: &ArgMatches,
) -> Result<bool> {
if let Some(_) = matches.subcommand_matches("idle") {
debug!("idle command matched"); debug!("idle command matched");
let mut imap_conn = ImapConnector::new(&account)?;
imap_conn.idle(&config, &mbox)?; let mut imap_conn = ImapConnector::new(&app.account)?;
imap_conn.idle(&app.config, &app.mbox)?;
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }

View file

@ -1,8 +1,10 @@
use clap; use clap;
use env_logger;
use error_chain::error_chain; use error_chain::error_chain;
use log::{debug, error, trace}; use log::{debug, error, trace};
use std::{env, path::PathBuf, process::exit}; use std::{env, path::PathBuf, process::exit};
mod app;
mod comp; mod comp;
mod config; mod config;
mod flag; mod flag;
@ -15,17 +17,14 @@ mod smtp;
mod table; mod table;
use crate::{ use crate::{
app::App,
comp::cli::{comp_matches, comp_subcmds}, comp::cli::{comp_matches, comp_subcmds},
config::{cli::config_args, model::Config}, config::{cli::config_args, model::Config},
flag::cli::{flag_matches, flag_subcmds}, flag::cli::{flag_matches, flag_subcmds},
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::{ output::{cli::output_args, model::Output},
cli::output_args,
fmt::OutputFmt,
log::{init as init_logger, LogLevel},
},
}; };
error_chain! { error_chain! {
@ -36,11 +35,10 @@ 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);
} }
} }
fn build_app<'a>() -> clap::App<'a, 'a> { fn parse_args<'a>() -> clap::App<'a, 'a> {
clap::App::new(env!("CARGO_PKG_NAME")) clap::App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION")) .version(env!("CARGO_PKG_VERSION"))
.about(env!("CARGO_PKG_DESCRIPTION")) .about(env!("CARGO_PKG_DESCRIPTION"))
@ -56,38 +54,39 @@ fn build_app<'a>() -> clap::App<'a, 'a> {
} }
fn run() -> Result<()> { fn run() -> Result<()> {
let app = build_app(); env_logger::init_from_env(
let matches = app.get_matches(); env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "off"),
);
let output_fmt: OutputFmt = matches.value_of("output").unwrap().into(); let args = parse_args();
let log_level: LogLevel = matches.value_of("log-level").unwrap().into(); let arg_matches = args.get_matches();
init_logger(output_fmt, log_level)?;
// Check completion matches before the config init // Check completion before init config
if comp_matches(build_app(), &matches)? { if comp_matches(parse_args, &arg_matches)? {
return Ok(()); return Ok(());
} }
let custom_config: Option<PathBuf> = matches.value_of("config").map(|s| s.into()); let output = Output::new(arg_matches.value_of("output").unwrap());
debug!("custom config path: {:?}", custom_config); debug!("output: {:?}", output);
debug!("init config"); debug!("init config");
let custom_config: Option<PathBuf> = arg_matches.value_of("config").map(|s| s.into());
debug!("custom config path: {:?}", custom_config);
let config = Config::new(custom_config)?; let config = Config::new(custom_config)?;
trace!("config: {:?}", config); trace!("config: {:?}", config);
let account_name = matches.value_of("account"); let account_name = arg_matches.value_of("account");
debug!("init 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!("account: {:?}", account); trace!("account: {:?}", account);
let mbox = matches.value_of("mailbox").unwrap(); let mbox = arg_matches.value_of("mailbox").unwrap();
debug!("mailbox: {}", mbox); debug!("mailbox: {}", mbox);
debug!("begin matching"); debug!("begin matching");
let _matched = mbox_matches(&account, &matches)? let app = App::new(&config, &account, &output, &mbox, &arg_matches);
|| flag_matches(&account, &mbox, &matches)? let _matched =
|| imap_matches(&config, &account, &mbox, &matches)? mbox_matches(&app)? || flag_matches(&app)? || imap_matches(&app)? || msg_matches(&app)?;
|| msg_matches(&config, &account, &mbox, &matches)?;
Ok(()) Ok(())
} }
@ -95,13 +94,20 @@ fn run() -> Result<()> {
fn main() { fn main() {
if let Err(ref errs) = run() { if let Err(ref errs) = run() {
let mut errs = errs.iter(); let mut errs = errs.iter();
match errs.next() { match errs.next() {
None => (), None => (),
Some(err) => { Some(err) => {
error!("{}", err); error!("{}", err);
errs.for_each(|err| error!(" ↳ {}", err)); eprintln!("{}", err);
errs.for_each(|err| {
error!("{}", err);
eprintln!("{}", err);
});
} }
} }
exit(1); exit(1);
} else { } else {
exit(0); exit(0);

View file

@ -1,20 +1,17 @@
use clap::{self, App, Arg, ArgMatches, SubCommand}; use clap;
use error_chain::error_chain; use error_chain::error_chain;
use log::{debug, info, trace}; use log::{debug, trace};
use crate::{config::model::Account, imap::model::ImapConnector}; use crate::{app::App, imap::model::ImapConnector};
error_chain! { error_chain! {
links { links {
Config(crate::config::model::Error, crate::config::model::ErrorKind);
Imap(crate::imap::model::Error, crate::imap::model::ErrorKind); Imap(crate::imap::model::Error, crate::imap::model::ErrorKind);
MsgCli(crate::msg::cli::Error, crate::msg::cli::ErrorKind);
OutputUtils(crate::output::utils::Error, crate::output::utils::ErrorKind);
} }
} }
pub fn mbox_source_arg<'a>() -> Arg<'a, 'a> { pub fn mbox_source_arg<'a>() -> clap::Arg<'a, 'a> {
Arg::with_name("mailbox") clap::Arg::with_name("mailbox")
.short("m") .short("m")
.long("mailbox") .long("mailbox")
.help("Selects a specific mailbox") .help("Selects a specific mailbox")
@ -22,26 +19,27 @@ pub fn mbox_source_arg<'a>() -> Arg<'a, 'a> {
.default_value("INBOX") .default_value("INBOX")
} }
pub fn mbox_target_arg<'a>() -> Arg<'a, 'a> { pub fn mbox_target_arg<'a>() -> clap::Arg<'a, 'a> {
Arg::with_name("target") clap::Arg::with_name("target")
.help("Specifies the targetted mailbox") .help("Specifies the targetted mailbox")
.value_name("TARGET") .value_name("TARGET")
} }
pub fn mbox_subcmds<'s>() -> Vec<App<'s, 's>> { pub fn mbox_subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
vec![SubCommand::with_name("mailboxes") vec![clap::SubCommand::with_name("mailboxes")
.aliases(&["mailbox", "mboxes", "mbox", "m"]) .aliases(&["mailbox", "mboxes", "mbox", "m"])
.about("Lists all mailboxes")] .about("Lists all mailboxes")]
} }
pub fn mbox_matches(account: &Account, matches: &ArgMatches) -> Result<bool> { pub fn mbox_matches(app: &App) -> Result<bool> {
if let Some(_) = matches.subcommand_matches("mailboxes") { if let Some(_) = app.arg_matches.subcommand_matches("mailboxes") {
debug!("mailboxes command matched"); debug!("mailboxes command matched");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
let mboxes = imap_conn.list_mboxes()?; let mboxes = imap_conn.list_mboxes()?;
info!("{}", mboxes); debug!("found {} mailboxes", mboxes.0.len());
trace!("mailboxes: {:?}", mboxes); trace!("mailboxes: {:?}", mboxes);
app.output.print(mboxes);
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);

View file

@ -2,10 +2,7 @@ use imap;
use serde::Serialize; use serde::Serialize;
use std::fmt; use std::fmt;
use crate::{ use crate::table::{Cell, Row, Table};
output::fmt::{get_output_fmt, OutputFmt, Response},
table::{Cell, Row, Table},
};
// Mbox // Mbox
@ -42,7 +39,7 @@ impl Table for Mbox {
fn row(&self) -> Row { fn row(&self) -> Row {
Row::new() Row::new()
.cell(Cell::new(&self.delim).red()) .cell(Cell::new(&self.delim).white())
.cell(Cell::new(&self.name).green()) .cell(Cell::new(&self.name).green())
.cell(Cell::new(&self.attributes.join(", ")).shrinkable().yellow()) .cell(Cell::new(&self.attributes.join(", ")).shrinkable().yellow())
} }
@ -55,16 +52,6 @@ pub struct Mboxes(pub Vec<Mbox>);
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 {
unsafe { writeln!(f, "\n{}", Table::render(&self.0))
match get_output_fmt() {
&OutputFmt::Plain => {
writeln!(f, "\n{}", Table::render(&self.0))
}
&OutputFmt::Json => {
let res = serde_json::to_string(&Response::new(self)).unwrap();
write!(f, "{}", res)
}
}
}
} }
} }

View file

@ -1,10 +1,10 @@
use clap::{self, App, Arg, ArgMatches, SubCommand}; use clap;
use error_chain::error_chain; use error_chain::error_chain;
use log::{debug, error, info, trace}; use log::{debug, error, trace};
use std::{fs, ops::Deref}; use std::{fs, ops::Deref};
use crate::{ use crate::{
config::model::{Account, Config}, app::App,
flag::model::Flag, flag::model::Flag,
imap::model::ImapConnector, imap::model::ImapConnector,
input, input,
@ -15,7 +15,6 @@ use crate::{
error_chain! { error_chain! {
links { links {
Config(crate::config::model::Error, crate::config::model::ErrorKind);
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);
@ -26,30 +25,30 @@ error_chain! {
} }
} }
pub fn uid_arg<'a>() -> Arg<'a, 'a> { pub fn uid_arg<'a>() -> clap::Arg<'a, 'a> {
Arg::with_name("uid") clap::Arg::with_name("uid")
.help("Specifies the targetted message") .help("Specifies the targetted message")
.value_name("UID") .value_name("UID")
.required(true) .required(true)
} }
fn reply_all_arg<'a>() -> Arg<'a, 'a> { fn reply_all_arg<'a>() -> clap::Arg<'a, 'a> {
Arg::with_name("reply-all") clap::Arg::with_name("reply-all")
.help("Includes all recipients") .help("Includes all recipients")
.short("a") .short("a")
.long("all") .long("all")
} }
fn page_size_arg<'a>() -> Arg<'a, 'a> { fn page_size_arg<'a>() -> clap::Arg<'a, 'a> {
Arg::with_name("page-size") clap::Arg::with_name("page-size")
.help("Page size") .help("Page size")
.short("s") .short("s")
.long("size") .long("size")
.value_name("INT") .value_name("INT")
} }
fn page_arg<'a>() -> Arg<'a, 'a> { fn page_arg<'a>() -> clap::Arg<'a, 'a> {
Arg::with_name("page") clap::Arg::with_name("page")
.help("Page number") .help("Page number")
.short("p") .short("p")
.long("page") .long("page")
@ -57,8 +56,8 @@ fn page_arg<'a>() -> Arg<'a, 'a> {
.default_value("0") .default_value("0")
} }
fn attachment_arg<'a>() -> Arg<'a, 'a> { fn attachment_arg<'a>() -> clap::Arg<'a, 'a> {
Arg::with_name("attachments") clap::Arg::with_name("attachments")
.help("Adds attachment to the message") .help("Adds attachment to the message")
.short("a") .short("a")
.long("attachment") .long("attachment")
@ -67,41 +66,41 @@ fn attachment_arg<'a>() -> Arg<'a, 'a> {
.takes_value(true) .takes_value(true)
} }
pub fn msg_subcmds<'s>() -> Vec<App<'s, 's>> { pub fn msg_subcmds<'a>() -> Vec<clap::App<'a, 'a>> {
vec![ vec![
SubCommand::with_name("list") clap::SubCommand::with_name("list")
.aliases(&["lst", "l"]) .aliases(&["lst", "l"])
.about("Lists all messages") .about("Lists all messages")
.arg(page_size_arg()) .arg(page_size_arg())
.arg(page_arg()), .arg(page_arg()),
SubCommand::with_name("search") clap::SubCommand::with_name("search")
.aliases(&["query", "q", "s"]) .aliases(&["query", "q", "s"])
.about("Lists messages matching the given IMAP query") .about("Lists messages matching the given IMAP query")
.arg(page_size_arg()) .arg(page_size_arg())
.arg(page_arg()) .arg(page_arg())
.arg( .arg(
Arg::with_name("query") clap::Arg::with_name("query")
.help("IMAP query (see https://tools.ietf.org/html/rfc3501#section-6.4.4)") .help("IMAP query (see https://tools.ietf.org/html/rfc3501#section-6.4.4)")
.value_name("QUERY") .value_name("QUERY")
.multiple(true) .multiple(true)
.required(true), .required(true),
), ),
SubCommand::with_name("write") clap::SubCommand::with_name("write")
.aliases(&["w"]) .aliases(&["w"])
.about("Writes a new message") .about("Writes a new message")
.arg(attachment_arg()), .arg(attachment_arg()),
SubCommand::with_name("send") clap::SubCommand::with_name("send")
.about("Sends a raw message") .about("Sends a raw message")
.arg(Arg::with_name("message").raw(true)), .arg(clap::Arg::with_name("message").raw(true)),
SubCommand::with_name("save") clap::SubCommand::with_name("save")
.about("Saves a raw message") .about("Saves a raw message")
.arg(Arg::with_name("message").raw(true)), .arg(clap::Arg::with_name("message").raw(true)),
SubCommand::with_name("read") clap::SubCommand::with_name("read")
.aliases(&["r"]) .aliases(&["r"])
.about("Reads text bodies of a message") .about("Reads text bodies of a message")
.arg(uid_arg()) .arg(uid_arg())
.arg( .arg(
Arg::with_name("mime-type") clap::Arg::with_name("mime-type")
.help("MIME type to use") .help("MIME type to use")
.short("t") .short("t")
.long("mime-type") .long("mime-type")
@ -110,55 +109,55 @@ pub fn msg_subcmds<'s>() -> Vec<App<'s, 's>> {
.default_value("plain"), .default_value("plain"),
) )
.arg( .arg(
Arg::with_name("raw") clap::Arg::with_name("raw")
.help("Reads raw message") .help("Reads raw message")
.long("raw") .long("raw")
.short("r"), .short("r"),
), ),
SubCommand::with_name("attachments") clap::SubCommand::with_name("attachments")
.aliases(&["attach", "att", "a"]) .aliases(&["attach", "att", "a"])
.about("Downloads all message attachments") .about("Downloads all message attachments")
.arg(uid_arg()), .arg(uid_arg()),
SubCommand::with_name("reply") clap::SubCommand::with_name("reply")
.aliases(&["rep", "re"]) .aliases(&["rep", "re"])
.about("Answers to a message") .about("Answers to a message")
.arg(uid_arg()) .arg(uid_arg())
.arg(reply_all_arg()), .arg(reply_all_arg()),
SubCommand::with_name("forward") clap::SubCommand::with_name("forward")
.aliases(&["fwd", "f"]) .aliases(&["fwd", "f"])
.about("Forwards a message") .about("Forwards a message")
.arg(uid_arg()), .arg(uid_arg()),
SubCommand::with_name("copy") clap::SubCommand::with_name("copy")
.aliases(&["cp", "c"]) .aliases(&["cp", "c"])
.about("Copies a message to the targetted mailbox") .about("Copies a message to the targetted mailbox")
.arg(uid_arg()) .arg(uid_arg())
.arg(mbox_target_arg()), .arg(mbox_target_arg()),
SubCommand::with_name("move") clap::SubCommand::with_name("move")
.aliases(&["mv", "m"]) .aliases(&["mv", "m"])
.about("Moves a message to the targetted mailbox") .about("Moves a message to the targetted mailbox")
.arg(uid_arg()) .arg(uid_arg())
.arg(mbox_target_arg()), .arg(mbox_target_arg()),
SubCommand::with_name("delete") clap::SubCommand::with_name("delete")
.aliases(&["remove", "rm", "del", "d"]) .aliases(&["remove", "rm", "del", "d"])
.about("Deletes a message") .about("Deletes a message")
.arg(uid_arg()), .arg(uid_arg()),
SubCommand::with_name("template") clap::SubCommand::with_name("template")
.aliases(&["tpl", "t"]) .aliases(&["tpl", "t"])
.about("Generates a message template") .about("Generates a message template")
.subcommand( .subcommand(
SubCommand::with_name("new") clap::SubCommand::with_name("new")
.aliases(&["n"]) .aliases(&["n"])
.about("Generates a new message template"), .about("Generates a new message template"),
) )
.subcommand( .subcommand(
SubCommand::with_name("reply") clap::SubCommand::with_name("reply")
.aliases(&["rep", "r"]) .aliases(&["rep", "r"])
.about("Generates a reply message template") .about("Generates a reply message template")
.arg(uid_arg()) .arg(uid_arg())
.arg(reply_all_arg()), .arg(reply_all_arg()),
) )
.subcommand( .subcommand(
SubCommand::with_name("forward") clap::SubCommand::with_name("forward")
.aliases(&["fwd", "fw", "f"]) .aliases(&["fwd", "fw", "f"])
.about("Generates a forward message template") .about("Generates a forward message template")
.arg(uid_arg()), .arg(uid_arg()),
@ -166,19 +165,14 @@ pub fn msg_subcmds<'s>() -> Vec<App<'s, 's>> {
] ]
} }
pub fn msg_matches( pub fn msg_matches(app: &App) -> Result<bool> {
config: &Config, if let Some(matches) = app.arg_matches.subcommand_matches("list") {
account: &Account,
mbox: &str,
matches: &ArgMatches,
) -> Result<bool> {
if let Some(matches) = matches.subcommand_matches("list") {
debug!("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(app.config.default_page_size(&app.account));
debug!("page size: {}", &page_size); debug!("page size: {}", &page_size);
let page: usize = matches let page: usize = matches
.value_of("page") .value_of("page")
@ -187,23 +181,23 @@ pub fn msg_matches(
.unwrap_or_default(); .unwrap_or_default();
debug!("page: {}", &page); debug!("page: {}", &page);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?; let msgs = imap_conn.list_msgs(&app.mbox, &page_size, &page)?;
let msgs = Msgs::from(&msgs); let msgs = Msgs::from(&msgs);
info!("{}", msgs);
trace!("messages: {:?}", msgs); trace!("messages: {:?}", msgs);
app.output.print(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) = app.arg_matches.subcommand_matches("search") {
debug!("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(app.config.default_page_size(&app.account));
debug!("page size: {}", &page_size); debug!("page size: {}", &page_size);
let page: usize = matches let page: usize = matches
.value_of("page") .value_of("page")
@ -238,17 +232,17 @@ pub fn msg_matches(
.join(" "); .join(" ");
debug!("query: {}", &page); debug!("query: {}", &page);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?; let msgs = imap_conn.search_msgs(&app.mbox, &query, &page_size, &page)?;
let msgs = Msgs::from(&msgs); let msgs = Msgs::from(&msgs);
info!("{}", msgs);
trace!("messages: {:?}", msgs); trace!("messages: {:?}", msgs);
app.output.print(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) = app.arg_matches.subcommand_matches("read") {
debug!("read command matched"); debug!("read command matched");
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
@ -258,30 +252,30 @@ pub fn msg_matches(
let raw = matches.is_present("raw"); let raw = matches.is_present("raw");
debug!("raw: {}", raw); debug!("raw: {}", raw);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
let msg = imap_conn.read_msg(&mbox, &uid)?; let msg = imap_conn.read_msg(&app.mbox, &uid)?;
if raw { if raw {
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); app.output.print(msg);
} else { } else {
let msg = ReadableMsg::from_bytes(&mime, &msg)?; let msg = ReadableMsg::from_bytes(&mime, &msg)?;
info!("{}", msg); app.output.print(msg);
} }
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("attachments") { if let Some(matches) = app.arg_matches.subcommand_matches("attachments") {
debug!("attachments command matched"); debug!("attachments command matched");
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
debug!("uid: {}", &uid); debug!("uid: {}", &uid);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
let msg = imap_conn.read_msg(&mbox, &uid)?; let msg = imap_conn.read_msg(&app.mbox, &uid)?;
let attachments = Attachments::from_bytes(&msg)?; let attachments = Attachments::from_bytes(&msg)?;
debug!( debug!(
"{} attachment(s) found for message {}", "{} attachment(s) found for message {}",
@ -289,30 +283,37 @@ pub fn msg_matches(
&uid &uid
); );
for attachment in attachments.0.iter() { for attachment in attachments.0.iter() {
let filepath = config.downloads_filepath(&account, &attachment.filename); let filepath = app
.config
.downloads_filepath(&app.account, &attachment.filename);
debug!("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!(
debug!(
"{} attachment(s) successfully downloaded", "{} attachment(s) successfully downloaded",
&attachments.0.len() &attachments.0.len()
); );
app.output.print(format!(
"{} attachment(s) successfully downloaded",
&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) = app.arg_matches.subcommand_matches("write") {
debug!("write command matched"); debug!("write command matched");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
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<_>>();
let tpl = Msg::build_new_tpl(&config, &account)?; let tpl = Msg::build_new_tpl(&app.config, &app.account)?;
let content = input::open_editor_with_tpl(tpl.to_string().as_bytes())?; let content = input::open_editor_with_tpl(tpl.to_string().as_bytes())?;
let mut msg = Msg::from(content); let mut msg = Msg::from(content);
msg.attachments = attachments; msg.attachments = attachments;
@ -323,10 +324,10 @@ pub fn msg_matches(
input::PostEditChoice::Send => { input::PostEditChoice::Send => {
debug!("sending message…"); debug!("sending message…");
let msg = msg.to_sendable_msg()?; let msg = msg.to_sendable_msg()?;
smtp::send(&account, &msg)?; smtp::send(&app.account, &msg)?;
imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?;
input::remove_draft()?; input::remove_draft()?;
info!("Message successfully sent"); app.output.print("Message successfully sent");
break; break;
} }
input::PostEditChoice::Edit => { input::PostEditChoice::Edit => {
@ -338,7 +339,7 @@ pub fn msg_matches(
debug!("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"); app.output.print("Message successfully saved to Drafts");
break; break;
} }
input::PostEditChoice::Discard => { input::PostEditChoice::Discard => {
@ -353,7 +354,7 @@ pub fn msg_matches(
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("reply") { if let Some(matches) = app.arg_matches.subcommand_matches("reply") {
debug!("reply command matched"); debug!("reply command matched");
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
@ -366,12 +367,12 @@ pub fn msg_matches(
debug!("found {} attachments", attachments.len()); debug!("found {} attachments", attachments.len());
trace!("attachments: {:?}", attachments); trace!("attachments: {:?}", attachments);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&app.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(&app.config, &app.account)?
} else { } else {
msg.build_reply_tpl(&config, &account)? msg.build_reply_tpl(&app.config, &app.account)?
}; };
let content = input::open_editor_with_tpl(&tpl.to_string().as_bytes())?; let content = input::open_editor_with_tpl(&tpl.to_string().as_bytes())?;
@ -384,11 +385,11 @@ pub fn msg_matches(
input::PostEditChoice::Send => { input::PostEditChoice::Send => {
debug!("sending message…"); debug!("sending message…");
let msg = msg.to_sendable_msg()?; let msg = msg.to_sendable_msg()?;
smtp::send(&account, &msg)?; smtp::send(&app.account, &msg)?;
imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?;
imap_conn.add_flags(&mbox, uid, "\\Answered")?; imap_conn.add_flags(&app.mbox, uid, "\\Answered")?;
input::remove_draft()?; input::remove_draft()?;
info!("Message successfully sent"); app.output.print("Message successfully sent");
break; break;
} }
input::PostEditChoice::Edit => { input::PostEditChoice::Edit => {
@ -400,7 +401,7 @@ pub fn msg_matches(
debug!("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"); app.output.print("Message successfully saved to Drafts");
break; break;
} }
input::PostEditChoice::Discard => { input::PostEditChoice::Discard => {
@ -416,7 +417,7 @@ pub fn msg_matches(
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("forward") { if let Some(matches) = app.arg_matches.subcommand_matches("forward") {
debug!("forward command matched"); debug!("forward command matched");
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
@ -429,9 +430,9 @@ pub fn msg_matches(
debug!("found {} attachments", attachments.len()); debug!("found {} attachments", attachments.len());
trace!("attachments: {:?}", attachments); trace!("attachments: {:?}", attachments);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&app.mbox, &uid)?);
let tpl = msg.build_forward_tpl(&config, &account)?; let tpl = msg.build_forward_tpl(&app.config, &app.account)?;
let content = input::open_editor_with_tpl(&tpl.to_string().as_bytes())?; let content = input::open_editor_with_tpl(&tpl.to_string().as_bytes())?;
let mut msg = Msg::from(content); let mut msg = Msg::from(content);
msg.attachments = attachments; msg.attachments = attachments;
@ -442,10 +443,10 @@ pub fn msg_matches(
input::PostEditChoice::Send => { input::PostEditChoice::Send => {
debug!("sending message…"); debug!("sending message…");
let msg = msg.to_sendable_msg()?; let msg = msg.to_sendable_msg()?;
smtp::send(&account, &msg)?; smtp::send(&app.account, &msg)?;
imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?;
input::remove_draft()?; input::remove_draft()?;
info!("Message successfully sent"); app.output.print("Message successfully sent");
break; break;
} }
input::PostEditChoice::Edit => { input::PostEditChoice::Edit => {
@ -457,7 +458,7 @@ pub fn msg_matches(
debug!("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"); app.output.print("Message successfully saved to Drafts");
break; break;
} }
input::PostEditChoice::Discard => { input::PostEditChoice::Discard => {
@ -473,14 +474,14 @@ pub fn msg_matches(
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("template") { if let Some(matches) = app.arg_matches.subcommand_matches("template") {
debug!("template command matched"); debug!("template command matched");
if let Some(_) = matches.subcommand_matches("new") { if let Some(_) = matches.subcommand_matches("new") {
debug!("new command matched"); debug!("new command matched");
let tpl = Msg::build_new_tpl(&config, &account)?; let tpl = Msg::build_new_tpl(&app.config, &app.account)?;
info!("{}", tpl);
trace!("tpl: {:?}", tpl); trace!("tpl: {:?}", tpl);
app.output.print(tpl);
} }
if let Some(matches) = matches.subcommand_matches("reply") { if let Some(matches) = matches.subcommand_matches("reply") {
@ -489,15 +490,15 @@ pub fn msg_matches(
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
debug!("uid: {}", uid); debug!("uid: {}", uid);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&app.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(&app.config, &app.account)?
} else { } else {
msg.build_reply_tpl(&config, &account)? msg.build_reply_tpl(&app.config, &app.account)?
}; };
info!("{}", tpl);
trace!("tpl: {:?}", tpl); trace!("tpl: {:?}", tpl);
app.output.print(tpl);
imap_conn.logout(); imap_conn.logout();
} }
@ -508,11 +509,11 @@ pub fn msg_matches(
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
debug!("uid: {}", uid); debug!("uid: {}", uid);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&app.mbox, &uid)?);
let tpl = msg.build_forward_tpl(&config, &account)?; let tpl = msg.build_forward_tpl(&app.config, &app.account)?;
info!("{}", tpl);
trace!("tpl: {:?}", tpl); trace!("tpl: {:?}", tpl);
app.output.print(tpl);
imap_conn.logout(); imap_conn.logout();
} }
@ -520,7 +521,7 @@ pub fn msg_matches(
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("copy") { if let Some(matches) = app.arg_matches.subcommand_matches("copy") {
debug!("copy command matched"); debug!("copy command matched");
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
@ -528,18 +529,22 @@ pub fn msg_matches(
let target = matches.value_of("target").unwrap(); let target = matches.value_of("target").unwrap();
debug!("target: {}", &target); debug!("target: {}", &target);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&app.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!("Message {} successfully copied to folder `{}`", uid, target); debug!("message {} successfully copied to folder `{}`", uid, target);
app.output.print(format!(
"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) = app.arg_matches.subcommand_matches("move") {
debug!("move command matched"); debug!("move command matched");
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
@ -547,55 +552,61 @@ pub fn msg_matches(
let target = matches.value_of("target").unwrap(); let target = matches.value_of("target").unwrap();
debug!("target: {}", &target); debug!("target: {}", &target);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); let msg = Msg::from(imap_conn.read_msg(&app.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(&app.mbox, uid, "\\Seen \\Deleted")?;
info!("Message {} successfully moved to folder `{}`", uid, target); debug!("message {} successfully moved to folder `{}`", uid, target);
app.output.print(format!(
"Message {} successfully moved to folder `{}`",
uid, target
));
imap_conn.expunge(&mbox)?; imap_conn.expunge(&app.mbox)?;
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("delete") { if let Some(matches) = app.arg_matches.subcommand_matches("delete") {
debug!("delete command matched"); debug!("delete command matched");
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
debug!("uid: {}", &uid); debug!("uid: {}", &uid);
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
imap_conn.add_flags(&mbox, uid, "\\Seen \\Deleted")?; imap_conn.add_flags(&app.mbox, uid, "\\Seen \\Deleted")?;
info!("Message {} successfully deleted", uid); debug!("message {} successfully deleted", uid);
app.output
.print(format!("Message {} successfully deleted", uid));
imap_conn.expunge(&mbox)?; imap_conn.expunge(&app.mbox)?;
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("send") { if let Some(matches) = app.arg_matches.subcommand_matches("send") {
debug!("send command matched"); debug!("send command matched");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.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());
let msg = msg.to_sendable_msg()?; let msg = msg.to_sendable_msg()?;
smtp::send(&account, &msg)?; smtp::send(&app.account, &msg)?;
imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?;
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }
if let Some(matches) = matches.subcommand_matches("save") { if let Some(matches) = app.arg_matches.subcommand_matches("save") {
debug!("save command matched"); debug!("save command matched");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.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());
imap_conn.append_msg(&mbox, &msg.to_vec()?, &[Flag::Seen])?; imap_conn.append_msg(&app.mbox, &msg.to_vec()?, &[Flag::Seen])?;
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
@ -604,10 +615,11 @@ pub fn msg_matches(
{ {
debug!("default list command matched"); debug!("default list command matched");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&app.account)?;
let msgs = imap_conn.list_msgs(&mbox, &config.default_page_size(&account), &0)?; let msgs =
imap_conn.list_msgs(&app.mbox, &app.config.default_page_size(&app.account), &0)?;
let msgs = Msgs::from(&msgs); let msgs = Msgs::from(&msgs);
info!("{}", msgs); app.output.print(msgs);
imap_conn.logout(); imap_conn.logout();
Ok(true) Ok(true)

View file

@ -15,7 +15,6 @@ use uuid::Uuid;
use crate::{ use crate::{
config::model::{Account, Config}, config::model::{Account, Config},
flag::model::{Flag, Flags}, flag::model::{Flag, Flags},
output::fmt::{get_output_fmt, OutputFmt, Response},
table::{Cell, Row, Table}, table::{Cell, Row, Table},
}; };
@ -33,17 +32,7 @@ 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 {
unsafe { write!(f, "{}", self.0)
match get_output_fmt() {
&OutputFmt::Plain => {
write!(f, "{}", self.0)
}
&OutputFmt::Json => {
let res = serde_json::to_string(&Response::new(self)).unwrap();
write!(f, "{}", res)
}
}
}
} }
} }
@ -132,17 +121,7 @@ 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 {
unsafe { writeln!(f, "{}", self.content)
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)
}
}
}
} }
} }
@ -677,16 +656,6 @@ 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 {
unsafe { writeln!(f, "\n{}", Table::render(&self.0))
match get_output_fmt() {
&OutputFmt::Plain => {
writeln!(f, "\n{}", Table::render(&self.0))
}
&OutputFmt::Json => {
let res = serde_json::to_string(&Response::new(self)).unwrap();
write!(f, "{}", res)
}
}
}
} }
} }

View file

@ -1,15 +1,7 @@
use serde::Serialize; use serde::Serialize;
use std::fmt; use std::fmt;
pub static mut OUTPUT_FMT: &'static OutputFmt = &OutputFmt::Plain; // Output format
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 { pub enum OutputFmt {
Plain, Plain,
@ -38,6 +30,8 @@ impl fmt::Display for OutputFmt {
} }
} }
// Response helper
#[derive(Serialize)] #[derive(Serialize)]
pub struct Response<T: Serialize + fmt::Display> { pub struct Response<T: Serialize + fmt::Display> {
response: T, response: T,
@ -49,17 +43,4 @@ impl<T: Serialize + fmt::Display> Response<T> {
} }
} }
impl<T: Serialize + fmt::Display> fmt::Display for Response<T> { // Print helper
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,83 +0,0 @@
use chrono::Local;
use env_logger;
use error_chain::error_chain;
use log::{self, debug, Level, LevelFilter};
use std::{fmt, io, io::Write, ops::Deref};
use super::fmt::{set_output_fmt, OutputFmt};
error_chain! {}
// Log level wrapper
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.deref())
}
}
impl Deref for LogLevel {
type Target = LevelFilter;
fn deref(&self) -> &Self::Target {
&self.0
}
}
// Init
pub fn init(fmt: OutputFmt, filter: LogLevel) -> Result<()> {
let level_filter = filter.deref();
let level = level_filter.to_level();
match fmt {
OutputFmt::Plain => {
set_output_fmt(&OutputFmt::Plain);
}
OutputFmt::Json => {
set_output_fmt(&OutputFmt::Json);
}
};
env_logger::Builder::new()
.target(env_logger::Target::Stdout)
.format(move |buf, record| match level {
None => Ok(()),
Some(Level::Info) => match record.metadata().level() {
Level::Info => write!(buf, "{}", record.args()),
Level::Error => writeln!(&mut io::stderr(), "{}", record.args()),
_ => writeln!(buf, "{}", record.args()),
},
_ => {
writeln!(
buf,
"[{} {:5} {}] {}",
Local::now().format("%Y-%m-%dT%H:%M:%S"),
record.metadata().level(),
record.module_path().unwrap_or_default(),
record.args(),
)
}
})
.filter_level(*level_filter)
.init();
debug!("output format: {}", fmt);
debug!("log level: {}", filter);
Ok(())
}

View file

@ -1,4 +1,3 @@
pub(crate) mod cli; pub(crate) mod cli;
pub(crate) mod fmt; pub(crate) mod model;
pub(crate) mod log;
pub(crate) mod utils; pub(crate) mod utils;

67
src/output/model.rs Normal file
View file

@ -0,0 +1,67 @@
use serde::Serialize;
use std::fmt;
// Output format
#[derive(Debug)]
pub enum OutputFmt {
Plain,
Json,
}
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 {
let fmt = match *self {
OutputFmt::Json => "JSON",
OutputFmt::Plain => "PLAIN",
};
write!(f, "{}", fmt)
}
}
// JSON output helper
#[derive(Debug, Serialize)]
pub struct OutputJson<T: Serialize> {
response: T,
}
impl<T: Serialize> OutputJson<T> {
pub fn new(response: T) -> Self {
Self { response }
}
}
// Output
#[derive(Debug)]
pub struct Output {
fmt: OutputFmt,
}
impl Output {
pub fn new(fmt: &str) -> Self {
Self { fmt: fmt.into() }
}
pub fn print<T: Serialize + fmt::Display>(&self, item: T) {
match self.fmt {
OutputFmt::Plain => {
println!("{}", item)
}
OutputFmt::Json => {
print!("{}", serde_json::to_string(&OutputJson::new(item)).unwrap())
}
}
}
}