mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-15 20:07:57 +08:00
improve modules structure
This commit is contained in:
Generated
+19
-147
@@ -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"
|
||||
|
||||
+7
-16
@@ -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"
|
||||
|
||||
+12
-8
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+77
-76
@@ -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<String>,
|
||||
pub signature: Option<String>,
|
||||
pub signature_delim: Option<String>,
|
||||
pub downloads_dir: Option<PathBuf>,
|
||||
|
||||
pub accounts: HashMap<String, AccountConfig>,
|
||||
// pub account: Option<AccountsConfig>,
|
||||
}
|
||||
|
||||
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<ImapConfig>,
|
||||
pub smtp: Option<SmtpConfig>,
|
||||
|
||||
#[serde(deserialize_with = "shell_expanded_string")]
|
||||
pub email: String,
|
||||
pub display_name: Option<String>,
|
||||
pub signature: Option<String>,
|
||||
pub signature_delim: Option<String>,
|
||||
pub downloads_dir: Option<PathBuf>,
|
||||
// pub backend: Option<Backend>,
|
||||
// #[cfg(feature = "pgp")]
|
||||
// pub pgp: Option<PgpConfig>,
|
||||
// #[cfg(not(feature = "pgp"))]
|
||||
// #[serde(default)]
|
||||
// #[serde(skip_serializing, deserialize_with = "missing_pgp_feature")]
|
||||
// pub pgp: Option<()>,
|
||||
|
||||
// pub folder: Option<FolderConfig>,
|
||||
// pub envelope: Option<EnvelopeConfig>,
|
||||
// pub message: Option<MessageConfig>,
|
||||
// pub template: Option<TemplateConfig>,
|
||||
pub imap: Option<ImapConfig>,
|
||||
pub smtp: Option<SmtpConfig>,
|
||||
}
|
||||
|
||||
/// 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<PathBuf>,
|
||||
}
|
||||
|
||||
/// 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<RustlsCryptoConfig>,
|
||||
}
|
||||
|
||||
/// 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<SaslMechanismConfig> {
|
||||
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<String>,
|
||||
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<String>,
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
pub authcid: String,
|
||||
pub passwd: SecretConfig,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct SaslAnonymousConfig {
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
struct ShellExpandedStringVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ShellExpandedStringVisitor {
|
||||
|
||||
@@ -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<usize>,
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod ids;
|
||||
@@ -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<usize>,
|
||||
|
||||
#[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<u16>,
|
||||
|
||||
/// 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 <condition> → filter envelopes that do not match the
|
||||
/// condition
|
||||
///
|
||||
/// • <condition> and <condition> → filter envelopes that match
|
||||
/// both conditions
|
||||
///
|
||||
/// • <condition> or <condition> → filter envelopes that match
|
||||
/// one of the conditions
|
||||
///
|
||||
/// ◦ date <yyyy-mm-dd> → filter envelopes that match the given
|
||||
/// date
|
||||
///
|
||||
/// ◦ before <yyyy-mm-dd> → filter envelopes with date strictly
|
||||
/// before the given one
|
||||
///
|
||||
/// ◦ after <yyyy-mm-dd> → filter envelopes with date stricly
|
||||
/// after the given one
|
||||
///
|
||||
/// ◦ from <pattern> → filter envelopes with senders matching the
|
||||
/// given pattern
|
||||
///
|
||||
/// ◦ to <pattern> → filter envelopes with recipients matching
|
||||
/// the given pattern
|
||||
///
|
||||
/// ◦ subject <pattern> → filter envelopes with subject matching
|
||||
/// the given pattern
|
||||
///
|
||||
/// ◦ body <pattern> → filter envelopes with text bodies matching
|
||||
/// the given pattern
|
||||
///
|
||||
/// ◦ flag <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
|
||||
///
|
||||
/// ◦ <kind> asc → sort envelopes by the given kind in ascending
|
||||
/// order
|
||||
///
|
||||
/// ◦ <kind> 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<Vec<String>>,
|
||||
}
|
||||
|
||||
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::<SearchEmailsQuery>());
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<usize>,
|
||||
|
||||
/// 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<Vec<String>>,
|
||||
}
|
||||
|
||||
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::<SearchEmailsQuery>());
|
||||
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)
|
||||
// }
|
||||
// }
|
||||
@@ -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<IdOrFlag>,
|
||||
}
|
||||
|
||||
#[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::<usize>().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<usize>, 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)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod ids_and_flags;
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod arg;
|
||||
pub mod command;
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod arg;
|
||||
pub mod command;
|
||||
pub mod flag;
|
||||
@@ -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<String>,
|
||||
}
|
||||
|
||||
impl MessageRawBodyArg {
|
||||
pub fn raw(self) -> String {
|
||||
self.raw.join(" ").replace('\r', "").replace('\n', "\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for MessageRawBodyArg {
|
||||
type Target = Vec<String>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.raw
|
||||
}
|
||||
}
|
||||
@@ -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:?}"))
|
||||
}
|
||||
}
|
||||
@@ -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<String>,
|
||||
}
|
||||
|
||||
impl MessageRawArg {
|
||||
pub fn raw(self) -> String {
|
||||
self.raw.join(" ").replace('\r', "").replace('\n', "\r\n")
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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<PathBuf>,
|
||||
}
|
||||
|
||||
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,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod command;
|
||||
@@ -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"
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
@@ -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<String>,
|
||||
|
||||
/// 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(())
|
||||
}
|
||||
}
|
||||
@@ -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<PathBuf>,
|
||||
|
||||
#[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(())
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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<Self> {
|
||||
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::<u8>::new();
|
||||
let mut body = Vec::<u8>::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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -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<String>,
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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::<Vec<String>>()
|
||||
.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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut state = serializer.serialize_struct("MessageAdded", 2)?;
|
||||
state.serialize_field("folder", self.folder)?;
|
||||
state.serialize_field("id", self.id.as_str())?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
@@ -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::<Vec<_>>()
|
||||
.join("\r\n")
|
||||
};
|
||||
|
||||
backend.send_message_then_save_copy(msg.as_bytes()).await?;
|
||||
|
||||
printer.out("Message successfully sent!")
|
||||
}
|
||||
}
|
||||
@@ -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<String>,
|
||||
|
||||
#[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::<usize>().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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod arg;
|
||||
pub mod attachment;
|
||||
pub mod command;
|
||||
pub mod template;
|
||||
@@ -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<String>,
|
||||
}
|
||||
|
||||
impl TemplateRawBodyArg {
|
||||
pub fn raw(self) -> String {
|
||||
self.raw.join(" ").replace('\r', "")
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TemplateRawBodyArg {
|
||||
type Target = Vec<String>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.raw
|
||||
}
|
||||
}
|
||||
@@ -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<String>,
|
||||
}
|
||||
|
||||
impl TemplateRawArg {
|
||||
pub fn raw(self) -> String {
|
||||
self.raw.join(" ").replace('\r', "")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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: <https://crates.io/crates/mml-lib>.
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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::<Vec<String>>()
|
||||
.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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut state = serializer.serialize_struct("TemplateAdded", 2)?;
|
||||
state.serialize_field("folder", self.folder)?;
|
||||
state.serialize_field("id", self.id.as_str())?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
@@ -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::<Vec<_>>()
|
||||
.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!")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod arg;
|
||||
pub mod command;
|
||||
@@ -1,8 +0,0 @@
|
||||
pub mod envelope;
|
||||
pub mod message;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use self::{
|
||||
envelope::flag,
|
||||
message::{attachment, template},
|
||||
};
|
||||
+6
-7
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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()?;
|
||||
@@ -1 +1,6 @@
|
||||
pub mod command;
|
||||
pub mod get;
|
||||
pub mod list;
|
||||
pub mod search;
|
||||
pub mod sort;
|
||||
pub mod thread;
|
||||
|
||||
@@ -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()?;
|
||||
@@ -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()?;
|
||||
@@ -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<ThreadingAlgorithm<'static>> {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()?;
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
@@ -1 +1,5 @@
|
||||
pub mod add;
|
||||
pub mod command;
|
||||
pub mod list;
|
||||
pub mod remove;
|
||||
pub mod set;
|
||||
|
||||
@@ -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()?;
|
||||
@@ -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()?;
|
||||
@@ -1 +0,0 @@
|
||||
pub mod name;
|
||||
@@ -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;
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()?;
|
||||
@@ -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()?;
|
||||
@@ -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()?;
|
||||
@@ -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()?;
|
||||
@@ -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;
|
||||
|
||||
@@ -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()?;
|
||||
@@ -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()?;
|
||||
@@ -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()?;
|
||||
@@ -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()?;
|
||||
@@ -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()?;
|
||||
@@ -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;
|
||||
@@ -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()?;
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()?;
|
||||
@@ -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()?;
|
||||
@@ -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 {
|
||||
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
|
||||
@@ -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)?;
|
||||
@@ -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;
|
||||
|
||||
@@ -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()?;
|
||||
@@ -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);
|
||||
@@ -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
|
||||
+1
-3
@@ -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)
|
||||
}
|
||||
|
||||
+3
-3
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
mod command;
|
||||
pub mod send;
|
||||
|
||||
pub use self::command::MessageCommand;
|
||||
@@ -1 +1,2 @@
|
||||
pub mod command;
|
||||
pub mod send;
|
||||
|
||||
@@ -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() {
|
||||
@@ -0,0 +1,10 @@
|
||||
MIME-Version: 1.0
|
||||
From: <clement.douin@posteo.net>
|
||||
To: <pimalaya.org@gmail.com>
|
||||
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!
|
||||
Reference in New Issue
Block a user