From e187bb3f0dd0166d69df4d84f1f63e1d005b4cbe Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Thu, 23 May 2024 15:07:34 +0300 Subject: [PATCH] Add tools subcommand with smtp shell for debugging Add tools subcommand, with options to launch imap, smtp or jmap shells. Currently only smtp is implemented, imap is working but with quirks. To use, run $ meli tools smtp-shell Where account name is what you have in your config file. For convenience, typing an invalid name will list all the valid names: $ meli tools smtp-shell "asdf" The configuration file does not contain the account `asdf`. It contains the following: user@example.com work personal account Try again with a valid account name. Signed-off-by: Manos Pitsidianakis --- meli/src/args.rs | 28 ++++++++-- meli/src/main.rs | 3 ++ meli/src/subcommands.rs | 110 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 4 deletions(-) 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(()) +}