release v0.5.4 (#285)

* replace bsd3 license by bsd4

* add attachments with save and send commands (#284)

* set up tpl save and send commands

* improve msg save and send handlers

* add vim msg#add_attachment fn

* improve vim logs

* update changelog

* add attachment keybind vim doc

* reverse range order fetch envelopes (#276)

* bump version v0.5.4
This commit is contained in:
Clément DOUIN
2022-02-05 00:29:57 +01:00
committed by GitHub
parent 0e452d8a47
commit e33a9a72e9
13 changed files with 243 additions and 91 deletions
+2 -2
View File
@@ -144,11 +144,11 @@ impl<'a> ImapServiceInterface<'a> for ImapService<'a> {
let cursor = (page * page_size) as i64;
let begin = 1.max(last_seq - cursor);
let end = begin - begin.min(*page_size as i64) + 1;
format!("{}:{}", begin, end)
format!("{}:{}", end, begin)
} else {
String::from("1:*")
};
debug!("range: {:?}", range);
debug!("range: {}", range);
let fetches = self
.sess()?
+5 -5
View File
@@ -23,7 +23,7 @@ type Raw = bool;
type All = bool;
type RawMsg<'a> = &'a str;
type Query = String;
type AttachmentsPaths<'a> = Vec<&'a str>;
type AttachmentPaths<'a> = Vec<&'a str>;
type MaxTableWidth = Option<usize>;
/// Message commands.
@@ -31,15 +31,15 @@ pub enum Command<'a> {
Attachments(Seq<'a>),
Copy(Seq<'a>, Mbox<'a>),
Delete(Seq<'a>),
Forward(Seq<'a>, AttachmentsPaths<'a>),
Forward(Seq<'a>, AttachmentPaths<'a>),
List(MaxTableWidth, Option<PageSize>, Page),
Move(Seq<'a>, Mbox<'a>),
Read(Seq<'a>, TextMime<'a>, Raw),
Reply(Seq<'a>, All, AttachmentsPaths<'a>),
Reply(Seq<'a>, All, AttachmentPaths<'a>),
Save(RawMsg<'a>),
Search(Query, MaxTableWidth, Option<PageSize>, Page),
Send(RawMsg<'a>),
Write(AttachmentsPaths<'a>),
Write(AttachmentPaths<'a>),
Flag(Option<flag_arg::Command<'a>>),
Tpl(Option<tpl_arg::Command<'a>>),
@@ -256,7 +256,7 @@ fn page_arg<'a>() -> Arg<'a, 'a> {
}
/// Message attachment argument.
fn attachment_arg<'a>() -> Arg<'a, 'a> {
pub fn attachment_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("attachments")
.help("Adds attachment to the message")
.short("a")
+30 -13
View File
@@ -5,7 +5,7 @@
use anyhow::{Context, Result};
use atty::Stream;
use imap::types::Flag;
use log::{debug, trace};
use log::{debug, info, trace};
use std::{
borrow::Cow,
convert::{TryFrom, TryInto},
@@ -244,14 +244,25 @@ pub fn reply<
imap.add_flags(seq, &flags)
}
/// Save a raw message to the targetted mailbox.
/// Saves a raw message to the targetted mailbox.
pub fn save<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a>>(
mbox: &Mbox,
raw_msg: &str,
printer: &mut Printer,
imap: &mut ImapService,
) -> Result<()> {
let raw_msg = if atty::is(Stream::Stdin) || printer.is_json() {
info!("entering save message handler");
debug!("mailbox: {}", mbox);
let flags = Flags::try_from(vec![Flag::Seen])?;
debug!("flags: {}", flags);
let is_tty = atty::is(Stream::Stdin);
debug!("is tty: {}", is_tty);
let is_json = printer.is_json();
debug!("is json: {}", is_json);
let raw_msg = if is_tty || is_json {
raw_msg.replace("\r", "").replace("\n", "\r\n")
} else {
io::stdin()
@@ -261,8 +272,6 @@ pub fn save<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a>>(
.collect::<Vec<String>>()
.join("\r\n")
};
let flags = Flags::try_from(vec![Flag::Seen])?;
imap.append_raw_msg_with_flags(mbox, raw_msg.as_bytes(), flags)
}
@@ -297,7 +306,19 @@ pub fn send<
imap: &mut ImapService,
smtp: &mut SmtpService,
) -> Result<()> {
let raw_msg = if atty::is(Stream::Stdin) || printer.is_json() {
info!("entering send message handler");
let mbox = Mbox::new(&account.sent_folder);
debug!("mailbox: {}", mbox);
let flags = Flags::try_from(vec![Flag::Seen])?;
debug!("flags: {}", flags);
let is_tty = atty::is(Stream::Stdin);
debug!("is tty: {}", is_tty);
let is_json = printer.is_json();
debug!("is json: {}", is_json);
let raw_msg = if is_tty || is_json {
raw_msg.replace("\r", "").replace("\n", "\r\n")
} else {
io::stdin()
@@ -307,15 +328,11 @@ pub fn send<
.collect::<Vec<String>>()
.join("\r\n")
};
trace!("raw message: {:?}", raw_msg);
let envelope: lettre::address::Envelope = Msg::from_tpl(&raw_msg)?.try_into()?;
trace!("envelope: {:?}", envelope);
let msg = Msg::from_tpl(&raw_msg)?;
let envelope: lettre::address::Envelope = msg.try_into()?;
smtp.send_raw_msg(&envelope, raw_msg.as_bytes())?;
debug!("message sent!");
// Save message to sent folder
let mbox = Mbox::new(&account.sent_folder);
let flags = Flags::try_from(vec![Flag::Seen])?;
imap.append_raw_msg_with_flags(&mbox, raw_msg.as_bytes(), flags)
}
+65 -43
View File
@@ -4,12 +4,14 @@
use anyhow::Result;
use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand};
use log::{debug, trace};
use log::{debug, info, trace};
use crate::domain::msg::msg_arg;
type Seq<'a> = &'a str;
type All = bool;
type ReplyAll = bool;
type AttachmentPaths<'a> = Vec<&'a str>;
type Tpl<'a> = &'a str;
#[derive(Debug, Default)]
pub struct TplOverride<'a> {
@@ -23,69 +25,77 @@ pub struct TplOverride<'a> {
pub sig: Option<&'a str>,
}
impl<'a> From<&'a ArgMatches<'a>> for TplOverride<'a> {
fn from(matches: &'a ArgMatches<'a>) -> Self {
Self {
subject: matches.value_of("subject"),
from: matches.values_of("from").map(|v| v.collect()),
to: matches.values_of("to").map(|v| v.collect()),
cc: matches.values_of("cc").map(|v| v.collect()),
bcc: matches.values_of("bcc").map(|v| v.collect()),
headers: matches.values_of("headers").map(|v| v.collect()),
body: matches.value_of("body"),
sig: matches.value_of("signature"),
}
}
}
/// Message template commands.
pub enum Command<'a> {
New(TplOverride<'a>),
Reply(Seq<'a>, All, TplOverride<'a>),
Reply(Seq<'a>, ReplyAll, TplOverride<'a>),
Forward(Seq<'a>, TplOverride<'a>),
Save(AttachmentPaths<'a>, Tpl<'a>),
Send(AttachmentPaths<'a>, Tpl<'a>),
}
/// Message template command matcher.
pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Command<'a>>> {
if let Some(m) = m.subcommand_matches("new") {
debug!("new command matched");
let tpl = TplOverride {
subject: m.value_of("subject"),
from: m.values_of("from").map(|v| v.collect()),
to: m.values_of("to").map(|v| v.collect()),
cc: m.values_of("cc").map(|v| v.collect()),
bcc: m.values_of("bcc").map(|v| v.collect()),
headers: m.values_of("headers").map(|v| v.collect()),
body: m.value_of("body"),
sig: m.value_of("signature"),
};
trace!(r#"template args: "{:?}""#, tpl);
info!("new command matched");
let tpl = TplOverride::from(m);
trace!("template override: {:?}", tpl);
return Ok(Some(Command::New(tpl)));
}
if let Some(m) = m.subcommand_matches("reply") {
debug!("reply command matched");
info!("reply command matched");
let seq = m.value_of("seq").unwrap();
trace!(r#"seq: "{}""#, seq);
debug!("sequence: {}", seq);
let all = m.is_present("reply-all");
trace!("reply all: {}", all);
let tpl = TplOverride {
subject: m.value_of("subject"),
from: m.values_of("from").map(|v| v.collect()),
to: m.values_of("to").map(|v| v.collect()),
cc: m.values_of("cc").map(|v| v.collect()),
bcc: m.values_of("bcc").map(|v| v.collect()),
headers: m.values_of("headers").map(|v| v.collect()),
body: m.value_of("body"),
sig: m.value_of("signature"),
};
trace!(r#"template args: "{:?}""#, tpl);
debug!("reply all: {}", all);
let tpl = TplOverride::from(m);
trace!("template override: {:?}", tpl);
return Ok(Some(Command::Reply(seq, all, tpl)));
}
if let Some(m) = m.subcommand_matches("forward") {
debug!("forward command matched");
info!("forward command matched");
let seq = m.value_of("seq").unwrap();
trace!(r#"seq: "{}""#, seq);
let tpl = TplOverride {
subject: m.value_of("subject"),
from: m.values_of("from").map(|v| v.collect()),
to: m.values_of("to").map(|v| v.collect()),
cc: m.values_of("cc").map(|v| v.collect()),
bcc: m.values_of("bcc").map(|v| v.collect()),
headers: m.values_of("headers").map(|v| v.collect()),
body: m.value_of("body"),
sig: m.value_of("signature"),
};
trace!(r#"template args: "{:?}""#, tpl);
debug!("sequence: {}", seq);
let tpl = TplOverride::from(m);
trace!("template args: {:?}", tpl);
return Ok(Some(Command::Forward(seq, tpl)));
}
if let Some(m) = m.subcommand_matches("save") {
info!("save command matched");
let attachment_paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect();
trace!("attachments paths: {:?}", attachment_paths);
let tpl = m.value_of("template").unwrap_or_default();
trace!("template: {}", tpl);
return Ok(Some(Command::Save(attachment_paths, tpl)));
}
if let Some(m) = m.subcommand_matches("send") {
info!("send command matched");
let attachment_paths: Vec<&str> = m.values_of("attachments").unwrap_or_default().collect();
trace!("attachments paths: {:?}", attachment_paths);
let tpl = m.value_of("template").unwrap_or_default();
trace!("template: {}", tpl);
return Ok(Some(Command::Send(attachment_paths, tpl)));
}
Ok(None)
}
@@ -154,7 +164,7 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
)
.subcommand(
SubCommand::with_name("reply")
.aliases(&["rep", "r"])
.aliases(&["rep", "re", "r"])
.about("Generates a reply message template")
.arg(msg_arg::seq_arg())
.arg(msg_arg::reply_all_arg())
@@ -166,5 +176,17 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
.about("Generates a forward message template")
.arg(msg_arg::seq_arg())
.args(&tpl_args()),
)
.subcommand(
SubCommand::with_name("save")
.about("Saves a message based on the given template")
.arg(&msg_arg::attachment_arg())
.arg(Arg::with_name("template").raw(true)),
)
.subcommand(
SubCommand::with_name("send")
.about("Sends a message based on the given template")
.arg(&msg_arg::attachment_arg())
.arg(Arg::with_name("template").raw(true)),
)]
}
+63
View File
@@ -3,12 +3,19 @@
//! This module gathers all message template commands.
use anyhow::Result;
use atty::Stream;
use imap::types::Flag;
use std::{
convert::{TryFrom, TryInto},
io::{self, BufRead},
};
use crate::{
config::Account,
domain::{
imap::ImapServiceInterface,
msg::{Msg, TplOverride},
Flags, Mbox, SmtpServiceInterface,
},
output::PrinterService,
};
@@ -53,3 +60,59 @@ pub fn forward<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a
.to_tpl(opts, account);
printer.print(tpl)
}
/// Saves a message based on a template.
pub fn save<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a>>(
mbox: &Mbox,
attachments_paths: Vec<&str>,
tpl: &str,
printer: &mut Printer,
imap: &mut ImapService,
) -> Result<()> {
let tpl = if atty::is(Stream::Stdin) || printer.is_json() {
tpl.replace("\r", "")
} else {
io::stdin()
.lock()
.lines()
.filter_map(Result::ok)
.collect::<Vec<String>>()
.join("\n")
};
let msg = Msg::from_tpl(&tpl)?.add_attachments(attachments_paths)?;
let raw_msg: Vec<u8> = TryInto::try_into(&msg)?;
let flags = Flags::try_from(vec![Flag::Seen])?;
imap.append_raw_msg_with_flags(mbox, &raw_msg, flags)?;
printer.print("Template successfully saved")
}
/// Sends a message based on a template.
pub fn send<
'a,
Printer: PrinterService,
ImapService: ImapServiceInterface<'a>,
SmtpService: SmtpServiceInterface,
>(
mbox: &Mbox,
attachments_paths: Vec<&str>,
tpl: &str,
printer: &mut Printer,
imap: &mut ImapService,
smtp: &mut SmtpService,
) -> Result<()> {
let tpl = if atty::is(Stream::Stdin) || printer.is_json() {
tpl.replace("\r", "")
} else {
io::stdin()
.lock()
.lines()
.filter_map(Result::ok)
.collect::<Vec<String>>()
.join("\n")
};
let msg = Msg::from_tpl(&tpl)?.add_attachments(attachments_paths)?;
let sent_msg = smtp.send_msg(&msg)?;
let flags = Flags::try_from(vec![Flag::Seen])?;
imap.append_raw_msg_with_flags(mbox, &sent_msg.formatted(), flags)?;
printer.print("Template successfully sent")
}
+6
View File
@@ -176,6 +176,12 @@ fn main() -> Result<()> {
Some(tpl_arg::Command::Forward(seq, tpl)) => {
return tpl_handler::forward(seq, tpl, &account, &mut printer, &mut imap);
}
Some(tpl_arg::Command::Save(atts, tpl)) => {
return tpl_handler::save(&mbox, atts, tpl, &mut printer, &mut imap);
}
Some(tpl_arg::Command::Send(atts, tpl)) => {
return tpl_handler::send(&mbox, atts, tpl, &mut printer, &mut imap, &mut smtp);
}
_ => (),
},
_ => (),