drastically simplified configs

Also started to refactor wizard (WIP).
This commit is contained in:
Clément DOUIN 2023-05-16 00:11:37 +02:00
parent 0ff77b5179
commit d814ae904a
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
16 changed files with 458 additions and 416 deletions

46
Cargo.lock generated
View file

@ -1630,23 +1630,6 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime-msg-builder"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3981dce6db3e7f9faa1124409a6b94436902ecb2670f374d361789d61eb34ac"
dependencies = [
"ammonia",
"chumsky 0.9.0",
"html-escape",
"lettre",
"log",
"regex",
"shellexpand",
"thiserror",
"tree_magic",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -2068,7 +2051,7 @@ dependencies = [
[[package]]
name = "pimalaya-email"
version = "0.7.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#715546fabf7246a7f4bff8371aff77f116ae9b9a"
source = "git+https://git.sr.ht/~soywod/pimalaya#05818504b399a911b88255b7d15592ee834bfbb3"
dependencies = [
"advisory-lock",
"ammonia",
@ -2084,11 +2067,11 @@ dependencies = [
"maildir",
"mailparse",
"md5",
"mime-msg-builder",
"native-tls",
"notmuch",
"once_cell",
"ouroboros",
"pimalaya-email-tpl",
"pimalaya-oauth2",
"pimalaya-process",
"pimalaya-secret",
@ -2107,10 +2090,27 @@ dependencies = [
"webpki-roots",
]
[[package]]
name = "pimalaya-email-tpl"
version = "0.1.0"
source = "git+https://git.sr.ht/~soywod/pimalaya#05818504b399a911b88255b7d15592ee834bfbb3"
dependencies = [
"ammonia",
"chumsky 0.9.0",
"html-escape",
"lettre",
"log",
"pimalaya-process",
"regex",
"shellexpand",
"thiserror",
"tree_magic",
]
[[package]]
name = "pimalaya-keyring"
version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#715546fabf7246a7f4bff8371aff77f116ae9b9a"
source = "git+https://git.sr.ht/~soywod/pimalaya#05818504b399a911b88255b7d15592ee834bfbb3"
dependencies = [
"keyring",
"log",
@ -2120,7 +2120,7 @@ dependencies = [
[[package]]
name = "pimalaya-oauth2"
version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#715546fabf7246a7f4bff8371aff77f116ae9b9a"
source = "git+https://git.sr.ht/~soywod/pimalaya#05818504b399a911b88255b7d15592ee834bfbb3"
dependencies = [
"log",
"oauth2",
@ -2132,7 +2132,7 @@ dependencies = [
[[package]]
name = "pimalaya-process"
version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#715546fabf7246a7f4bff8371aff77f116ae9b9a"
source = "git+https://git.sr.ht/~soywod/pimalaya#05818504b399a911b88255b7d15592ee834bfbb3"
dependencies = [
"log",
"thiserror",
@ -2141,7 +2141,7 @@ dependencies = [
[[package]]
name = "pimalaya-secret"
version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#715546fabf7246a7f4bff8371aff77f116ae9b9a"
source = "git+https://git.sr.ht/~soywod/pimalaya#05818504b399a911b88255b7d15592ee834bfbb3"
dependencies = [
"log",
"pimalaya-keyring",

View file

@ -6,7 +6,8 @@
use anyhow::{anyhow, Context, Result};
use dirs::{config_dir, home_dir};
use log::{debug, trace};
use pimalaya_email::{AccountConfig, BackendConfig, EmailHooks, EmailTextPlainFormat};
use pimalaya_email::{AccountConfig, EmailHooks, EmailTextPlainFormat};
use pimalaya_process::Cmd;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs, path::PathBuf};
use toml;
@ -33,11 +34,32 @@ pub struct DeserializedConfig {
pub email_reading_headers: Option<Vec<String>>,
#[serde(default, with = "EmailTextPlainFormatDef")]
pub email_reading_format: EmailTextPlainFormat,
pub email_reading_verify_cmd: Option<String>,
pub email_reading_decrypt_cmd: Option<String>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_reading_verify_cmd: Option<Cmd>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_reading_decrypt_cmd: Option<Cmd>,
pub email_writing_headers: Option<Vec<String>>,
pub email_writing_sign_cmd: Option<String>,
pub email_writing_encrypt_cmd: Option<String>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_writing_sign_cmd: Option<Cmd>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_writing_encrypt_cmd: Option<Cmd>,
pub email_sending_save_copy: Option<bool>,
#[serde(
default,
with = "EmailHooksDef",
@ -54,13 +76,15 @@ impl DeserializedConfig {
pub fn from_opt_path(path: Option<&str>) -> Result<Self> {
debug!("path: {:?}", path);
let config: Self = match path.map(|s| s.into()).or_else(Self::path) {
Some(path) => {
let content = fs::read_to_string(path).context("cannot read config file")?;
toml::from_str(&content).context("cannot parse config file")?
}
None => wizard()?,
};
// let config: Self = match path.map(|s| s.into()).or_else(Self::path) {
// Some(path) => {
// let content = fs::read_to_string(path).context("cannot read config file")?;
// toml::from_str(&content).context("cannot parse config file")?
// }
// None => wizard()?,
// };
let config = wizard()?;
if config.accounts.is_empty() {
return Err(anyhow!("config file must contain at least one account"));
@ -90,17 +114,16 @@ impl DeserializedConfig {
.filter(|p| p.exists())
}
pub fn to_configs(&self, account_name: Option<&str>) -> Result<(AccountConfig, BackendConfig)> {
pub fn to_account_config(&self, account_name: Option<&str>) -> Result<AccountConfig> {
let (account_name, deserialized_account_config) = match account_name {
Some("default") | Some("") | None => self
.accounts
.iter()
.find_map(|(name, account)| {
if account.is_default() {
Some((name.clone(), account))
} else {
None
}
account
.default
.filter(|default| *default == true)
.map(|_| (name.clone(), account))
})
.ok_or_else(|| anyhow!("cannot find default account")),
Some(name) => self
@ -110,16 +133,15 @@ impl DeserializedConfig {
.ok_or_else(|| anyhow!(format!("cannot find account {}", name))),
}?;
let (account_config, backend_config) =
deserialized_account_config.to_configs(account_name, self);
Ok((account_config, backend_config))
Ok(deserialized_account_config.to_account_config(account_name, self))
}
}
#[cfg(test)]
mod tests {
use pimalaya_email::{EmailSender, MaildirConfig, PasswdConfig, SendmailConfig};
use pimalaya_email::{
BackendConfig, MaildirConfig, PasswdConfig, SenderConfig, SendmailConfig,
};
use pimalaya_secret::Secret;
#[cfg(feature = "notmuch-backend")]
@ -132,14 +154,6 @@ mod tests {
use std::io::Write;
use tempfile::NamedTempFile;
use crate::account::DeserializedBaseAccountConfig;
#[cfg(feature = "imap-backend")]
use crate::account::DeserializedImapAccountConfig;
use crate::account::DeserializedMaildirAccountConfig;
#[cfg(feature = "notmuch-backend")]
use crate::account::DeserializedNotmuchAccountConfig;
use super::*;
fn make_config(config: &str) -> Result<DeserializedConfig> {
@ -159,9 +173,23 @@ mod tests {
}
#[test]
fn account_missing_backend_field() {
fn account_missing_email_field() {
let config = make_config("[account]");
assert!(config
.unwrap_err()
.root_cause()
.to_string()
.contains("missing field `email`"));
}
#[test]
fn account_missing_backend_field() {
let config = make_config(
"[account]
email = \"test@localhost\"",
);
assert!(config
.unwrap_err()
.root_cause()
@ -173,6 +201,7 @@ mod tests {
fn account_invalid_backend_field() {
let config = make_config(
"[account]
email = \"test@localhost\"
backend = \"bad\"",
);
@ -183,20 +212,6 @@ mod tests {
.contains("unknown variant `bad`"));
}
#[test]
fn account_missing_email_field() {
let config = make_config(
"[account]
backend = \"none\"",
);
assert!(config
.unwrap_err()
.root_cause()
.to_string()
.contains("missing field `email`"));
}
#[test]
fn imap_account_missing_host_field() {
let config = make_config(
@ -420,6 +435,8 @@ mod tests {
#[cfg(feature = "smtp-sender")]
#[test]
fn account_smtp_sender_minimum_config() {
use pimalaya_email::SenderConfig;
let config = make_config(
"[account]
email = \"test@localhost\"
@ -437,9 +454,9 @@ mod tests {
DeserializedConfig {
accounts: HashMap::from_iter([(
"account".into(),
DeserializedAccountConfig::None(DeserializedBaseAccountConfig {
DeserializedAccountConfig {
email: "test@localhost".into(),
email_sender: EmailSender::Smtp(SmtpConfig {
sender: SenderConfig::Smtp(SmtpConfig {
host: "localhost".into(),
port: 25,
login: "login".into(),
@ -448,8 +465,8 @@ mod tests {
}),
..SmtpConfig::default()
}),
..DeserializedBaseAccountConfig::default()
})
..DeserializedAccountConfig::default()
}
)]),
..DeserializedConfig::default()
}
@ -471,13 +488,13 @@ mod tests {
DeserializedConfig {
accounts: HashMap::from_iter([(
"account".into(),
DeserializedAccountConfig::None(DeserializedBaseAccountConfig {
DeserializedAccountConfig {
email: "test@localhost".into(),
email_sender: EmailSender::Sendmail(SendmailConfig {
cmd: "echo send".into(),
sender: SenderConfig::Sendmail(SendmailConfig {
cmd: Cmd::from("echo send")
}),
..DeserializedBaseAccountConfig::default()
})
..DeserializedAccountConfig::default()
}
)]),
..DeserializedConfig::default()
}
@ -503,12 +520,9 @@ mod tests {
DeserializedConfig {
accounts: HashMap::from_iter([(
"account".into(),
DeserializedAccountConfig::Imap(DeserializedImapAccountConfig {
base: DeserializedBaseAccountConfig {
email: "test@localhost".into(),
..DeserializedBaseAccountConfig::default()
},
backend: ImapConfig {
DeserializedAccountConfig {
email: "test@localhost".into(),
backend: BackendConfig::Imap(ImapConfig {
host: "localhost".into(),
port: 993,
login: "login".into(),
@ -516,8 +530,9 @@ mod tests {
passwd: Secret::new_cmd(String::from("echo password"))
}),
..ImapConfig::default()
}
})
}),
..DeserializedAccountConfig::default()
}
)]),
..DeserializedConfig::default()
}
@ -538,15 +553,13 @@ mod tests {
DeserializedConfig {
accounts: HashMap::from_iter([(
"account".into(),
DeserializedAccountConfig::Maildir(DeserializedMaildirAccountConfig {
base: DeserializedBaseAccountConfig {
email: "test@localhost".into(),
..DeserializedBaseAccountConfig::default()
},
backend: MaildirConfig {
DeserializedAccountConfig {
email: "test@localhost".into(),
backend: BackendConfig::Maildir(MaildirConfig {
root_dir: "/tmp/maildir".into(),
}
})
}),
..DeserializedAccountConfig::default()
}
)]),
..DeserializedConfig::default()
}
@ -569,15 +582,13 @@ mod tests {
DeserializedConfig {
accounts: HashMap::from_iter([(
"account".into(),
DeserializedAccountConfig::Notmuch(DeserializedNotmuchAccountConfig {
base: DeserializedBaseAccountConfig {
email: "test@localhost".into(),
..DeserializedBaseAccountConfig::default()
},
backend: NotmuchConfig {
DeserializedAccountConfig {
email: "test@localhost".into(),
backend: BackendConfig::Notmuch(NotmuchConfig {
db_path: "/tmp/notmuch.db".into(),
}
})
}),
..DeserializedAccountConfig::default()
}
)]),
..DeserializedConfig::default()
}

View file

@ -1,7 +1,7 @@
use pimalaya_email::{
folder::sync::Strategy as SyncFoldersStrategy, EmailHooks, EmailSender, EmailTextPlainFormat,
folder::sync::Strategy as SyncFoldersStrategy, BackendConfig, EmailHooks, EmailTextPlainFormat,
ImapAuthConfig, MaildirConfig, OAuth2Config, OAuth2Method, OAuth2Scopes, PasswdConfig,
SendmailConfig, SmtpAuthConfig, SmtpConfig,
SenderConfig, SendmailConfig, SmtpAuthConfig, SmtpConfig,
};
use pimalaya_keyring::Entry;
use pimalaya_process::Cmd;
@ -29,7 +29,7 @@ pub struct PipelineDef;
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Cmd", from = "SingleCmdOrPipeline")]
pub struct SingleCmdOrPipelineDef;
pub struct CmdDef;
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
@ -49,11 +49,36 @@ impl From<SingleCmdOrPipeline> for Cmd {
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Option<Cmd>", from = "OptionSingleCmdOrPipeline")]
pub struct OptionCmdDef;
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum OptionSingleCmdOrPipeline {
#[default]
None,
#[serde(with = "SingleCmdDef")]
SingleCmd(Cmd),
#[serde(with = "PipelineDef")]
Pipeline(Cmd),
}
impl From<OptionSingleCmdOrPipeline> for Option<Cmd> {
fn from(cmd: OptionSingleCmdOrPipeline) -> Option<Cmd> {
match cmd {
OptionSingleCmdOrPipeline::None => None,
OptionSingleCmdOrPipeline::SingleCmd(cmd) => Some(cmd),
OptionSingleCmdOrPipeline::Pipeline(cmd) => Some(cmd),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Secret", rename_all = "kebab-case")]
pub enum SecretDef {
Raw(String),
#[serde(with = "SingleCmdOrPipelineDef")]
#[serde(with = "CmdDef")]
Cmd(Cmd),
#[serde(with = "EntryDef")]
Keyring(Entry),
@ -68,6 +93,21 @@ pub enum OAuth2MethodDef {
OAuthBearer,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "BackendConfig", tag = "backend", rename_all = "kebab-case")]
pub enum BackendConfigDef {
#[default]
None,
#[cfg(feature = "imap-backend")]
#[serde(with = "ImapConfigDef")]
Imap(ImapConfig),
#[serde(with = "MaildirConfigDef")]
Maildir(MaildirConfig),
#[cfg(feature = "notmuch-backend")]
#[serde(with = "NotmuchConfigDef")]
Notmuch(NotmuchConfig),
}
#[cfg(feature = "imap-backend")]
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "ImapConfig")]
@ -106,7 +146,12 @@ pub enum ImapAuthConfigDef {
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "PasswdConfig")]
pub struct ImapPasswdConfigDef {
#[serde(rename = "imap-passwd", with = "SecretDef", default)]
#[serde(
rename = "imap-passwd",
with = "SecretDef",
default,
skip_serializing_if = "Secret::is_undefined_entry"
)]
pub passwd: Secret,
}
@ -117,15 +162,30 @@ pub struct ImapOAuth2ConfigDef {
pub method: OAuth2Method,
#[serde(rename = "imap-oauth2-client-id")]
pub client_id: String,
#[serde(rename = "imap-oauth2-client-secret", with = "SecretDef", default)]
#[serde(
rename = "imap-oauth2-client-secret",
with = "SecretDef",
default,
skip_serializing_if = "Secret::is_undefined_entry"
)]
pub client_secret: Secret,
#[serde(rename = "imap-oauth2-auth-url")]
pub auth_url: String,
#[serde(rename = "imap-oauth2-token-url")]
pub token_url: String,
#[serde(rename = "imap-oauth2-access-token", with = "SecretDef", default)]
#[serde(
rename = "imap-oauth2-access-token",
with = "SecretDef",
default,
skip_serializing_if = "Secret::is_undefined_entry"
)]
pub access_token: Secret,
#[serde(rename = "imap-oauth2-refresh-token", with = "SecretDef", default)]
#[serde(
rename = "imap-oauth2-refresh-token",
with = "SecretDef",
default,
skip_serializing_if = "Secret::is_undefined_entry"
)]
pub refresh_token: Secret,
#[serde(flatten, with = "ImapOAuth2ScopesDef")]
pub scopes: OAuth2Scopes,
@ -172,8 +232,8 @@ pub enum EmailTextPlainFormatDef {
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "EmailSender", tag = "sender", rename_all = "kebab-case")]
pub enum EmailSenderDef {
#[serde(remote = "SenderConfig", tag = "sender", rename_all = "kebab-case")]
pub enum SenderConfigDef {
#[default]
None,
#[serde(with = "SmtpConfigDef")]
@ -213,7 +273,12 @@ pub enum SmtpAuthConfigDef {
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "PasswdConfig", default)]
pub struct SmtpPasswdConfigDef {
#[serde(rename = "smtp-passwd", with = "SecretDef", default)]
#[serde(
rename = "smtp-passwd",
with = "SecretDef",
default,
skip_serializing_if = "Secret::is_undefined_entry"
)]
pub passwd: Secret,
}
@ -224,15 +289,30 @@ pub struct SmtpOAuth2ConfigDef {
pub method: OAuth2Method,
#[serde(rename = "smtp-oauth2-client-id")]
pub client_id: String,
#[serde(rename = "smtp-oauth2-client-secret", with = "SecretDef", default)]
#[serde(
rename = "smtp-oauth2-client-secret",
with = "SecretDef",
default,
skip_serializing_if = "Secret::is_undefined_entry"
)]
pub client_secret: Secret,
#[serde(rename = "smtp-oauth2-auth-url")]
pub auth_url: String,
#[serde(rename = "smtp-oauth2-token-url")]
pub token_url: String,
#[serde(rename = "smtp-oauth2-access-token", with = "SecretDef", default)]
#[serde(
rename = "smtp-oauth2-access-token",
with = "SecretDef",
default,
skip_serializing_if = "Secret::is_undefined_entry"
)]
pub access_token: Secret,
#[serde(rename = "smtp-oauth2-refresh-token", with = "SecretDef", default)]
#[serde(
rename = "smtp-oauth2-refresh-token",
with = "SecretDef",
default,
skip_serializing_if = "Secret::is_undefined_entry"
)]
pub refresh_token: Secret,
#[serde(flatten, with = "SmtpOAuth2ScopesDef")]
pub scopes: OAuth2Scopes,
@ -249,11 +329,11 @@ pub enum SmtpOAuth2ScopesDef {
Scopes(Vec<String>),
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "SendmailConfig", rename_all = "kebab-case")]
pub struct SendmailConfigDef {
#[serde(rename = "sendmail-cmd")]
cmd: String,
#[serde(rename = "sendmail-cmd", with = "CmdDef")]
cmd: Cmd,
}
/// Represents the email hooks. Useful for doing extra email
@ -262,7 +342,12 @@ pub struct SendmailConfigDef {
#[serde(remote = "EmailHooks", rename_all = "kebab-case")]
pub struct EmailHooksDef {
/// Represents the hook called just before sending an email.
pub pre_send: Option<String>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub pre_send: Option<Cmd>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]

View file

@ -1,23 +1,27 @@
use anyhow::Result;
use dialoguer::{Input, Select};
use pimalaya_email::ImapConfig;
use pimalaya_email::{BackendConfig, ImapConfig};
use crate::account::{
DeserializedAccountConfig, DeserializedBaseAccountConfig, DeserializedImapAccountConfig,
};
use crate::account::DeserializedAccountConfig;
use super::{SECURITY_PROTOCOLS, THEME};
use super::{AUTH_MECHANISMS, CMD, KEYRING, RAW, SECRET, SECURITY_PROTOCOLS, THEME};
#[cfg(feature = "imap-backend")]
pub(crate) fn configure(base: DeserializedBaseAccountConfig) -> Result<DeserializedAccountConfig> {
pub(crate) fn configure(base: &DeserializedAccountConfig) -> Result<BackendConfig> {
// TODO: Validate by checking as valid URI
let mut backend = ImapConfig {
host: Input::with_theme(&*THEME)
.with_prompt("Enter the IMAP host:")
.default(format!("imap.{}", base.email.rsplit_once('@').unwrap().1))
.interact()?,
..Default::default()
};
use dialoguer::Password;
use pimalaya_email::{ImapAuthConfig, PasswdConfig};
use pimalaya_secret::Secret;
use super::PASSWD;
let mut imap_config = ImapConfig::default();
imap_config.host = Input::with_theme(&*THEME)
.with_prompt("What is your IMAP host:")
.default(format!("imap.{}", base.email.rsplit_once('@').unwrap().1))
.interact()?;
let default_port = match Select::with_theme(&*THEME)
.with_prompt("Which security protocol do you want to use?")
@ -26,35 +30,60 @@ pub(crate) fn configure(base: DeserializedBaseAccountConfig) -> Result<Deseriali
.interact_opt()?
{
Some(idx) if SECURITY_PROTOCOLS[idx] == "SSL/TLS" => {
backend.ssl = Some(true);
imap_config.ssl = Some(true);
993
}
Some(idx) if SECURITY_PROTOCOLS[idx] == "STARTTLS" => {
backend.starttls = Some(true);
imap_config.starttls = Some(true);
143
}
_ => 143,
};
backend.port = Input::with_theme(&*THEME)
.with_prompt("Enter the IMAP port:")
imap_config.port = Input::with_theme(&*THEME)
.with_prompt("Which IMAP port would you like to use?")
.validate_with(|input: &String| input.parse::<u16>().map(|_| ()))
.default(default_port.to_string())
.interact()
.map(|input| input.parse::<u16>().unwrap())?;
backend.login = Input::with_theme(&*THEME)
.with_prompt("Enter your IMAP login:")
imap_config.login = Input::with_theme(&*THEME)
.with_prompt("What is your IMAP login?")
.default(base.email.clone())
.interact()?;
let auth = Select::with_theme(&*THEME)
.with_prompt("Which IMAP authentication mechanism would you like to use?")
.items(AUTH_MECHANISMS)
.default(0)
.interact_opt()?;
imap_config.auth = match auth {
Some(idx) if AUTH_MECHANISMS[idx] == PASSWD => {
let secret = Select::with_theme(&*THEME)
.with_prompt("How would you like to store your password?")
.items(SECRET)
.default(0)
.interact_opt()?;
match secret {
Some(idx) if SECRET[idx] == RAW => ImapAuthConfig::Passwd(PasswdConfig {
passwd: Secret::new_raw(
Password::with_theme(&*THEME)
.with_prompt("What is your IMAP password?")
.interact()?,
),
}),
_ => ImapAuthConfig::default(),
}
}
_ => ImapAuthConfig::default(),
};
// FIXME: add all variants: password, password command and oauth2
// backend.passwd_cmd = Input::with_theme(&*THEME)
// .with_prompt("What shell command should we run to get your password?")
// .default(format!("pass show {}", &base.email))
// .interact()?;
Ok(DeserializedAccountConfig::Imap(
DeserializedImapAccountConfig { base, backend },
))
Ok(BackendConfig::Imap(imap_config))
}

View file

@ -1,13 +1,13 @@
use super::THEME;
use crate::account::{
DeserializedAccountConfig, DeserializedBaseAccountConfig, DeserializedMaildirAccountConfig,
};
use anyhow::Result;
use dialoguer::Input;
use dirs::home_dir;
use pimalaya_email::MaildirConfig;
use pimalaya_email::{BackendConfig, MaildirConfig};
use super::THEME;
pub(crate) fn configure() -> Result<BackendConfig> {
let mut maildir_config = MaildirConfig::default();
pub(crate) fn configure(base: DeserializedBaseAccountConfig) -> Result<DeserializedAccountConfig> {
let input = if let Some(home) = home_dir() {
Input::with_theme(&*THEME)
.default(home.join("Mail").display().to_string())
@ -19,12 +19,7 @@ pub(crate) fn configure(base: DeserializedBaseAccountConfig) -> Result<Deseriali
.interact_text()?
};
Ok(DeserializedAccountConfig::Maildir(
DeserializedMaildirAccountConfig {
base,
backend: MaildirConfig {
root_dir: input.into(),
},
},
))
maildir_config.root_dir = input.into();
Ok(BackendConfig::Maildir(maildir_config))
}

View file

@ -8,26 +8,37 @@ mod smtp;
mod validators;
use super::DeserializedConfig;
use crate::account::{DeserializedAccountConfig, DeserializedBaseAccountConfig};
use crate::account::DeserializedAccountConfig;
use anyhow::{anyhow, Result};
use console::style;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Password, Select};
use log::trace;
use once_cell::sync::Lazy;
use pimalaya_email::{BackendConfig, SenderConfig};
use std::{fs, io, process};
const BACKENDS: &[&str] = &[
"Maildir",
#[cfg(feature = "imap-backend")]
"IMAP",
"Maildir",
#[cfg(feature = "notmuch-backend")]
"Notmuch",
"None",
];
const SENDERS: &[&str] = &["SMTP", "Sendmail"];
const SECURITY_PROTOCOLS: &[&str] = &["SSL/TLS", "STARTTLS", "None"];
const AUTH_MECHANISMS: &[&str] = &[PASSWD, OAUTH2];
const PASSWD: &str = "Password";
const OAUTH2: &str = "OAuth 2.0";
const SECRET: &[&str] = &[RAW, CMD, KEYRING];
const RAW: &str = "In clear, in your configuration (not recommanded)";
const CMD: &str = "From a shell command";
const KEYRING: &str = "From your system's global keyring";
// A wizard should have pretty colors 💅
static THEME: Lazy<ColorfulTheme> = Lazy::new(ColorfulTheme::default);
@ -45,9 +56,10 @@ pub(crate) fn wizard() -> Result<DeserializedConfig> {
}
// Determine path to save to
let path = dirs::config_dir()
.map(|p| p.join("himalaya").join("config.toml"))
.ok_or_else(|| anyhow!("The wizard could not determine the config directory. Aborting"))?;
// let path = dirs::config_dir()
// .map(|p| p.join("himalaya").join("config.toml"))
// .ok_or_else(|| anyhow!("The wizard could not determine the config directory. Aborting"))?;
let path = std::path::PathBuf::from("/home/soywod/config.wizard.toml");
let mut config = DeserializedConfig::default();
@ -74,7 +86,7 @@ pub(crate) fn wizard() -> Result<DeserializedConfig> {
// If one acounts is setup, make it the default. If multiple accounts are setup, decide which
// will be the default. If no accounts are setup, exit the process
let default = match config.accounts.len() {
let default_account = match config.accounts.len() {
1 => Some(config.accounts.values_mut().next().unwrap()),
i if i > 1 => {
let accounts = config.accounts.clone();
@ -97,14 +109,8 @@ pub(crate) fn wizard() -> Result<DeserializedConfig> {
_ => process::exit(0),
};
match default {
Some(DeserializedAccountConfig::None(default)) => default.default = Some(true),
Some(DeserializedAccountConfig::Maildir(default)) => default.base.default = Some(true),
#[cfg(feature = "imap-backend")]
Some(DeserializedAccountConfig::Imap(default)) => default.base.default = Some(true),
#[cfg(feature = "notmuch-backend")]
Some(DeserializedAccountConfig::Notmuch(default)) => default.base.default = Some(true),
_ => {}
if let Some(account) = default_account {
account.default = Some(true);
}
// Serialize config to file
@ -117,18 +123,18 @@ pub(crate) fn wizard() -> Result<DeserializedConfig> {
}
fn configure_account() -> Result<Option<DeserializedAccountConfig>> {
let mut base = configure_base()?;
let sender = Select::with_theme(&*THEME)
.with_prompt("Which sender would you like use with your account?")
.items(SENDERS)
.default(0)
.interact_opt()?;
let mut config = DeserializedAccountConfig::default();
base.email_sender = match sender {
Some(idx) if SENDERS[idx] == "SMTP" => smtp::configure(&base),
Some(idx) if SENDERS[idx] == "Sendmail" => sendmail::configure(),
_ => return Ok(None),
}?;
config.email = Input::with_theme(&*THEME)
.with_prompt("What is your email address?")
.validate_with(validators::EmailValidator)
.interact()?;
config.display_name = Some(
Input::with_theme(&*THEME)
.with_prompt("Which name would you like to display with your email?")
.interact()?,
);
let backend = Select::with_theme(&*THEME)
.with_prompt("Which backend would you like to configure your account for?")
@ -136,32 +142,28 @@ fn configure_account() -> Result<Option<DeserializedAccountConfig>> {
.default(0)
.interact_opt()?;
match backend {
Some(idx) if BACKENDS[idx] == "Maildir" => Ok(Some(maildir::configure(base)?)),
#[cfg(feature = "imap-backend")]
Some(idx) if BACKENDS[idx] == "IMAP" => Ok(Some(imap::configure(base)?)),
#[cfg(feature = "notmuch-backend")]
Some(idx) if BACKENDS[idx] == "Notmuch" => Ok(Some(notmuch::configure(base)?)),
_ => Ok(None),
}
}
config.backend = match backend {
Some(idx) if BACKENDS[idx] == "IMAP" => imap::configure(&config),
Some(idx) if BACKENDS[idx] == "Maildir" => maildir::configure(),
Some(idx) if BACKENDS[idx] == "Notmuch" => notmuch::configure(),
Some(idx) if BACKENDS[idx] == "None" => Ok(BackendConfig::None),
_ => return Ok(None),
}?;
fn configure_base() -> Result<DeserializedBaseAccountConfig> {
let mut base_account_config = DeserializedBaseAccountConfig {
email: Input::with_theme(&*THEME)
.with_prompt("Enter your email:")
.validate_with(validators::EmailValidator)
.interact()?,
..Default::default()
};
let sender = Select::with_theme(&*THEME)
.with_prompt("Which sender would you like use with your account?")
.items(SENDERS)
.default(0)
.interact_opt()?;
base_account_config.display_name = Some(
Input::with_theme(&*THEME)
.with_prompt("Enter display name:")
.interact()?,
);
config.sender = match sender {
Some(idx) if SENDERS[idx] == "SMTP" => smtp::configure(&config),
Some(idx) if SENDERS[idx] == "Sendmail" => sendmail::configure(),
Some(idx) if SENDERS[idx] == "None" => Ok(SenderConfig::None),
_ => return Ok(None),
}?;
Ok(base_account_config)
Ok(Some(config))
}
pub(crate) fn prompt_passwd(prompt: &str) -> io::Result<String> {

View file

@ -1,13 +1,13 @@
use super::THEME;
use crate::account::{
DeserializedAccountConfig, DeserializedBaseAccountConfig, DeserializedNotmuchAccountConfig,
};
use anyhow::Result;
use dialoguer::Input;
use pimalaya_email::{NotmuchBackend, NotmuchConfig};
use pimalaya_email::{BackendConfig, NotmuchBackend, NotmuchConfig};
pub(crate) fn configure(base: DeserializedBaseAccountConfig) -> Result<DeserializedAccountConfig> {
let db_path = match NotmuchBackend::get_default_db_path() {
use super::THEME;
pub(crate) fn configure() -> Result<BackendConfig> {
let mut notmuch_config = NotmuchConfig::default();
notmuch_config.db_path = match NotmuchBackend::get_default_db_path() {
Ok(db) => db,
_ => {
let input: String = Input::with_theme(&*THEME)
@ -17,9 +17,5 @@ pub(crate) fn configure(base: DeserializedBaseAccountConfig) -> Result<Deseriali
}
};
let backend = NotmuchConfig { db_path };
Ok(DeserializedAccountConfig::Notmuch(
DeserializedNotmuchAccountConfig { base, backend },
))
Ok(BackendConfig::Notmuch(notmuch_config))
}

View file

@ -1,13 +1,17 @@
use super::THEME;
use anyhow::Result;
use dialoguer::Input;
use pimalaya_email::{EmailSender, SendmailConfig};
use pimalaya_email::{SenderConfig, SendmailConfig};
pub(crate) fn configure() -> Result<EmailSender> {
Ok(EmailSender::Sendmail(SendmailConfig {
cmd: Input::with_theme(&*THEME)
.with_prompt("Enter an external command to send a mail: ")
.default("/usr/bin/msmtp".to_owned())
.interact()?,
}))
use super::THEME;
pub(crate) fn configure() -> Result<SenderConfig> {
let mut sendmail_config = SendmailConfig::default();
sendmail_config.cmd = Input::with_theme(&*THEME)
.with_prompt("Enter an external command to send an email: ")
.default("/usr/bin/msmtp".to_owned())
.interact()?
.into();
Ok(SenderConfig::Sendmail(sendmail_config))
}

View file

@ -1,14 +1,16 @@
use super::{SECURITY_PROTOCOLS, THEME};
use crate::account::DeserializedBaseAccountConfig;
use anyhow::Result;
use dialoguer::{Input, Select};
use pimalaya_email::{EmailSender, SmtpConfig};
use pimalaya_email::{SenderConfig, SmtpConfig};
pub(crate) fn configure(base: &DeserializedBaseAccountConfig) -> Result<EmailSender> {
use crate::account::DeserializedAccountConfig;
use super::{SECURITY_PROTOCOLS, THEME};
pub(crate) fn configure(config: &DeserializedAccountConfig) -> Result<SenderConfig> {
let mut smtp_config = SmtpConfig {
host: Input::with_theme(&*THEME)
.with_prompt("Enter the SMTP host: ")
.default(format!("smtp.{}", base.email.rsplit_once('@').unwrap().1))
.default(format!("smtp.{}", config.email.rsplit_once('@').unwrap().1))
.interact()?,
..Default::default()
};
@ -39,7 +41,7 @@ pub(crate) fn configure(base: &DeserializedBaseAccountConfig) -> Result<EmailSen
smtp_config.login = Input::with_theme(&*THEME)
.with_prompt("Enter your SMTP login:")
.default(base.email.clone())
.default(config.email.clone())
.interact()?;
// FIXME: add all variants: password, password command and oauth2
@ -48,5 +50,5 @@ pub(crate) fn configure(base: &DeserializedBaseAccountConfig) -> Result<EmailSen
// .default(format!("pass show {}", &base.email))
// .interact()?;
Ok(EmailSender::Smtp(smtp_config))
Ok(SenderConfig::Smtp(smtp_config))
}

View file

@ -5,6 +5,7 @@
//! accounts from the config file.
use anyhow::Result;
use pimalaya_email::BackendConfig;
use serde::Serialize;
use std::{collections::hash_map::Iter, ops::Deref};
@ -39,19 +40,19 @@ impl PrintTable for Accounts {
impl From<Iter<'_, String, DeserializedAccountConfig>> for Accounts {
fn from(map: Iter<'_, String, DeserializedAccountConfig>) -> Self {
let mut accounts: Vec<_> = map
.map(|(name, account)| match account {
DeserializedAccountConfig::Maildir(config) => {
Account::new(name, "maildir", config.base.default.unwrap_or_default())
.map(|(name, account)| match &account.backend {
BackendConfig::None => Account::new(name, "none", false),
BackendConfig::Maildir(_) => {
Account::new(name, "maildir", account.default.unwrap_or_default())
}
#[cfg(feature = "imap-backend")]
DeserializedAccountConfig::Imap(config) => {
Account::new(name, "imap", config.base.default.unwrap_or_default())
BackendConfig::Imap(_) => {
Account::new(name, "imap", account.default.unwrap_or_default())
}
#[cfg(feature = "notmuch-backend")]
DeserializedAccountConfig::Notmuch(config) => {
Account::new(name, "notmuch", config.base.default.unwrap_or_default())
BackendConfig::Notmuch(_) => {
Account::new(name, "notmuch", account.default.unwrap_or_default())
}
DeserializedAccountConfig::None(..) => Account::new(name, "none", false),
})
.collect();
accounts.sort_by(|a, b| b.name.partial_cmp(&a.name).unwrap());

View file

@ -5,93 +5,18 @@
use pimalaya_email::{
folder::sync::Strategy as SyncFoldersStrategy, AccountConfig, BackendConfig, EmailHooks,
EmailSender, EmailTextPlainFormat, ImapAuthConfig, MaildirConfig,
EmailTextPlainFormat, SenderConfig,
};
use pimalaya_process::Cmd;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf};
#[cfg(feature = "imap-backend")]
use pimalaya_email::ImapConfig;
#[cfg(feature = "notmuch-backend")]
use pimalaya_email::NotmuchConfig;
use crate::config::{prelude::*, DeserializedConfig};
/// Represents all existing kind of account config.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(tag = "backend", rename_all = "kebab-case")]
pub enum DeserializedAccountConfig {
None(DeserializedBaseAccountConfig),
Maildir(DeserializedMaildirAccountConfig),
#[cfg(feature = "imap-backend")]
Imap(DeserializedImapAccountConfig),
#[cfg(feature = "notmuch-backend")]
Notmuch(DeserializedNotmuchAccountConfig),
}
impl DeserializedAccountConfig {
pub fn to_configs(
&self,
name: String,
global_config: &DeserializedConfig,
) -> (AccountConfig, BackendConfig) {
match self {
DeserializedAccountConfig::None(config) => (
config.to_account_config(name, global_config),
BackendConfig::None,
),
DeserializedAccountConfig::Maildir(config) => (
config.base.to_account_config(name, global_config),
BackendConfig::Maildir(config.backend.clone()),
),
#[cfg(feature = "imap-backend")]
DeserializedAccountConfig::Imap(config) => {
let mut imap_config = config.backend.clone();
match &mut imap_config.auth {
ImapAuthConfig::Passwd(secret) => {
secret.replace_undefined_entry_with(format!("{name}-imap-passwd"));
}
ImapAuthConfig::OAuth2(config) => {
config.client_secret.replace_undefined_entry_with(format!(
"{name}-imap-oauth2-client-secret"
));
config.access_token.replace_undefined_entry_with(format!(
"{name}-imap-oauth2-access-token"
));
config.refresh_token.replace_undefined_entry_with(format!(
"{name}-imap-oauth2-refresh-token"
));
}
};
let account_config = config.base.to_account_config(name, global_config);
(account_config, BackendConfig::Imap(imap_config))
}
#[cfg(feature = "notmuch-backend")]
DeserializedAccountConfig::Notmuch(config) => (
config.base.to_account_config(name, global_config),
BackendConfig::Notmuch(config.backend.clone()),
),
}
}
pub fn is_default(&self) -> bool {
match self {
DeserializedAccountConfig::None(config) => config.default.unwrap_or_default(),
DeserializedAccountConfig::Maildir(config) => config.base.default.unwrap_or_default(),
#[cfg(feature = "imap-backend")]
DeserializedAccountConfig::Imap(config) => config.base.default.unwrap_or_default(),
#[cfg(feature = "notmuch-backend")]
DeserializedAccountConfig::Notmuch(config) => config.base.default.unwrap_or_default(),
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeserializedBaseAccountConfig {
#[serde(tag = "backend", rename_all = "kebab-case")]
pub struct DeserializedAccountConfig {
pub email: String,
pub default: Option<bool>,
pub display_name: Option<String>,
@ -106,13 +31,32 @@ pub struct DeserializedBaseAccountConfig {
pub email_reading_headers: Option<Vec<String>>,
#[serde(default, with = "EmailTextPlainFormatDef")]
pub email_reading_format: EmailTextPlainFormat,
pub email_reading_verify_cmd: Option<String>,
pub email_reading_decrypt_cmd: Option<String>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_reading_verify_cmd: Option<Cmd>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_reading_decrypt_cmd: Option<Cmd>,
pub email_writing_headers: Option<Vec<String>>,
pub email_writing_sign_cmd: Option<String>,
pub email_writing_encrypt_cmd: Option<String>,
#[serde(flatten, with = "EmailSenderDef")]
pub email_sender: EmailSender,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_writing_sign_cmd: Option<Cmd>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_writing_encrypt_cmd: Option<Cmd>,
pub email_sending_save_copy: Option<bool>,
#[serde(
default,
with = "EmailHooksDef",
@ -125,9 +69,14 @@ pub struct DeserializedBaseAccountConfig {
pub sync_dir: Option<PathBuf>,
#[serde(default, with = "SyncFoldersStrategyDef")]
pub sync_folders_strategy: SyncFoldersStrategy,
#[serde(flatten, with = "BackendConfigDef")]
pub backend: BackendConfig,
#[serde(flatten, with = "SenderConfigDef")]
pub sender: SenderConfig,
}
impl DeserializedBaseAccountConfig {
impl DeserializedAccountConfig {
pub fn to_account_config(&self, name: String, config: &DeserializedConfig) -> AccountConfig {
let mut folder_aliases = config
.folder_aliases
@ -222,39 +171,16 @@ impl DeserializedBaseAccountConfig {
.as_ref()
.map(ToOwned::to_owned)
.or_else(|| config.email_writing_headers.as_ref().map(ToOwned::to_owned)),
email_sender: self.email_sender.to_owned(),
email_sending_save_copy: self.email_sending_save_copy.unwrap_or(true),
email_hooks: EmailHooks {
pre_send: self.email_hooks.pre_send.clone(),
},
sync: self.sync,
sync_dir: self.sync_dir.clone(),
sync_folders_strategy: self.sync_folders_strategy.clone(),
backend: self.backend.clone(),
sender: self.sender.clone(),
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg(feature = "imap-backend")]
pub struct DeserializedImapAccountConfig {
#[serde(flatten)]
pub base: DeserializedBaseAccountConfig,
#[serde(flatten, with = "ImapConfigDef")]
pub backend: ImapConfig,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct DeserializedMaildirAccountConfig {
#[serde(flatten)]
pub base: DeserializedBaseAccountConfig,
#[serde(flatten, with = "MaildirConfigDef")]
pub backend: MaildirConfig,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[cfg(feature = "notmuch-backend")]
pub struct DeserializedNotmuchAccountConfig {
#[serde(flatten)]
pub base: DeserializedBaseAccountConfig,
#[serde(flatten, with = "NotmuchConfigDef")]
pub backend: NotmuchConfig,
}

View file

@ -7,7 +7,7 @@ use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use log::{info, trace, warn};
use pimalaya_email::{
folder::sync::Strategy as SyncFoldersStrategy, AccountConfig, Backend, BackendConfig,
BackendSyncBuilder, BackendSyncProgressEvent, EmailSender, ImapAuthConfig, SmtpAuthConfig,
BackendSyncBuilder, BackendSyncProgressEvent, ImapAuthConfig, SenderConfig, SmtpAuthConfig,
};
use crate::{
@ -20,16 +20,12 @@ use crate::{
};
/// Configure the current selected account
pub fn configure(
account_config: &AccountConfig,
backend_config: &BackendConfig,
reset: bool,
) -> Result<()> {
pub fn configure(config: &AccountConfig, reset: bool) -> Result<()> {
info!("entering the configure account handler");
if reset {
#[cfg(feature = "imap-backend")]
if let BackendConfig::Imap(imap_config) = backend_config {
if let BackendConfig::Imap(imap_config) = &config.backend {
let reset = match &imap_config.auth {
ImapAuthConfig::Passwd(passwd) => passwd.reset(),
ImapAuthConfig::OAuth2(oauth2) => oauth2.reset(),
@ -41,7 +37,7 @@ pub fn configure(
}
#[cfg(feature = "smtp-sender")]
if let EmailSender::Smtp(smtp_config) = &account_config.email_sender {
if let SenderConfig::Smtp(smtp_config) = &config.sender {
let reset = match &smtp_config.auth {
SmtpAuthConfig::Passwd(passwd) => passwd.reset(),
SmtpAuthConfig::OAuth2(oauth2) => oauth2.reset(),
@ -54,7 +50,7 @@ pub fn configure(
}
#[cfg(feature = "imap-backend")]
if let BackendConfig::Imap(imap_config) = backend_config {
if let BackendConfig::Imap(imap_config) = &config.backend {
match &imap_config.auth {
ImapAuthConfig::Passwd(passwd) => {
passwd.configure(|| prompt_passwd("Enter your IMAP password:"))
@ -66,7 +62,7 @@ pub fn configure(
}
#[cfg(feature = "smtp-sender")]
if let EmailSender::Smtp(smtp_config) = &account_config.email_sender {
if let SenderConfig::Smtp(smtp_config) = &config.sender {
match &smtp_config.auth {
SmtpAuthConfig::Passwd(passwd) => {
passwd.configure(|| prompt_passwd("Enter your SMTP password:"))
@ -296,9 +292,7 @@ mod tests {
use termcolor::ColorSpec;
use crate::{
account::{
DeserializedAccountConfig, DeserializedBaseAccountConfig, DeserializedImapAccountConfig,
},
account::DeserializedAccountConfig,
printer::{Print, PrintTable, WriteColor},
};
@ -370,13 +364,11 @@ mod tests {
let deserialized_config = DeserializedConfig {
accounts: HashMap::from_iter([(
"account-1".into(),
DeserializedAccountConfig::Imap(DeserializedImapAccountConfig {
base: DeserializedBaseAccountConfig {
default: Some(true),
..DeserializedBaseAccountConfig::default()
},
backend: ImapConfig::default(),
}),
DeserializedAccountConfig {
default: Some(true),
backend: BackendConfig::Imap(ImapConfig::default()),
..DeserializedAccountConfig::default()
},
)]),
..DeserializedConfig::default()
};

View file

@ -165,9 +165,9 @@ pub fn list<P: Printer>(
/// [mailto]: https://en.wikipedia.org/wiki/Mailto
pub fn mailto<P: Printer>(
config: &AccountConfig,
printer: &mut P,
backend: &mut dyn Backend,
sender: &mut dyn Sender,
printer: &mut P,
url: &Url,
) -> Result<()> {
let mut tpl = TplBuilder::default().to(url.path());

View file

@ -74,8 +74,8 @@ pub fn save<P: Printer>(
})
.compile(
CompilerBuilder::default()
.some_pgp_sign_cmd(config.email_writing_sign_cmd.as_ref())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.as_ref()),
.some_pgp_sign_cmd(config.email_writing_sign_cmd.clone())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()),
)?;
let id = backend.add_email(folder, &email, &Flags::default())?;
@ -104,8 +104,8 @@ pub fn send<P: Printer>(
})
.compile(
CompilerBuilder::default()
.some_pgp_sign_cmd(config.email_writing_sign_cmd.as_ref())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.as_ref()),
.some_pgp_sign_cmd(config.email_writing_sign_cmd.clone())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()),
)?;
sender.send(&email)?;
backend.add_email(folder, &email, &Flags::default())?;

View file

@ -51,17 +51,16 @@ fn main() -> Result<()> {
if raw_args.len() > 1 && raw_args[1].starts_with("mailto:") {
let url = Url::parse(&raw_args[1])?;
let config = DeserializedConfig::from_opt_path(None)?;
let (account_config, backend_config) = config.to_configs(None)?;
let mut backend =
BackendBuilder::new().build(account_config.clone(), backend_config.clone())?;
let mut sender = SenderBuilder::build(&account_config)?;
let account_config = config.to_account_config(None)?;
let mut backend = BackendBuilder::new().build(&account_config)?;
let mut sender = SenderBuilder::new().build(&account_config)?;
let mut printer = StdoutPrinter::default();
return email::handlers::mailto(
&account_config,
&mut printer,
backend.as_mut(),
sender.as_mut(),
&mut printer,
&url,
);
}
@ -88,12 +87,12 @@ fn main() -> Result<()> {
// inits config
let config = DeserializedConfig::from_opt_path(config::args::parse_arg(&m))?;
let (account_config, backend_config) = config.to_configs(account::args::parse_arg(&m))?;
let account_config = config.to_account_config(account::args::parse_arg(&m))?;
let folder = folder::args::parse_source_arg(&m);
// checks IMAP commands
#[cfg(feature = "imap-backend")]
if let BackendConfig::Imap(imap_config) = &backend_config {
if let BackendConfig::Imap(imap_config) = &account_config.backend {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
// FIXME: find a way to downcast `backend` instead of
@ -112,7 +111,7 @@ fn main() -> Result<()> {
}
// inits services
let mut sender = SenderBuilder::build(&account_config)?;
let mut sender = SenderBuilder::new().build(&account_config)?;
let mut printer = StdoutPrinter::try_from(&m)?;
let disable_cache = cache::args::parse_disable_cache_flag(&m);
@ -125,7 +124,7 @@ fn main() -> Result<()> {
let backend = BackendBuilder::new()
.sessions_pool_size(8)
.disable_cache(true)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
account::handlers::sync(
&account_config,
&mut printer,
@ -137,7 +136,7 @@ fn main() -> Result<()> {
return Ok(());
}
Some(account::args::Cmd::Configure(reset)) => {
return account::handlers::configure(&account_config, &backend_config, reset);
return account::handlers::configure(&account_config, reset);
}
_ => (),
}
@ -151,13 +150,13 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder)?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
return folder::handlers::create(&mut printer, backend.as_mut(), &folder);
}
Some(folder::args::Cmd::List(max_width)) => {
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
return folder::handlers::list(
&account_config,
&mut printer,
@ -169,7 +168,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
return folder::handlers::expunge(&mut printer, backend.as_mut(), &folder);
}
Some(folder::args::Cmd::Delete) => {
@ -179,7 +178,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder)?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
return folder::handlers::delete(&mut printer, backend.as_mut(), &folder);
}
_ => (),
@ -191,7 +190,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::attachments(
&account_config,
@ -206,7 +205,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::copy(
&account_config,
@ -222,7 +221,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::delete(
&account_config,
@ -237,7 +236,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::forward(
&account_config,
@ -255,7 +254,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::list(
&account_config,
@ -272,7 +271,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::move_(
&account_config,
@ -288,7 +287,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::read(
&account_config,
@ -307,7 +306,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::reply(
&account_config,
@ -326,7 +325,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::save(
&account_config,
@ -341,7 +340,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::search(
&account_config,
@ -359,7 +358,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return email::handlers::sort(
&account_config,
@ -377,7 +376,7 @@ fn main() -> Result<()> {
Some(email::args::Cmd::Send(raw_email)) => {
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
return email::handlers::send(
&account_config,
&mut printer,
@ -391,7 +390,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return flag::handlers::set(
&mut printer,
@ -406,7 +405,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return flag::handlers::add(
&mut printer,
@ -421,7 +420,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return flag::handlers::remove(
&mut printer,
@ -439,7 +438,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return tpl::handlers::forward(
&account_config,
@ -459,7 +458,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return tpl::handlers::reply(
&account_config,
@ -477,7 +476,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?;
return tpl::handlers::save(
&account_config,
@ -492,7 +491,7 @@ fn main() -> Result<()> {
let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?;
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
return tpl::handlers::send(
&account_config,
&mut printer,
@ -507,7 +506,7 @@ fn main() -> Result<()> {
Some(email::args::Cmd::Write(headers, body)) => {
let mut backend = BackendBuilder::new()
.disable_cache(disable_cache)
.build(account_config.clone(), backend_config.clone())?;
.build(&account_config)?;
return email::handlers::write(
&account_config,
&mut printer,

View file

@ -75,8 +75,8 @@ pub fn edit_tpl_with_editor<P: Printer>(
printer.print_log("Sending email…")?;
let email = tpl.compile(
CompilerBuilder::default()
.some_pgp_sign_cmd(config.email_writing_sign_cmd.as_ref())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.as_ref()),
.some_pgp_sign_cmd(config.email_writing_sign_cmd.clone())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()),
)?;
sender.send(&email)?;
let sent_folder = config.sent_folder_alias()?;
@ -98,8 +98,8 @@ pub fn edit_tpl_with_editor<P: Printer>(
let draft_folder = config.folder_alias("drafts")?;
let email = tpl.compile(
CompilerBuilder::default()
.some_pgp_sign_cmd(config.email_writing_sign_cmd.as_ref())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.as_ref()),
.some_pgp_sign_cmd(config.email_writing_sign_cmd.clone())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()),
)?;
backend.add_email(
&draft_folder,