release v0.2.5

This commit is contained in:
Clément DOUIN 2021-04-12 00:21:08 +02:00
parent 557c5b79bc
commit e8ae4b025f
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
12 changed files with 100 additions and 72 deletions

View file

@ -7,9 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.2.5] - 2021-04-12
### Fixed ### Fixed
- Expunge mbox after `move` and `delete` cmd [#83] - Expunge mbox after `move` and `delete` cmd [#83]
- JSON output [#89]
## [0.2.4] - 2021-04-09 ## [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] - Password from command [#22]
- Set up README [#20] - 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.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.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 [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 [#75]: https://github.com/soywod/himalaya/issues/75
[#79]: https://github.com/soywod/himalaya/issues/79 [#79]: https://github.com/soywod/himalaya/issues/79
[#83]: https://github.com/soywod/himalaya/issues/83 [#83]: https://github.com/soywod/himalaya/issues/83
[#89]: https://github.com/soywod/himalaya/issues/89

2
Cargo.lock generated
View file

@ -306,7 +306,7 @@ dependencies = [
[[package]] [[package]]
name = "himalaya" name = "himalaya"
version = "0.2.4" version = "0.2.5"
dependencies = [ dependencies = [
"clap", "clap",
"env_logger", "env_logger",

View file

@ -1,7 +1,7 @@
[package] [package]
name = "himalaya" name = "himalaya"
description = "📫 Minimalist CLI email client" description = "📫 Minimalist CLI email client"
version = "0.2.4" version = "0.2.5"
authors = ["soywod <clement.douin@posteo.net>"] authors = ["soywod <clement.douin@posteo.net>"]
edition = "2018" edition = "2018"

View file

@ -82,7 +82,7 @@ for all the options.*
## Usage ## Usage
``` ```
himalaya 0.2.4 himalaya 0.2.5
soywod <clement.douin@posteo.net> soywod <clement.douin@posteo.net>
📫 Minimalist CLI email client 📫 Minimalist CLI email client

View file

@ -1,7 +1,8 @@
use clap::{self, App, Arg, ArgMatches, SubCommand}; use clap::{self, App, Arg, ArgMatches, SubCommand};
use error_chain::error_chain; use error_chain::error_chain;
use log::debug;
use crate::{config::model::Config, imap::model::ImapConnector, output::utils::print}; use crate::{config::model::Config, imap::model::ImapConnector, info};
error_chain! { error_chain! {
links { links {
@ -36,13 +37,14 @@ pub fn mbox_subcmds<'a>() -> Vec<App<'a, 'a>> {
pub fn mbox_matches(matches: &ArgMatches) -> Result<bool> { pub fn mbox_matches(matches: &ArgMatches) -> Result<bool> {
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");
if let Some(_) = matches.subcommand_matches("mailboxes") { if let Some(_) = matches.subcommand_matches("mailboxes") {
debug!("Subcommand matched: mailboxes");
let mut imap_conn = ImapConnector::new(&account)?; let mut imap_conn = ImapConnector::new(&account)?;
let mboxes = imap_conn.list_mboxes()?; let mboxes = imap_conn.list_mboxes()?;
print(&output_fmt, &silent, mboxes)?; info!(&mboxes);
imap_conn.logout(); imap_conn.logout();
return Ok(true); return Ok(true);
} }

View file

@ -1,13 +1,13 @@
use clap::{self, App, Arg, ArgMatches, SubCommand}; use clap::{self, App, Arg, ArgMatches, SubCommand};
use error_chain::error_chain; use error_chain::error_chain;
use log::{debug, error, info}; use log::{debug, error};
use std::{fs, ops::Deref}; use std::{fs, ops::Deref};
use crate::{ use crate::{
config::model::Config, config::model::Config,
flag::model::Flag, flag::model::Flag,
imap::model::ImapConnector, imap::model::ImapConnector,
input, info, input,
mbox::cli::mbox_target_arg, mbox::cli::mbox_target_arg,
msg::model::{Attachments, Msg, Msgs, ReadableMsg}, msg::model::{Attachments, Msg, Msgs, ReadableMsg},
smtp, smtp,
@ -183,7 +183,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?; let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?;
let msgs = Msgs::from(&msgs); let msgs = Msgs::from(&msgs);
info!("{}", msgs); info!(&msgs);
imap_conn.logout(); imap_conn.logout();
return Ok(()); 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 = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?;
let msgs = Msgs::from(&msgs); let msgs = Msgs::from(&msgs);
info!("{}", msgs); info!(&msgs);
imap_conn.logout(); imap_conn.logout();
return Ok(()); return Ok(());
@ -247,10 +247,10 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
let msg = String::from_utf8(msg) let msg = String::from_utf8(msg)
.chain_err(|| "Could not decode raw message as utf8 string")?; .chain_err(|| "Could not decode raw message as utf8 string")?;
let msg = msg.trim_end_matches("\n"); let msg = msg.trim_end_matches("\n");
info!("{}", msg); info!(&msg);
} else { } else {
let msg = ReadableMsg::from_bytes(&mime, &msg)?; let msg = ReadableMsg::from_bytes(&mime, &msg)?;
info!("{}", msg); info!(&msg);
} }
imap_conn.logout(); imap_conn.logout();
@ -268,15 +268,18 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
let attachments = Attachments::from_bytes(&msg)?; let attachments = Attachments::from_bytes(&msg)?;
debug!( debug!(
"{} attachment(s) found for message {}", "{} attachment(s) found for message {}",
attachments.0.len(), &attachments.0.len(),
uid &uid
); );
attachments.0.iter().for_each(|attachment| { attachments.0.iter().for_each(|attachment| {
let filepath = config.downloads_filepath(&account, &attachment.filename); let filepath = config.downloads_filepath(&account, &attachment.filename);
debug!("Downloading {}…", &attachment.filename); debug!("Downloading {}…", &attachment.filename);
fs::write(filepath, &attachment.raw).unwrap() fs::write(filepath, &attachment.raw).unwrap()
}); });
info!("{} attachment(s) successfully downloaded", &uid); info!(&format!(
"{} attachment(s) successfully downloaded",
&attachments.0.len()
));
imap_conn.logout(); imap_conn.logout();
return Ok(()); return Ok(());
@ -333,7 +336,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
debug!("Subcommand matched: new"); debug!("Subcommand matched: new");
let tpl = Msg::build_new_tpl(&config, &account)?; let tpl = Msg::build_new_tpl(&config, &account)?;
info!("{}", tpl); info!(&tpl);
} }
if let Some(matches) = matches.subcommand_matches("reply") { if let Some(matches) = matches.subcommand_matches("reply") {
@ -349,7 +352,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
} else { } else {
msg.build_reply_tpl(&config, &account)? msg.build_reply_tpl(&config, &account)?
}; };
info!("{}", tpl); info!(&tpl);
imap_conn.logout(); 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 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)?;
info!("{}", tpl); info!(&tpl);
imap_conn.logout(); imap_conn.logout();
} }
@ -486,10 +489,10 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
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!( info!(&format!(
"Message {} successfully copied to folder `{}`", "Message {} successfully copied to folder `{}`",
&uid, &target &uid, &target
); ));
imap_conn.logout(); imap_conn.logout();
return Ok(()); return Ok(());
@ -509,10 +512,10 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
flags.push(Flag::Seen); flags.push(Flag::Seen);
imap_conn.append_msg(target, &msg.raw, msg.flags.deref())?; imap_conn.append_msg(target, &msg.raw, msg.flags.deref())?;
imap_conn.add_flags(mbox, uid, "\\Seen \\Deleted")?; imap_conn.add_flags(mbox, uid, "\\Seen \\Deleted")?;
info!( info!(&format!(
"Message {} successfully moved to folder `{}`", "Message {} successfully moved to folder `{}`",
&uid, &target &uid, &target
); ));
imap_conn.expunge(mbox)?; imap_conn.expunge(mbox)?;
imap_conn.logout(); imap_conn.logout();
@ -527,7 +530,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
debug!("UID: {}", &uid); debug!("UID: {}", &uid);
imap_conn.add_flags(mbox, uid, "\\Seen \\Deleted")?; 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.expunge(mbox)?;
imap_conn.logout(); imap_conn.logout();
@ -563,7 +566,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
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);
info!("{}", &msgs); info!(&msgs);
imap_conn.logout(); imap_conn.logout();
Ok(()) Ok(())

View file

@ -1,8 +1,18 @@
use std::fmt; 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 { pub enum OutputFmt {
Json,
Plain, Plain,
Json,
} }
impl From<&str> for OutputFmt { impl From<&str> for OutputFmt {

View file

@ -1,11 +1,31 @@
use error_chain::error_chain; use error_chain::error_chain;
use log::{Level, LevelFilter, Metadata, Record}; use log::{self, Level, LevelFilter, Metadata, Record};
use std::fmt; use std::fmt;
use super::fmt::OutputFmt; use super::fmt::{set_output_fmt, OutputFmt};
error_chain! {} error_chain! {}
// Macros
#[macro_export]
macro_rules! info {
($t:expr) => {
use crate::output::fmt::{get_output_fmt, OutputFmt};
use log::info as log_info;
unsafe {
match get_output_fmt() {
OutputFmt::Plain => log_info!("{}", $t.to_string()),
OutputFmt::Json => {
// Should be safe enough to `.unwrap()` since it's
// formatted by Himalaya itself
log_info!("{{\"response\":{}}}", serde_json::to_string($t).unwrap())
}
};
}
};
}
// Log level struct // Log level struct
pub struct LogLevel(pub LevelFilter); pub struct LogLevel(pub LevelFilter);
@ -30,7 +50,7 @@ impl fmt::Display for LogLevel {
// Plain logger // Plain logger
struct PlainLogger; pub struct PlainLogger;
impl log::Log for PlainLogger { impl log::Log for PlainLogger {
fn enabled(&self, metadata: &Metadata) -> bool { fn enabled(&self, metadata: &Metadata) -> bool {
@ -52,7 +72,7 @@ impl log::Log for PlainLogger {
// JSON logger // JSON logger
struct JsonLogger; pub struct JsonLogger;
impl log::Log for JsonLogger { impl log::Log for JsonLogger {
fn enabled(&self, metadata: &Metadata) -> bool { fn enabled(&self, metadata: &Metadata) -> bool {
@ -64,9 +84,7 @@ impl log::Log for JsonLogger {
if let Level::Error = record.level() { if let Level::Error = record.level() {
eprintln!("{}", record.args()); eprintln!("{}", record.args());
} else { } else {
// Should be safe enough to `.unwrap()` since it's print!("{}", record.args());
// formatted by Himalaya itself
print!("{}", serde_json::to_string(record.args()).unwrap());
} }
} }
} }
@ -77,9 +95,15 @@ impl log::Log for JsonLogger {
// Init // Init
pub fn init(fmt: &OutputFmt, level: &LogLevel) -> Result<()> { pub fn init(fmt: &OutputFmt, level: &LogLevel) -> Result<()> {
log::set_boxed_logger(match fmt { log::set_logger(match fmt {
&OutputFmt::Json => Box::new(JsonLogger), &OutputFmt::Plain => {
&OutputFmt::Plain => Box::new(PlainLogger), set_output_fmt(&OutputFmt::Plain);
&PlainLogger
}
&OutputFmt::Json => {
set_output_fmt(&OutputFmt::Json);
&JsonLogger
}
}) })
.map(|()| log::set_max_level(level.0)) .map(|()| log::set_max_level(level.0))
.chain_err(|| "Could not init logger") .chain_err(|| "Could not init logger")

View file

@ -1,8 +1,5 @@
use error_chain::error_chain; use error_chain::error_chain;
use serde::{ use serde::ser::{self, SerializeStruct};
ser::{self, SerializeStruct},
Serialize,
};
use std::{fmt, process::Command, result}; use std::{fmt, process::Command, result};
error_chain! { error_chain! {
@ -40,17 +37,3 @@ pub fn run_cmd(cmd: &str) -> Result<String> {
Ok(String::from_utf8(output.stdout)?) Ok(String::from_utf8(output.stdout)?)
} }
pub fn print<T: fmt::Display + Serialize>(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(())
}

View file

@ -170,6 +170,7 @@ function! himalaya#msg#attachments()
let mbox = himalaya#mbox#curr_mbox() let mbox = himalaya#mbox#curr_mbox()
let msg_id = stridx(bufname("%"), "Himalaya messages") == 0 ? s:get_focused_msg_id() : s:msg_id 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) let msg = s:cli("--mailbox %s attachments %d", [shellescape(mbox), msg_id], "Downloading attachments", 0)
call himalaya#shared#log#info(msg)
catch catch
if !empty(v:exception) if !empty(v:exception)
redraw | call himalaya#shared#log#err(v:exception) redraw | call himalaya#shared#log#err(v:exception)

View file

@ -9,7 +9,7 @@ function! himalaya#shared#cli#call(cmd, args, log, should_throw)
try try
let res = eval(res) let res = eval(res)
redraw | call himalaya#shared#log#info(printf("%s [OK]", a:log)) redraw | call himalaya#shared#log#info(printf("%s [OK]", a:log))
return res return res.response
catch catch
redraw | call himalaya#shared#log#info(printf("%s [ERR]", a:log)) redraw | call himalaya#shared#log#info(printf("%s [ERR]", a:log))
for line in split(res, "\n") for line in split(res, "\n")

View file

@ -2,23 +2,23 @@ if exists("b:current_syntax")
finish finish
endif endif
syntax match hya_sep /|/ syntax match hym_sep /|/
syntax match hya_uid /^|.\{-}|/ contains=hya_sep syntax match hym_uid /^|.\{-}|/ contains=hym_sep
syntax match hya_flags /^|.\{-}|.\{-}|/ contains=hya_uid,hya_sep syntax match hym_flags /^|.\{-}|.\{-}|/ contains=hym_uid,hym_sep
syntax match hya_subject /^|.\{-}|.\{-}|.\{-}|/ contains=hya_uid,hya_flags,hya_sep syntax match hym_subject /^|.\{-}|.\{-}|.\{-}|/ contains=hym_uid,hym_flags,hym_sep
syntax match hya_sender /^|.\{-}|.\{-}|.\{-}|.\{-}|/ contains=hya_uid,hya_flags,hya_subject,hya_sep syntax match hym_sender /^|.\{-}|.\{-}|.\{-}|.\{-}|/ contains=hym_uid,hym_flags,hym_subject,hym_sep
syntax match hya_date /^|.\{-}|.\{-}|.\{-}|.\{-}|.\{-}|/ contains=hya_uid,hya_flags,hya_subject,hya_sender,hya_sep syntax match hym_date /^|.\{-}|.\{-}|.\{-}|.\{-}|.\{-}|/ contains=hym_uid,hym_flags,hym_subject,hym_sender,hym_sep
syntax match hya_head /.*\%1l/ contains=hya_sep syntax match hym_head /.*\%1l/ contains=hym_sep
syntax match hya_unseen /^|.\{-}|N.*$/ contains=hya_sep syntax match hym_unseen /^|.\{-}|N.*$/ contains=hym_sep
highlight default link hya_sep VertSplit highlight hym_head term=bold,underline cterm=bold,underline gui=bold,underline
highlight default link hya_uid Identifier highlight hym_unseen term=bold cterm=bold gui=bold
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 hya_head term=bold,underline cterm=bold,underline gui=bold,underline highlight default link hym_sep VertSplit
highlight hya_unseen term=bold cterm=bold gui=bold 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" let b:current_syntax = "himalaya-msg-list"