diff --git a/meli/src/args.rs b/meli/src/args.rs index b60f1120..bd5b64e4 100644 --- a/meli/src/args.rs +++ b/meli/src/args.rs @@ -63,19 +63,21 @@ pub enum SubCommand { #[structopt(value_name = "CONFIG_PATH", parse(from_os_str))] path: Option, }, - #[structopt(visible_alias="docs", aliases=&["docs", "manpage", "manpages"])] #[structopt(display_order = 3)] + /// Testing tools such as IMAP, SMTP shells for debugging. + Tools(ToolOpt), + #[structopt(visible_alias="docs", aliases=&["docs", "manpage", "manpages"])] + #[structopt(display_order = 4)] /// print documentation page and exit (Piping to a pager is recommended.). Man(ManOpt), - - #[structopt(display_order = 4)] + #[structopt(display_order = 5)] /// Install manual pages to the first location provided by `$MANPATH` / /// `manpath(1)`, unless you specify the directory as an argument. InstallMan { #[structopt(value_name = "DESTINATION_PATH", parse(from_os_str))] destination_path: Option, }, - #[structopt(display_order = 5)] + #[structopt(display_order = 6)] /// Print compile time feature flags of this binary CompiledWith, /// Print log file location. @@ -101,3 +103,21 @@ pub struct ManOpt { )] pub no_raw: Option>, } + +#[derive(Debug, StructOpt)] +pub enum ToolOpt { + ImapShell { + #[structopt(value_name = "CONFIG_TOML_ACCOUNT_NAME")] + account: String, + }, + #[cfg(feature = "smtp")] + SmtpShell { + #[structopt(value_name = "CONFIG_TOML_ACCOUNT_NAME")] + account: String, + }, + #[cfg(feature = "jmap")] + JmapShell { + #[structopt(value_name = "CONFIG_TOML_ACCOUNT_NAME")] + account: String, + }, +} diff --git a/meli/src/main.rs b/meli/src/main.rs index 4cf925fe..1d25352d 100644 --- a/meli/src/main.rs +++ b/meli/src/main.rs @@ -95,6 +95,9 @@ fn run_app(opt: Opt) -> Result<()> { Some(SubCommand::TestConfig { path }) => { return subcommands::test_config(path); } + Some(SubCommand::Tools(toolopt)) => { + return subcommands::tool(opt.config, toolopt); + } Some(SubCommand::CreateConfig { path }) => { return subcommands::create_config(path); } diff --git a/meli/src/subcommands.rs b/meli/src/subcommands.rs index bdc2a126..dfdbc52b 100644 --- a/meli/src/subcommands.rs +++ b/meli/src/subcommands.rs @@ -172,3 +172,113 @@ pub fn view( ))); Ok(state) } + +pub fn tool(path: Option, opt: crate::args::ToolOpt) -> Result<()> { + use melib::utils::futures::timeout; + + use crate::{args::ToolOpt, conf::composing::SendMail}; + + let config_path = if let Some(path) = path { + path.expand() + } else { + crate::conf::get_config_file()? + }; + let conf = conf::FileSettings::validate(config_path, true, false)?; + let account = match opt { + ToolOpt::ImapShell { ref account } => account, + #[cfg(feature = "smtp")] + ToolOpt::SmtpShell { ref account } => account, + #[cfg(feature = "jmap")] + ToolOpt::JmapShell { ref account } => account, + }; + if !conf.accounts.contains_key(&account.clone()) { + println!( + "The configuration file does not contain the account `{}`. It contains the following:", + account + ); + for a in conf.accounts.keys() { + println!("{}", a); + } + return Err("Try again with a valid account name.".into()); + } + let file_account_conf = conf.accounts[account].clone(); + let account_conf: conf::AccountConf = file_account_conf.clone().into(); + match opt { + #[cfg(feature = "smtp")] + ToolOpt::SmtpShell { .. } => { + let send_mail = file_account_conf + .conf_override + .composing + .send_mail + .as_ref() + .unwrap_or(&conf.composing.send_mail) + .clone(); + let SendMail::Smtp(smtp_conf) = send_mail else { + panic!( + "smtp shell requires an smtp configuration for account {}", + account + ); + }; + std::thread::spawn(move || { + let ex = melib::smol::Executor::new(); + futures::executor::block_on(ex.run(futures::future::pending::<()>())); + }); + + let mut conn = futures::executor::block_on( + melib::smtp::SmtpConnection::new_connection(smtp_conf), + )?; + let mut input = String::new(); + let mut res = String::with_capacity(8 * 1024); + println!( + "Connected. Type a valid SMTP command such as EHLO and hit Return. Exit with \ + Ctrl+C or with the command QUIT." + ); + loop { + use std::io; + input.clear(); + res.clear(); + + match io::stdin().read_line(&mut input) { + Ok(_) => { + futures::executor::block_on(timeout( + None, + conn.send_command(&[input.trim().as_bytes()]), + )) + .unwrap() + .unwrap(); + futures::executor::block_on(timeout(None, conn.read_lines(&mut res, None))) + .unwrap() + .unwrap(); + println!("\rC: {}", input.trim()); + print!("S: {}", res); + if input.trim().eq_ignore_ascii_case("quit") { + break; + } + } + Err(error) => println!("error: {}", error), + } + } + } + ToolOpt::ImapShell { .. } => { + let mut imap = melib::imap::ImapType::new( + &account_conf.account, + Box::new(|_| true), + melib::BackendEventConsumer::new(std::sync::Arc::new(|_, _| ())), + )?; + + std::thread::spawn(move || { + let ex = melib::smol::Executor::new(); + futures::executor::block_on(ex.run(futures::future::pending::<()>())); + }); + (imap.as_any_mut()) + .downcast_mut::() + .unwrap() + .shell(); + } + #[cfg(feature = "jmap")] + ToolOpt::JmapShell { .. } => { + unimplemented!("Sorry, no JMAP shell yet."); + } + } + Ok(()) +}