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
- 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):
- 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 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].
### Fixed
@ -807,4 +808,5 @@ Few major concepts changed:
[#95]: https://todo.sr.ht/~soywod/pimalaya/95
[#172]: https://todo.sr.ht/~soywod/pimalaya/172
[#173]: https://todo.sr.ht/~soywod/pimalaya/173
[#184]: https://todo.sr.ht/~soywod/pimalaya/184
[#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]
name = "himalaya"
description = "CLI to manage emails"
version = "1.0.0-beta.3"
version = "1.0.0-beta.4"
authors = ["soywod <clement.douin@posteo.net>"]
edition = "2021"
license = "MIT"
@ -13,7 +13,7 @@ repository = "https://github.com/soywod/himalaya/"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs", "--document-private-items"]
[features]
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"]
[dev-dependencies]
async-trait = "0.1"
tempfile = "3.3"
[dependencies]
@ -60,29 +59,30 @@ clap_mangen = "0.2"
console = "0.15.2"
dialoguer = "0.10.2"
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"
env_logger = "0.8"
erased-serde = "0.3"
indicatif = "0.17"
keyring-lib = "=0.3.2"
keyring-lib = { version = "=0.4.0", features = ["derive"] }
log = "0.4"
mail-builder = "0.3"
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"
once_cell = "1.16"
process-lib = "=0.3.1"
secret-lib = "=0.3.3"
serde = { version = "1.0", features = ["derive"] }
process-lib = { version = "=0.4.1", features = ["derive"] }
secret-lib = { version = "=0.4.1", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde-toml-merge = "0.3"
serde_json = "1"
shellexpand-utils = "=0.2.0"
sled = "=0.34.7"
termcolor = "1"
terminal_size = "0.1"
tokio = { version = "1.23", default-features = false, features = ["macros", "rt-multi-thread"] }
toml = "0.7.4"
toml_edit = "0.19.8"
toml = "0.8"
toml_edit = "0.22"
unicode-width = "0.1"
url = "2.2"
uuid = { version = "0.8", features = ["v4"] }
@ -91,5 +91,14 @@ uuid = { version = "0.8", features = ["v4"] }
version = "0.1"
[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" }
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)]
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
/// applicable). If the path does not point to a valid file, the
/// wizard will propose to assist you in the creation of the
/// configuration file.
/// The given paths are shell-expanded then canonicalized (if
/// applicable). If the first path does not point to a valid file,
/// the wizard will propose to assist you in the creation of the
/// 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(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 terminal.
@ -116,39 +118,34 @@ pub enum HimalayaCommand {
}
impl HimalayaCommand {
#[allow(unused)]
pub async fn execute(
self,
printer: &mut impl Printer,
config_path: Option<&PathBuf>,
) -> Result<()> {
pub async fn execute(self, printer: &mut impl Printer, config_paths: &[PathBuf]) -> Result<()> {
match self {
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
}
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
}
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
}
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
}
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
}
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
}
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
}
Self::Manual(cmd) => cmd.execute(printer).await,

View file

@ -1,21 +1,17 @@
pub mod wizard;
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, bail, Context, Result};
use dirs::{config_dir, home_dir};
use email::{
account::config::AccountConfig, config::Config, envelope::config::EnvelopeConfig,
flag::config::FlagConfig, folder::config::FolderConfig, message::config::MessageConfig,
};
use log::debug;
use serde::{Deserialize, Serialize};
use serde_toml_merge::merge;
use shellexpand_utils::{canonicalize, expand};
use std::{
collections::HashMap,
fs,
path::{Path, PathBuf},
sync::Arc,
};
use toml;
use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
use toml::{self, Value};
#[cfg(feature = "account-sync")]
use crate::backend::BackendKind;
@ -34,14 +30,48 @@ pub struct 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
/// if its content cannot be parsed.
fn from_path(path: &Path) -> Result<Self> {
let 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:?}"))
/// Returns an error if a configuration file cannot be read or if
/// a content cannot be parsed.
fn from_paths(paths: &[PathBuf]) -> Result<Self> {
match paths.len() {
0 => {
// 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.
@ -51,7 +81,7 @@ impl TomlConfig {
/// program stops.
///
/// 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 std::process;
@ -75,8 +105,8 @@ impl TomlConfig {
/// Read and parse the TOML configuration from default paths.
pub async fn from_default_paths() -> Result<Self> {
match Self::first_valid_default_path() {
Some(path) => Self::from_path(&path),
None => Self::from_wizard(Self::default_path()?).await,
Some(path) => Self::from_paths(&[path]),
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
/// configuration at the first valid default path, otherwise
/// create it using the wizard. wizard.
pub async fn from_some_path_or_default(path: Option<impl Into<PathBuf>>) -> Result<Self> {
match path.map(Into::into) {
Some(ref path) if path.exists() => Self::from_path(path),
Some(path) => Self::from_wizard(path).await,
_ => Self::from_default_paths().await,
pub async fn from_paths_or_default(paths: &[PathBuf]) -> Result<Self> {
match paths.len() {
0 => Self::from_default_paths().await,
_ if paths[0].exists() => Self::from_paths(paths),
_ => Self::from_wizard(&paths[0]).await,
}
}
@ -157,14 +187,14 @@ impl TomlConfig {
if let Some(imap_config) = toml_account_config.imap.as_mut() {
imap_config
.auth
.replace_undefined_keyring_entries(&account_name);
.replace_undefined_keyring_entries(&account_name)?;
}
#[cfg(feature = "smtp")]
if let Some(smtp_config) = toml_account_config.smtp.as_mut() {
smtp_config
.auth
.replace_undefined_keyring_entries(&account_name);
.replace_undefined_keyring_entries(&account_name)?;
}
Ok((account_name, toml_account_config))

View file

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

View file

@ -163,10 +163,10 @@ pub(crate) async fn configure(
.with_prompt("IMAP OAuth 2.0 client secret")
.interact()?;
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
.client_secret
.set_keyring_entry_secret(&client_secret)
.set_only_keyring(&client_secret)
.await?;
let default_auth_url = autoconfig_oauth2
@ -278,19 +278,13 @@ pub(crate) async fn configure(
.await?;
config.access_token =
Secret::new_keyring_entry(format!("{account_name}-imap-oauth2-access-token"));
config
.access_token
.set_keyring_entry_secret(access_token)
.await?;
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-access-token"))?;
config.access_token.set_only_keyring(access_token).await?;
if let Some(refresh_token) = &refresh_token {
config.refresh_token =
Secret::new_keyring_entry(format!("{account_name}-imap-oauth2-refresh-token"));
config
.refresh_token
.set_keyring_entry_secret(refresh_token)
.await?;
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-refresh-token"))?;
config.refresh_token.set_only_keyring(refresh_token).await?;
}
ImapAuthConfig::OAuth2(config)
@ -303,14 +297,14 @@ pub(crate) async fn configure(
let secret = match secret_idx {
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
.set_keyring_entry_secret(prompt::passwd("IMAP password")?)
.set_only_keyring(prompt::passwd("IMAP password")?)
.await?;
secret
}
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)
.with_prompt("Shell command")
.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")
.interact()?;
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
.client_secret
.set_keyring_entry_secret(&client_secret)
.set_only_keyring(&client_secret)
.await?;
config.auth_url = Input::with_theme(&*THEME)
@ -500,19 +494,13 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result<Backend
.await?;
config.access_token =
Secret::new_keyring_entry(format!("{account_name}-imap-oauth2-access-token"));
config
.access_token
.set_keyring_entry_secret(access_token)
.await?;
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-access-token"))?;
config.access_token.set_only_keyring(access_token).await?;
if let Some(refresh_token) = &refresh_token {
config.refresh_token =
Secret::new_keyring_entry(format!("{account_name}-imap-oauth2-refresh-token"));
config
.refresh_token
.set_keyring_entry_secret(refresh_token)
.await?;
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-refresh-token"))?;
config.refresh_token.set_only_keyring(refresh_token).await?;
}
ImapAuthConfig::OAuth2(config)
@ -525,14 +513,14 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result<Backend
let secret = match secret_idx {
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
.set_keyring_entry_secret(prompt::passwd("IMAP password")?)
.set_only_keyring(prompt::passwd("IMAP password")?)
.await?;
secret
}
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)
.with_prompt("Shell command")
.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
// mailto message command
if let Some(ref url) = std::env::args()
let mailto = std::env::args()
.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 config = TomlConfig::from_default_paths().await?;
@ -38,9 +39,9 @@ async fn main() -> Result<()> {
let mut printer = StdoutPrinter::new(cli.output, cli.color);
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 => {
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()
.execute(&mut printer, &config)
.await

View file

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

View file

@ -8,7 +8,7 @@ use email::{
};
use log::debug;
use mml::MmlCompilerBuilder;
use process::SingleCmd;
use process::SingleCommand;
use std::{env, fs, sync::Arc};
use crate::{
@ -25,7 +25,7 @@ pub async fn open_with_tpl(tpl: String) -> Result<String> {
debug!("open editor");
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)
.run()
.await