improve tpl builders api

This commit is contained in:
Clément DOUIN 2023-05-30 00:34:15 +02:00
parent 5a2d842cbe
commit 65ac0c7702
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
7 changed files with 174 additions and 109 deletions

88
Cargo.lock generated
View file

@ -1018,6 +1018,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "gethostname"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a329e22866dd78b35d2c639a4a23d7b950aeae300dfd79f4fb19f74055c2404"
dependencies = [
"libc",
"windows",
]
[[package]]
name = "getrandom"
version = "0.2.8"
@ -1255,7 +1265,7 @@ checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
dependencies = [
"http",
"hyper",
"rustls",
"rustls 0.20.8",
"tokio",
"tokio-rustls",
]
@ -1440,7 +1450,7 @@ dependencies = [
"nom 7.1.1",
"once_cell",
"quoted_printable",
"rustls",
"rustls 0.20.8",
"rustls-pemfile",
"serde",
"socket2",
@ -1530,11 +1540,19 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "mail-builder"
version = "0.3.0"
source = "git+https://github.com/stalwartlabs/mail-builder.git#7986275cd89340eef2cb18daea25ced53f51db07"
dependencies = [
"gethostname 0.4.1",
]
[[package]]
name = "mail-parser"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2e03aa1d18528b45d0e79e46790f38cfeece6cce3af17a85912677272f36cd"
checksum = "e4158a1c18963244e083888b21465846dfb68d6170850ed1ab4742edd57c9d47"
dependencies = [
"encoding_rs",
]
@ -1545,7 +1563,7 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d2d08d52a925272eda99f8fe9e91237b1cb958804ee0628cc398ebd1bbc426f"
dependencies = [
"gethostname",
"gethostname 0.2.3",
"mailparse",
"memmap2",
]
@ -2044,8 +2062,7 @@ dependencies = [
[[package]]
name = "pimalaya-email"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ffcfdf5fbbca7539e3d762a5c5b3b2b6fd58fc3d996b2295f094c7f394553ad"
source = "git+https://git.sr.ht/~soywod/pimalaya#f62efe4f4f3fe4d1493056cef50d8ebdc28f681b"
dependencies = [
"advisory-lock",
"ammonia",
@ -2057,6 +2074,7 @@ dependencies = [
"imap-proto",
"lettre",
"log",
"mail-builder",
"mail-parser",
"maildir",
"mailparse",
@ -2073,7 +2091,7 @@ dependencies = [
"regex",
"rfc2047-decoder",
"rusqlite",
"rustls",
"rustls 0.21.1",
"rustls-native-certs",
"shellexpand",
"thiserror",
@ -2086,17 +2104,14 @@ dependencies = [
[[package]]
name = "pimalaya-email-tpl"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd0a03c25c249b598bddd24a0fe1c06d044c9bb8362644792e902146c8b5b613"
version = "0.2.0"
source = "git+https://git.sr.ht/~soywod/pimalaya#f62efe4f4f3fe4d1493056cef50d8ebdc28f681b"
dependencies = [
"ammonia",
"chumsky 0.9.0",
"html-escape",
"lettre",
"log",
"mail-builder",
"mail-parser",
"pimalaya-process",
"regex",
"shellexpand",
"thiserror",
"tree_magic",
@ -2401,7 +2416,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls 0.20.8",
"rustls-pemfile",
"serde",
"serde_json",
@ -2506,6 +2521,18 @@ dependencies = [
"webpki",
]
[[package]]
name = "rustls"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e"
dependencies = [
"log",
"ring",
"rustls-webpki",
"sct",
]
[[package]]
name = "rustls-connector"
version = "0.16.1"
@ -2513,7 +2540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c6a18f8d10f71bce9bca6eaeb80429460e652f3bcf0381f0c5f8954abf7b3b8"
dependencies = [
"log",
"rustls",
"rustls 0.20.8",
"rustls-native-certs",
"webpki",
]
@ -2539,6 +2566,16 @@ dependencies = [
"base64 0.21.0",
]
[[package]]
name = "rustls-webpki"
version = "0.100.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "ryu"
version = "1.0.11"
@ -2966,7 +3003,7 @@ version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
dependencies = [
"rustls",
"rustls 0.20.8",
"tokio",
"webpki",
]
@ -3341,6 +3378,21 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.36.1"

View file

@ -51,7 +51,7 @@ indicatif = "0.17"
log = "0.4"
md5 = "0.7.0"
once_cell = "1.16.0"
pimalaya-email = "=0.8.0"
pimalaya-email = { git = "https://git.sr.ht/~soywod/pimalaya" }
pimalaya-keyring = "=0.0.1"
pimalaya-oauth2 = "=0.0.2"
pimalaya-process = "=0.0.2"

View file

@ -1,9 +1,7 @@
use anyhow::{anyhow, Context, Result};
use atty::Stream;
use log::{debug, trace};
use pimalaya_email::{
AccountConfig, Backend, Email, Flag, Flags, Sender, ShowTextPartsStrategy, Tpl, TplBuilder,
};
use pimalaya_email::{AccountConfig, Backend, Email, EmailBuilder, Flag, Flags, Sender};
use std::{
fs,
io::{self, BufRead},
@ -114,20 +112,22 @@ pub fn forward<P: Printer>(
sender: &mut dyn Sender,
folder: &str,
id: &str,
headers: Option<Vec<&str>>,
headers: Option<Vec<(&str, &str)>>,
body: Option<&str>,
) -> Result<()> {
let folder = config.folder_alias(folder)?;
let ids = id_mapper.get_ids([id])?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let tpl = backend
.get_emails(&folder, ids)?
.first()
.ok_or_else(|| anyhow!("cannot find email {}", id))?
.to_forward_tpl_builder(config)?
.set_some_raw_headers(headers)
.some_text_plain_part(body)
.build();
.to_forward_tpl_builder(config)
.some_headers(headers)
.some_body(body)
.build()?;
trace!("initial template: {}", *tpl);
editor::edit_tpl_with_editor(config, printer, backend, sender, tpl)?;
Ok(())
@ -170,19 +170,24 @@ pub fn mailto<P: Printer>(
printer: &mut P,
url: &Url,
) -> Result<()> {
let mut tpl = TplBuilder::default().to(url.path());
let mut builder = EmailBuilder::new().to(url.path());
for (key, val) in url.query_pairs() {
match key.to_lowercase().as_bytes() {
b"cc" => tpl = tpl.cc(val),
b"bcc" => tpl = tpl.bcc(val),
b"subject" => tpl = tpl.subject(val),
b"body" => tpl = tpl.text_plain_part(val.as_bytes()),
b"cc" => builder = builder.cc(val.to_string()),
b"bcc" => builder = builder.bcc(val.to_string()),
b"subject" => builder = builder.subject(val),
b"body" => builder = builder.text_body(val),
_ => (),
}
}
editor::edit_tpl_with_editor(config, printer, backend, sender, tpl.build())
let tpl = config
.generate_tpl_interpreter()
.show_only_headers(config.email_writing_headers())
.interpret_msg_builder(builder)?;
editor::edit_tpl_with_editor(config, printer, backend, sender, tpl)
}
pub fn move_<P: Printer>(
@ -209,8 +214,9 @@ pub fn read<P: Printer>(
backend: &mut dyn Backend,
folder: &str,
ids: Vec<&str>,
text_mime: &str,
sanitize: bool,
// TODO: map this to ShowTextsStrategy
_text_mime: &str,
_sanitize: bool,
raw: bool,
headers: Vec<&str>,
) -> Result<()> {
@ -230,20 +236,10 @@ pub fn read<P: Printer>(
// display what can be displayed
bodies.push_str(&String::from_utf8_lossy(email.raw()?).into_owned());
} else {
let tpl = email
.to_read_tpl_builder(config)?
.show_headers(config.email_reading_headers())
.show_headers(&headers)
.show_text_parts_only(true)
.use_show_text_parts_strategy(if text_mime == "plain" {
ShowTextPartsStrategy::PlainOtherwiseHtml
} else {
ShowTextPartsStrategy::HtmlOtherwisePlain
})
.sanitize_text_parts(sanitize)
.build();
bodies.push_str(&<Tpl as Into<String>>::into(tpl));
let tpl: String = email
.to_read_tpl(&config, |i| i.show_additional_headers(&headers))?
.into();
bodies.push_str(&tpl);
}
glue = "\n\n";
@ -261,20 +257,23 @@ pub fn reply<P: Printer>(
folder: &str,
id: &str,
all: bool,
headers: Option<Vec<&str>>,
headers: Option<Vec<(&str, &str)>>,
body: Option<&str>,
) -> Result<()> {
let folder = config.folder_alias(folder)?;
let ids = id_mapper.get_ids([id])?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let tpl = backend
.get_emails(&folder, ids)?
.first()
.ok_or_else(|| anyhow!("cannot find email {}", id))?
.to_reply_tpl_builder(config, all)?
.set_some_raw_headers(headers)
.some_text_plain_part(body)
.build();
.to_reply_tpl_builder(config)
.some_headers(headers)
.some_body(body)
.reply_all(all)
.build()?;
trace!("initial template: {}", *tpl);
editor::edit_tpl_with_editor(config, printer, backend, sender, tpl)?;
backend.add_flags(&folder, vec![id], &Flags::from_iter([Flag::Answered]))?;
@ -397,13 +396,13 @@ pub fn write<P: Printer>(
printer: &mut P,
backend: &mut dyn Backend,
sender: &mut dyn Sender,
headers: Option<Vec<&str>>,
headers: Option<Vec<(&str, &str)>>,
body: Option<&str>,
) -> Result<()> {
let tpl = Email::new_tpl_builder(config)?
.set_some_raw_headers(headers)
.some_text_plain_part(body)
.build();
let tpl = Email::new_tpl_builder(config)
.some_headers(headers)
.some_body(body)
.build()?;
trace!("initial template: {}", *tpl);
editor::edit_tpl_with_editor(config, printer, backend, sender, tpl)?;
Ok(())

View file

@ -5,6 +5,7 @@
use anyhow::Result;
use clap::{Arg, ArgAction, ArgMatches, Command};
use log::warn;
use crate::email;
@ -20,7 +21,7 @@ const CMD_WRITE: &str = "write";
pub const CMD_TPL: &str = "template";
pub type RawTpl = String;
pub type Headers<'a> = Option<Vec<&'a str>>;
pub type Headers<'a> = Option<Vec<(&'a str, &'a str)>>;
pub type Body<'a> = Option<&'a str>;
/// Represents the template commands.
@ -121,8 +122,16 @@ pub fn args() -> Vec<Arg> {
/// Represents the template headers argument parser.
pub fn parse_headers_arg(m: &ArgMatches) -> Headers<'_> {
m.get_many(ARG_HEADERS)
.map(|h| h.map(String::as_str).collect::<Vec<_>>())
m.get_many::<String>(ARG_HEADERS).map(|h| {
h.filter_map(|h| match h.split_once(':') {
Some((key, val)) => Some((key, val.trim())),
None => {
warn!("invalid raw header {h:?}, skipping it");
None
}
})
.collect()
})
}
/// Represents the template body argument parser.

View file

@ -1,6 +1,6 @@
use anyhow::{anyhow, Result};
use atty::Stream;
use pimalaya_email::{AccountConfig, Backend, CompilerBuilder, Email, Flags, Sender, Tpl};
use pimalaya_email::{AccountConfig, Backend, Email, Flags, Sender, Tpl};
use std::io::{stdin, BufRead};
use crate::{printer::Printer, IdMapper};
@ -12,21 +12,23 @@ pub fn forward<P: Printer>(
backend: &mut dyn Backend,
folder: &str,
id: &str,
headers: Option<Vec<&str>>,
headers: Option<Vec<(&str, &str)>>,
body: Option<&str>,
) -> Result<()> {
let ids = id_mapper.get_ids([id])?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let tpl = backend
let tpl: String = backend
.get_emails(folder, ids)?
.first()
.ok_or_else(|| anyhow!("cannot find email {}", id))?
.to_forward_tpl_builder(config)?
.set_some_raw_headers(headers)
.some_text_plain_part(body)
.build();
.to_forward_tpl_builder(config)
.some_headers(headers)
.some_body(body)
.build()?
.into();
printer.print(<Tpl as Into<String>>::into(tpl))
printer.print(tpl)
}
pub fn reply<P: Printer>(
@ -37,21 +39,24 @@ pub fn reply<P: Printer>(
folder: &str,
id: &str,
all: bool,
headers: Option<Vec<&str>>,
headers: Option<Vec<(&str, &str)>>,
body: Option<&str>,
) -> Result<()> {
let ids = id_mapper.get_ids([id])?;
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let tpl = backend
let tpl: String = backend
.get_emails(folder, ids)?
.first()
.ok_or_else(|| anyhow!("cannot find email {}", id))?
.to_reply_tpl_builder(config, all)?
.set_some_raw_headers(headers)
.some_text_plain_part(body)
.build();
.to_reply_tpl_builder(config)
.some_headers(headers)
.some_body(body)
.reply_all(all)
.build()?
.into();
printer.print(<Tpl as Into<String>>::into(tpl))
printer.print(tpl)
}
pub fn save<P: Printer>(
@ -72,11 +77,10 @@ pub fn save<P: Printer>(
.collect::<Vec<String>>()
.join("\n")
})
.compile(
CompilerBuilder::default()
.some_pgp_sign_cmd(config.email_writing_sign_cmd.clone())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()),
)?;
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone())
.compile()?
.write_to_vec()?;
let id = backend.add_email(folder, &email, &Flags::default())?;
id_mapper.create_alias(id)?;
@ -102,11 +106,10 @@ pub fn send<P: Printer>(
.collect::<Vec<String>>()
.join("\n")
})
.compile(
CompilerBuilder::default()
.some_pgp_sign_cmd(config.email_writing_sign_cmd.clone())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()),
)?;
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone())
.compile()?
.write_to_vec()?;
sender.send(&email)?;
if config.email_sending_save_copy {
backend.add_email(folder, &email, &Flags::default())?;
@ -115,15 +118,17 @@ pub fn send<P: Printer>(
Ok(())
}
pub fn write<'a, P: Printer>(
config: &'a AccountConfig,
printer: &'a mut P,
headers: Option<Vec<&str>>,
pub fn write<P: Printer>(
config: &AccountConfig,
printer: &mut P,
headers: Option<Vec<(&str, &str)>>,
body: Option<&str>,
) -> Result<()> {
let tpl = Email::new_tpl_builder(config)?
.set_some_raw_headers(headers)
.some_text_plain_part(body)
.build();
printer.print(<Tpl as Into<String>>::into(tpl))
let tpl: String = Email::new_tpl_builder(config)
.some_headers(headers)
.some_body(body)
.build()?
.into();
printer.print(tpl)
}

View file

@ -2,7 +2,7 @@ use anyhow::{Context, Result};
use log::debug;
use pimalaya_email::{
email::{local_draft_path, remove_local_draft},
AccountConfig, Backend, CompilerBuilder, Flag, Flags, Sender, Tpl,
AccountConfig, Backend, Flag, Flags, Sender, Tpl,
};
use std::{env, fs, process::Command};
@ -73,11 +73,11 @@ pub fn edit_tpl_with_editor<P: Printer>(
match choice::post_edit() {
Ok(PostEditChoice::Send) => {
printer.print_log("Sending email…")?;
let email = tpl.compile(
CompilerBuilder::default()
let email = tpl
.some_pgp_sign_cmd(config.email_writing_sign_cmd.clone())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()),
)?;
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone())
.compile()?
.write_to_vec()?;
sender.send(&email)?;
if config.email_sending_save_copy {
let sent_folder = config.sent_folder_alias()?;
@ -98,11 +98,11 @@ pub fn edit_tpl_with_editor<P: Printer>(
}
Ok(PostEditChoice::RemoteDraft) => {
let draft_folder = config.folder_alias("drafts")?;
let email = tpl.compile(
CompilerBuilder::default()
let email = tpl
.some_pgp_sign_cmd(config.email_writing_sign_cmd.clone())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()),
)?;
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone())
.compile()?
.write_to_vec()?;
backend.add_email(
&draft_folder,
&email,

View file

@ -8,7 +8,7 @@ use anyhow::{Context, Result};
use log::trace;
use pimalaya_email::EmailTextPlainFormat;
use termcolor::{Color, ColorSpec};
use terminal_size;
use terminal_size::terminal_size;
use unicode_width::UnicodeWidthStr;
use crate::printer::{Print, PrintTableOpts, WriteColor};
@ -175,7 +175,7 @@ where
EmailTextPlainFormat::Flowed => 0,
EmailTextPlainFormat::Auto => opts
.max_width
.or_else(|| terminal_size::terminal_size().map(|(w, _)| w.0 as usize))
.or_else(|| terminal_size().map(|(w, _)| w.0 as usize))
.unwrap_or(DEFAULT_TERM_WIDTH),
};
let mut table = vec![Self::head()];