From 65ac0c770286d99284096ce1377285688617bc98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Tue, 30 May 2023 00:34:15 +0200 Subject: [PATCH] improve tpl builders api --- Cargo.lock | 88 ++++++++++++++++++++++++++++-------- Cargo.toml | 2 +- src/domain/email/handlers.rs | 79 ++++++++++++++++---------------- src/domain/tpl/args.rs | 15 ++++-- src/domain/tpl/handlers.rs | 73 ++++++++++++++++-------------- src/ui/editor.rs | 22 ++++----- src/ui/table/table.rs | 4 +- 7 files changed, 174 insertions(+), 109 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f49d5bd..3c42264 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 0acfb65..8e550bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/domain/email/handlers.rs b/src/domain/email/handlers.rs index fa587f5..976698a 100644 --- a/src/domain/email/handlers.rs +++ b/src/domain/email/handlers.rs @@ -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( sender: &mut dyn Sender, folder: &str, id: &str, - headers: Option>, + headers: Option>, 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::>(); + 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( 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_( @@ -209,8 +214,9 @@ pub fn read( 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( // 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(&>::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( folder: &str, id: &str, all: bool, - headers: Option>, + headers: Option>, 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::>(); + 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( printer: &mut P, backend: &mut dyn Backend, sender: &mut dyn Sender, - headers: Option>, + headers: Option>, 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(()) diff --git a/src/domain/tpl/args.rs b/src/domain/tpl/args.rs index 50e17f5..71ae87b 100644 --- a/src/domain/tpl/args.rs +++ b/src/domain/tpl/args.rs @@ -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>; +pub type Headers<'a> = Option>; pub type Body<'a> = Option<&'a str>; /// Represents the template commands. @@ -121,8 +122,16 @@ pub fn args() -> Vec { /// 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::>()) + m.get_many::(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. diff --git a/src/domain/tpl/handlers.rs b/src/domain/tpl/handlers.rs index 90b14cf..22878ab 100644 --- a/src/domain/tpl/handlers.rs +++ b/src/domain/tpl/handlers.rs @@ -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( backend: &mut dyn Backend, folder: &str, id: &str, - headers: Option>, + headers: Option>, body: Option<&str>, ) -> Result<()> { let ids = id_mapper.get_ids([id])?; let ids = ids.iter().map(String::as_str).collect::>(); - 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(>::into(tpl)) + printer.print(tpl) } pub fn reply( @@ -37,21 +39,24 @@ pub fn reply( folder: &str, id: &str, all: bool, - headers: Option>, + headers: Option>, body: Option<&str>, ) -> Result<()> { let ids = id_mapper.get_ids([id])?; let ids = ids.iter().map(String::as_str).collect::>(); - 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(>::into(tpl)) + printer.print(tpl) } pub fn save( @@ -72,11 +77,10 @@ pub fn save( .collect::>() .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_sign_cmd(config.email_writing_sign_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( .collect::>() .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_sign_cmd(config.email_writing_sign_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( Ok(()) } -pub fn write<'a, P: Printer>( - config: &'a AccountConfig, - printer: &'a mut P, - headers: Option>, +pub fn write( + config: &AccountConfig, + printer: &mut P, + headers: Option>, body: Option<&str>, ) -> Result<()> { - let tpl = Email::new_tpl_builder(config)? - .set_some_raw_headers(headers) - .some_text_plain_part(body) - .build(); - printer.print(>::into(tpl)) + let tpl: String = Email::new_tpl_builder(config) + .some_headers(headers) + .some_body(body) + .build()? + .into(); + + printer.print(tpl) } diff --git a/src/ui/editor.rs b/src/ui/editor.rs index fd6334c..fd8e261 100644 --- a/src/ui/editor.rs +++ b/src/ui/editor.rs @@ -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( match choice::post_edit() { Ok(PostEditChoice::Send) => { printer.print_log("Sending email…")?; - let email = tpl.compile( - CompilerBuilder::default() - .some_pgp_sign_cmd(config.email_writing_sign_cmd.clone()) - .some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()), - )?; + let email = tpl + .some_pgp_sign_cmd(config.email_writing_sign_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( } Ok(PostEditChoice::RemoteDraft) => { let draft_folder = config.folder_alias("drafts")?; - let email = tpl.compile( - CompilerBuilder::default() - .some_pgp_sign_cmd(config.email_writing_sign_cmd.clone()) - .some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()), - )?; + let email = tpl + .some_pgp_sign_cmd(config.email_writing_sign_cmd.clone()) + .some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()) + .compile()? + .write_to_vec()?; backend.add_email( &draft_folder, &email, diff --git a/src/ui/table/table.rs b/src/ui/table/table.rs index 4c43540..6e4343d 100644 --- a/src/ui/table/table.rs +++ b/src/ui/table/table.rs @@ -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()];