diff --git a/Cargo.lock b/Cargo.lock index 0aada856..8bef91db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,17 +91,6 @@ dependencies = [ "object", ] -[[package]] -name = "ariadne" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "367fd0ad87307588d087544707bc5fbf4805ded96c7db922b70d368fa1cb5702" -dependencies = [ - "concolor", - "unicode-width 0.1.14", - "yansi", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -136,12 +125,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.11.0" @@ -168,12 +151,6 @@ dependencies = [ "syn", ] -[[package]] -name = "bumpalo" -version = "3.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" - [[package]] name = "bytes" version = "1.11.1" @@ -333,23 +310,6 @@ dependencies = [ "unicode-width 0.2.2", ] -[[package]] -name = "concolor" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "318d6c16e73b3a900eb212ad6a82fc7d298c5ab8184c7a9998646455bc474a16" -dependencies = [ - "bitflags 1.3.2", - "concolor-query", - "is-terminal", -] - -[[package]] -name = "concolor-query" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a90734b3d5dcf656e7624cca6bce9c3a90ee11f900e80141a7427ccfb3d317" - [[package]] name = "core-foundation" version = "0.10.1" @@ -372,7 +332,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.11.0", + "bitflags", "crossterm_winapi", "libc", "mio", @@ -389,7 +349,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.0", + "bitflags", "crossterm_winapi", "document-features", "parking_lot", @@ -611,7 +571,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ - "bitflags 2.11.0", + "bitflags", "libc", "libgit2-sys", "log", @@ -641,18 +601,11 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - [[package]] name = "himalaya" version = "1.2.0" dependencies = [ "anyhow", - "ariadne", "chrono", "clap", "comfy-table", @@ -673,11 +626,9 @@ dependencies = [ "rustls-platform-verifier", "secrecy", "serde", - "serde_json", "shellexpand", "uds_windows", "url", - "uuid", ] [[package]] @@ -909,17 +860,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "is-terminal" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.61.2", -] - [[package]] name = "is-wsl" version = "0.4.0" @@ -998,16 +938,6 @@ dependencies = [ "libc", ] -[[package]] -name = "js-sys" -version = "0.3.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - [[package]] name = "leb128fmt" version = "0.1.0" @@ -1228,7 +1158,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.11.0", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -1254,6 +1184,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "openssl-src" +version = "300.5.5+3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.111" @@ -1262,6 +1201,7 @@ checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] @@ -1503,7 +1443,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] @@ -1603,7 +1543,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1685,12 +1625,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - [[package]] name = "same-file" version = "1.0.6" @@ -1731,7 +1665,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -2177,17 +2111,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uuid" -version = "1.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" -dependencies = [ - "getrandom 0.4.2", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "vcpkg" version = "0.2.15" @@ -2228,51 +2151,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasm-bindgen" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" -dependencies = [ - "unicode-ident", -] - [[package]] name = "wasm-encoder" version = "0.244.0" @@ -2301,7 +2179,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags", "hashbrown 0.15.5", "indexmap", "semver", @@ -2717,7 +2595,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags", "indexmap", "log", "serde", @@ -2753,12 +2631,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - [[package]] name = "yoke" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 23ed8c93..946540bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,33 +12,26 @@ documentation = "https://github.com/pimalaya/himalaya" repository = "https://github.com/pimalaya/himalaya" [package.metadata.docs.rs] -features = ["imap", "maildir", "smtp", "sendmail", "oauth2", "wizard", "pgp-commands", "pgp-native"] +all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["smtp", "imap", "rustls-ring"] -smtp = ["dep:io-smtp"] +default = ["imap", "smtp", "rustls-ring"] + imap = ["dep:io-imap"] +smtp = ["dep:io-smtp"] + rustls-aws = ["dep:rustls-platform-verifier", "rustls/aws-lc-rs"] rustls-ring = ["dep:rustls-platform-verifier", "rustls/ring"] native-tls = ["dep:native-tls"] -# maildir = ["email-lib/maildir", "pimalaya-tui/maildir"] -# notmuch = ["email-lib/notmuch", "pimalaya-tui/notmuch"] -# smtp = ["email-lib/smtp", "pimalaya-tui/smtp"] -# sendmail = ["email-lib/sendmail", "pimalaya-tui/sendmail"] -# keyring = ["email-lib/keyring", "pimalaya-tui/keyring", "secret-lib/keyring"] -# oauth2 = ["email-lib/oauth2", "pimalaya-tui/oauth2", "keyring"] -# wizard = ["email-lib/autoconfig", "pimalaya-tui/wizard"] -# pgp-commands = ["email-lib/pgp-commands", "mml-lib/pgp-commands", "pimalaya-tui/pgp-commands"] -# pgp-gpg = ["email-lib/pgp-gpg", "mml-lib/pgp-gpg", "pimalaya-tui/pgp-gpg"] -# pgp-native = ["email-lib/pgp-native", "mml-lib/pgp-native", "pimalaya-tui/pgp-native"] + +vendored = ["native-tls?/vendored"] [build-dependencies] pimalaya-toolbox = { version = "0.0.4", default-features = false, features = ["build"] } [dependencies] anyhow = "1" -ariadne = { version = "0.2", features = ["auto-color"] } chrono = { version = "0.4", default-features = false } clap = { version = "4.4", features = ["derive", "env", "wrap_help"] } comfy-table = "7" @@ -59,10 +52,8 @@ rustls = { version = "0.23", default-features = false, optional = true } rustls-platform-verifier = { version = "0.6", optional = true } secrecy = "0.10" serde = { version = "1", features = ["derive"] } -serde_json = "1" shellexpand = "3.1" url = { version = "2.2", features = ["serde"] } -uuid = { version = "1.19", features = ["v4"] } [target.'cfg(windows)'.dependencies] uds_windows = "1" diff --git a/src/cli.rs b/src/cli.rs index 47bca099..f308dba1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -61,31 +61,35 @@ pub enum BackendCommand { } impl BackendCommand { - pub fn execute( + pub fn exec( self, printer: &mut impl Printer, config_paths: &[PathBuf], account_name: Option<&str>, ) -> Result<()> { match self { + #[cfg(feature = "imap")] Self::Imap(cmd) => { let config = Config::from_paths_or_default(config_paths)?; - let (_, account_config) = config.get_account(account_name)?; + let (account_name, account_config) = config.get_account(account_name)?; let Some(imap_config) = account_config.imap else { - bail!("IMAP config is missing for this account") + bail!("IMAP config is missing for account `{account_name}`") }; - cmd.execute(printer, imap_config) + cmd.exec(printer, imap_config) } + #[cfg(feature = "smtp")] Self::Smtp(cmd) => { let config = Config::from_paths_or_default(config_paths)?; - let (_, account_config) = config.get_account(account_name)?; + let (account_name, account_config) = config.get_account(account_name)?; let Some(smtp_config) = account_config.smtp else { - bail!("SMTP config is missing for this account") + bail!("SMTP config is missing for account `{account_name}`") }; - cmd.execute(printer, smtp_config) + cmd.exec(printer, smtp_config) } + #[allow(unreachable_patterns)] + _ => bail!("No backend available"), } } } diff --git a/src/config.rs b/src/config.rs index 3c6f5cf4..105d725e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,16 +6,18 @@ use secrecy::SecretString; use serde::{de::Visitor, Deserialize, Deserializer}; use url::Url; +/// Global configuration. +/// +/// Represents the whole TOML user's configuration file. #[derive(Clone, Debug, Default, Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct Config { - #[serde(alias = "name")] pub display_name: Option, pub signature: Option, pub signature_delim: Option, pub downloads_dir: Option, + pub accounts: HashMap, - // pub account: Option, } impl TomlConfig for Config { @@ -39,35 +41,62 @@ impl TomlConfig for Config { } } -/// The account configuration. +/// Account configuration. #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct AccountConfig { #[serde(default)] pub default: bool, - pub imap: Option, - pub smtp: Option, + #[serde(deserialize_with = "shell_expanded_string")] pub email: String, pub display_name: Option, pub signature: Option, pub signature_delim: Option, pub downloads_dir: Option, - // pub backend: Option, - // #[cfg(feature = "pgp")] - // pub pgp: Option, - // #[cfg(not(feature = "pgp"))] - // #[serde(default)] - // #[serde(skip_serializing, deserialize_with = "missing_pgp_feature")] - // pub pgp: Option<()>, - // pub folder: Option, - // pub envelope: Option, - // pub message: Option, - // pub template: Option, + pub imap: Option, + pub smtp: Option, } -/// The account configuration. +/// IMAP configuration. +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub struct ImapConfig { + pub url: Url, + + #[serde(default)] + pub tls: TlsConfig, + #[serde(default)] + pub starttls: bool, + #[serde(default)] + pub sasl: SaslConfig, + + #[serde(default)] + pub ext: ImapExtensionsConfig, +} + +/// IMAP extensions configuration. +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub struct ImapExtensionsConfig { + #[serde(default)] + id: ImapIdExtensionConfig, +} + +/// IMAP ID configuration. +/// +/// https://www.rfc-editor.org/rfc/rfc2971.html +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub struct ImapIdExtensionConfig { + /// Automatically sends the ID command straight after + /// authentication. + #[serde(default)] + send_after_auth: bool, +} + +/// SMTP configuration. #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct SmtpConfig { @@ -81,44 +110,7 @@ pub struct SmtpConfig { pub sasl: SaslConfig, } -/// The account configuration. -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -pub struct ImapConfig { - pub url: Url, - - #[serde(default)] - pub tls: TlsConfig, - #[serde(default)] - pub starttls: bool, - #[serde(default)] - pub sasl: SaslConfig, - - /// The IMAP extensions configuration. - #[serde(default)] - pub extensions: ImapExtensionsConfig, -} - -/// The IMAP configuration dedicated to extensions. -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -pub struct ImapExtensionsConfig { - #[serde(default)] - id: ImapIdExtensionConfig, -} - -/// The IMAP configuration dedicated to the ID extension. -/// -/// https://www.rfc-editor.org/rfc/rfc2971.html -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -pub struct ImapIdExtensionConfig { - /// Automatically sends the ID command straight after - /// authentication. - #[serde(default)] - always_after_auth: bool, -} - +/// SSL/TLS configuration. #[derive(Clone, Debug, Default, Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct TlsConfig { @@ -128,6 +120,7 @@ pub struct TlsConfig { pub cert: Option, } +/// SSL/TLS provider configuration. #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub enum TlsProviderConfig { @@ -135,12 +128,14 @@ pub enum TlsProviderConfig { NativeTls, } +/// Rustls configuration. #[derive(Clone, Debug, Default, Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct RustlsConfig { pub crypto: Option, } +/// Rustls crypto provider configuration. #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub enum RustlsCryptoConfig { @@ -148,6 +143,7 @@ pub enum RustlsCryptoConfig { Ring, } +/// SASL configuration. #[derive(Clone, Debug, Default, Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct SaslConfig { @@ -162,6 +158,7 @@ fn default_sasl_mechanisms() -> Vec { vec![SaslMechanismConfig::Plain, SaslMechanismConfig::Login] } +/// SASL mechanism configuration. #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum SaslMechanismConfig { @@ -170,6 +167,31 @@ pub enum SaslMechanismConfig { Anonymous, } +/// SASL LOGIN configuration. +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub struct SaslLoginConfig { + pub username: String, + pub password: SecretConfig, +} + +/// SASL PLAIN configuration. +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub struct SaslPlainConfig { + pub authzid: Option, + pub authcid: String, + pub passwd: SecretConfig, +} + +/// SASL ANONYMOUS configuration. +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub struct SaslAnonymousConfig { + pub message: Option, +} + +/// Secret configuration. #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum SecretConfig { @@ -208,27 +230,6 @@ impl SecretConfig { } } -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -pub struct SaslLoginConfig { - pub username: String, - pub password: SecretConfig, -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -pub struct SaslPlainConfig { - pub authzid: Option, - pub authcid: String, - pub passwd: SecretConfig, -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -pub struct SaslAnonymousConfig { - pub message: Option, -} - struct ShellExpandedStringVisitor; impl<'de> Visitor<'de> for ShellExpandedStringVisitor { diff --git a/src/email/envelope/arg/ids.rs b/src/email/envelope/arg/ids.rs deleted file mode 100644 index 2ed73d9e..00000000 --- a/src/email/envelope/arg/ids.rs +++ /dev/null @@ -1,17 +0,0 @@ -use clap::Parser; - -/// The envelope id argument parser. -#[derive(Debug, Parser)] -pub struct EnvelopeIdArg { - /// The envelope id. - #[arg(value_name = "ID", required = true)] - pub id: usize, -} - -/// The envelopes ids arguments parser. -#[derive(Debug, Parser)] -pub struct EnvelopeIdsArgs { - /// The list of envelopes ids. - #[arg(value_name = "ID", required = true)] - pub ids: Vec, -} diff --git a/src/email/envelope/arg/mod.rs b/src/email/envelope/arg/mod.rs deleted file mode 100644 index ff6b00d1..00000000 --- a/src/email/envelope/arg/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod ids; diff --git a/src/email/envelope/command/list.rs b/src/email/envelope/command/list.rs deleted file mode 100644 index a81c836e..00000000 --- a/src/email/envelope/command/list.rs +++ /dev/null @@ -1,218 +0,0 @@ -use std::{process::exit, sync::Arc}; - -use ariadne::{Color, Label, Report, ReportKind, Source}; -use clap::Parser; -use color_eyre::Result; -use email::{ - backend::feature::BackendFeatureSource, config::Config, email::search_query, - envelope::list::ListEnvelopesOptions, search_query::SearchEmailsQuery, -}; -use pimalaya_tui::{ - himalaya::{backend::BackendBuilder, config::EnvelopesTable}, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, config::TomlConfig, - folder::arg::name::FolderNameOptionalFlag, -}; - -/// Search and sort envelopes as a list. -/// -/// This command allows you to list envelopes included in the given -/// folder, matching the given query. -#[derive(Debug, Parser)] -pub struct EnvelopeListCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - /// The page number. - /// - /// The page number starts from 1 (which is the default). Giving a - /// page number to big will result in a out of bound error. - #[arg(long, short, value_name = "NUMBER", default_value = "1")] - pub page: usize, - - /// The page size. - /// - /// Determine the amount of envelopes a page should contain. - #[arg(long, short = 's', value_name = "NUMBER")] - pub page_size: Option, - - #[command(flatten)] - pub account: AccountNameFlag, - - /// The maximum width the table should not exceed. - /// - /// This argument will force the table not to exceed the given - /// width in pixels. Columns may shrink with ellipsis in order to - /// fit the width. - #[arg(long = "max-width", short = 'w')] - #[arg(name = "table_max_width", value_name = "PIXELS")] - pub table_max_width: Option, - - /// The list envelopes filter and sort query. - /// - /// The query can be a filter query, a sort query or both - /// together. - /// - /// A filter query is composed of operators and conditions. There - /// is 3 operators and 8 conditions: - /// - /// • not → filter envelopes that do not match the - /// condition - /// - /// • and → filter envelopes that match - /// both conditions - /// - /// • or → filter envelopes that match - /// one of the conditions - /// - /// ◦ date → filter envelopes that match the given - /// date - /// - /// ◦ before → filter envelopes with date strictly - /// before the given one - /// - /// ◦ after → filter envelopes with date stricly - /// after the given one - /// - /// ◦ from → filter envelopes with senders matching the - /// given pattern - /// - /// ◦ to → filter envelopes with recipients matching - /// the given pattern - /// - /// ◦ subject → filter envelopes with subject matching - /// the given pattern - /// - /// ◦ body → filter envelopes with text bodies matching - /// the given pattern - /// - /// ◦ flag → filter envelopes matching the given flag - /// - /// A sort query starts by "order by", and is composed of kinds - /// and orders. There is 4 kinds and 2 orders: - /// - /// • date [order] → sort envelopes by date - /// - /// • from [order] → sort envelopes by sender - /// - /// • to [order] → sort envelopes by recipient - /// - /// • subject [order] → sort envelopes by subject - /// - /// ◦ asc → sort envelopes by the given kind in ascending - /// order - /// - /// ◦ desc → sort envelopes by the given kind in - /// descending order - /// - /// Examples: - /// - /// subject foo and body bar → filter envelopes containing "foo" - /// in their subject and "bar" in their text bodies - /// - /// order by date desc subject → sort envelopes by descending date - /// (most recent first), then by ascending subject - /// - /// subject foo and body bar order by date desc subject → - /// combination of the 2 previous examples - #[arg(allow_hyphen_values = true, trailing_var_arg = true)] - pub query: Option>, -} - -impl Default for EnvelopeListCommand { - fn default() -> Self { - Self { - folder: Default::default(), - page: 1, - page_size: Default::default(), - account: Default::default(), - query: Default::default(), - table_max_width: Default::default(), - } - } -} - -impl EnvelopeListCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing list envelopes command"); - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let toml_account_config = Arc::new(toml_account_config); - - let folder = &self.folder.name; - let page = 1.max(self.page) - 1; - let page_size = self - .page_size - .unwrap_or_else(|| account_config.get_envelope_list_page_size()); - - let backend = BackendBuilder::new( - toml_account_config.clone(), - Arc::new(account_config), - |builder| { - builder - .without_features() - .with_list_envelopes(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - let query = self - .query - .map(|query| query.join(" ").parse::()); - let query = match query { - None => None, - Some(Ok(query)) => Some(query), - Some(Err(main_err)) => { - let source = "query"; - let search_query::error::Error::ParseError(errs, query) = &main_err; - for err in errs { - Report::build(ReportKind::Error, source, err.span().start) - .with_message(main_err.to_string()) - .with_label( - Label::new((source, err.span().into_range())) - .with_message(err.reason().to_string()) - .with_color(Color::Red), - ) - .finish() - .eprint((source, Source::from(&query))) - .unwrap(); - } - - exit(0) - } - }; - - let opts = ListEnvelopesOptions { - page, - page_size, - query, - }; - - let envelopes = backend.list_envelopes(folder, opts).await?; - let table = EnvelopesTable::from(envelopes) - .with_some_width(self.table_max_width) - .with_some_preset(toml_account_config.envelope_list_table_preset()) - .with_some_unseen_char(toml_account_config.envelope_list_table_unseen_char()) - .with_some_replied_char(toml_account_config.envelope_list_table_replied_char()) - .with_some_flagged_char(toml_account_config.envelope_list_table_flagged_char()) - .with_some_attachment_char(toml_account_config.envelope_list_table_attachment_char()) - .with_some_id_color(toml_account_config.envelope_list_table_id_color()) - .with_some_flags_color(toml_account_config.envelope_list_table_flags_color()) - .with_some_subject_color(toml_account_config.envelope_list_table_subject_color()) - .with_some_sender_color(toml_account_config.envelope_list_table_sender_color()) - .with_some_date_color(toml_account_config.envelope_list_table_date_color()); - - printer.out(table) - } -} diff --git a/src/email/envelope/command/mod.rs b/src/email/envelope/command/mod.rs deleted file mode 100644 index 6966e592..00000000 --- a/src/email/envelope/command/mod.rs +++ /dev/null @@ -1,35 +0,0 @@ -pub mod list; -pub mod thread; - -use clap::Subcommand; -use color_eyre::Result; -use pimalaya_tui::terminal::cli::printer::Printer; - -use crate::config::TomlConfig; - -use self::{list::EnvelopeListCommand, thread::EnvelopeThreadCommand}; - -/// List, search and sort your envelopes. -/// -/// An envelope is a small representation of a message. It contains an -/// identifier (given by the backend), some flags as well as few -/// headers from the message itself. This subcommand allows you to -/// manage them. -#[derive(Debug, Subcommand)] -pub enum EnvelopeSubcommand { - #[command(alias = "lst")] - List(EnvelopeListCommand), - - #[command()] - Thread(EnvelopeThreadCommand), -} - -impl EnvelopeSubcommand { - #[allow(unused)] - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - match self { - Self::List(cmd) => cmd.execute(printer, config).await, - Self::Thread(cmd) => cmd.execute(printer, config).await, - } - } -} diff --git a/src/email/envelope/command/thread.rs b/src/email/envelope/command/thread.rs deleted file mode 100644 index 189a3024..00000000 --- a/src/email/envelope/command/thread.rs +++ /dev/null @@ -1,201 +0,0 @@ -use ariadne::{Label, Report, ReportKind, Source}; -use clap::Parser; -use color_eyre::Result; -use email::{ - backend::feature::BackendFeatureSource, config::Config, email::search_query, - envelope::list::ListEnvelopesOptions, search_query::SearchEmailsQuery, -}; -use pimalaya_tui::{ - himalaya::{backend::BackendBuilder, config::EnvelopesTree}, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use std::{process::exit, sync::Arc}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, config::TomlConfig, - folder::arg::name::FolderNameOptionalFlag, -}; - -/// Search and sort envelopes as a thread. -/// -/// This command allows you to thread envelopes included in the given -/// folder, matching the given query. -#[derive(Debug, Parser)] -pub struct EnvelopeThreadCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub account: AccountNameFlag, - - /// Show only threads that contain the given envelope identifier. - #[arg(long, short)] - pub id: Option, - - /// The list envelopes filter and sort query. - /// - /// See `envelope list --help` for more information. - #[arg(allow_hyphen_values = true, trailing_var_arg = true)] - pub query: Option>, -} - -impl EnvelopeThreadCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing thread envelopes command"); - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - let folder = &self.folder.name; - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_thread_envelopes(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - let query = self - .query - .map(|query| query.join(" ").parse::()); - let query = match query { - None => None, - Some(Ok(query)) => Some(query), - Some(Err(main_err)) => { - let source = "query"; - let search_query::error::Error::ParseError(errs, query) = &main_err; - for err in errs { - Report::build(ReportKind::Error, source, err.span().start) - .with_message(main_err.to_string()) - .with_label( - Label::new((source, err.span().into_range())) - .with_message(err.reason().to_string()) - .with_color(ariadne::Color::Red), - ) - .finish() - .eprint((source, Source::from(&query))) - .unwrap(); - } - - exit(0) - } - }; - - let opts = ListEnvelopesOptions { - page: 0, - page_size: 0, - query, - }; - - let envelopes = match self.id { - Some(id) => backend.thread_envelope(folder, id, opts).await, - None => backend.thread_envelopes(folder, opts).await, - }?; - - let tree = EnvelopesTree::new(account_config, envelopes); - - printer.out(tree) - } -} - -// #[cfg(test)] -// mod test { -// use email::{account::config::AccountConfig, envelope::ThreadedEnvelope}; -// use petgraph::graphmap::DiGraphMap; - -// use super::write_tree; - -// macro_rules! e { -// ($id:literal) => { -// ThreadedEnvelope { -// id: $id, -// message_id: $id, -// from: "", -// subject: "", -// date: Default::default(), -// } -// }; -// } - -// #[test] -// fn tree_1() { -// let config = AccountConfig::default(); -// let mut buf = Vec::new(); -// let mut graph = DiGraphMap::new(); -// graph.add_edge(e!("0"), e!("1"), 0); -// graph.add_edge(e!("0"), e!("2"), 0); -// graph.add_edge(e!("0"), e!("3"), 0); - -// write_tree(&config, &mut buf, &graph, e!("0"), String::new(), 0).unwrap(); -// let buf = String::from_utf8_lossy(&buf); - -// let expected = " -// 0 -// ├─ 1 -// ├─ 2 -// └─ 3 -// "; -// assert_eq!(expected.trim_start(), buf) -// } - -// #[test] -// fn tree_2() { -// let config = AccountConfig::default(); -// let mut buf = Vec::new(); -// let mut graph = DiGraphMap::new(); -// graph.add_edge(e!("0"), e!("1"), 0); -// graph.add_edge(e!("1"), e!("2"), 1); -// graph.add_edge(e!("1"), e!("3"), 1); - -// write_tree(&config, &mut buf, &graph, e!("0"), String::new(), 0).unwrap(); -// let buf = String::from_utf8_lossy(&buf); - -// let expected = " -// 0 -// └─ 1 -// ├─ 2 -// └─ 3 -// "; -// assert_eq!(expected.trim_start(), buf) -// } - -// #[test] -// fn tree_3() { -// let config = AccountConfig::default(); -// let mut buf = Vec::new(); -// let mut graph = DiGraphMap::new(); -// graph.add_edge(e!("0"), e!("1"), 0); -// graph.add_edge(e!("1"), e!("2"), 1); -// graph.add_edge(e!("2"), e!("22"), 2); -// graph.add_edge(e!("1"), e!("3"), 1); -// graph.add_edge(e!("0"), e!("4"), 0); -// graph.add_edge(e!("4"), e!("5"), 1); -// graph.add_edge(e!("5"), e!("6"), 2); - -// write_tree(&config, &mut buf, &graph, e!("0"), String::new(), 0).unwrap(); -// let buf = String::from_utf8_lossy(&buf); - -// let expected = " -// 0 -// ├─ 1 -// │ ├─ 2 -// │ │ └─ 22 -// │ └─ 3 -// └─ 4 -// └─ 5 -// └─ 6 -// "; -// assert_eq!(expected.trim_start(), buf) -// } -// } diff --git a/src/email/envelope/flag/arg/ids_and_flags.rs b/src/email/envelope/flag/arg/ids_and_flags.rs deleted file mode 100644 index 8827a817..00000000 --- a/src/email/envelope/flag/arg/ids_and_flags.rs +++ /dev/null @@ -1,48 +0,0 @@ -use clap::Parser; -use email::flag::{Flag, Flags}; -use tracing::debug; - -/// The ids and/or flags arguments parser. -#[derive(Debug, Parser)] -pub struct IdsAndFlagsArgs { - /// The list of ids and/or flags. - /// - /// Every argument that can be parsed as an integer is considered - /// an id, otherwise it is considered as a flag. - #[arg(value_name = "ID-OR-FLAG", required = true)] - pub ids_and_flags: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub enum IdOrFlag { - Id(usize), - Flag(Flag), -} - -impl From<&str> for IdOrFlag { - fn from(value: &str) -> Self { - value.parse::().map(Self::Id).unwrap_or_else(|err| { - let flag = Flag::from(value); - debug!("cannot parse {value} as usize, parsing it as flag {flag}"); - debug!("{err:?}"); - Self::Flag(flag) - }) - } -} - -pub fn into_tuple(ids_and_flags: &[IdOrFlag]) -> (Vec, Flags) { - ids_and_flags.iter().fold( - (Vec::default(), Flags::default()), - |(mut ids, mut flags), arg| { - match arg { - IdOrFlag::Id(id) => { - ids.push(*id); - } - IdOrFlag::Flag(flag) => { - flags.insert(flag.to_owned()); - } - }; - (ids, flags) - }, - ) -} diff --git a/src/email/envelope/flag/arg/mod.rs b/src/email/envelope/flag/arg/mod.rs deleted file mode 100644 index c98b755a..00000000 --- a/src/email/envelope/flag/arg/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod ids_and_flags; diff --git a/src/email/envelope/flag/command/add.rs b/src/email/envelope/flag/command/add.rs deleted file mode 100644 index c54b416a..00000000 --- a/src/email/envelope/flag/command/add.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::Result; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, - config::TomlConfig, - flag::arg::ids_and_flags::{into_tuple, IdsAndFlagsArgs}, - folder::arg::name::FolderNameOptionalFlag, -}; - -/// Add flag(s) to the given envelope. -/// -/// This command allows you to attach the given flag(s) to the given -/// envelope(s). -#[derive(Debug, Parser)] -pub struct FlagAddCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub args: IdsAndFlagsArgs, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl FlagAddCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing add flag(s) command"); - - let folder = &self.folder.name; - let (ids, flags) = into_tuple(&self.args.ids_and_flags); - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - Arc::new(account_config), - |builder| { - builder - .without_features() - .with_add_flags(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - backend.add_flags(folder, &ids, &flags).await?; - - printer.out(format!("Flag(s) {flags} successfully added!\n")) - } -} diff --git a/src/email/envelope/flag/command/mod.rs b/src/email/envelope/flag/command/mod.rs deleted file mode 100644 index 106b9bb3..00000000 --- a/src/email/envelope/flag/command/mod.rs +++ /dev/null @@ -1,42 +0,0 @@ -mod add; -mod remove; -mod set; - -use clap::Subcommand; -use color_eyre::Result; -use pimalaya_tui::terminal::cli::printer::Printer; - -use crate::config::TomlConfig; - -use self::{add::FlagAddCommand, remove::FlagRemoveCommand, set::FlagSetCommand}; - -/// Add, change and remove your envelopes flags. -/// -/// A flag is a tag associated to an envelope. Existing flags are -/// seen, answered, flagged, deleted, draft. Other flags are -/// considered custom, which are not always supported. -#[derive(Debug, Subcommand)] -pub enum FlagSubcommand { - #[command(arg_required_else_help = true)] - #[command(alias = "create")] - Add(FlagAddCommand), - - #[command(arg_required_else_help = true)] - #[command(aliases = ["update", "change", "replace"])] - Set(FlagSetCommand), - - #[command(arg_required_else_help = true)] - #[command(aliases = ["rm", "delete", "del"])] - Remove(FlagRemoveCommand), -} - -impl FlagSubcommand { - #[allow(unused)] - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - match self { - Self::Add(cmd) => cmd.execute(printer, config).await, - Self::Set(cmd) => cmd.execute(printer, config).await, - Self::Remove(cmd) => cmd.execute(printer, config).await, - } - } -} diff --git a/src/email/envelope/flag/command/remove.rs b/src/email/envelope/flag/command/remove.rs deleted file mode 100644 index 1d73a7b9..00000000 --- a/src/email/envelope/flag/command/remove.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::Result; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, - config::TomlConfig, - flag::arg::ids_and_flags::{into_tuple, IdsAndFlagsArgs}, - folder::arg::name::FolderNameOptionalFlag, -}; - -/// Remove flag(s) from a given envelope. -/// -/// This command allows you to remove the given flag(s) from the given -/// envelope(s). -#[derive(Debug, Parser)] -pub struct FlagRemoveCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub args: IdsAndFlagsArgs, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl FlagRemoveCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing remove flag(s) command"); - - let folder = &self.folder.name; - let (ids, flags) = into_tuple(&self.args.ids_and_flags); - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - Arc::new(account_config), - |builder| { - builder - .without_features() - .with_remove_flags(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - backend.remove_flags(folder, &ids, &flags).await?; - - printer.out(format!("Flag(s) {flags} successfully removed!\n")) - } -} diff --git a/src/email/envelope/flag/command/set.rs b/src/email/envelope/flag/command/set.rs deleted file mode 100644 index df473d71..00000000 --- a/src/email/envelope/flag/command/set.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::Result; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, - config::TomlConfig, - flag::arg::ids_and_flags::{into_tuple, IdsAndFlagsArgs}, - folder::arg::name::FolderNameOptionalFlag, -}; - -/// Replace flag(s) of a given envelope. -/// -/// This command allows you to replace existing flags of the given -/// envelope(s) with the given flag(s). -#[derive(Debug, Parser)] -pub struct FlagSetCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub args: IdsAndFlagsArgs, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl FlagSetCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing set flag(s) command"); - - let folder = &self.folder.name; - let (ids, flags) = into_tuple(&self.args.ids_and_flags); - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - Arc::new(account_config), - |builder| { - builder - .without_features() - .with_set_flags(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - backend.set_flags(folder, &ids, &flags).await?; - - printer.out(format!("Flag(s) {flags} successfully replaced!\n")) - } -} diff --git a/src/email/envelope/flag/mod.rs b/src/email/envelope/flag/mod.rs deleted file mode 100644 index 1a3a73a9..00000000 --- a/src/email/envelope/flag/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod arg; -pub mod command; diff --git a/src/email/envelope/mod.rs b/src/email/envelope/mod.rs deleted file mode 100644 index e84d06ee..00000000 --- a/src/email/envelope/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod arg; -pub mod command; -pub mod flag; diff --git a/src/email/message/arg/body.rs b/src/email/message/arg/body.rs deleted file mode 100644 index 5ceeb19e..00000000 --- a/src/email/message/arg/body.rs +++ /dev/null @@ -1,25 +0,0 @@ -use clap::Parser; -use std::ops::Deref; - -/// The raw message body argument parser. -#[derive(Debug, Parser)] -pub struct MessageRawBodyArg { - /// Prefill the template with a custom body. - #[arg(trailing_var_arg = true)] - #[arg(name = "body_raw", value_name = "BODY")] - pub raw: Vec, -} - -impl MessageRawBodyArg { - pub fn raw(self) -> String { - self.raw.join(" ").replace('\r', "").replace('\n', "\r\n") - } -} - -impl Deref for MessageRawBodyArg { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.raw - } -} diff --git a/src/email/message/arg/header.rs b/src/email/message/arg/header.rs deleted file mode 100644 index 73713798..00000000 --- a/src/email/message/arg/header.rs +++ /dev/null @@ -1,20 +0,0 @@ -use clap::Parser; - -/// The envelope id argument parser. -#[derive(Debug, Parser)] -pub struct HeaderRawArgs { - /// Prefill the template with custom headers. - /// - /// A raw header should follow the pattern KEY:VAL. - #[arg(long = "header", short = 'H', required = false)] - #[arg(name = "header-raw", value_name = "KEY:VAL", value_parser = raw_header_parser)] - pub raw: Vec<(String, String)>, -} - -pub fn raw_header_parser(raw_header: &str) -> Result<(String, String), String> { - if let Some((key, val)) = raw_header.split_once(':') { - Ok((key.trim().to_owned(), val.trim().to_owned())) - } else { - Err(format!("cannot parse raw header {raw_header:?}")) - } -} diff --git a/src/email/message/arg/mod.rs b/src/email/message/arg/mod.rs deleted file mode 100644 index d78368d3..00000000 --- a/src/email/message/arg/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -use clap::Parser; - -pub mod body; -pub mod header; -pub mod reply; - -/// The raw message argument parser. -#[derive(Debug, Parser)] -pub struct MessageRawArg { - /// The raw message, including headers and body. - #[arg(trailing_var_arg = true)] - #[arg(name = "message_raw", value_name = "MESSAGE")] - pub raw: Vec, -} - -impl MessageRawArg { - pub fn raw(self) -> String { - self.raw.join(" ").replace('\r', "").replace('\n', "\r\n") - } -} diff --git a/src/email/message/arg/reply.rs b/src/email/message/arg/reply.rs deleted file mode 100644 index 20491dc7..00000000 --- a/src/email/message/arg/reply.rs +++ /dev/null @@ -1,12 +0,0 @@ -use clap::Parser; - -/// The reply to all argument parser. -#[derive(Debug, Parser)] -pub struct MessageReplyAllArg { - /// Reply to all recipients. - /// - /// This argument will add all recipients for the To and Cc - /// headers. - #[arg(long, short = 'A')] - pub all: bool, -} diff --git a/src/email/message/attachment/command/download.rs b/src/email/message/attachment/command/download.rs deleted file mode 100644 index 774e638a..00000000 --- a/src/email/message/attachment/command/download.rs +++ /dev/null @@ -1,113 +0,0 @@ -use clap::Parser; -use color_eyre::{eyre::Context, Result}; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use std::{fs, path::PathBuf, sync::Arc}; -use tracing::info; -use uuid::Uuid; - -use crate::{ - account::arg::name::AccountNameFlag, config::TomlConfig, dir_parser, - envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameOptionalFlag, -}; - -/// Download all attachments found in the given message. -/// -/// This command allows you to download all attachments found for the -/// given message to your downloads directory. -#[derive(Debug, Parser)] -pub struct AttachmentDownloadCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub envelopes: EnvelopeIdsArgs, - - #[command(flatten)] - pub account: AccountNameFlag, - - /// Override the download directory. - /// - /// If omitted, it uses the downloads directory from the config, - /// or `XDG_DOWNLOAD_DIR`. - #[arg(short = 'd', long, value_name = "PATH", value_parser = dir_parser)] - pub downloads_dir: Option, -} - -impl AttachmentDownloadCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing download attachment(s) command"); - - let folder = &self.folder.name; - let ids = &self.envelopes.ids; - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_get_messages(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - let emails = backend.get_messages(folder, ids).await?; - - let mut emails_count = 0; - let mut attachments_count = 0; - - let mut ids = ids.iter(); - for email in emails.to_vec() { - let id = ids.next().unwrap(); - let attachments = email.attachments()?; - - if attachments.is_empty() { - printer.log(format!("No attachment found for message {id}!\n"))?; - continue; - } else { - emails_count += 1; - } - - printer.log(format!( - "{} attachment(s) found for message {id}!\n", - attachments.len() - ))?; - - for attachment in attachments { - let filename: PathBuf = attachment - .filename - .unwrap_or_else(|| Uuid::new_v4().to_string()) - .into(); - let filepath = account_config - .get_download_file_path(self.downloads_dir.as_deref(), &filename)?; - printer.log(format!("Downloading {:?}…\n", filepath))?; - fs::write(&filepath, &attachment.body) - .with_context(|| format!("cannot save attachment at {filepath:?}"))?; - attachments_count += 1; - } - } - - match attachments_count { - 0 => printer.out("No attachment found!\n"), - 1 => printer.out("Downloaded 1 attachment!\n"), - n => printer.out(format!( - "Downloaded {} attachment(s) from {} messages(s)!\n", - n, emails_count, - )), - } - } -} diff --git a/src/email/message/attachment/command/mod.rs b/src/email/message/attachment/command/mod.rs deleted file mode 100644 index e661fe5f..00000000 --- a/src/email/message/attachment/command/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -mod download; - -use clap::Subcommand; -use color_eyre::Result; -use pimalaya_tui::terminal::cli::printer::Printer; - -use crate::config::TomlConfig; - -use self::download::AttachmentDownloadCommand; - -/// Download your message attachments. -/// -/// A message body can be composed of multiple MIME parts. An -/// attachment is the representation of a binary part of a message -/// body. -#[derive(Debug, Subcommand)] -pub enum AttachmentSubcommand { - #[command(arg_required_else_help = true, alias = "dl")] - Download(AttachmentDownloadCommand), -} - -impl AttachmentSubcommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - match self { - Self::Download(cmd) => cmd.execute(printer, config).await, - } - } -} diff --git a/src/email/message/attachment/mod.rs b/src/email/message/attachment/mod.rs deleted file mode 100644 index 9fe79612..00000000 --- a/src/email/message/attachment/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod command; diff --git a/src/email/message/command/copy.rs b/src/email/message/command/copy.rs deleted file mode 100644 index 4f01e160..00000000 --- a/src/email/message/command/copy.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::Result; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, - config::TomlConfig, - envelope::arg::ids::EnvelopeIdsArgs, - folder::arg::name::{SourceFolderNameOptionalFlag, TargetFolderNameArg}, -}; - -/// Copy the message associated to the given envelope id(s) to the -/// given target folder. -#[derive(Debug, Parser)] -pub struct MessageCopyCommand { - #[command(flatten)] - pub source_folder: SourceFolderNameOptionalFlag, - - #[command(flatten)] - pub target_folder: TargetFolderNameArg, - - #[command(flatten)] - pub envelopes: EnvelopeIdsArgs, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl MessageCopyCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing copy message(s) command"); - - let source = &self.source_folder.name; - let target = &self.target_folder.name; - let ids = &self.envelopes.ids; - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - Arc::new(account_config), - |builder| { - builder - .without_features() - .with_copy_messages(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - backend.copy_messages(source, target, ids).await?; - - printer.out(format!( - "Message(s) successfully copied from {source} to {target}!\n" - )) - } -} diff --git a/src/email/message/command/delete.rs b/src/email/message/command/delete.rs deleted file mode 100644 index 598dd3eb..00000000 --- a/src/email/message/command/delete.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::Result; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs, - folder::arg::name::FolderNameOptionalFlag, -}; - -/// Mark as deleted the message associated to the given envelope id(s). -/// -/// This command does not really delete the message: if the given -/// folder points to the trash folder, it adds the "deleted" flag to -/// its envelope, otherwise it moves it to the trash folder. Only the -/// expunge folder command truly deletes messages. -#[derive(Debug, Parser)] -pub struct MessageDeleteCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub envelopes: EnvelopeIdsArgs, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl MessageDeleteCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing delete message(s) command"); - - let folder = &self.folder.name; - let ids = &self.envelopes.ids; - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - Arc::new(account_config), - |builder| { - builder - .without_features() - .with_delete_messages(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - backend.delete_messages(folder, ids).await?; - - printer.out(format!("Message(s) successfully removed from {folder}!\n")) - } -} diff --git a/src/email/message/command/edit.rs b/src/email/message/command/edit.rs deleted file mode 100644 index 05292327..00000000 --- a/src/email/message/command/edit.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::{eyre::eyre, Result}; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::{backend::BackendBuilder, editor}, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, config::TomlConfig, envelope::arg::ids::EnvelopeIdArg, - folder::arg::name::FolderNameOptionalFlag, -}; - -/// Edit the message associated to the given envelope id. -/// -/// This command allows you to edit the given message using the -/// editor defined in your environment variable $EDITOR. When the -/// edition process finishes, you can choose between saving or sending -/// the final message. -#[derive(Debug, Parser)] -pub struct MessageEditCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub envelope: EnvelopeIdArg, - - /// List of headers that should be visible at the top of the - /// message. - /// - /// If a given header is not found in the message, it will not be - /// visible. If no header is given, defaults to the one set up in - /// your TOML configuration file. - #[arg(long = "header", short = 'H', value_name = "NAME")] - pub headers: Vec, - - /// Edit the message on place. - /// - /// If set, the original message being edited will be removed at - /// the end of the command. Useful when you need, for example, to - /// edit a draft, send it then remove it from the Drafts folder. - #[arg(long, short = 'p')] - pub on_place: bool, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl MessageEditCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing edit message command"); - - let folder = &self.folder.name; - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_add_message(BackendFeatureSource::Context) - .with_send_message(BackendFeatureSource::Context) - .with_delete_messages(BackendFeatureSource::Context) - }, - ) - .build() - .await?; - - let id = self.envelope.id; - let tpl = backend - .get_messages(folder, &[id]) - .await? - .first() - .ok_or(eyre!("cannot find message"))? - .to_read_tpl(&account_config, |mut tpl| { - if !self.headers.is_empty() { - tpl = tpl.with_show_only_headers(&self.headers); - } - - tpl - }) - .await?; - - editor::edit_tpl_with_editor(account_config, printer, &backend, tpl).await?; - - if self.on_place { - backend.delete_messages(folder, &[id]).await?; - } - - Ok(()) - } -} diff --git a/src/email/message/command/export.rs b/src/email/message/command/export.rs deleted file mode 100644 index 3ae331a4..00000000 --- a/src/email/message/command/export.rs +++ /dev/null @@ -1,155 +0,0 @@ -use std::{ - env::temp_dir, - fs, - io::{stdout, Write}, - path::PathBuf, - sync::Arc, -}; - -use clap::Parser; -use color_eyre::{eyre::eyre, Result}; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{himalaya::backend::BackendBuilder, terminal::config::TomlConfig as _}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, config::TomlConfig, envelope::arg::ids::EnvelopeIdArg, - folder::arg::name::FolderNameOptionalFlag, -}; - -/// Export the message associated to the given envelope id. -/// -/// This command allows you to export a message. A message can be -/// fully exported in one single file, or exported in multiple files -/// (one per MIME part found in the message). This is useful, for -/// example, to read a HTML message. -#[derive(Debug, Parser)] -pub struct MessageExportCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub envelope: EnvelopeIdArg, - - /// Export the full raw message as one unique .eml file. - /// - /// The raw message represents the headers and the body as it is - /// on the backend, unedited: not decoded nor decrypted. This is - /// useful for debugging faulty messages, but also for - /// saving/sending/transfering messages. - #[arg(long, short = 'F')] - pub full: bool, - - /// Try to open the exported message, when applicable. - /// - /// This argument only works with full message export, or when - /// HTML or plain text is present in the export. - #[arg(long, short = 'O')] - pub open: bool, - - /// Where the message should be exported to. - /// - /// The destination should point to a valid directory. If `--full` - /// is given, it can also point to a .eml file. - #[arg(long, short, alias = "dest")] - pub destination: Option, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl MessageExportCommand { - pub async fn execute(self, config: &TomlConfig) -> Result<()> { - info!("executing export message command"); - - let folder = &self.folder.name; - let id = &self.envelope.id; - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_get_messages(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - let msgs = backend.get_messages(folder, &[*id]).await?; - let msg = msgs.first().ok_or(eyre!("cannot find message {id}"))?; - - if self.full { - let bytes = msg.raw()?; - - match self.destination { - Some(mut dest) if dest.is_dir() => { - dest.push(format!("{id}.eml")); - fs::write(&dest, bytes)?; - let dest = dest.display(); - println!("Message {id} successfully exported at {dest}!"); - } - Some(dest) => { - fs::write(&dest, bytes)?; - let dest = dest.display(); - println!("Message {id} successfully exported at {dest}!"); - } - None => { - stdout().write_all(bytes)?; - } - }; - } else { - let dest = match self.destination { - Some(dest) if dest.is_dir() => { - let dest = msg.download_parts(&dest)?; - let d = dest.display(); - println!("Message {id} successfully exported in {d}!"); - dest - } - Some(dest) if dest.is_file() => { - let dest = dest.parent().unwrap_or(&dest); - let dest = msg.download_parts(&dest)?; - let d = dest.display(); - println!("Message {id} successfully exported in {d}!"); - dest - } - Some(dest) => { - return Err(eyre!("Destination {} does not exist!", dest.display())); - } - None => { - let dest = temp_dir(); - let dest = msg.download_parts(&dest)?; - let d = dest.display(); - println!("Message {id} successfully exported in {d}!"); - dest - } - }; - - if self.open { - let index_html = dest.join("index.html"); - if index_html.exists() { - return Ok(open::that(index_html)?); - } - - let plain_txt = dest.join("plain.txt"); - if plain_txt.exists() { - return Ok(open::that(plain_txt)?); - } - - println!("--open was passed but nothing to open, ignoring"); - } - } - - Ok(()) - } -} diff --git a/src/email/message/command/forward.rs b/src/email/message/command/forward.rs deleted file mode 100644 index 3de9c53b..00000000 --- a/src/email/message/command/forward.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::{eyre::eyre, Result}; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::{backend::BackendBuilder, editor}, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, - config::TomlConfig, - envelope::arg::ids::EnvelopeIdArg, - folder::arg::name::FolderNameOptionalFlag, - message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs}, -}; - -/// Forward the message associated to the given envelope id. -/// -/// This command allows you to forward the given message using the -/// editor defined in your environment variable $EDITOR. When the -/// edition process finishes, you can choose between saving or sending -/// the final message. -#[derive(Debug, Parser)] -pub struct MessageForwardCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub envelope: EnvelopeIdArg, - - #[command(flatten)] - pub headers: HeaderRawArgs, - - #[command(flatten)] - pub body: MessageRawBodyArg, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl MessageForwardCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing forward message command"); - - let folder = &self.folder.name; - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_add_message(BackendFeatureSource::Context) - .with_send_message(BackendFeatureSource::Context) - }, - ) - .build() - .await?; - - let id = self.envelope.id; - let tpl = backend - .get_messages(folder, &[id]) - .await? - .first() - .ok_or(eyre!("cannot find message"))? - .to_forward_tpl_builder(account_config.clone()) - .with_headers(self.headers.raw) - .with_body(self.body.raw()) - .build() - .await?; - editor::edit_tpl_with_editor(account_config, printer, &backend, tpl).await - } -} diff --git a/src/email/message/command/mailto.rs b/src/email/message/command/mailto.rs deleted file mode 100644 index 32dca8d4..00000000 --- a/src/email/message/command/mailto.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::Result; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::{backend::BackendBuilder, editor}, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; -use url::Url; - -use crate::{account::arg::name::AccountNameFlag, config::TomlConfig}; - -/// Parse and edit a message from the given mailto URL string. -/// -/// This command allows you to edit a message from the mailto format -/// using the editor defined in your environment variable -/// $EDITOR. When the edition process finishes, you can choose between -/// saving or sending the final message. -#[derive(Debug, Parser)] -pub struct MessageMailtoCommand { - /// The mailto url. - #[arg()] - pub url: Url, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl MessageMailtoCommand { - pub fn new(url: &str) -> Result { - Ok(Self { - url: Url::parse(url)?, - account: Default::default(), - }) - } - - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing mailto message command"); - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_add_message(BackendFeatureSource::Context) - .with_send_message(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - let mut msg = Vec::::new(); - let mut body = Vec::::new(); - - msg.extend(b"Content-Type: text/plain; charset=utf-8\r\n"); - - for (key, val) in self.url.query_pairs() { - if key.eq_ignore_ascii_case("body") { - body.extend(val.as_bytes()); - } else { - msg.extend(key.as_bytes()); - msg.extend(b": "); - msg.extend(val.as_bytes()); - msg.extend(b"\r\n"); - } - } - - msg.extend(b"\r\n"); - msg.extend(body); - - if let Some(sig) = account_config.find_full_signature() { - msg.extend(b"\r\n"); - msg.extend(sig.as_bytes()); - } - - let tpl = account_config - .generate_tpl_interpreter() - .with_show_only_headers(account_config.get_message_write_headers()) - .build() - .from_bytes(msg) - .await? - .into(); - - editor::edit_tpl_with_editor(account_config, printer, &backend, tpl).await - } -} diff --git a/src/email/message/command/mod.rs b/src/email/message/command/mod.rs deleted file mode 100644 index 5ab1ba8b..00000000 --- a/src/email/message/command/mod.rs +++ /dev/null @@ -1,94 +0,0 @@ -pub mod copy; -pub mod delete; -pub mod edit; -pub mod export; -pub mod forward; -pub mod mailto; -pub mod r#move; -pub mod read; -pub mod reply; -pub mod save; -pub mod send; -pub mod thread; -pub mod write; - -use clap::Subcommand; -use color_eyre::Result; -use pimalaya_tui::terminal::cli::printer::Printer; - -use crate::config::TomlConfig; - -use self::{ - copy::MessageCopyCommand, delete::MessageDeleteCommand, edit::MessageEditCommand, - export::MessageExportCommand, forward::MessageForwardCommand, mailto::MessageMailtoCommand, - r#move::MessageMoveCommand, read::MessageReadCommand, reply::MessageReplyCommand, - save::MessageSaveCommand, send::MessageSendCommand, thread::MessageThreadCommand, - write::MessageWriteCommand, -}; - -/// Read, write, send, copy, move and delete your messages. -/// -/// A message is the content of an email. It is composed of headers -/// (located at the top of the message) and a body (located at the -/// bottom of the message). Both are separated by two new lines. This -/// subcommand allows you to manage them. -#[derive(Debug, Subcommand)] -pub enum MessageSubcommand { - #[command(arg_required_else_help = true)] - Read(MessageReadCommand), - - #[command(arg_required_else_help = true)] - Export(MessageExportCommand), - - #[command(arg_required_else_help = true)] - Thread(MessageThreadCommand), - - #[command(aliases = ["add", "create", "new", "compose"])] - Write(MessageWriteCommand), - - Reply(MessageReplyCommand), - - #[command(aliases = ["fwd", "fd"])] - Forward(MessageForwardCommand), - - Edit(MessageEditCommand), - - Mailto(MessageMailtoCommand), - - Save(MessageSaveCommand), - - Send(MessageSendCommand), - - #[command(arg_required_else_help = true)] - #[command(aliases = ["cpy", "cp"])] - Copy(MessageCopyCommand), - - #[command(arg_required_else_help = true)] - #[command(alias = "mv")] - Move(MessageMoveCommand), - - #[command(arg_required_else_help = true)] - #[command(aliases = ["remove", "rm"])] - Delete(MessageDeleteCommand), -} - -impl MessageSubcommand { - #[allow(unused)] - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - match self { - Self::Read(cmd) => cmd.execute(printer, config).await, - Self::Export(cmd) => cmd.execute(config).await, - Self::Thread(cmd) => cmd.execute(printer, config).await, - Self::Write(cmd) => cmd.execute(printer, config).await, - Self::Reply(cmd) => cmd.execute(printer, config).await, - Self::Forward(cmd) => cmd.execute(printer, config).await, - Self::Edit(cmd) => cmd.execute(printer, config).await, - Self::Mailto(cmd) => cmd.execute(printer, config).await, - Self::Save(cmd) => cmd.execute(printer, config).await, - Self::Send(cmd) => cmd.execute(printer, config).await, - Self::Copy(cmd) => cmd.execute(printer, config).await, - Self::Move(cmd) => cmd.execute(printer, config).await, - Self::Delete(cmd) => cmd.execute(printer, config).await, - } - } -} diff --git a/src/email/message/command/move.rs b/src/email/message/command/move.rs deleted file mode 100644 index 7e4ef1f8..00000000 --- a/src/email/message/command/move.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::Result; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -#[allow(unused)] -use crate::{ - account::arg::name::AccountNameFlag, - config::TomlConfig, - envelope::arg::ids::EnvelopeIdsArgs, - folder::arg::name::{SourceFolderNameOptionalFlag, TargetFolderNameArg}, -}; - -/// Move the message associated to the given envelope id(s) to the -/// given target folder. -#[derive(Debug, Parser)] -pub struct MessageMoveCommand { - #[command(flatten)] - pub source_folder: SourceFolderNameOptionalFlag, - - #[command(flatten)] - pub target_folder: TargetFolderNameArg, - - #[command(flatten)] - pub envelopes: EnvelopeIdsArgs, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl MessageMoveCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing move message(s) command"); - - let source = &self.source_folder.name; - let target = &self.target_folder.name; - let ids = &self.envelopes.ids; - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - Arc::new(account_config), - |builder| { - builder - .without_features() - .with_move_messages(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - backend.move_messages(source, target, ids).await?; - - printer.out(format!( - "Message(s) successfully moved from {source} to {target}!\n" - )) - } -} diff --git a/src/email/message/command/read.rs b/src/email/message/command/read.rs deleted file mode 100644 index 33ece677..00000000 --- a/src/email/message/command/read.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::Result; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -#[allow(unused)] -use crate::{ - account::arg::name::AccountNameFlag, config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs, - folder::arg::name::FolderNameOptionalFlag, -}; - -/// Read a human-friendly version of the message associated to the -/// given envelope id(s). -/// -/// This command allows you to read a message. When reading a message, -/// the "seen" flag is automatically applied to the corresponding -/// envelope. To prevent this behaviour, use the "--preview" flag. -#[derive(Debug, Parser)] -pub struct MessageReadCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub envelopes: EnvelopeIdsArgs, - - /// Read the message without applying the "seen" flag to its - /// corresponding envelope. - #[arg(long, short)] - pub preview: bool, - - /// Read only the body of the message. - /// - /// All headers will be removed from the message. - #[arg(long)] - #[arg(conflicts_with = "headers")] - pub no_headers: bool, - - /// List of headers that should be visible at the top of the - /// message. - /// - /// If a given header is not found in the message, it will not be - /// visible. If no header is given, defaults to the one set up in - /// your TOML configuration file. - #[arg(long = "header", short = 'H', value_name = "NAME")] - #[arg(conflicts_with = "no_headers")] - pub headers: Vec, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl MessageReadCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing read message(s) command"); - - let folder = &self.folder.name; - let ids = &self.envelopes.ids; - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_get_messages(BackendFeatureSource::Context) - .with_peek_messages(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - let emails = if self.preview { - backend.peek_messages(folder, ids).await - } else { - backend.get_messages(folder, ids).await - }?; - - let mut glue = ""; - let mut bodies = String::default(); - - for email in emails.to_vec() { - bodies.push_str(glue); - - let tpl = email - .to_read_tpl(&account_config, |mut tpl| { - if self.no_headers { - tpl = tpl.with_hide_all_headers(); - } else if !self.headers.is_empty() { - tpl = tpl.with_show_only_headers(&self.headers); - } - - tpl - }) - .await?; - bodies.push_str(&tpl); - - glue = "\n\n"; - } - - printer.out(bodies) - } -} diff --git a/src/email/message/command/reply.rs b/src/email/message/command/reply.rs deleted file mode 100644 index 803848ea..00000000 --- a/src/email/message/command/reply.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::{eyre::eyre, Result}; -use email::{backend::feature::BackendFeatureSource, config::Config, flag::Flag}; -use pimalaya_tui::{ - himalaya::{backend::BackendBuilder, editor}, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, - config::TomlConfig, - envelope::arg::ids::EnvelopeIdArg, - folder::arg::name::FolderNameOptionalFlag, - message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs, reply::MessageReplyAllArg}, -}; - -/// Reply to the message associated to the given envelope id. -/// -/// This command allows you to reply to the given message using the -/// editor defined in your environment variable $EDITOR. When the -/// edition process finishes, you can choose between saving or sending -/// the final message. -#[derive(Debug, Parser)] -pub struct MessageReplyCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub envelope: EnvelopeIdArg, - - #[command(flatten)] - pub reply: MessageReplyAllArg, - - #[command(flatten)] - pub headers: HeaderRawArgs, - - #[command(flatten)] - pub body: MessageRawBodyArg, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl MessageReplyCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing reply message command"); - - let folder = &self.folder.name; - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_add_message(BackendFeatureSource::Context) - .with_send_message(BackendFeatureSource::Context) - }, - ) - .build() - .await?; - - let id = self.envelope.id; - let tpl = backend - .get_messages(folder, &[id]) - .await? - .first() - .ok_or(eyre!("cannot find message {id}"))? - .to_reply_tpl_builder(account_config.clone()) - .with_headers(self.headers.raw) - .with_body(self.body.raw()) - .with_reply_all(self.reply.all) - .build() - .await?; - - editor::edit_tpl_with_editor(account_config, printer, &backend, tpl).await?; - - backend.add_flag(folder, &[id], Flag::Answered).await?; - - Ok(()) - } -} diff --git a/src/email/message/command/save.rs b/src/email/message/command/save.rs deleted file mode 100644 index e0ff0036..00000000 --- a/src/email/message/command/save.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::{ - fmt, - io::{self, BufRead, IsTerminal}, - sync::Arc, -}; - -use clap::Parser; -use color_eyre::Result; -use email::{backend::feature::BackendFeatureSource, config::Config, envelope::SingleId}; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use serde::{ser::SerializeStruct, Serialize, Serializer}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, config::TomlConfig, - folder::arg::name::FolderNameOptionalFlag, message::arg::MessageRawArg, -}; - -/// Save the given raw message to the given folder. -/// -/// This command allows you to add a raw message to the given folder. -#[derive(Debug, Parser)] -pub struct MessageSaveCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub message: MessageRawArg, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl MessageSaveCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing save message command"); - - let folder = &self.folder.name; - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - Arc::new(account_config), - |builder| { - builder - .without_features() - .with_add_message(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - let is_tty = io::stdin().is_terminal(); - let is_json = printer.is_json(); - let msg = if !self.message.raw.is_empty() || is_tty || is_json { - self.message.raw() - } else { - io::stdin() - .lock() - .lines() - .map_while(Result::ok) - .collect::>() - .join("\r\n") - }; - - let id = backend.add_message(folder, msg.as_bytes()).await?; - - printer.out(MessageAdded { folder, id }) - } -} - -struct MessageAdded<'a> { - folder: &'a String, - id: SingleId, -} - -impl fmt::Display for MessageAdded<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let id = self.id.as_str(); - let folder = self.folder; - writeln!(f, "Message {id} successfully saved to {folder}") - } -} - -impl Serialize for MessageAdded<'_> { - fn serialize(&self, serializer: S) -> Result { - let mut state = serializer.serialize_struct("MessageAdded", 2)?; - state.serialize_field("folder", self.folder)?; - state.serialize_field("id", self.id.as_str())?; - state.end() - } -} diff --git a/src/email/message/command/send.rs b/src/email/message/command/send.rs deleted file mode 100644 index c2ad3fc9..00000000 --- a/src/email/message/command/send.rs +++ /dev/null @@ -1,67 +0,0 @@ -use clap::Parser; -use color_eyre::Result; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use std::{ - io::{self, BufRead, IsTerminal}, - sync::Arc, -}; -use tracing::info; - -use crate::{account::arg::name::AccountNameFlag, config::TomlConfig, message::arg::MessageRawArg}; - -/// Send the given raw message. -/// -/// This command allows you to send a raw message and to save a copy -/// to your send folder. -#[derive(Debug, Parser)] -pub struct MessageSendCommand { - #[command(flatten)] - pub message: MessageRawArg, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl MessageSendCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing send message command"); - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - Arc::new(account_config), - |builder| { - builder - .without_features() - .with_add_message(BackendFeatureSource::Context) - .with_send_message(BackendFeatureSource::Context) - }, - ) - .build() - .await?; - - let msg = if io::stdin().is_terminal() { - self.message.raw() - } else { - io::stdin() - .lock() - .lines() - .map_while(Result::ok) - .collect::>() - .join("\r\n") - }; - - backend.send_message_then_save_copy(msg.as_bytes()).await?; - - printer.out("Message successfully sent!") - } -} diff --git a/src/email/message/command/thread.rs b/src/email/message/command/thread.rs deleted file mode 100644 index 05d6d9b4..00000000 --- a/src/email/message/command/thread.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::Result; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::envelope::arg::ids::EnvelopeIdArg; -#[allow(unused)] -use crate::{ - account::arg::name::AccountNameFlag, config::TomlConfig, envelope::arg::ids::EnvelopeIdsArgs, - folder::arg::name::FolderNameOptionalFlag, -}; - -/// Read human-friendly version of messages associated to the -/// given envelope id's thread. -/// -/// This command allows you to thread a message. When threading a message, -/// the "seen" flag is automatically applied to the corresponding -/// envelope. To prevent this behaviour, use the --preview flag. -#[derive(Debug, Parser)] -pub struct MessageThreadCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub envelope: EnvelopeIdArg, - - /// Thread the message without applying the "seen" flag to its - /// corresponding envelope. - #[arg(long, short)] - pub preview: bool, - - /// Thread only the body of the message. - /// - /// All headers will be removed from the message. - #[arg(long)] - #[arg(conflicts_with = "headers")] - pub no_headers: bool, - - /// List of headers that should be visible at the top of the - /// message. - /// - /// If a given header is not found in the message, it will not be - /// visible. If no header is given, defaults to the one set up in - /// your TOML configuration file. - #[arg(long = "header", short = 'H', value_name = "NAME")] - #[arg(conflicts_with = "no_headers")] - pub headers: Vec, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl MessageThreadCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing thread message(s) command"); - - let folder = &self.folder.name; - let id = &self.envelope.id; - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_get_messages(BackendFeatureSource::Context) - .with_peek_messages(BackendFeatureSource::Context) - .with_thread_envelopes(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - let envelopes = backend - .thread_envelope(folder, *id, Default::default()) - .await?; - - let ids: Vec<_> = envelopes - .graph() - .nodes() - .filter_map(|e| { - let id = e.id.parse::().unwrap_or(0); - if id > 0 { Some(id) } else { None } - }) - .collect(); - - let emails = if self.preview { - backend.peek_messages(folder, &ids).await - } else { - backend.get_messages(folder, &ids).await - }?; - - let mut glue = ""; - let mut bodies = String::default(); - - for (i, email) in emails.to_vec().iter().enumerate() { - bodies.push_str(glue); - bodies.push_str(&format!("-------- Message {} --------\n\n", ids[i])); - - let tpl = email - .to_read_tpl(&account_config, |mut tpl| { - if self.no_headers { - tpl = tpl.with_hide_all_headers(); - } else if !self.headers.is_empty() { - tpl = tpl.with_show_only_headers(&self.headers); - } - - tpl - }) - .await?; - - bodies.push_str(&tpl); - glue = "\n\n"; - } - - printer.out(bodies) - } -} diff --git a/src/email/message/command/write.rs b/src/email/message/command/write.rs deleted file mode 100644 index 02b4a5cb..00000000 --- a/src/email/message/command/write.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::Result; -use email::{ - config::Config, - {backend::feature::BackendFeatureSource, message::Message}, -}; -use pimalaya_tui::{ - himalaya::{backend::BackendBuilder, editor}, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, - config::TomlConfig, - message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs}, -}; - -/// Compose a new message, from scratch. -/// -/// This command allows you to write a new message using the editor -/// defined in your environment variable $EDITOR. When the edition -/// process finishes, you can choose between saving or sending the -/// final message. -#[derive(Debug, Parser)] -pub struct MessageWriteCommand { - #[command(flatten)] - pub headers: HeaderRawArgs, - - #[command(flatten)] - pub body: MessageRawBodyArg, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl MessageWriteCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing write message command"); - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_add_message(BackendFeatureSource::Context) - .with_send_message(BackendFeatureSource::Context) - }, - ) - .build() - .await?; - - let tpl = Message::new_tpl_builder(account_config.clone()) - .with_headers(self.headers.raw) - .with_body(self.body.raw()) - .build() - .await?; - - editor::edit_tpl_with_editor(account_config, printer, &backend, tpl).await - } -} diff --git a/src/email/message/mod.rs b/src/email/message/mod.rs deleted file mode 100644 index 57d3b73c..00000000 --- a/src/email/message/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod arg; -pub mod attachment; -pub mod command; -pub mod template; diff --git a/src/email/message/template/arg/body.rs b/src/email/message/template/arg/body.rs deleted file mode 100644 index 47966587..00000000 --- a/src/email/message/template/arg/body.rs +++ /dev/null @@ -1,25 +0,0 @@ -use clap::Parser; -use std::ops::Deref; - -/// The raw template body argument parser. -#[derive(Debug, Parser)] -pub struct TemplateRawBodyArg { - /// Prefill the template with a custom MML body. - #[arg(trailing_var_arg = true)] - #[arg(name = "body_raw", value_name = "BODY")] - pub raw: Vec, -} - -impl TemplateRawBodyArg { - pub fn raw(self) -> String { - self.raw.join(" ").replace('\r', "") - } -} - -impl Deref for TemplateRawBodyArg { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.raw - } -} diff --git a/src/email/message/template/arg/mod.rs b/src/email/message/template/arg/mod.rs deleted file mode 100644 index eeb0224f..00000000 --- a/src/email/message/template/arg/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -pub mod body; - -use clap::Parser; - -/// The raw template argument parser. -#[derive(Debug, Parser)] -pub struct TemplateRawArg { - /// The raw template, including headers and MML body. - #[arg(trailing_var_arg = true)] - #[arg(name = "template_raw", value_name = "TEMPLATE")] - pub raw: Vec, -} - -impl TemplateRawArg { - pub fn raw(self) -> String { - self.raw.join(" ").replace('\r', "") - } -} diff --git a/src/email/message/template/command/forward.rs b/src/email/message/template/command/forward.rs deleted file mode 100644 index e7c637f2..00000000 --- a/src/email/message/template/command/forward.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::{eyre::eyre, Result}; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, - config::TomlConfig, - envelope::arg::ids::EnvelopeIdArg, - folder::arg::name::FolderNameOptionalFlag, - message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs}, -}; - -/// Generate a template for forwarding a message. -/// -/// The generated template is prefilled with your email in a From -/// header as well as your signature. The forwarded message is also -/// prefilled in the body of the template, prefixed by a separator. -#[derive(Debug, Parser)] -pub struct TemplateForwardCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub envelope: EnvelopeIdArg, - - #[command(flatten)] - pub headers: HeaderRawArgs, - - #[command(flatten)] - pub body: MessageRawBodyArg, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl TemplateForwardCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing forward template command"); - - let folder = &self.folder.name; - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_get_messages(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - let id = self.envelope.id; - let tpl = backend - .get_messages(folder, &[id]) - .await? - .first() - .ok_or(eyre!("cannot find message {id}"))? - .to_forward_tpl_builder(account_config) - .with_headers(self.headers.raw) - .with_body(self.body.raw()) - .build() - .await?; - - printer.out(tpl) - } -} diff --git a/src/email/message/template/command/mod.rs b/src/email/message/template/command/mod.rs deleted file mode 100644 index 542303b0..00000000 --- a/src/email/message/template/command/mod.rs +++ /dev/null @@ -1,55 +0,0 @@ -mod forward; -mod reply; -mod save; -mod send; -mod write; - -use clap::Subcommand; -use color_eyre::Result; -use pimalaya_tui::terminal::cli::printer::Printer; - -use crate::config::TomlConfig; - -use self::{ - forward::TemplateForwardCommand, reply::TemplateReplyCommand, save::TemplateSaveCommand, - send::TemplateSendCommand, write::TemplateWriteCommand, -}; - -/// Generate, save and send message templates. -/// -/// A template is an editable version of a message (headers + -/// body). It uses a specific language called MML that allows you to -/// attach file or encrypt content. This subcommand allows you manage -/// them. -/// -/// Learn more about MML at: . -#[derive(Debug, Subcommand)] -pub enum TemplateSubcommand { - #[command(aliases = ["add", "create", "new", "compose"])] - Write(TemplateWriteCommand), - - #[command(arg_required_else_help = true)] - Reply(TemplateReplyCommand), - - #[command(arg_required_else_help = true)] - #[command(alias = "fwd")] - Forward(TemplateForwardCommand), - - #[command()] - Save(TemplateSaveCommand), - - #[command()] - Send(TemplateSendCommand), -} - -impl TemplateSubcommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - match self { - Self::Write(cmd) => cmd.execute(printer, config).await, - Self::Reply(cmd) => cmd.execute(printer, config).await, - Self::Forward(cmd) => cmd.execute(printer, config).await, - Self::Save(cmd) => cmd.execute(printer, config).await, - Self::Send(cmd) => cmd.execute(printer, config).await, - } - } -} diff --git a/src/email/message/template/command/reply.rs b/src/email/message/template/command/reply.rs deleted file mode 100644 index 126a72a3..00000000 --- a/src/email/message/template/command/reply.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::{eyre::eyre, Result}; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, - config::TomlConfig, - envelope::arg::ids::EnvelopeIdArg, - folder::arg::name::FolderNameOptionalFlag, - message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs, reply::MessageReplyAllArg}, -}; - -/// Generate a template for replying to a message. -/// -/// The generated template is prefilled with your email in a From -/// header as well as your signature. The replied message is also -/// prefilled in the body of the template, with all lines prefixed by -/// the symbol greater than ">". -#[derive(Debug, Parser)] -pub struct TemplateReplyCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub envelope: EnvelopeIdArg, - - #[command(flatten)] - pub reply: MessageReplyAllArg, - - #[command(flatten)] - pub headers: HeaderRawArgs, - - #[command(flatten)] - pub body: MessageRawBodyArg, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl TemplateReplyCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing reply template command"); - - let folder = &self.folder.name; - let id = self.envelope.id; - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_get_messages(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - let tpl = backend - .get_messages(folder, &[id]) - .await? - .first() - .ok_or(eyre!("cannot find message {id}"))? - .to_reply_tpl_builder(account_config) - .with_headers(self.headers.raw) - .with_body(self.body.raw()) - .with_reply_all(self.reply.all) - .build() - .await?; - - printer.out(tpl) - } -} diff --git a/src/email/message/template/command/save.rs b/src/email/message/template/command/save.rs deleted file mode 100644 index 7791e6b1..00000000 --- a/src/email/message/template/command/save.rs +++ /dev/null @@ -1,115 +0,0 @@ -use std::{ - fmt, - io::{self, BufRead, IsTerminal}, - sync::Arc, -}; - -use clap::Parser; -use color_eyre::Result; -use email::{backend::feature::BackendFeatureSource, config::Config, envelope::SingleId}; -use mml::MmlCompilerBuilder; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use serde::{ser::SerializeStruct, Serialize, Serializer}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, config::TomlConfig, email::template::arg::TemplateRawArg, - folder::arg::name::FolderNameOptionalFlag, -}; - -/// Save a template to a folder. -/// -/// This command allows you to save a template to the given -/// folder. The template is compiled into a MIME message before being -/// saved to the folder. If you want to save a raw message, use the -/// message save command instead. -#[derive(Debug, Parser)] -pub struct TemplateSaveCommand { - #[command(flatten)] - pub folder: FolderNameOptionalFlag, - - #[command(flatten)] - pub template: TemplateRawArg, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl TemplateSaveCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing save template command"); - - let folder = &self.folder.name; - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_add_message(BackendFeatureSource::Context) - }, - ) - .without_sending_backend() - .build() - .await?; - - let is_tty = io::stdin().is_terminal(); - let is_json = printer.is_json(); - let tpl = if !self.template.raw.is_empty() || is_tty || is_json { - self.template.raw() - } else { - io::stdin() - .lock() - .lines() - .map_while(Result::ok) - .collect::>() - .join("\n") - }; - - #[allow(unused_mut)] - let mut compiler = MmlCompilerBuilder::new(); - - #[cfg(any(feature = "pgp-gpg", feature = "pgp-commands", feature = "pgp-native"))] - compiler.set_some_pgp(account_config.pgp.clone()); - - let msg = compiler.build(tpl.as_str())?.compile().await?.into_vec()?; - - let id = backend.add_message(folder, &msg).await?; - - printer.out(TemplateAdded { folder, id }) - } -} - -struct TemplateAdded<'a> { - folder: &'a String, - id: SingleId, -} - -impl fmt::Display for TemplateAdded<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let id = self.id.as_str(); - let folder = self.folder; - writeln!(f, "Template {id} successfully saved to {folder}") - } -} - -impl Serialize for TemplateAdded<'_> { - fn serialize(&self, serializer: S) -> Result { - let mut state = serializer.serialize_struct("TemplateAdded", 2)?; - state.serialize_field("folder", self.folder)?; - state.serialize_field("id", self.id.as_str())?; - state.end() - } -} diff --git a/src/email/message/template/command/send.rs b/src/email/message/template/command/send.rs deleted file mode 100644 index bced69fe..00000000 --- a/src/email/message/template/command/send.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::{ - io::{self, BufRead, IsTerminal}, - sync::Arc, -}; - -use clap::Parser; -use color_eyre::Result; -use email::{backend::feature::BackendFeatureSource, config::Config}; -use mml::MmlCompilerBuilder; -use pimalaya_tui::{ - himalaya::backend::BackendBuilder, - terminal::{cli::printer::Printer, config::TomlConfig as _}, -}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, config::TomlConfig, email::template::arg::TemplateRawArg, -}; - -/// Send a template. -/// -/// This command allows you to send a template and save a copy to the -/// sent folder. The template is compiled into a MIME message before -/// being sent. If you want to send a raw message, use the message -/// send command instead. -#[derive(Debug, Parser)] -pub struct TemplateSendCommand { - #[command(flatten)] - pub template: TemplateRawArg, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl TemplateSendCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing send template command"); - - let (toml_account_config, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let account_config = Arc::new(account_config); - - let backend = BackendBuilder::new( - Arc::new(toml_account_config), - account_config.clone(), - |builder| { - builder - .without_features() - .with_add_message(BackendFeatureSource::Context) - .with_send_message(BackendFeatureSource::Context) - }, - ) - .build() - .await?; - - let tpl = if io::stdin().is_terminal() { - self.template.raw() - } else { - io::stdin() - .lock() - .lines() - .map_while(Result::ok) - .collect::>() - .join("\n") - }; - - #[allow(unused_mut)] - let mut compiler = MmlCompilerBuilder::new(); - - #[cfg(any(feature = "pgp-gpg", feature = "pgp-commands", feature = "pgp-native"))] - compiler.set_some_pgp(account_config.pgp.clone()); - - let msg = compiler.build(tpl.as_str())?.compile().await?.into_vec()?; - - backend.send_message_then_save_copy(&msg).await?; - - printer.out("Message successfully sent!") - } -} diff --git a/src/email/message/template/command/write.rs b/src/email/message/template/command/write.rs deleted file mode 100644 index a2e46e61..00000000 --- a/src/email/message/template/command/write.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::sync::Arc; - -use clap::Parser; -use color_eyre::Result; -use email::{config::Config, message::Message}; -use pimalaya_tui::terminal::{cli::printer::Printer, config::TomlConfig as _}; -use tracing::info; - -use crate::{ - account::arg::name::AccountNameFlag, config::TomlConfig, - email::template::arg::body::TemplateRawBodyArg, message::arg::header::HeaderRawArgs, -}; - -/// Generate a template for writing a new message from scratch. -/// -/// The generated template is prefilled with your email in a From -/// header as well as your signature. -#[derive(Debug, Parser)] -pub struct TemplateWriteCommand { - #[command(flatten)] - pub headers: HeaderRawArgs, - - #[command(flatten)] - pub body: TemplateRawBodyArg, - - #[command(flatten)] - pub account: AccountNameFlag, -} - -impl TemplateWriteCommand { - pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing write template command"); - - let (_, account_config) = config - .clone() - .into_account_configs(self.account.name.as_deref(), |c: &Config, name| { - c.account(name).ok() - })?; - - let tpl = Message::new_tpl_builder(Arc::new(account_config)) - .with_headers(self.headers.raw) - .with_body(self.body.raw()) - .build() - .await?; - - printer.out(tpl) - } -} diff --git a/src/email/message/template/mod.rs b/src/email/message/template/mod.rs deleted file mode 100644 index 1a3a73a9..00000000 --- a/src/email/message/template/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod arg; -pub mod command; diff --git a/src/email/mod.rs b/src/email/mod.rs deleted file mode 100644 index 550c6b06..00000000 --- a/src/email/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod envelope; -pub mod message; - -#[doc(inline)] -pub use self::{ - envelope::flag, - message::{attachment, template}, -}; diff --git a/src/imap/command.rs b/src/imap/command.rs index 8b0d317e..5506413e 100644 --- a/src/imap/command.rs +++ b/src/imap/command.rs @@ -19,7 +19,6 @@ use crate::{ #[command(rename_all = "lowercase")] pub enum ImapCommand { #[command(subcommand)] - #[command(aliases = ["envelope", "env"])] Envelopes(EnvelopeCommand), #[command(subcommand)] Flags(FlagCommand), @@ -27,17 +26,17 @@ pub enum ImapCommand { #[command(aliases = ["mboxes", "mbox"])] Mailboxes(MailboxCommand), #[command(subcommand)] - #[command(aliases = ["message", "msg"])] + #[command(aliases = ["msgs", "msg"])] Messages(MessageCommand), } impl ImapCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { match self { - Self::Envelopes(cmd) => cmd.execute(printer, config), - Self::Flags(cmd) => cmd.execute(printer, config), - Self::Mailboxes(cmd) => cmd.execute(printer, config), - Self::Messages(cmd) => cmd.execute(printer, config), + Self::Envelopes(cmd) => cmd.exec(printer, config), + Self::Flags(cmd) => cmd.exec(printer, config), + Self::Mailboxes(cmd) => cmd.exec(printer, config), + Self::Messages(cmd) => cmd.exec(printer, config), } } } diff --git a/src/imap/envelope/command/mod.rs b/src/imap/envelope/command.rs similarity index 61% rename from src/imap/envelope/command/mod.rs rename to src/imap/envelope/command.rs index ceedf415..20401042 100644 --- a/src/imap/envelope/command/mod.rs +++ b/src/imap/envelope/command.rs @@ -1,16 +1,10 @@ -pub mod get; -pub mod list; -pub mod search; -pub mod sort; -pub mod thread; - use anyhow::Result; use clap::Subcommand; use pimalaya_toolbox::terminal::printer::Printer; use crate::{ config::ImapConfig, - imap::envelope::command::{ + imap::envelope::{ get::GetEnvelopeCommand, list::ListEnvelopesCommand, search::SearchEnvelopesCommand, sort::SortEnvelopesCommand, thread::ThreadEnvelopesCommand, }, @@ -31,13 +25,13 @@ pub enum EnvelopeCommand { } impl EnvelopeCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { match self { - Self::List(cmd) => cmd.execute(printer, config), - Self::Get(cmd) => cmd.execute(printer, config), - Self::Search(cmd) => cmd.execute(printer, config), - Self::Sort(cmd) => cmd.execute(printer, config), - Self::Thread(cmd) => cmd.execute(printer, config), + Self::List(cmd) => cmd.exec(printer, config), + Self::Get(cmd) => cmd.exec(printer, config), + Self::Search(cmd) => cmd.exec(printer, config), + Self::Sort(cmd) => cmd.exec(printer, config), + Self::Thread(cmd) => cmd.exec(printer, config), } } } diff --git a/src/imap/envelope/command/get.rs b/src/imap/envelope/get.rs similarity index 94% rename from src/imap/envelope/command/get.rs rename to src/imap/envelope/get.rs index 1e57023b..6cab8931 100644 --- a/src/imap/envelope/command/get.rs +++ b/src/imap/envelope/get.rs @@ -17,8 +17,8 @@ use serde::{Serialize, Serializer}; use crate::{ config::ImapConfig, imap::{ - envelope::command::list::{decode_mime, format_addresses}, - mailbox::arg::name::MailboxNameOptionalFlag, + envelope::list::{decode_mime, format_addresses}, + mailbox::arg::MailboxNameOptionalFlag, stream, }, }; @@ -43,7 +43,7 @@ pub struct GetEnvelopeCommand { } impl GetEnvelopeCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; @@ -63,9 +63,8 @@ impl GetEnvelopeCommand { // FETCH envelope let id = NonZeroU32::new(self.id).ok_or_else(|| anyhow::anyhow!("ID must be non-zero"))?; - let item_names = MacroOrMessageDataItemNames::MessageDataItemNames(vec![ - MessageDataItemName::Envelope, - ]); + let item_names = + MacroOrMessageDataItemNames::MessageDataItemNames(vec![MessageDataItemName::Envelope]); let mut arg = None; let mut coroutine = ImapFetchFirst::new(context, id, item_names, !self.seq); diff --git a/src/imap/envelope/command/list.rs b/src/imap/envelope/list.rs similarity index 97% rename from src/imap/envelope/command/list.rs rename to src/imap/envelope/list.rs index 3bf7f800..c185f4e9 100644 --- a/src/imap/envelope/command/list.rs +++ b/src/imap/envelope/list.rs @@ -18,7 +18,7 @@ use pimalaya_toolbox::terminal::printer::Printer; use rfc2047_decoder::{Decoder, RecoverStrategy}; use serde::{Serialize, Serializer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalArg, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameOptionalArg, imap::stream}; /// Decode RFC 2047 MIME-encoded string, falling back to original on error. pub fn decode_mime(s: &str) -> String { @@ -52,7 +52,7 @@ pub struct ListEnvelopesCommand { } impl ListEnvelopesCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/envelope/mod.rs b/src/imap/envelope/mod.rs index 9fe79612..016a6859 100644 --- a/src/imap/envelope/mod.rs +++ b/src/imap/envelope/mod.rs @@ -1 +1,6 @@ pub mod command; +pub mod get; +pub mod list; +pub mod search; +pub mod sort; +pub mod thread; diff --git a/src/imap/envelope/command/search.rs b/src/imap/envelope/search.rs similarity index 97% rename from src/imap/envelope/command/search.rs rename to src/imap/envelope/search.rs index cb7368da..ed0e8624 100644 --- a/src/imap/envelope/command/search.rs +++ b/src/imap/envelope/search.rs @@ -15,7 +15,7 @@ use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::Printer; use serde::{Serialize, Serializer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalArg, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameOptionalArg, imap::stream}; /// Search messages by criteria. /// @@ -57,7 +57,7 @@ pub struct SearchEnvelopesCommand { } impl SearchEnvelopesCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/envelope/command/sort.rs b/src/imap/envelope/sort.rs similarity index 95% rename from src/imap/envelope/command/sort.rs rename to src/imap/envelope/sort.rs index 54da2186..b0beb685 100644 --- a/src/imap/envelope/command/sort.rs +++ b/src/imap/envelope/sort.rs @@ -16,9 +16,7 @@ use serde::{Serialize, Serializer}; use crate::{ config::ImapConfig, - imap::{ - envelope::command::search::parse_query, mailbox::arg::name::MailboxNameOptionalArg, stream, - }, + imap::{envelope::search::parse_query, mailbox::arg::MailboxNameOptionalArg, stream}, }; /// Sort messages by criteria. @@ -58,7 +56,7 @@ pub struct SortEnvelopesCommand { } impl SortEnvelopesCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/envelope/command/thread.rs b/src/imap/envelope/thread.rs similarity index 95% rename from src/imap/envelope/command/thread.rs rename to src/imap/envelope/thread.rs index 70f37bd0..8ce6cdd8 100644 --- a/src/imap/envelope/command/thread.rs +++ b/src/imap/envelope/thread.rs @@ -17,8 +17,8 @@ use serde::{Serialize, Serializer}; use crate::{ config::ImapConfig, imap::{ - envelope::command::{list::decode_mime, search::parse_query}, - mailbox::arg::name::MailboxNameOptionalArg, + envelope::{list::decode_mime, search::parse_query}, + mailbox::arg::MailboxNameOptionalArg, stream, }, }; @@ -50,7 +50,7 @@ pub struct ThreadEnvelopesCommand { } impl ThreadEnvelopesCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; @@ -80,7 +80,9 @@ impl ThreadEnvelopesCommand { let (context, threads) = loop { match coroutine.resume(arg.take()) { ImapThreadResult::Io { io } => arg = Some(handle(&mut stream, io)?), - ImapThreadResult::Ok { context, threads, .. } => break (context, threads), + ImapThreadResult::Ok { + context, threads, .. + } => break (context, threads), ImapThreadResult::Err { err, .. } => bail!(err), } }; @@ -106,9 +108,7 @@ fn parse_algorithm(s: &str) -> Result> { match s.to_lowercase().as_str() { "references" => Ok(ThreadingAlgorithm::References), "orderedsubject" => Ok(ThreadingAlgorithm::OrderedSubject), - _ => bail!( - "Unknown threading algorithm: {s}. Valid options: references, orderedsubject" - ), + _ => bail!("Unknown threading algorithm: {s}. Valid options: references, orderedsubject"), } } diff --git a/src/imap/flag/command/add.rs b/src/imap/flag/add.rs similarity index 92% rename from src/imap/flag/command/add.rs rename to src/imap/flag/add.rs index f2486765..2e7648fe 100644 --- a/src/imap/flag/command/add.rs +++ b/src/imap/flag/add.rs @@ -10,7 +10,7 @@ use io_imap::{ use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::{Message, Printer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameOptionalFlag, imap::stream}; /// Add flags to messages. /// @@ -35,7 +35,7 @@ pub struct AddFlagsCommand { } impl AddFlagsCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/flag/command/mod.rs b/src/imap/flag/command.rs similarity index 62% rename from src/imap/flag/command/mod.rs rename to src/imap/flag/command.rs index 271b22af..024c5d35 100644 --- a/src/imap/flag/command/mod.rs +++ b/src/imap/flag/command.rs @@ -1,15 +1,10 @@ -pub mod add; -pub mod list; -pub mod remove; -pub mod set; - use anyhow::Result; use clap::Subcommand; use pimalaya_toolbox::terminal::printer::Printer; use crate::{ config::ImapConfig, - imap::flag::command::{ + imap::flag::{ add::AddFlagsCommand, list::ListFlagsCommand, remove::RemoveFlagsCommand, set::SetFlagsCommand, }, @@ -29,12 +24,12 @@ pub enum FlagCommand { } impl FlagCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { match self { - Self::List(cmd) => cmd.execute(printer, config), - Self::Add(cmd) => cmd.execute(printer, config), - Self::Set(cmd) => cmd.execute(printer, config), - Self::Remove(cmd) => cmd.execute(printer, config), + Self::List(cmd) => cmd.exec(printer, config), + Self::Add(cmd) => cmd.exec(printer, config), + Self::Set(cmd) => cmd.exec(printer, config), + Self::Remove(cmd) => cmd.exec(printer, config), } } } diff --git a/src/imap/flag/command/list.rs b/src/imap/flag/list.rs similarity index 92% rename from src/imap/flag/command/list.rs rename to src/imap/flag/list.rs index 091c5479..d95c6076 100644 --- a/src/imap/flag/command/list.rs +++ b/src/imap/flag/list.rs @@ -11,7 +11,7 @@ use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::Printer; use serde::{Serialize, Serializer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalArg, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameOptionalArg, imap::stream}; /// List available flags for a mailbox. /// @@ -25,7 +25,7 @@ pub struct ListFlagsCommand { } impl ListFlagsCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; @@ -46,7 +46,10 @@ impl ListFlagsCommand { } }; - let table = FlagsTable { flags, permanent_flags }; + let table = FlagsTable { + flags, + permanent_flags, + }; printer.out(table)?; Ok(()) diff --git a/src/imap/flag/mod.rs b/src/imap/flag/mod.rs index 9fe79612..1fcd0bcf 100644 --- a/src/imap/flag/mod.rs +++ b/src/imap/flag/mod.rs @@ -1 +1,5 @@ +pub mod add; pub mod command; +pub mod list; +pub mod remove; +pub mod set; diff --git a/src/imap/flag/command/remove.rs b/src/imap/flag/remove.rs similarity index 92% rename from src/imap/flag/command/remove.rs rename to src/imap/flag/remove.rs index e5d99b78..929bb731 100644 --- a/src/imap/flag/command/remove.rs +++ b/src/imap/flag/remove.rs @@ -10,7 +10,7 @@ use io_imap::{ use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::{Message, Printer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameOptionalFlag, imap::stream}; /// Remove flags from messages. /// @@ -35,7 +35,7 @@ pub struct RemoveFlagsCommand { } impl RemoveFlagsCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/flag/command/set.rs b/src/imap/flag/set.rs similarity index 92% rename from src/imap/flag/command/set.rs rename to src/imap/flag/set.rs index dcac5b3d..083204d8 100644 --- a/src/imap/flag/command/set.rs +++ b/src/imap/flag/set.rs @@ -10,7 +10,7 @@ use io_imap::{ use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::{Message, Printer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameOptionalFlag, imap::stream}; /// Set flags on messages (replacing existing flags). /// @@ -35,7 +35,7 @@ pub struct SetFlagsCommand { } impl SetFlagsCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/mailbox/arg/name.rs b/src/imap/mailbox/arg.rs similarity index 100% rename from src/imap/mailbox/arg/name.rs rename to src/imap/mailbox/arg.rs diff --git a/src/imap/mailbox/arg/mod.rs b/src/imap/mailbox/arg/mod.rs deleted file mode 100644 index c427f91a..00000000 --- a/src/imap/mailbox/arg/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod name; diff --git a/src/imap/mailbox/command/close.rs b/src/imap/mailbox/close.rs similarity index 92% rename from src/imap/mailbox/command/close.rs rename to src/imap/mailbox/close.rs index 0b714236..b32fc17c 100644 --- a/src/imap/mailbox/command/close.rs +++ b/src/imap/mailbox/close.rs @@ -19,7 +19,7 @@ use crate::{config::ImapConfig, imap::stream}; pub struct CloseMailboxCommand; impl CloseMailboxCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mut arg = None; diff --git a/src/imap/mailbox/command/mod.rs b/src/imap/mailbox/command.rs similarity index 55% rename from src/imap/mailbox/command/mod.rs rename to src/imap/mailbox/command.rs index 317038ce..822ffded 100644 --- a/src/imap/mailbox/command/mod.rs +++ b/src/imap/mailbox/command.rs @@ -1,23 +1,10 @@ -pub mod close; -pub mod create; -pub mod delete; -pub mod expunge; -pub mod list; -pub mod purge; -pub mod rename; -pub mod select; -pub mod status; -pub mod subscribe; -pub mod unselect; -pub mod unsubscribe; - use anyhow::Result; use clap::Subcommand; use pimalaya_toolbox::terminal::printer::Printer; use crate::{ config::ImapConfig, - imap::mailbox::command::{ + imap::mailbox::{ close::CloseMailboxCommand, create::CreateMailboxCommand, delete::DeleteMailboxCommand, expunge::ExpungeMailboxCommand, list::ListMailboxesCommand, purge::PurgeMailboxCommand, rename::RenameMailboxCommand, select::SelectMailboxCommand, status::StatusMailboxCommand, @@ -50,20 +37,20 @@ pub enum MailboxCommand { } impl MailboxCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { match self { - Self::Close(cmd) => cmd.execute(printer, config), - Self::Create(cmd) => cmd.execute(printer, config), - Self::Delete(cmd) => cmd.execute(printer, config), - Self::Expunge(cmd) => cmd.execute(printer, config), - Self::List(cmd) => cmd.execute(printer, config), - Self::Purge(cmd) => cmd.execute(printer, config), - Self::Rename(cmd) => cmd.execute(printer, config), - Self::Select(cmd) => cmd.execute(printer, config), - Self::Status(cmd) => cmd.execute(printer, config), - Self::Subscribe(cmd) => cmd.execute(printer, config), - Self::Unselect(cmd) => cmd.execute(printer, config), - Self::Unsubscribe(cmd) => cmd.execute(printer, config), + Self::Close(cmd) => cmd.exec(printer, config), + Self::Create(cmd) => cmd.exec(printer, config), + Self::Delete(cmd) => cmd.exec(printer, config), + Self::Expunge(cmd) => cmd.exec(printer, config), + Self::List(cmd) => cmd.exec(printer, config), + Self::Purge(cmd) => cmd.exec(printer, config), + Self::Rename(cmd) => cmd.exec(printer, config), + Self::Select(cmd) => cmd.exec(printer, config), + Self::Status(cmd) => cmd.exec(printer, config), + Self::Subscribe(cmd) => cmd.exec(printer, config), + Self::Unselect(cmd) => cmd.exec(printer, config), + Self::Unsubscribe(cmd) => cmd.exec(printer, config), } } } diff --git a/src/imap/mailbox/command/create.rs b/src/imap/mailbox/create.rs similarity index 85% rename from src/imap/mailbox/command/create.rs rename to src/imap/mailbox/create.rs index 00c1f50c..62ebab0e 100644 --- a/src/imap/mailbox/command/create.rs +++ b/src/imap/mailbox/create.rs @@ -4,7 +4,7 @@ use io_imap::coroutines::create::*; use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::{Message, Printer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameArg, imap::stream}; /// Create the given mailbox. /// @@ -17,7 +17,7 @@ pub struct CreateMailboxCommand { } impl CreateMailboxCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/mailbox/command/delete.rs b/src/imap/mailbox/delete.rs similarity index 85% rename from src/imap/mailbox/command/delete.rs rename to src/imap/mailbox/delete.rs index f49069ad..480a5fcb 100644 --- a/src/imap/mailbox/command/delete.rs +++ b/src/imap/mailbox/delete.rs @@ -4,7 +4,7 @@ use io_imap::coroutines::delete::*; use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::{Message, Printer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameArg, imap::stream}; /// Delete the given mailbox. /// @@ -17,7 +17,7 @@ pub struct DeleteMailboxCommand { } impl DeleteMailboxCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/mailbox/command/expunge.rs b/src/imap/mailbox/expunge.rs similarity index 91% rename from src/imap/mailbox/command/expunge.rs rename to src/imap/mailbox/expunge.rs index 53a2fc50..1b69509c 100644 --- a/src/imap/mailbox/command/expunge.rs +++ b/src/imap/mailbox/expunge.rs @@ -4,7 +4,7 @@ use io_imap::coroutines::{expunge::*, select::*}; use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::{Message, Printer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameArg, imap::stream}; /// Expunge the given mailbox. /// @@ -26,7 +26,7 @@ pub struct ExpungeMailboxCommand { } impl ExpungeMailboxCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (mut context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/mailbox/command/list.rs b/src/imap/mailbox/list.rs similarity index 98% rename from src/imap/mailbox/command/list.rs rename to src/imap/mailbox/list.rs index 68c283c8..532e0b73 100644 --- a/src/imap/mailbox/command/list.rs +++ b/src/imap/mailbox/list.rs @@ -32,7 +32,7 @@ pub struct ListMailboxesCommand { } impl ListMailboxesCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let reference = self.reference.try_into()?; diff --git a/src/imap/mailbox/mod.rs b/src/imap/mailbox/mod.rs index 1a3a73a9..2fe8fea0 100644 --- a/src/imap/mailbox/mod.rs +++ b/src/imap/mailbox/mod.rs @@ -1,2 +1,14 @@ pub mod arg; +pub mod close; pub mod command; +pub mod create; +pub mod delete; +pub mod expunge; +pub mod list; +pub mod purge; +pub mod rename; +pub mod select; +pub mod status; +pub mod subscribe; +pub mod unselect; +pub mod unsubscribe; diff --git a/src/imap/mailbox/command/purge.rs b/src/imap/mailbox/purge.rs similarity index 93% rename from src/imap/mailbox/command/purge.rs rename to src/imap/mailbox/purge.rs index c18a21f3..d9642526 100644 --- a/src/imap/mailbox/command/purge.rs +++ b/src/imap/mailbox/purge.rs @@ -7,7 +7,7 @@ use io_imap::{ use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::{Message, Printer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameArg, imap::stream}; /// Shortcut for marking as deleted all envelopes then expunging the /// given mailbox. @@ -30,7 +30,7 @@ pub struct PurgeMailboxCommand { } impl PurgeMailboxCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (mut context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/mailbox/command/rename.rs b/src/imap/mailbox/rename.rs similarity index 88% rename from src/imap/mailbox/command/rename.rs rename to src/imap/mailbox/rename.rs index f883f73d..0b27ead3 100644 --- a/src/imap/mailbox/command/rename.rs +++ b/src/imap/mailbox/rename.rs @@ -7,7 +7,7 @@ use pimalaya_toolbox::terminal::printer::{Message, Printer}; use crate::{ config::ImapConfig, imap::{ - mailbox::arg::name::{MailboxNameArg, TargetMailboxNameArg}, + mailbox::arg::{MailboxNameArg, TargetMailboxNameArg}, stream, }, }; @@ -25,7 +25,7 @@ pub struct RenameMailboxCommand { } impl RenameMailboxCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let from = self.from.name.try_into()?; diff --git a/src/imap/mailbox/command/select.rs b/src/imap/mailbox/select.rs similarity index 89% rename from src/imap/mailbox/command/select.rs rename to src/imap/mailbox/select.rs index 0a8d59a7..05ce2671 100644 --- a/src/imap/mailbox/command/select.rs +++ b/src/imap/mailbox/select.rs @@ -6,7 +6,7 @@ use pimalaya_toolbox::terminal::printer::{Message, Printer}; use crate::{ config::ImapConfig, - imap::{mailbox::arg::name::MailboxNameArg, stream}, + imap::{mailbox::arg::MailboxNameArg, stream}, }; /// Select the given mailbox. @@ -24,7 +24,7 @@ pub struct SelectMailboxCommand { } impl SelectMailboxCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/mailbox/command/status.rs b/src/imap/mailbox/status.rs similarity index 96% rename from src/imap/mailbox/command/status.rs rename to src/imap/mailbox/status.rs index f646dbf1..0bb320a5 100644 --- a/src/imap/mailbox/command/status.rs +++ b/src/imap/mailbox/status.rs @@ -9,7 +9,7 @@ use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::Printer; use serde::{Serialize, Serializer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameArg, imap::stream}; /// Get the status of the given mailbox. /// @@ -22,7 +22,7 @@ pub struct StatusMailboxCommand { } impl StatusMailboxCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/mailbox/command/subscribe.rs b/src/imap/mailbox/subscribe.rs similarity index 85% rename from src/imap/mailbox/command/subscribe.rs rename to src/imap/mailbox/subscribe.rs index f9e28a9e..2f3d7686 100644 --- a/src/imap/mailbox/command/subscribe.rs +++ b/src/imap/mailbox/subscribe.rs @@ -4,7 +4,7 @@ use io_imap::coroutines::subscribe::*; use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::{Message, Printer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameArg, imap::stream}; /// Subscribe to the given mailbox. /// @@ -17,7 +17,7 @@ pub struct SubscribeMailboxCommand { } impl SubscribeMailboxCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/mailbox/command/unselect.rs b/src/imap/mailbox/unselect.rs similarity index 92% rename from src/imap/mailbox/command/unselect.rs rename to src/imap/mailbox/unselect.rs index 260deb69..9bdcbfb6 100644 --- a/src/imap/mailbox/command/unselect.rs +++ b/src/imap/mailbox/unselect.rs @@ -18,7 +18,7 @@ use crate::{config::ImapConfig, imap::stream}; pub struct UnselectMailboxCommand; impl UnselectMailboxCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mut arg = None; diff --git a/src/imap/mailbox/command/unsubscribe.rs b/src/imap/mailbox/unsubscribe.rs similarity index 85% rename from src/imap/mailbox/command/unsubscribe.rs rename to src/imap/mailbox/unsubscribe.rs index 98749028..5d1bb357 100644 --- a/src/imap/mailbox/command/unsubscribe.rs +++ b/src/imap/mailbox/unsubscribe.rs @@ -4,7 +4,7 @@ use io_imap::coroutines::unsubscribe::*; use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::{Message, Printer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameArg, imap::stream}; /// Unsubscribe from the given mailbox. /// @@ -17,7 +17,7 @@ pub struct UnsubscribeMailboxCommand { } impl UnsubscribeMailboxCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/message/command/mod.rs b/src/imap/message/command.rs similarity index 57% rename from src/imap/message/command/mod.rs rename to src/imap/message/command.rs index 25135480..608773c0 100644 --- a/src/imap/message/command/mod.rs +++ b/src/imap/message/command.rs @@ -1,18 +1,10 @@ -pub mod copy; -pub mod delete; -pub mod export; -pub mod get; -pub mod r#move; -pub mod read; -pub mod save; - use anyhow::Result; use clap::Subcommand; use pimalaya_toolbox::terminal::printer::Printer; use crate::{ config::ImapConfig, - imap::message::command::{ + imap::message::{ copy::CopyMessageCommand, delete::DeleteMessageCommand, export::ExportMessageCommand, get::GetMessageCommand, r#move::MoveMessageCommand, read::ReadMessageCommand, save::SaveMessageCommand, @@ -36,15 +28,15 @@ pub enum MessageCommand { } impl MessageCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { match self { - Self::Save(cmd) => cmd.execute(printer, config), - Self::Get(cmd) => cmd.execute(printer, config), - Self::Read(cmd) => cmd.execute(printer, config), - Self::Export(cmd) => cmd.execute(printer, config), - Self::Copy(cmd) => cmd.execute(printer, config), - Self::Move(cmd) => cmd.execute(printer, config), - Self::Delete(cmd) => cmd.execute(printer, config), + Self::Save(cmd) => cmd.exec(printer, config), + Self::Get(cmd) => cmd.exec(printer, config), + Self::Read(cmd) => cmd.exec(printer, config), + Self::Export(cmd) => cmd.exec(printer, config), + Self::Copy(cmd) => cmd.exec(printer, config), + Self::Move(cmd) => cmd.exec(printer, config), + Self::Delete(cmd) => cmd.exec(printer, config), } } } diff --git a/src/imap/message/command/copy.rs b/src/imap/message/copy.rs similarity index 92% rename from src/imap/message/command/copy.rs rename to src/imap/message/copy.rs index 6b49d08c..e17285f2 100644 --- a/src/imap/message/command/copy.rs +++ b/src/imap/message/copy.rs @@ -7,7 +7,7 @@ use io_imap::{ use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::{Message, Printer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameOptionalFlag, imap::stream}; /// Copy messages to another mailbox. /// @@ -32,7 +32,7 @@ pub struct CopyMessageCommand { } impl CopyMessageCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/message/command/delete.rs b/src/imap/message/delete.rs similarity index 93% rename from src/imap/message/command/delete.rs rename to src/imap/message/delete.rs index 26dcdbf3..e4de340e 100644 --- a/src/imap/message/command/delete.rs +++ b/src/imap/message/delete.rs @@ -7,7 +7,7 @@ use io_imap::{ use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::{Message, Printer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameOptionalFlag, imap::stream}; /// Delete messages from a mailbox. /// @@ -28,7 +28,7 @@ pub struct DeleteMessageCommand { } impl DeleteMessageCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/message/command/export.rs b/src/imap/message/export.rs similarity index 94% rename from src/imap/message/command/export.rs rename to src/imap/message/export.rs index be8a8d2a..73ce6415 100644 --- a/src/imap/message/command/export.rs +++ b/src/imap/message/export.rs @@ -15,7 +15,7 @@ use io_stream::runtimes::std::handle; use mail_parser::{MessageParser, MimeHeaders}; use pimalaya_toolbox::terminal::printer::{Message, Printer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameOptionalFlag, imap::stream}; /// Export type for message export. #[derive(Debug, Clone, clap::ValueEnum)] @@ -61,7 +61,7 @@ pub struct ExportMessageCommand { } impl ExportMessageCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; @@ -81,13 +81,12 @@ impl ExportMessageCommand { // FETCH with BODY.PEEK[] to avoid marking as read let id = NonZeroU32::new(self.id).ok_or_else(|| anyhow::anyhow!("ID must be non-zero"))?; - let item_names = MacroOrMessageDataItemNames::MessageDataItemNames(vec![ - MessageDataItemName::BodyExt { + let item_names = + MacroOrMessageDataItemNames::MessageDataItemNames(vec![MessageDataItemName::BodyExt { section: None, partial: None, peek: true, - }, - ]); + }]); let mut arg = None; let mut coroutine = ImapFetchFirst::new(context, id, item_names, !self.seq); @@ -163,11 +162,9 @@ impl ExportMessageCommand { for (i, part) in message.parts.iter().enumerate() { // Get content type - let content_type = part.content_type().map(|ct| { - match &ct.c_subtype { - Some(sub) => format!("{}/{}", ct.c_type, sub), - None => ct.c_type.to_string(), - } + let content_type = part.content_type().map(|ct| match &ct.c_subtype { + Some(sub) => format!("{}/{}", ct.c_type, sub), + None => ct.c_type.to_string(), }); // Skip multipart container parts diff --git a/src/imap/message/command/get.rs b/src/imap/message/get.rs similarity index 95% rename from src/imap/message/command/get.rs rename to src/imap/message/get.rs index 863924a8..711cbf31 100644 --- a/src/imap/message/command/get.rs +++ b/src/imap/message/get.rs @@ -8,11 +8,11 @@ use io_imap::{ types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName}, }; use io_stream::runtimes::std::handle; -use mail_parser::{Address, Addr, ContentType, MessageParser, MimeHeaders}; +use mail_parser::{Addr, Address, ContentType, MessageParser, MimeHeaders}; use pimalaya_toolbox::terminal::printer::Printer; use serde::Serialize; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameOptionalFlag, imap::stream}; /// Get a message and display its structure. /// @@ -33,7 +33,7 @@ pub struct GetMessageCommand { } impl GetMessageCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; @@ -53,13 +53,12 @@ impl GetMessageCommand { // FETCH with BODY.PEEK[] to avoid marking as read let id = NonZeroU32::new(self.id).ok_or_else(|| anyhow::anyhow!("ID must be non-zero"))?; - let item_names = MacroOrMessageDataItemNames::MessageDataItemNames(vec![ - MessageDataItemName::BodyExt { + let item_names = + MacroOrMessageDataItemNames::MessageDataItemNames(vec![MessageDataItemName::BodyExt { section: None, partial: None, peek: true, - }, - ]); + }]); let mut arg = None; let mut coroutine = ImapFetchFirst::new(context, id, item_names, !self.seq); @@ -290,7 +289,10 @@ impl fmt::Display for MessageStructure { ])); } if let Some(in_reply_to) = &self.headers.in_reply_to { - table.add_row(Row::from([Cell::new("In-Reply-To"), Cell::new(in_reply_to)])); + table.add_row(Row::from([ + Cell::new("In-Reply-To"), + Cell::new(in_reply_to), + ])); } writeln!(f)?; diff --git a/src/imap/message/mod.rs b/src/imap/message/mod.rs index 9fe79612..222509ad 100644 --- a/src/imap/message/mod.rs +++ b/src/imap/message/mod.rs @@ -1 +1,8 @@ pub mod command; +pub mod copy; +pub mod delete; +pub mod export; +pub mod get; +pub mod r#move; +pub mod read; +pub mod save; diff --git a/src/imap/message/command/move.rs b/src/imap/message/move.rs similarity index 92% rename from src/imap/message/command/move.rs rename to src/imap/message/move.rs index 86bf94b3..56b94493 100644 --- a/src/imap/message/command/move.rs +++ b/src/imap/message/move.rs @@ -7,7 +7,7 @@ use io_imap::{ use io_stream::runtimes::std::handle; use pimalaya_toolbox::terminal::printer::{Message, Printer}; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameOptionalFlag, imap::stream}; /// Move messages to another mailbox. /// @@ -33,7 +33,7 @@ pub struct MoveMessageCommand { } impl MoveMessageCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; diff --git a/src/imap/message/command/read.rs b/src/imap/message/read.rs similarity index 92% rename from src/imap/message/command/read.rs rename to src/imap/message/read.rs index c08b5ee5..1121ad06 100644 --- a/src/imap/message/command/read.rs +++ b/src/imap/message/read.rs @@ -11,7 +11,7 @@ use mail_parser::MessageParser; use pimalaya_toolbox::terminal::printer::Printer; use serde::Serialize; -use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream}; +use crate::{config::ImapConfig, imap::mailbox::arg::MailboxNameOptionalFlag, imap::stream}; /// Read message content. /// @@ -40,7 +40,7 @@ pub struct ReadMessageCommand { } impl ReadMessageCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let mailbox = self.mailbox.name.try_into()?; @@ -60,13 +60,12 @@ impl ReadMessageCommand { // FETCH with BODY.PEEK[] to avoid marking as read let id = NonZeroU32::new(self.id).ok_or_else(|| anyhow::anyhow!("ID must be non-zero"))?; - let item_names = MacroOrMessageDataItemNames::MessageDataItemNames(vec![ - MessageDataItemName::BodyExt { + let item_names = + MacroOrMessageDataItemNames::MessageDataItemNames(vec![MessageDataItemName::BodyExt { section: None, partial: None, peek: true, - }, - ]); + }]); let mut arg = None; let mut coroutine = ImapFetchFirst::new(context, id, item_names, !self.seq); diff --git a/src/imap/message/command/save.rs b/src/imap/message/save.rs similarity index 96% rename from src/imap/message/command/save.rs rename to src/imap/message/save.rs index aab8522f..1a82ab77 100644 --- a/src/imap/message/command/save.rs +++ b/src/imap/message/save.rs @@ -35,7 +35,7 @@ pub struct SaveMessageCommand { } impl SaveMessageCommand { - pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> { let (mut context, mut stream) = stream::connect(config)?; // Read message from stdin diff --git a/src/main.rs b/src/main.rs index 53556796..2b28fc21 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,9 +19,7 @@ fn main() { let config_paths = cli.config_paths.as_ref(); let account_name = cli.account.name.as_deref(); - let result = cli - .command - .execute(&mut printer, config_paths, account_name); + let result = cli.command.exec(&mut printer, config_paths, account_name); ErrorReport::eval(&mut printer, result) } diff --git a/src/smtp/command.rs b/src/smtp/command.rs index f90e6462..3633fcb4 100644 --- a/src/smtp/command.rs +++ b/src/smtp/command.rs @@ -13,14 +13,14 @@ use crate::{config::SmtpConfig, smtp::message::command::MessageCommand}; #[command(rename_all = "lowercase")] pub enum SmtpCommand { #[command(subcommand)] - #[command(aliases = ["message", "msg"])] + #[command(aliases = ["msgs", "msg"])] Messages(MessageCommand), } impl SmtpCommand { - pub fn execute(self, printer: &mut impl Printer, config: SmtpConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: SmtpConfig) -> Result<()> { match self { - Self::Messages(cmd) => cmd.execute(printer, config), + Self::Messages(cmd) => cmd.exec(printer, config), } } } diff --git a/src/smtp/message/command/command.rs b/src/smtp/message/command.rs similarity index 64% rename from src/smtp/message/command/command.rs rename to src/smtp/message/command.rs index 3f6562ef..57d56f0e 100644 --- a/src/smtp/message/command/command.rs +++ b/src/smtp/message/command.rs @@ -2,7 +2,7 @@ use anyhow::Result; use clap::Subcommand; use pimalaya_toolbox::terminal::printer::Printer; -use crate::{config::SmtpConfig, smtp::message::command::send::SendMessageCommand}; +use crate::{config::SmtpConfig, smtp::message::send::SendMessageCommand}; /// Manage messages. /// @@ -15,9 +15,9 @@ pub enum MessageCommand { } impl MessageCommand { - pub fn execute(self, printer: &mut impl Printer, config: SmtpConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: SmtpConfig) -> Result<()> { match self { - Self::Send(cmd) => cmd.execute(printer, config), + Self::Send(cmd) => cmd.exec(printer, config), } } } diff --git a/src/smtp/message/command/mod.rs b/src/smtp/message/command/mod.rs deleted file mode 100644 index b9196c4e..00000000 --- a/src/smtp/message/command/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod command; -pub mod send; - -pub use self::command::MessageCommand; diff --git a/src/smtp/message/mod.rs b/src/smtp/message/mod.rs index 9fe79612..3bfb26e5 100644 --- a/src/smtp/message/mod.rs +++ b/src/smtp/message/mod.rs @@ -1 +1,2 @@ pub mod command; +pub mod send; diff --git a/src/smtp/message/command/send.rs b/src/smtp/message/send.rs similarity index 98% rename from src/smtp/message/command/send.rs rename to src/smtp/message/send.rs index 39384ed4..9cd3beb3 100644 --- a/src/smtp/message/command/send.rs +++ b/src/smtp/message/send.rs @@ -28,7 +28,7 @@ pub struct SendMessageCommand { } impl SendMessageCommand { - pub fn execute(self, printer: &mut impl Printer, config: SmtpConfig) -> Result<()> { + pub fn exec(self, printer: &mut impl Printer, config: SmtpConfig) -> Result<()> { let (context, mut stream) = stream::connect(config)?; let message = if stdin().is_terminal() || printer.is_json() { diff --git a/test.eml b/test.eml new file mode 100644 index 00000000..764bc166 --- /dev/null +++ b/test.eml @@ -0,0 +1,10 @@ +MIME-Version: 1.0 +From: +To: +Subject: Test +Message-ID: <189af1cfd6621170.2d8f0b477933eb9f.5c085132da6091e3@soywod> +Date: Sun, 8 Mar 2026 18:41:18 +0000 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Hello! diff --git a/test.mml b/test.mml new file mode 100644 index 00000000..8f1658a1 --- /dev/null +++ b/test.mml @@ -0,0 +1,5 @@ +From: clement.douin@posteo.net +To: pimalaya.org@gmail.com +Subject: Test + +Hello!