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
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 243 additions and 91 deletions

View file

@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.5.4] - 2022-02-05
### Fixed
- Add attachments with save and send commands [#47] [#259]
- Invalid sequence set [#276]
## [0.5.3] - 2022-02-03 ## [0.5.3] - 2022-02-03
### Added ### Added
@ -273,7 +280,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.5.3...HEAD [unreleased]: https://github.com/soywod/himalaya/compare/v0.5.4...HEAD
[0.5.4]: https://github.com/soywod/himalaya/compare/v0.5.3...v0.5.4
[0.5.3]: https://github.com/soywod/himalaya/compare/v0.5.2...v0.5.3 [0.5.3]: https://github.com/soywod/himalaya/compare/v0.5.2...v0.5.3
[0.5.2]: https://github.com/soywod/himalaya/compare/v0.5.1...v0.5.2 [0.5.2]: https://github.com/soywod/himalaya/compare/v0.5.1...v0.5.2
[0.5.1]: https://github.com/soywod/himalaya/compare/v0.5.0...v0.5.1 [0.5.1]: https://github.com/soywod/himalaya/compare/v0.5.0...v0.5.1
@ -325,6 +333,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#39]: https://github.com/soywod/himalaya/issues/39 [#39]: https://github.com/soywod/himalaya/issues/39
[#40]: https://github.com/soywod/himalaya/issues/40 [#40]: https://github.com/soywod/himalaya/issues/40
[#41]: https://github.com/soywod/himalaya/issues/41 [#41]: https://github.com/soywod/himalaya/issues/41
[#47]: https://github.com/soywod/himalaya/issues/47
[#48]: https://github.com/soywod/himalaya/issues/48 [#48]: https://github.com/soywod/himalaya/issues/48
[#50]: https://github.com/soywod/himalaya/issues/50 [#50]: https://github.com/soywod/himalaya/issues/50
[#58]: https://github.com/soywod/himalaya/issues/58 [#58]: https://github.com/soywod/himalaya/issues/58
@ -383,9 +392,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#228]: https://github.com/soywod/himalaya/issues/228 [#228]: https://github.com/soywod/himalaya/issues/228
[#229]: https://github.com/soywod/himalaya/issues/229 [#229]: https://github.com/soywod/himalaya/issues/229
[#249]: https://github.com/soywod/himalaya/issues/249 [#249]: https://github.com/soywod/himalaya/issues/249
[#259]: https://github.com/soywod/himalaya/issues/259
[#268]: https://github.com/soywod/himalaya/issues/268 [#268]: https://github.com/soywod/himalaya/issues/268
[#272]: https://github.com/soywod/himalaya/issues/272 [#272]: https://github.com/soywod/himalaya/issues/272
[#273]: https://github.com/soywod/himalaya/issues/273 [#273]: https://github.com/soywod/himalaya/issues/273
[#276]: https://github.com/soywod/himalaya/issues/276 [#276]: https://github.com/soywod/himalaya/issues/276
[#271]: https://github.com/soywod/himalaya/issues/271 [#271]: https://github.com/soywod/himalaya/issues/271
[#276]: https://github.com/soywod/himalaya/issues/276
[#280]: https://github.com/soywod/himalaya/issues/280 [#280]: https://github.com/soywod/himalaya/issues/280

2
Cargo.lock generated
View file

@ -361,7 +361,7 @@ dependencies = [
[[package]] [[package]]
name = "himalaya" name = "himalaya"
version = "0.5.3" version = "0.5.4"
dependencies = [ dependencies = [
"ammonia", "ammonia",
"anyhow", "anyhow",

View file

@ -1,7 +1,7 @@
[package] [package]
name = "himalaya" name = "himalaya"
description = "Command-line interface for email management" description = "Command-line interface for email management"
version = "0.5.3" version = "0.5.4"
authors = ["soywod <clement.douin@posteo.net>"] authors = ["soywod <clement.douin@posteo.net>"]
edition = "2018" edition = "2018"
license-file = "LICENSE" license-file = "LICENSE"

44
LICENSE
View file

@ -1,30 +1,32 @@
Copyright © 2020,2021 soywod <clement.douin@posteo.net> Copyright (c) 2020-2021, soywod (Clément DOUIN) <clement.douin@posteo.net>
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright 1. Redistributions of source code must retain the above copyright notice, this
notice, this list of conditions and the following disclaimer. list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above 2. Redistributions in binary form must reproduce the above copyright notice,
copyright notice, this list of conditions and the following this list of conditions and the following disclaimer in the documentation
disclaimer in the documentation and/or other materials provided and/or other materials provided with the distribution.
with the distribution.
* Neither the name of Author name here nor the names of other 3. All advertising materials mentioning features or use of this software must
contributors may be used to endorse or promote products derived display the following acknowledgement:
from this software without specific prior written permission. This product includes software developed by Clément DOUIN.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 4. Neither the name of the copyright holder nor the names of its
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT contributors may be used to endorse or promote products derived from
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR this software without specific prior written permission.
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -144,11 +144,11 @@ impl<'a> ImapServiceInterface<'a> for ImapService<'a> {
let cursor = (page * page_size) as i64; let cursor = (page * page_size) as i64;
let begin = 1.max(last_seq - cursor); let begin = 1.max(last_seq - cursor);
let end = begin - begin.min(*page_size as i64) + 1; let end = begin - begin.min(*page_size as i64) + 1;
format!("{}:{}", begin, end) format!("{}:{}", end, begin)
} else { } else {
String::from("1:*") String::from("1:*")
}; };
debug!("range: {:?}", range); debug!("range: {}", range);
let fetches = self let fetches = self
.sess()? .sess()?

View file

@ -23,7 +23,7 @@ type Raw = bool;
type All = bool; type All = bool;
type RawMsg<'a> = &'a str; type RawMsg<'a> = &'a str;
type Query = String; type Query = String;
type AttachmentsPaths<'a> = Vec<&'a str>; type AttachmentPaths<'a> = Vec<&'a str>;
type MaxTableWidth = Option<usize>; type MaxTableWidth = Option<usize>;
/// Message commands. /// Message commands.
@ -31,15 +31,15 @@ pub enum Command<'a> {
Attachments(Seq<'a>), Attachments(Seq<'a>),
Copy(Seq<'a>, Mbox<'a>), Copy(Seq<'a>, Mbox<'a>),
Delete(Seq<'a>), Delete(Seq<'a>),
Forward(Seq<'a>, AttachmentsPaths<'a>), Forward(Seq<'a>, AttachmentPaths<'a>),
List(MaxTableWidth, Option<PageSize>, Page), List(MaxTableWidth, Option<PageSize>, Page),
Move(Seq<'a>, Mbox<'a>), Move(Seq<'a>, Mbox<'a>),
Read(Seq<'a>, TextMime<'a>, Raw), Read(Seq<'a>, TextMime<'a>, Raw),
Reply(Seq<'a>, All, AttachmentsPaths<'a>), Reply(Seq<'a>, All, AttachmentPaths<'a>),
Save(RawMsg<'a>), Save(RawMsg<'a>),
Search(Query, MaxTableWidth, Option<PageSize>, Page), Search(Query, MaxTableWidth, Option<PageSize>, Page),
Send(RawMsg<'a>), Send(RawMsg<'a>),
Write(AttachmentsPaths<'a>), Write(AttachmentPaths<'a>),
Flag(Option<flag_arg::Command<'a>>), Flag(Option<flag_arg::Command<'a>>),
Tpl(Option<tpl_arg::Command<'a>>), Tpl(Option<tpl_arg::Command<'a>>),
@ -256,7 +256,7 @@ fn page_arg<'a>() -> Arg<'a, 'a> {
} }
/// Message attachment argument. /// Message attachment argument.
fn attachment_arg<'a>() -> Arg<'a, 'a> { pub fn attachment_arg<'a>() -> Arg<'a, 'a> {
Arg::with_name("attachments") Arg::with_name("attachments")
.help("Adds attachment to the message") .help("Adds attachment to the message")
.short("a") .short("a")

View file

@ -5,7 +5,7 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use atty::Stream; use atty::Stream;
use imap::types::Flag; use imap::types::Flag;
use log::{debug, trace}; use log::{debug, info, trace};
use std::{ use std::{
borrow::Cow, borrow::Cow,
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
@ -244,14 +244,25 @@ pub fn reply<
imap.add_flags(seq, &flags) 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>>( pub fn save<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a>>(
mbox: &Mbox, mbox: &Mbox,
raw_msg: &str, raw_msg: &str,
printer: &mut Printer, printer: &mut Printer,
imap: &mut ImapService, imap: &mut ImapService,
) -> Result<()> { ) -> 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") raw_msg.replace("\r", "").replace("\n", "\r\n")
} else { } else {
io::stdin() io::stdin()
@ -261,8 +272,6 @@ pub fn save<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a>>(
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\r\n") .join("\r\n")
}; };
let flags = Flags::try_from(vec![Flag::Seen])?;
imap.append_raw_msg_with_flags(mbox, raw_msg.as_bytes(), flags) imap.append_raw_msg_with_flags(mbox, raw_msg.as_bytes(), flags)
} }
@ -297,7 +306,19 @@ pub fn send<
imap: &mut ImapService, imap: &mut ImapService,
smtp: &mut SmtpService, smtp: &mut SmtpService,
) -> Result<()> { ) -> 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") raw_msg.replace("\r", "").replace("\n", "\r\n")
} else { } else {
io::stdin() io::stdin()
@ -307,15 +328,11 @@ pub fn send<
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\r\n") .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())?; 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) imap.append_raw_msg_with_flags(&mbox, raw_msg.as_bytes(), flags)
} }

View file

@ -4,12 +4,14 @@
use anyhow::Result; use anyhow::Result;
use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand}; use clap::{self, App, AppSettings, Arg, ArgMatches, SubCommand};
use log::{debug, trace}; use log::{debug, info, trace};
use crate::domain::msg::msg_arg; use crate::domain::msg::msg_arg;
type Seq<'a> = &'a str; 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)] #[derive(Debug, Default)]
pub struct TplOverride<'a> { pub struct TplOverride<'a> {
@ -23,69 +25,77 @@ pub struct TplOverride<'a> {
pub sig: Option<&'a str>, 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. /// Message template commands.
pub enum Command<'a> { pub enum Command<'a> {
New(TplOverride<'a>), New(TplOverride<'a>),
Reply(Seq<'a>, All, TplOverride<'a>), Reply(Seq<'a>, ReplyAll, TplOverride<'a>),
Forward(Seq<'a>, TplOverride<'a>), Forward(Seq<'a>, TplOverride<'a>),
Save(AttachmentPaths<'a>, Tpl<'a>),
Send(AttachmentPaths<'a>, Tpl<'a>),
} }
/// Message template command matcher. /// Message template command matcher.
pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Command<'a>>> { pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Command<'a>>> {
if let Some(m) = m.subcommand_matches("new") { if let Some(m) = m.subcommand_matches("new") {
debug!("new command matched"); info!("new command matched");
let tpl = TplOverride { let tpl = TplOverride::from(m);
subject: m.value_of("subject"), trace!("template override: {:?}", tpl);
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);
return Ok(Some(Command::New(tpl))); return Ok(Some(Command::New(tpl)));
} }
if let Some(m) = m.subcommand_matches("reply") { if let Some(m) = m.subcommand_matches("reply") {
debug!("reply command matched"); info!("reply command matched");
let seq = m.value_of("seq").unwrap(); let seq = m.value_of("seq").unwrap();
trace!(r#"seq: "{}""#, seq); debug!("sequence: {}", seq);
let all = m.is_present("reply-all"); let all = m.is_present("reply-all");
trace!("reply all: {}", all); debug!("reply all: {}", all);
let tpl = TplOverride { let tpl = TplOverride::from(m);
subject: m.value_of("subject"), trace!("template override: {:?}", tpl);
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);
return Ok(Some(Command::Reply(seq, all, tpl))); return Ok(Some(Command::Reply(seq, all, tpl)));
} }
if let Some(m) = m.subcommand_matches("forward") { if let Some(m) = m.subcommand_matches("forward") {
debug!("forward command matched"); info!("forward command matched");
let seq = m.value_of("seq").unwrap(); let seq = m.value_of("seq").unwrap();
trace!(r#"seq: "{}""#, seq); debug!("sequence: {}", seq);
let tpl = TplOverride { let tpl = TplOverride::from(m);
subject: m.value_of("subject"), trace!("template args: {:?}", tpl);
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);
return Ok(Some(Command::Forward(seq, 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) Ok(None)
} }
@ -154,7 +164,7 @@ pub fn subcmds<'a>() -> Vec<App<'a, 'a>> {
) )
.subcommand( .subcommand(
SubCommand::with_name("reply") SubCommand::with_name("reply")
.aliases(&["rep", "r"]) .aliases(&["rep", "re", "r"])
.about("Generates a reply message template") .about("Generates a reply message template")
.arg(msg_arg::seq_arg()) .arg(msg_arg::seq_arg())
.arg(msg_arg::reply_all_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") .about("Generates a forward message template")
.arg(msg_arg::seq_arg()) .arg(msg_arg::seq_arg())
.args(&tpl_args()), .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)),
)] )]
} }

View file

@ -3,12 +3,19 @@
//! This module gathers all message template commands. //! This module gathers all message template commands.
use anyhow::Result; use anyhow::Result;
use atty::Stream;
use imap::types::Flag;
use std::{
convert::{TryFrom, TryInto},
io::{self, BufRead},
};
use crate::{ use crate::{
config::Account, config::Account,
domain::{ domain::{
imap::ImapServiceInterface, imap::ImapServiceInterface,
msg::{Msg, TplOverride}, msg::{Msg, TplOverride},
Flags, Mbox, SmtpServiceInterface,
}, },
output::PrinterService, output::PrinterService,
}; };
@ -53,3 +60,59 @@ pub fn forward<'a, Printer: PrinterService, ImapService: ImapServiceInterface<'a
.to_tpl(opts, account); .to_tpl(opts, account);
printer.print(tpl) 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")
}

View file

@ -176,6 +176,12 @@ fn main() -> Result<()> {
Some(tpl_arg::Command::Forward(seq, tpl)) => { Some(tpl_arg::Command::Forward(seq, tpl)) => {
return tpl_handler::forward(seq, tpl, &account, &mut printer, &mut imap); 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);
}
_ => (), _ => (),
}, },
_ => (), _ => (),

View file

@ -155,6 +155,16 @@ nmap gD <plug>(himalaya-msg-delete)
![gif](https://user-images.githubusercontent.com/10437171/110708795-84387900-81fb-11eb-8f8a-f7e7862e816d.gif) ![gif](https://user-images.githubusercontent.com/10437171/110708795-84387900-81fb-11eb-8f8a-f7e7862e816d.gif)
| Function | Default binding |
| --- | --- |
| Add attachment | `ga` |
They can be customized:
```vim
nmap ga <plug>(himalaya-msg-add-attachment)
```
When you exit this special buffer, you will be prompted 4 choices: When you exit this special buffer, you will be prompted 4 choices:
- `Send`: sends the message - `Send`: sends the message

View file

@ -5,6 +5,7 @@ let s:plain_req = function("himalaya#request#plain")
let s:msg_id = 0 let s:msg_id = 0
let s:draft = "" let s:draft = ""
let s:attachment_paths = []
function! himalaya#msg#list_with(account, mbox, page, should_throw) function! himalaya#msg#list_with(account, mbox, page, should_throw)
let pos = getpos(".") let pos = getpos(".")
@ -254,6 +255,7 @@ endfunction
function! himalaya#msg#draft_handle() function! himalaya#msg#draft_handle()
try try
let account = himalaya#account#curr() let account = himalaya#account#curr()
let attachments = join(map(s:attachment_paths, "'--attachment '.v:val"), " ")
while 1 while 1
let choice = input("(s)end, (d)raft, (q)uit or (c)ancel? ") let choice = input("(s)end, (d)raft, (q)uit or (c)ancel? ")
let choice = tolower(choice)[0] let choice = tolower(choice)[0]
@ -261,15 +263,15 @@ function! himalaya#msg#draft_handle()
if choice == "s" if choice == "s"
return s:cli( return s:cli(
\"--account %s send -- %s", \"--account %s template send %s -- %s",
\[shellescape(account), shellescape(s:draft)], \[shellescape(account), attachments, shellescape(s:draft)],
\"Sending message", \"Sending message",
\0, \0,
\) \)
elseif choice == "d" elseif choice == "d"
return s:cli( return s:cli(
\"--account %s --mailbox Drafts save -- %s", \"--account %s --mailbox Drafts template save %s -- %s",
\[shellescape(account), shellescape(s:draft)], \[shellescape(account), attachments, shellescape(s:draft)],
\"Saving draft", \"Saving draft",
\0, \0,
\) \)
@ -336,6 +338,21 @@ function! himalaya#msg#complete_contact(findstart, base)
endtry endtry
endfunction endfunction
function! himalaya#msg#add_attachment()
try
let attachment_path = input("Attachment path: ", "", "file")
if empty(expand(glob(attachment_path)))
throw "The file does not exist"
endif
call add(s:attachment_paths, attachment_path)
redraw | call himalaya#shared#log#info("Attachment added!")
catch
if !empty(v:exception)
redraw | call himalaya#shared#log#err(v:exception)
endif
endtry
endfunction
" Utils " Utils
" https://newbedev.com/get-usable-window-width-in-vim-script " https://newbedev.com/get-usable-window-width-in-vim-script

View file

@ -7,6 +7,10 @@ if exists("g:himalaya_complete_contact_cmd")
setlocal completefunc=himalaya#msg#complete_contact setlocal completefunc=himalaya#msg#complete_contact
endif endif
call himalaya#shared#bindings#define([
\["n", "ga", "msg#add_attachment"],
\])
augroup himalaya_write augroup himalaya_write
autocmd! * <buffer> autocmd! * <buffer>
autocmd BufWriteCmd <buffer> call himalaya#msg#draft_save() autocmd BufWriteCmd <buffer> call himalaya#msg#draft_save()