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 <account-name>

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 <manos@pitsidianak.is>
This commit is contained in:
Manos Pitsidianakis 2024-05-23 15:07:34 +03:00
parent 74a3539f88
commit e187bb3f0d
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0
3 changed files with 137 additions and 4 deletions

View File

@ -63,19 +63,21 @@ pub enum SubCommand {
#[structopt(value_name = "CONFIG_PATH", parse(from_os_str))] #[structopt(value_name = "CONFIG_PATH", parse(from_os_str))]
path: Option<PathBuf>, path: Option<PathBuf>,
}, },
#[structopt(visible_alias="docs", aliases=&["docs", "manpage", "manpages"])]
#[structopt(display_order = 3)] #[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.). /// print documentation page and exit (Piping to a pager is recommended.).
Man(ManOpt), Man(ManOpt),
#[structopt(display_order = 5)]
#[structopt(display_order = 4)]
/// Install manual pages to the first location provided by `$MANPATH` / /// Install manual pages to the first location provided by `$MANPATH` /
/// `manpath(1)`, unless you specify the directory as an argument. /// `manpath(1)`, unless you specify the directory as an argument.
InstallMan { InstallMan {
#[structopt(value_name = "DESTINATION_PATH", parse(from_os_str))] #[structopt(value_name = "DESTINATION_PATH", parse(from_os_str))]
destination_path: Option<PathBuf>, destination_path: Option<PathBuf>,
}, },
#[structopt(display_order = 5)] #[structopt(display_order = 6)]
/// Print compile time feature flags of this binary /// Print compile time feature flags of this binary
CompiledWith, CompiledWith,
/// Print log file location. /// Print log file location.
@ -101,3 +103,21 @@ pub struct ManOpt {
)] )]
pub no_raw: Option<Option<bool>>, pub no_raw: Option<Option<bool>>,
} }
#[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,
},
}

View File

@ -95,6 +95,9 @@ fn run_app(opt: Opt) -> Result<()> {
Some(SubCommand::TestConfig { path }) => { Some(SubCommand::TestConfig { path }) => {
return subcommands::test_config(path); return subcommands::test_config(path);
} }
Some(SubCommand::Tools(toolopt)) => {
return subcommands::tool(opt.config, toolopt);
}
Some(SubCommand::CreateConfig { path }) => { Some(SubCommand::CreateConfig { path }) => {
return subcommands::create_config(path); return subcommands::create_config(path);
} }

View File

@ -172,3 +172,113 @@ pub fn view(
))); )));
Ok(state) Ok(state)
} }
pub fn tool(path: Option<PathBuf>, 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::<melib::imap::ImapType>()
.unwrap()
.shell();
}
#[cfg(feature = "jmap")]
ToolOpt::JmapShell { .. } => {
unimplemented!("Sorry, no JMAP shell yet.");
}
}
Ok(())
}