bump deps, make global config option repeatable

This commit is contained in:
Clément DOUIN 2024-03-16 22:20:19 +01:00
parent 3868c62511
commit 7ee710634b
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
10 changed files with 374 additions and 359 deletions

View file

@ -13,9 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Made the global `--config|-c` option repeatable: the first option is considered the path to the main config, and successive options are considered partial overrides [#184].
- Changed the `envelope list` options (see `envelope list --help` for more details): - Changed the `envelope list` options (see `envelope list --help` for more details):
- the folder argument became a flag `--folder <name>` - The folder argument became a flag `--folder <name>`.
- the query argument has been added at the end of the command to filter and sort results [#39] - The query argument has been added at the end of the command to filter and sort results [#39].
### Fixed ### Fixed
@ -807,4 +808,5 @@ Few major concepts changed:
[#95]: https://todo.sr.ht/~soywod/pimalaya/95 [#95]: https://todo.sr.ht/~soywod/pimalaya/95
[#172]: https://todo.sr.ht/~soywod/pimalaya/172 [#172]: https://todo.sr.ht/~soywod/pimalaya/172
[#173]: https://todo.sr.ht/~soywod/pimalaya/173 [#173]: https://todo.sr.ht/~soywod/pimalaya/173
[#184]: https://todo.sr.ht/~soywod/pimalaya/184
[#188]: https://todo.sr.ht/~soywod/pimalaya/188 [#188]: https://todo.sr.ht/~soywod/pimalaya/188

446
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
[package] [package]
name = "himalaya" name = "himalaya"
description = "CLI to manage emails" description = "CLI to manage emails"
version = "1.0.0-beta.3" version = "1.0.0-beta.4"
authors = ["soywod <clement.douin@posteo.net>"] authors = ["soywod <clement.douin@posteo.net>"]
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
@ -13,7 +13,7 @@ repository = "https://github.com/soywod/himalaya/"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs", "--document-private-items"]
[features] [features]
default = [ default = [
@ -46,7 +46,6 @@ pgp-gpg = ["email-lib/pgp-gpg", "mml-lib/pgp-gpg", "pgp"]
pgp-native = ["email-lib/pgp-native", "mml-lib/pgp-native", "pgp"] pgp-native = ["email-lib/pgp-native", "mml-lib/pgp-native", "pgp"]
[dev-dependencies] [dev-dependencies]
async-trait = "0.1"
tempfile = "3.3" tempfile = "3.3"
[dependencies] [dependencies]
@ -60,29 +59,30 @@ clap_mangen = "0.2"
console = "0.15.2" console = "0.15.2"
dialoguer = "0.10.2" dialoguer = "0.10.2"
dirs = "4" dirs = "4"
email-lib = { version = "=0.22.3", default-features = false } email-lib = { version = "=0.22.3", default-features = false, features = ["derive"] }
email_address = "0.2.4" email_address = "0.2.4"
env_logger = "0.8" env_logger = "0.8"
erased-serde = "0.3" erased-serde = "0.3"
indicatif = "0.17" indicatif = "0.17"
keyring-lib = "=0.3.2" keyring-lib = { version = "=0.4.0", features = ["derive"] }
log = "0.4" log = "0.4"
mail-builder = "0.3" mail-builder = "0.3"
md5 = "0.7" md5 = "0.7"
mml-lib = { version = "=1.0.7", default-features = false } mml-lib = { version = "=1.0.8", default-features = false, features = ["derive"] }
oauth-lib = "=0.1.0" oauth-lib = "=0.1.0"
once_cell = "1.16" once_cell = "1.16"
process-lib = "=0.3.1" process-lib = { version = "=0.4.1", features = ["derive"] }
secret-lib = "=0.3.3" secret-lib = { version = "=0.4.1", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde-toml-merge = "0.3"
serde_json = "1" serde_json = "1"
shellexpand-utils = "=0.2.0" shellexpand-utils = "=0.2.0"
sled = "=0.34.7" sled = "=0.34.7"
termcolor = "1" termcolor = "1"
terminal_size = "0.1" terminal_size = "0.1"
tokio = { version = "1.23", default-features = false, features = ["macros", "rt-multi-thread"] } tokio = { version = "1.23", default-features = false, features = ["macros", "rt-multi-thread"] }
toml = "0.7.4" toml = "0.8"
toml_edit = "0.19.8" toml_edit = "0.22"
unicode-width = "0.1" unicode-width = "0.1"
url = "2.2" url = "2.2"
uuid = { version = "0.8", features = ["v4"] } uuid = { version = "0.8", features = ["v4"] }
@ -91,5 +91,14 @@ uuid = { version = "0.8", features = ["v4"] }
version = "0.1" version = "0.1"
[patch.crates-io] [patch.crates-io]
email-lib = { git = "https://git.sr.ht/~soywod/pimalaya" } # waiting for alpha 7
chumsky = { git = "https://github.com/zesterer/chumsky.git", rev = "6837537" } chumsky = { git = "https://github.com/zesterer/chumsky.git", rev = "6837537" }
email-lib = { git = "https://git.sr.ht/~soywod/pimalaya" }
email-macros = { git = "https://git.sr.ht/~soywod/pimalaya" }
keyring-lib = { git = "https://git.sr.ht/~soywod/pimalaya" }
mml-lib = { git = "https://git.sr.ht/~soywod/pimalaya" }
oauth-lib = { git = "https://git.sr.ht/~soywod/pimalaya" }
pgp-lib = { git = "https://git.sr.ht/~soywod/pimalaya" }
process-lib = { git = "https://git.sr.ht/~soywod/pimalaya" }
secret-lib = { git = "https://git.sr.ht/~soywod/pimalaya" }
shellexpand-utils = { git = "https://git.sr.ht/~soywod/pimalaya" }

View file

@ -25,17 +25,19 @@ pub struct Cli {
#[command(subcommand)] #[command(subcommand)]
pub command: Option<HimalayaCommand>, pub command: Option<HimalayaCommand>,
/// Override the default configuration file path /// Override the default configuration file path.
/// ///
/// The given path is shell-expanded then canonicalized (if /// The given paths are shell-expanded then canonicalized (if
/// applicable). If the path does not point to a valid file, the /// applicable). If the first path does not point to a valid file,
/// wizard will propose to assist you in the creation of the /// the wizard will propose to assist you in the creation of the
/// configuration file. /// configuration file. Other paths are merged with the first one,
/// which allows you to separate your public config from your
/// private(s) one(s).
#[arg(short, long = "config", global = true)] #[arg(short, long = "config", global = true)]
#[arg(value_name = "PATH", value_parser = config::path_parser)] #[arg(value_name = "PATH", value_parser = config::path_parser)]
pub config_path: Option<PathBuf>, pub config_paths: Vec<PathBuf>,
/// Customize the output format /// Customize the output format.
/// ///
/// The output format determine how to display commands output to /// The output format determine how to display commands output to
/// the terminal. /// the terminal.
@ -116,39 +118,34 @@ pub enum HimalayaCommand {
} }
impl HimalayaCommand { impl HimalayaCommand {
#[allow(unused)] pub async fn execute(self, printer: &mut impl Printer, config_paths: &[PathBuf]) -> Result<()> {
pub async fn execute(
self,
printer: &mut impl Printer,
config_path: Option<&PathBuf>,
) -> Result<()> {
match self { match self {
Self::Account(cmd) => { Self::Account(cmd) => {
let config = TomlConfig::from_some_path_or_default(config_path).await?; let config = TomlConfig::from_paths_or_default(config_paths).await?;
cmd.execute(printer, &config).await cmd.execute(printer, &config).await
} }
Self::Folder(cmd) => { Self::Folder(cmd) => {
let config = TomlConfig::from_some_path_or_default(config_path).await?; let config = TomlConfig::from_paths_or_default(config_paths).await?;
cmd.execute(printer, &config).await cmd.execute(printer, &config).await
} }
Self::Envelope(cmd) => { Self::Envelope(cmd) => {
let config = TomlConfig::from_some_path_or_default(config_path).await?; let config = TomlConfig::from_paths_or_default(config_paths).await?;
cmd.execute(printer, &config).await cmd.execute(printer, &config).await
} }
Self::Flag(cmd) => { Self::Flag(cmd) => {
let config = TomlConfig::from_some_path_or_default(config_path).await?; let config = TomlConfig::from_paths_or_default(config_paths).await?;
cmd.execute(printer, &config).await cmd.execute(printer, &config).await
} }
Self::Message(cmd) => { Self::Message(cmd) => {
let config = TomlConfig::from_some_path_or_default(config_path).await?; let config = TomlConfig::from_paths_or_default(config_paths).await?;
cmd.execute(printer, &config).await cmd.execute(printer, &config).await
} }
Self::Attachment(cmd) => { Self::Attachment(cmd) => {
let config = TomlConfig::from_some_path_or_default(config_path).await?; let config = TomlConfig::from_paths_or_default(config_paths).await?;
cmd.execute(printer, &config).await cmd.execute(printer, &config).await
} }
Self::Template(cmd) => { Self::Template(cmd) => {
let config = TomlConfig::from_some_path_or_default(config_path).await?; let config = TomlConfig::from_paths_or_default(config_paths).await?;
cmd.execute(printer, &config).await cmd.execute(printer, &config).await
} }
Self::Manual(cmd) => cmd.execute(printer).await, Self::Manual(cmd) => cmd.execute(printer).await,

View file

@ -1,21 +1,17 @@
pub mod wizard; pub mod wizard;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use dirs::{config_dir, home_dir}; use dirs::{config_dir, home_dir};
use email::{ use email::{
account::config::AccountConfig, config::Config, envelope::config::EnvelopeConfig, account::config::AccountConfig, config::Config, envelope::config::EnvelopeConfig,
flag::config::FlagConfig, folder::config::FolderConfig, message::config::MessageConfig, flag::config::FlagConfig, folder::config::FolderConfig, message::config::MessageConfig,
}; };
use log::debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_toml_merge::merge;
use shellexpand_utils::{canonicalize, expand}; use shellexpand_utils::{canonicalize, expand};
use std::{ use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
collections::HashMap, use toml::{self, Value};
fs,
path::{Path, PathBuf},
sync::Arc,
};
use toml;
#[cfg(feature = "account-sync")] #[cfg(feature = "account-sync")]
use crate::backend::BackendKind; use crate::backend::BackendKind;
@ -34,14 +30,48 @@ pub struct TomlConfig {
} }
impl TomlConfig { impl TomlConfig {
/// Read and parse the TOML configuration at the given path. /// Read and parse the TOML configuration at the given paths.
/// ///
/// Returns an error if the configuration file cannot be read or /// Returns an error if a configuration file cannot be read or if
/// if its content cannot be parsed. /// a content cannot be parsed.
fn from_path(path: &Path) -> Result<Self> { fn from_paths(paths: &[PathBuf]) -> Result<Self> {
let content = match paths.len() {
fs::read_to_string(path).context(format!("cannot read config file at {path:?}"))?; 0 => {
toml::from_str(&content).context(format!("cannot parse config file at {path:?}")) // should never happen
bail!("cannot read config file from empty paths");
}
1 => {
let path = &paths[0];
let ref content = fs::read_to_string(path)
.context(format!("cannot read config file at {path:?}"))?;
toml::from_str(content).context(format!("cannot parse config file at {path:?}"))
}
_ => {
let path = &paths[0];
let mut merged_content = fs::read_to_string(path)
.context(format!("cannot read config file at {path:?}"))?
.parse::<Value>()?;
for path in &paths[1..] {
match fs::read_to_string(path) {
Ok(content) => {
merged_content = merge(merged_content, content.parse()?).unwrap();
}
Err(err) => {
debug!("skipping subconfig file at {path:?}: {err}");
continue;
}
}
}
merged_content
.try_into()
.context(format!("cannot parse merged config file at {path:?}"))
}
}
} }
/// Create and save a TOML configuration using the wizard. /// Create and save a TOML configuration using the wizard.
@ -51,7 +81,7 @@ impl TomlConfig {
/// program stops. /// program stops.
/// ///
/// NOTE: the wizard can only be used with interactive shells. /// NOTE: the wizard can only be used with interactive shells.
async fn from_wizard(path: PathBuf) -> Result<Self> { async fn from_wizard(path: &PathBuf) -> Result<Self> {
use dialoguer::Confirm; use dialoguer::Confirm;
use std::process; use std::process;
@ -75,8 +105,8 @@ impl TomlConfig {
/// Read and parse the TOML configuration from default paths. /// Read and parse the TOML configuration from default paths.
pub async fn from_default_paths() -> Result<Self> { pub async fn from_default_paths() -> Result<Self> {
match Self::first_valid_default_path() { match Self::first_valid_default_path() {
Some(path) => Self::from_path(&path), Some(path) => Self::from_paths(&[path]),
None => Self::from_wizard(Self::default_path()?).await, None => Self::from_wizard(&Self::default_path()?).await,
} }
} }
@ -92,11 +122,11 @@ impl TomlConfig {
/// If no path is given, then either read and parse the TOML /// If no path is given, then either read and parse the TOML
/// configuration at the first valid default path, otherwise /// configuration at the first valid default path, otherwise
/// create it using the wizard. wizard. /// create it using the wizard. wizard.
pub async fn from_some_path_or_default(path: Option<impl Into<PathBuf>>) -> Result<Self> { pub async fn from_paths_or_default(paths: &[PathBuf]) -> Result<Self> {
match path.map(Into::into) { match paths.len() {
Some(ref path) if path.exists() => Self::from_path(path), 0 => Self::from_default_paths().await,
Some(path) => Self::from_wizard(path).await, _ if paths[0].exists() => Self::from_paths(paths),
_ => Self::from_default_paths().await, _ => Self::from_wizard(&paths[0]).await,
} }
} }
@ -157,14 +187,14 @@ impl TomlConfig {
if let Some(imap_config) = toml_account_config.imap.as_mut() { if let Some(imap_config) = toml_account_config.imap.as_mut() {
imap_config imap_config
.auth .auth
.replace_undefined_keyring_entries(&account_name); .replace_undefined_keyring_entries(&account_name)?;
} }
#[cfg(feature = "smtp")] #[cfg(feature = "smtp")]
if let Some(smtp_config) = toml_account_config.smtp.as_mut() { if let Some(smtp_config) = toml_account_config.smtp.as_mut() {
smtp_config smtp_config
.auth .auth
.replace_undefined_keyring_entries(&account_name); .replace_undefined_keyring_entries(&account_name)?;
} }
Ok((account_name, toml_account_config)) Ok((account_name, toml_account_config))

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use dialoguer::{Confirm, Input, Select}; use dialoguer::{Confirm, Input, Select};
use shellexpand_utils::expand; use shellexpand_utils::expand;
use std::{fs, path::PathBuf, process}; use std::{fs, path::PathBuf, process};
use toml_edit::{Document, Item}; use toml_edit::{DocumentMut, Item};
use crate::{account, ui::THEME}; use crate::{account, ui::THEME};
@ -31,7 +31,7 @@ macro_rules! wizard_log {
}; };
} }
pub(crate) async fn configure(path: PathBuf) -> Result<TomlConfig> { pub(crate) async fn configure(path: &PathBuf) -> Result<TomlConfig> {
wizard_log!("Configuring your first account:"); wizard_log!("Configuring your first account:");
let mut config = TomlConfig::default(); let mut config = TomlConfig::default();
@ -103,7 +103,7 @@ pub(crate) async fn configure(path: PathBuf) -> Result<TomlConfig> {
} }
fn pretty_serialize(config: &TomlConfig) -> Result<String> { fn pretty_serialize(config: &TomlConfig) -> Result<String> {
let mut doc: Document = toml::to_string(&config)?.parse()?; let mut doc: DocumentMut = toml::to_string(&config)?.parse()?;
doc.iter_mut().for_each(|(_, item)| { doc.iter_mut().for_each(|(_, item)| {
if let Some(item) = item.as_table_mut() { if let Some(item) = item.as_table_mut() {
@ -299,7 +299,9 @@ folder.sync.permissions.delete = true
host: "localhost".into(), host: "localhost".into(),
port: 143, port: 143,
login: "test@localhost".into(), login: "test@localhost".into(),
auth: ImapAuthConfig::Passwd(PasswdConfig(Secret::new_cmd("pass show test"))), auth: ImapAuthConfig::Passwd(PasswdConfig(Secret::new_command(
"pass show test",
))),
..Default::default() ..Default::default()
}), }),
..Default::default() ..Default::default()
@ -330,7 +332,7 @@ imap.passwd.cmd = "pass show test"
host: "localhost".into(), host: "localhost".into(),
port: 143, port: 143,
login: "test@localhost".into(), login: "test@localhost".into(),
auth: ImapAuthConfig::Passwd(PasswdConfig(Secret::new_cmd(vec![ auth: ImapAuthConfig::Passwd(PasswdConfig(Secret::new_command(vec![
"pass show test", "pass show test",
"tr -d '[:blank:]'", "tr -d '[:blank:]'",
]))), ]))),
@ -424,7 +426,9 @@ maildir.root-dir = "/tmp/test"
host: "localhost".into(), host: "localhost".into(),
port: 143, port: 143,
login: "test@localhost".into(), login: "test@localhost".into(),
auth: SmtpAuthConfig::Passwd(PasswdConfig(Secret::new_cmd("pass show test"))), auth: SmtpAuthConfig::Passwd(PasswdConfig(Secret::new_command(
"pass show test",
))),
..Default::default() ..Default::default()
}), }),
..Default::default() ..Default::default()
@ -455,7 +459,7 @@ smtp.passwd.cmd = "pass show test"
host: "localhost".into(), host: "localhost".into(),
port: 143, port: 143,
login: "test@localhost".into(), login: "test@localhost".into(),
auth: SmtpAuthConfig::Passwd(PasswdConfig(Secret::new_cmd(vec![ auth: SmtpAuthConfig::Passwd(PasswdConfig(Secret::new_command(vec![
"pass show test", "pass show test",
"tr -d '[:blank:]'", "tr -d '[:blank:]'",
]))), ]))),

View file

@ -163,10 +163,10 @@ pub(crate) async fn configure(
.with_prompt("IMAP OAuth 2.0 client secret") .with_prompt("IMAP OAuth 2.0 client secret")
.interact()?; .interact()?;
config.client_secret = config.client_secret =
Secret::new_keyring_entry(format!("{account_name}-imap-oauth2-client-secret")); Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-client-secret"))?;
config config
.client_secret .client_secret
.set_keyring_entry_secret(&client_secret) .set_only_keyring(&client_secret)
.await?; .await?;
let default_auth_url = autoconfig_oauth2 let default_auth_url = autoconfig_oauth2
@ -278,19 +278,13 @@ pub(crate) async fn configure(
.await?; .await?;
config.access_token = config.access_token =
Secret::new_keyring_entry(format!("{account_name}-imap-oauth2-access-token")); Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-access-token"))?;
config config.access_token.set_only_keyring(access_token).await?;
.access_token
.set_keyring_entry_secret(access_token)
.await?;
if let Some(refresh_token) = &refresh_token { if let Some(refresh_token) = &refresh_token {
config.refresh_token = config.refresh_token =
Secret::new_keyring_entry(format!("{account_name}-imap-oauth2-refresh-token")); Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-refresh-token"))?;
config config.refresh_token.set_only_keyring(refresh_token).await?;
.refresh_token
.set_keyring_entry_secret(refresh_token)
.await?;
} }
ImapAuthConfig::OAuth2(config) ImapAuthConfig::OAuth2(config)
@ -303,14 +297,14 @@ pub(crate) async fn configure(
let secret = match secret_idx { let secret = match secret_idx {
Some(idx) if SECRETS[idx] == KEYRING => { Some(idx) if SECRETS[idx] == KEYRING => {
let secret = Secret::new_keyring_entry(format!("{account_name}-imap-passwd")); let secret = Secret::try_new_keyring_entry(format!("{account_name}-imap-passwd"))?;
secret secret
.set_keyring_entry_secret(prompt::passwd("IMAP password")?) .set_only_keyring(prompt::passwd("IMAP password")?)
.await?; .await?;
secret secret
} }
Some(idx) if SECRETS[idx] == RAW => Secret::new_raw(prompt::passwd("IMAP password")?), Some(idx) if SECRETS[idx] == RAW => Secret::new_raw(prompt::passwd("IMAP password")?),
Some(idx) if SECRETS[idx] == CMD => Secret::new_cmd( Some(idx) if SECRETS[idx] == CMD => Secret::new_command(
Input::with_theme(&*THEME) Input::with_theme(&*THEME)
.with_prompt("Shell command") .with_prompt("Shell command")
.default(format!("pass show {account_name}-imap-passwd")) .default(format!("pass show {account_name}-imap-passwd"))
@ -404,10 +398,10 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result<Backend
.with_prompt("IMAP OAuth 2.0 client secret") .with_prompt("IMAP OAuth 2.0 client secret")
.interact()?; .interact()?;
config.client_secret = config.client_secret =
Secret::new_keyring_entry(format!("{account_name}-imap-oauth2-client-secret")); Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-client-secret"))?;
config config
.client_secret .client_secret
.set_keyring_entry_secret(&client_secret) .set_only_keyring(&client_secret)
.await?; .await?;
config.auth_url = Input::with_theme(&*THEME) config.auth_url = Input::with_theme(&*THEME)
@ -500,19 +494,13 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result<Backend
.await?; .await?;
config.access_token = config.access_token =
Secret::new_keyring_entry(format!("{account_name}-imap-oauth2-access-token")); Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-access-token"))?;
config config.access_token.set_only_keyring(access_token).await?;
.access_token
.set_keyring_entry_secret(access_token)
.await?;
if let Some(refresh_token) = &refresh_token { if let Some(refresh_token) = &refresh_token {
config.refresh_token = config.refresh_token =
Secret::new_keyring_entry(format!("{account_name}-imap-oauth2-refresh-token")); Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-refresh-token"))?;
config config.refresh_token.set_only_keyring(refresh_token).await?;
.refresh_token
.set_keyring_entry_secret(refresh_token)
.await?;
} }
ImapAuthConfig::OAuth2(config) ImapAuthConfig::OAuth2(config)
@ -525,14 +513,14 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result<Backend
let secret = match secret_idx { let secret = match secret_idx {
Some(idx) if SECRETS[idx] == KEYRING => { Some(idx) if SECRETS[idx] == KEYRING => {
let secret = Secret::new_keyring_entry(format!("{account_name}-imap-passwd")); let secret = Secret::try_new_keyring_entry(format!("{account_name}-imap-passwd"))?;
secret secret
.set_keyring_entry_secret(prompt::passwd("IMAP password")?) .set_only_keyring(prompt::passwd("IMAP password")?)
.await?; .await?;
secret secret
} }
Some(idx) if SECRETS[idx] == RAW => Secret::new_raw(prompt::passwd("IMAP password")?), Some(idx) if SECRETS[idx] == RAW => Secret::new_raw(prompt::passwd("IMAP password")?),
Some(idx) if SECRETS[idx] == CMD => Secret::new_cmd( Some(idx) if SECRETS[idx] == CMD => Secret::new_command(
Input::with_theme(&*THEME) Input::with_theme(&*THEME)
.with_prompt("Shell command") .with_prompt("Shell command")
.default(format!("pass show {account_name}-imap-passwd")) .default(format!("pass show {account_name}-imap-passwd"))

View file

@ -22,10 +22,11 @@ async fn main() -> Result<()> {
// if the first argument starts by "mailto:", execute straight the // if the first argument starts by "mailto:", execute straight the
// mailto message command // mailto message command
if let Some(ref url) = std::env::args() let mailto = std::env::args()
.nth(1) .nth(1)
.filter(|arg| arg.starts_with("mailto:")) .filter(|arg| arg.starts_with("mailto:"));
{
if let Some(ref url) = mailto {
let mut printer = StdoutPrinter::default(); let mut printer = StdoutPrinter::default();
let config = TomlConfig::from_default_paths().await?; let config = TomlConfig::from_default_paths().await?;
@ -38,9 +39,9 @@ async fn main() -> Result<()> {
let mut printer = StdoutPrinter::new(cli.output, cli.color); let mut printer = StdoutPrinter::new(cli.output, cli.color);
match cli.command { match cli.command {
Some(cmd) => cmd.execute(&mut printer, cli.config_path.as_ref()).await, Some(cmd) => cmd.execute(&mut printer, cli.config_paths.as_ref()).await,
None => { None => {
let config = TomlConfig::from_some_path_or_default(cli.config_path.as_ref()).await?; let config = TomlConfig::from_paths_or_default(cli.config_paths.as_ref()).await?;
ListEnvelopesCommand::default() ListEnvelopesCommand::default()
.execute(&mut printer, &config) .execute(&mut printer, &config)
.await .await

View file

@ -163,10 +163,10 @@ pub(crate) async fn configure(
.with_prompt("SMTP OAuth 2.0 client secret") .with_prompt("SMTP OAuth 2.0 client secret")
.interact()?; .interact()?;
config.client_secret = config.client_secret =
Secret::new_keyring_entry(format!("{account_name}-smtp-oauth2-client-secret")); Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-client-secret"))?;
config config
.client_secret .client_secret
.set_keyring_entry_secret(&client_secret) .set_only_keyring(&client_secret)
.await?; .await?;
let default_auth_url = autoconfig_oauth2 let default_auth_url = autoconfig_oauth2
@ -278,19 +278,13 @@ pub(crate) async fn configure(
.await?; .await?;
config.access_token = config.access_token =
Secret::new_keyring_entry(format!("{account_name}-smtp-oauth2-access-token")); Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-access-token"))?;
config config.access_token.set_only_keyring(access_token).await?;
.access_token
.set_keyring_entry_secret(access_token)
.await?;
if let Some(refresh_token) = &refresh_token { if let Some(refresh_token) = &refresh_token {
config.refresh_token = config.refresh_token =
Secret::new_keyring_entry(format!("{account_name}-smtp-oauth2-refresh-token")); Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-refresh-token"))?;
config config.refresh_token.set_only_keyring(refresh_token).await?;
.refresh_token
.set_keyring_entry_secret(refresh_token)
.await?;
} }
SmtpAuthConfig::OAuth2(config) SmtpAuthConfig::OAuth2(config)
@ -303,14 +297,14 @@ pub(crate) async fn configure(
let secret = match secret_idx { let secret = match secret_idx {
Some(idx) if SECRETS[idx] == KEYRING => { Some(idx) if SECRETS[idx] == KEYRING => {
let secret = Secret::new_keyring_entry(format!("{account_name}-smtp-passwd")); let secret = Secret::try_new_keyring_entry(format!("{account_name}-smtp-passwd"))?;
secret secret
.set_keyring_entry_secret(prompt::passwd("SMTP password")?) .set_only_keyring(prompt::passwd("SMTP password")?)
.await?; .await?;
secret secret
} }
Some(idx) if SECRETS[idx] == RAW => Secret::new_raw(prompt::passwd("SMTP password")?), Some(idx) if SECRETS[idx] == RAW => Secret::new_raw(prompt::passwd("SMTP password")?),
Some(idx) if SECRETS[idx] == CMD => Secret::new_cmd( Some(idx) if SECRETS[idx] == CMD => Secret::new_command(
Input::with_theme(&*THEME) Input::with_theme(&*THEME)
.with_prompt("Shell command") .with_prompt("Shell command")
.default(format!("pass show {account_name}-smtp-passwd")) .default(format!("pass show {account_name}-smtp-passwd"))
@ -403,10 +397,10 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result<Backend
.with_prompt("SMTP OAuth 2.0 client secret") .with_prompt("SMTP OAuth 2.0 client secret")
.interact()?; .interact()?;
config.client_secret = config.client_secret =
Secret::new_keyring_entry(format!("{account_name}-smtp-oauth2-client-secret")); Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-client-secret"))?;
config config
.client_secret .client_secret
.set_keyring_entry_secret(&client_secret) .set_only_keyring(&client_secret)
.await?; .await?;
config.auth_url = Input::with_theme(&*THEME) config.auth_url = Input::with_theme(&*THEME)
@ -499,19 +493,13 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result<Backend
.await?; .await?;
config.access_token = config.access_token =
Secret::new_keyring_entry(format!("{account_name}-smtp-oauth2-access-token")); Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-access-token"))?;
config config.access_token.set_only_keyring(access_token).await?;
.access_token
.set_keyring_entry_secret(access_token)
.await?;
if let Some(refresh_token) = &refresh_token { if let Some(refresh_token) = &refresh_token {
config.refresh_token = config.refresh_token =
Secret::new_keyring_entry(format!("{account_name}-smtp-oauth2-refresh-token")); Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-refresh-token"))?;
config config.refresh_token.set_only_keyring(refresh_token).await?;
.refresh_token
.set_keyring_entry_secret(refresh_token)
.await?;
} }
SmtpAuthConfig::OAuth2(config) SmtpAuthConfig::OAuth2(config)
@ -524,14 +512,14 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result<Backend
let secret = match secret_idx { let secret = match secret_idx {
Some(idx) if SECRETS[idx] == KEYRING => { Some(idx) if SECRETS[idx] == KEYRING => {
let secret = Secret::new_keyring_entry(format!("{account_name}-smtp-passwd")); let secret = Secret::try_new_keyring_entry(format!("{account_name}-smtp-passwd"))?;
secret secret
.set_keyring_entry_secret(prompt::passwd("SMTP password")?) .set_only_keyring(prompt::passwd("SMTP password")?)
.await?; .await?;
secret secret
} }
Some(idx) if SECRETS[idx] == RAW => Secret::new_raw(prompt::passwd("SMTP password")?), Some(idx) if SECRETS[idx] == RAW => Secret::new_raw(prompt::passwd("SMTP password")?),
Some(idx) if SECRETS[idx] == CMD => Secret::new_cmd( Some(idx) if SECRETS[idx] == CMD => Secret::new_command(
Input::with_theme(&*THEME) Input::with_theme(&*THEME)
.with_prompt("Shell command") .with_prompt("Shell command")
.default(format!("pass show {account_name}-smtp-passwd")) .default(format!("pass show {account_name}-smtp-passwd"))

View file

@ -8,7 +8,7 @@ use email::{
}; };
use log::debug; use log::debug;
use mml::MmlCompilerBuilder; use mml::MmlCompilerBuilder;
use process::SingleCmd; use process::SingleCommand;
use std::{env, fs, sync::Arc}; use std::{env, fs, sync::Arc};
use crate::{ use crate::{
@ -25,7 +25,7 @@ pub async fn open_with_tpl(tpl: String) -> Result<String> {
debug!("open editor"); debug!("open editor");
let editor = env::var("EDITOR").context("cannot get editor from env var")?; let editor = env::var("EDITOR").context("cannot get editor from env var")?;
SingleCmd::from(format!("{editor} {}", &path.to_string_lossy())) SingleCommand::from(format!("{editor} {}", &path.to_string_lossy()))
.with_output_piped(false) .with_output_piped(false)
.run() .run()
.await .await