mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-25 12:30:22 +00:00
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:
parent
0e452d8a47
commit
e33a9a72e9
13 changed files with 243 additions and 91 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -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
2
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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
44
LICENSE
|
@ -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.
|
||||||
|
|
|
@ -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()?
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)),
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue