use anyhow::Result; use dialoguer::{theme::ColorfulTheme, Confirm, Input, Password, Select}; use once_cell::sync::Lazy; use shellexpand_utils::shellexpand_path; use std::{fs, io, path::PathBuf, process}; use toml_edit::{Document, Item}; use crate::account; use super::TomlConfig; #[macro_export] macro_rules! wizard_warn { ($($arg:tt)*) => { println!("{}", console::style(format!($($arg)*)).yellow().bold()); }; } #[macro_export] macro_rules! wizard_prompt { ($($arg:tt)*) => { format!("{}", console::style(format!($($arg)*)).italic()) }; } #[macro_export] macro_rules! wizard_log { ($($arg:tt)*) => { println!(""); println!("{}", console::style(format!($($arg)*)).underlined()); println!(""); }; } pub(crate) static THEME: Lazy = Lazy::new(ColorfulTheme::default); pub(crate) async fn configure(path: PathBuf) -> Result { wizard_log!("Configuring your first account:"); let mut config = TomlConfig::default(); while let Some((name, account_config)) = account::wizard::configure().await? { config.accounts.insert(name, account_config); if !Confirm::new() .with_prompt(wizard_prompt!( "Would you like to configure another account?" )) .default(false) .interact_opt()? .unwrap_or_default() { break; } wizard_log!("Configuring another account:"); } // If one account 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_account = match config.accounts.len() { 0 => { wizard_warn!("No account configured, exiting."); process::exit(0); } 1 => Some(config.accounts.values_mut().next().unwrap()), _ => { let accounts = config.accounts.clone(); let accounts: Vec<&String> = accounts.keys().collect(); println!("{} accounts have been configured.", accounts.len()); Select::with_theme(&*THEME) .with_prompt(wizard_prompt!( "Which account would you like to set as your default?" )) .items(&accounts) .default(0) .interact_opt()? .and_then(|idx| config.accounts.get_mut(accounts[idx])) } }; if let Some(account) = default_account { account.default = Some(true); } else { process::exit(0) } let path = Input::with_theme(&*THEME) .with_prompt(wizard_prompt!( "Where would you like to save your configuration?" )) .default(path.to_string_lossy().to_string()) .interact()?; let path = shellexpand_path(&path); println!("Writing the configuration to {path:?}…"); let mut doc = toml::to_string(&config)?.parse::()?; doc.iter_mut().for_each(|(_, item)| { set_table_dotted(item, "folder-aliases"); set_table_dotted(item, "sync-folders-strategy"); set_table_dotted(item, "folder"); get_table_mut(item, "folder").map(|item| { set_tables_dotted(item, ["add", "list", "expunge", "purge", "delete"]); }); set_table_dotted(item, "envelope"); get_table_mut(item, "envelope").map(|item| { set_tables_dotted(item, ["list", "get"]); }); set_table_dotted(item, "flag"); get_table_mut(item, "flag").map(|item| { set_tables_dotted(item, ["add", "set", "remove"]); }); set_table_dotted(item, "message"); get_table_mut(item, "message").map(|item| { set_tables_dotted( item, ["add", "send", "peek", "get", "copy", "move", "delete"], ); }); set_table_dotted(item, "maildir"); #[cfg(feature = "imap")] { set_table_dotted(item, "imap"); get_table_mut(item, "imap").map(|item| { set_tables_dotted(item, ["passwd", "oauth2"]); }); } #[cfg(feature = "notmuch")] set_table_dotted(item, "notmuch"); set_table_dotted(item, "sendmail"); #[cfg(feature = "smtp")] { set_table_dotted(item, "smtp"); get_table_mut(item, "smtp").map(|item| { set_tables_dotted(item, ["passwd", "oauth2"]); }); } #[cfg(feature = "pgp")] set_table_dotted(item, "pgp"); }); fs::create_dir_all(path.parent().unwrap_or(&path))?; fs::write(path, doc.to_string())?; Ok(config) } fn get_table_mut<'a>(item: &'a mut Item, key: &'a str) -> Option<&'a mut Item> { item.get_mut(key).filter(|item| item.is_table()) } fn set_table_dotted(item: &mut Item, key: &str) { get_table_mut(item, key) .and_then(|item| item.as_table_mut()) .map(|table| table.set_dotted(true)); } fn set_tables_dotted<'a>(item: &'a mut Item, keys: impl IntoIterator) { for key in keys { set_table_dotted(item, key) } } pub(crate) fn prompt_passwd(prompt: &str) -> io::Result { Password::with_theme(&*THEME) .with_prompt(prompt) .with_confirmation( "Confirm password", "Passwords do not match, please try again.", ) .interact() } pub(crate) fn prompt_secret(prompt: &str) -> io::Result { Password::with_theme(&*THEME) .with_prompt(prompt) .report(false) .interact() }