mirror of
https://github.com/soywod/himalaya.git
synced 2024-07-05 17:15:12 +00:00
refactor account with clap derive api
This commit is contained in:
parent
d2308221d7
commit
abe4c7f4ea
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -483,6 +483,7 @@ dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"strsim 0.10.0",
|
"strsim 0.10.0",
|
||||||
|
"terminal_size 0.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2094,7 +2095,7 @@ dependencies = [
|
||||||
"shellexpand-utils 0.1.0",
|
"shellexpand-utils 0.1.0",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"terminal_size",
|
"terminal_size 0.1.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.7.8",
|
"toml 0.7.8",
|
||||||
"toml_edit 0.19.15",
|
"toml_edit 0.19.15",
|
||||||
|
@ -4104,6 +4105,16 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "terminal_size"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
|
||||||
|
dependencies = [
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.50"
|
version = "1.0.50"
|
||||||
|
|
|
@ -59,11 +59,11 @@ version = "0.2"
|
||||||
version = "0.4.24"
|
version = "0.4.24"
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "4.0"
|
version = "4.4"
|
||||||
features = ["derive"]
|
features = ["derive", "wrap_help"]
|
||||||
|
|
||||||
[dependencies.clap_complete]
|
[dependencies.clap_complete]
|
||||||
version = "4.0"
|
version = "4.4"
|
||||||
|
|
||||||
[dependencies.clap_mangen]
|
[dependencies.clap_mangen]
|
||||||
version = "0.2"
|
version = "0.2"
|
||||||
|
|
|
@ -1,160 +0,0 @@
|
||||||
//! This module provides arguments related to the user account config.
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
|
||||||
use email::folder::sync::FolderSyncStrategy;
|
|
||||||
use log::info;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use crate::{folder, ui::table};
|
|
||||||
|
|
||||||
const ARG_ACCOUNT: &str = "account";
|
|
||||||
const ARG_DRY_RUN: &str = "dry-run";
|
|
||||||
const ARG_RESET: &str = "reset";
|
|
||||||
const CMD_ACCOUNT: &str = "account";
|
|
||||||
const CMD_CONFIGURE: &str = "configure";
|
|
||||||
const CMD_LIST: &str = "list";
|
|
||||||
const CMD_SYNC: &str = "sync";
|
|
||||||
|
|
||||||
type DryRun = bool;
|
|
||||||
type Reset = bool;
|
|
||||||
|
|
||||||
/// Represents the account commands.
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub enum Cmd {
|
|
||||||
/// Represents the list accounts command.
|
|
||||||
List(table::args::MaxTableWidth),
|
|
||||||
/// Represents the sync account command.
|
|
||||||
Sync(Option<FolderSyncStrategy>, DryRun),
|
|
||||||
/// Configure the current selected account.
|
|
||||||
Configure(Reset),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the account command matcher.
|
|
||||||
pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
|
|
||||||
let cmd = if let Some(m) = m.subcommand_matches(CMD_ACCOUNT) {
|
|
||||||
if let Some(m) = m.subcommand_matches(CMD_CONFIGURE) {
|
|
||||||
info!("configure account subcommand matched");
|
|
||||||
let reset = parse_reset_flag(m);
|
|
||||||
Some(Cmd::Configure(reset))
|
|
||||||
} else if let Some(m) = m.subcommand_matches(CMD_LIST) {
|
|
||||||
info!("list accounts subcommand matched");
|
|
||||||
let max_table_width = table::args::parse_max_width(m);
|
|
||||||
Some(Cmd::List(max_table_width))
|
|
||||||
} else if let Some(m) = m.subcommand_matches(CMD_SYNC) {
|
|
||||||
info!("sync account subcommand matched");
|
|
||||||
let dry_run = parse_dry_run_arg(m);
|
|
||||||
let include = folder::args::parse_include_arg(m);
|
|
||||||
let exclude = folder::args::parse_exclude_arg(m);
|
|
||||||
let folders_strategy = if let Some(folder) = folder::args::parse_global_source_arg(m) {
|
|
||||||
Some(FolderSyncStrategy::Include(HashSet::from_iter([
|
|
||||||
folder.to_owned()
|
|
||||||
])))
|
|
||||||
} else if !include.is_empty() {
|
|
||||||
Some(FolderSyncStrategy::Include(include.to_owned()))
|
|
||||||
} else if !exclude.is_empty() {
|
|
||||||
Some(FolderSyncStrategy::Exclude(exclude))
|
|
||||||
} else if folder::args::parse_all_arg(m) {
|
|
||||||
Some(FolderSyncStrategy::All)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
Some(Cmd::Sync(folders_strategy, dry_run))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the account subcommand.
|
|
||||||
pub fn subcmd() -> Command {
|
|
||||||
Command::new(CMD_ACCOUNT)
|
|
||||||
.about("Subcommand to manage accounts")
|
|
||||||
.long_about("Subcommand to manage accounts like configure, list or sync")
|
|
||||||
.aliases(["accounts", "acc"])
|
|
||||||
.subcommand_required(true)
|
|
||||||
.arg_required_else_help(true)
|
|
||||||
.subcommand(
|
|
||||||
Command::new(CMD_CONFIGURE)
|
|
||||||
.about("Configure the given account")
|
|
||||||
.aliases(["config", "conf", "cfg"])
|
|
||||||
.arg(reset_flag())
|
|
||||||
.arg(folder::args::source_arg(
|
|
||||||
"Define the account to be configured",
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
Command::new(CMD_LIST)
|
|
||||||
.about("List all accounts")
|
|
||||||
.long_about("List all accounts that are set up in the configuration file")
|
|
||||||
.arg(table::args::max_width()),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
Command::new(CMD_SYNC)
|
|
||||||
.about("Synchronize the given account locally")
|
|
||||||
.arg(folder::args::all_arg("Synchronize all folders"))
|
|
||||||
.arg(folder::args::include_arg(
|
|
||||||
"Synchronize only the given folders",
|
|
||||||
))
|
|
||||||
.arg(folder::args::exclude_arg(
|
|
||||||
"Synchronize all folders except the given ones",
|
|
||||||
))
|
|
||||||
.arg(dry_run()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the user account name argument. This argument allows
|
|
||||||
/// the user to select a different account than the default one.
|
|
||||||
pub fn global_args() -> impl IntoIterator<Item = Arg> {
|
|
||||||
[Arg::new(ARG_ACCOUNT)
|
|
||||||
.help("Override the default account")
|
|
||||||
.long_help(
|
|
||||||
"Override the default account
|
|
||||||
|
|
||||||
The given account will be used by default for all other commands (when applicable).",
|
|
||||||
)
|
|
||||||
.long("account")
|
|
||||||
.short('a')
|
|
||||||
.global(true)
|
|
||||||
.value_name("name")]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the user account name argument parser.
|
|
||||||
pub fn parse_global_arg(matches: &ArgMatches) -> Option<&str> {
|
|
||||||
matches.get_one::<String>(ARG_ACCOUNT).map(String::as_str)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the user account sync dry run flag. This flag allows
|
|
||||||
/// the user to see the changes of a sync without applying them.
|
|
||||||
pub fn dry_run() -> Arg {
|
|
||||||
Arg::new(ARG_DRY_RUN)
|
|
||||||
.help("Do not apply changes of the synchronization")
|
|
||||||
.long_help(
|
|
||||||
"Do not apply changes of the synchronization.
|
|
||||||
Changes can be visualized with the RUST_LOG=trace environment variable.",
|
|
||||||
)
|
|
||||||
.short('d')
|
|
||||||
.long("dry-run")
|
|
||||||
.action(ArgAction::SetTrue)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the user account sync dry run flag parser.
|
|
||||||
pub fn parse_dry_run_arg(m: &ArgMatches) -> bool {
|
|
||||||
m.get_flag(ARG_DRY_RUN)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_flag() -> Arg {
|
|
||||||
Arg::new(ARG_RESET)
|
|
||||||
.help("Reset the configuration")
|
|
||||||
.short('r')
|
|
||||||
.long("reset")
|
|
||||||
.action(ArgAction::SetTrue)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_reset_flag(m: &ArgMatches) -> bool {
|
|
||||||
m.get_flag(ARG_RESET)
|
|
||||||
}
|
|
115
src/account/command/configure.rs
Normal file
115
src/account/command/configure.rs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
|
#[cfg(feature = "imap")]
|
||||||
|
use email::imap::config::ImapAuthConfig;
|
||||||
|
#[cfg(feature = "smtp")]
|
||||||
|
use email::smtp::config::SmtpAuthConfig;
|
||||||
|
use log::{debug, info, warn};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::{
|
||||||
|
wizard::{prompt_passwd, prompt_secret},
|
||||||
|
TomlConfig,
|
||||||
|
},
|
||||||
|
printer::Printer,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Configure the given account
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Command {
|
||||||
|
/// The name of the account that needs to be configured
|
||||||
|
///
|
||||||
|
/// The account names are taken from the table at the root level
|
||||||
|
/// of your TOML configuration file.
|
||||||
|
#[arg(value_name = "NAME")]
|
||||||
|
pub account_name: String,
|
||||||
|
|
||||||
|
/// Force the account to reconfigure, even if it is already
|
||||||
|
/// configured
|
||||||
|
#[arg(long, short)]
|
||||||
|
pub force: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
|
info!("executing account configure command");
|
||||||
|
|
||||||
|
let (_, account_config) =
|
||||||
|
config.into_toml_account_config(Some(self.account_name.as_str()))?;
|
||||||
|
|
||||||
|
if self.force {
|
||||||
|
#[cfg(feature = "imap")]
|
||||||
|
if let Some(ref config) = account_config.imap {
|
||||||
|
let reset = match &config.auth {
|
||||||
|
ImapAuthConfig::Passwd(config) => config.reset(),
|
||||||
|
ImapAuthConfig::OAuth2(config) => config.reset(),
|
||||||
|
};
|
||||||
|
if let Err(err) = reset {
|
||||||
|
warn!("error while resetting imap secrets: {err}");
|
||||||
|
debug!("error while resetting imap secrets: {err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "smtp")]
|
||||||
|
if let Some(ref config) = account_config.smtp {
|
||||||
|
let reset = match &config.auth {
|
||||||
|
SmtpAuthConfig::Passwd(config) => config.reset(),
|
||||||
|
SmtpAuthConfig::OAuth2(config) => config.reset(),
|
||||||
|
};
|
||||||
|
if let Err(err) = reset {
|
||||||
|
warn!("error while resetting smtp secrets: {err}");
|
||||||
|
debug!("error while resetting smtp secrets: {err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "pgp")]
|
||||||
|
if let Some(ref config) = account_config.pgp {
|
||||||
|
account_config.pgp.reset().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "imap")]
|
||||||
|
if let Some(ref config) = account_config.imap {
|
||||||
|
match &config.auth {
|
||||||
|
ImapAuthConfig::Passwd(config) => {
|
||||||
|
config.configure(|| prompt_passwd("IMAP password")).await
|
||||||
|
}
|
||||||
|
ImapAuthConfig::OAuth2(config) => {
|
||||||
|
config
|
||||||
|
.configure(|| prompt_secret("IMAP OAuth 2.0 client secret"))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "smtp")]
|
||||||
|
if let Some(ref config) = account_config.smtp {
|
||||||
|
match &config.auth {
|
||||||
|
SmtpAuthConfig::Passwd(config) => {
|
||||||
|
config.configure(|| prompt_passwd("SMTP password")).await
|
||||||
|
}
|
||||||
|
SmtpAuthConfig::OAuth2(config) => {
|
||||||
|
config
|
||||||
|
.configure(|| prompt_secret("SMTP OAuth 2.0 client secret"))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "pgp")]
|
||||||
|
if let Some(ref config) = config.pgp {
|
||||||
|
config
|
||||||
|
.pgp
|
||||||
|
.configure(&config.email, || prompt_passwd("PGP secret key password"))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
printer.print(format!(
|
||||||
|
"Account {} successfully {}configured!",
|
||||||
|
self.account_name,
|
||||||
|
if self.force { "re" } else { "" }
|
||||||
|
))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
141
src/account/command/list.rs
Normal file
141
src/account/command/list.rs
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
account::Accounts,
|
||||||
|
config::TomlConfig,
|
||||||
|
printer::{PrintTableOpts, Printer},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// List all accounts
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Command {
|
||||||
|
/// Define a maximum width for the table
|
||||||
|
#[arg(long, short = 'w', name = "PIXELS")]
|
||||||
|
pub max_width: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
|
info!("executing account list command");
|
||||||
|
|
||||||
|
let accounts: Accounts = config.accounts.iter().into();
|
||||||
|
|
||||||
|
printer.print_table(
|
||||||
|
Box::new(accounts),
|
||||||
|
PrintTableOpts {
|
||||||
|
format: config
|
||||||
|
.email_reading_format
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&Default::default()),
|
||||||
|
max_width: self.max_width,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use email::{account::config::AccountConfig, imap::config::ImapConfig};
|
||||||
|
use std::{collections::HashMap, fmt::Debug, io};
|
||||||
|
use termcolor::ColorSpec;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
account::TomlAccountConfig,
|
||||||
|
backend::BackendKind,
|
||||||
|
printer::{Print, PrintTable, WriteColor},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_should_match_cmds_accounts() {
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
struct StringWriter {
|
||||||
|
content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl io::Write for StringWriter {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
self.content
|
||||||
|
.push_str(&String::from_utf8(buf.to_vec()).unwrap());
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
self.content = String::default();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl termcolor::WriteColor for StringWriter {
|
||||||
|
fn supports_color(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_color(&mut self, _spec: &ColorSpec) -> io::Result<()> {
|
||||||
|
io::Result::Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) -> io::Result<()> {
|
||||||
|
io::Result::Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WriteColor for StringWriter {}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct PrinterServiceTest {
|
||||||
|
pub writer: StringWriter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Printer for PrinterServiceTest {
|
||||||
|
fn print_table<T: Debug + PrintTable + erased_serde::Serialize + ?Sized>(
|
||||||
|
&mut self,
|
||||||
|
data: Box<T>,
|
||||||
|
opts: PrintTableOpts,
|
||||||
|
) -> Result<()> {
|
||||||
|
data.print_table(&mut self.writer, opts)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn print_log<T: Debug + Print>(&mut self, _data: T) -> Result<()> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
fn print<T: Debug + Print + serde::Serialize>(&mut self, _data: T) -> Result<()> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
fn is_json(&self) -> bool {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut printer = PrinterServiceTest::default();
|
||||||
|
let config = AccountConfig::default();
|
||||||
|
let deserialized_config = TomlConfig {
|
||||||
|
accounts: HashMap::from_iter([(
|
||||||
|
"account-1".into(),
|
||||||
|
TomlAccountConfig {
|
||||||
|
default: Some(true),
|
||||||
|
backend: Some(BackendKind::Imap),
|
||||||
|
imap: Some(ImapConfig::default()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)]),
|
||||||
|
..TomlConfig::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(list(None, &config, &deserialized_config, &mut printer).is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
concat![
|
||||||
|
"\n",
|
||||||
|
"NAME │BACKEND │DEFAULT \n",
|
||||||
|
"account-1 │imap │yes \n",
|
||||||
|
"\n"
|
||||||
|
],
|
||||||
|
printer.writer.content
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
34
src/account/command/mod.rs
Normal file
34
src/account/command/mod.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
mod configure;
|
||||||
|
mod list;
|
||||||
|
mod sync;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
use crate::{config::TomlConfig, printer::Printer};
|
||||||
|
|
||||||
|
/// Subcommand to manage accounts
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum Command {
|
||||||
|
/// Configure the given account
|
||||||
|
#[command(alias = "cfg")]
|
||||||
|
Configure(configure::Command),
|
||||||
|
|
||||||
|
/// List all exsting accounts
|
||||||
|
#[command(alias = "lst")]
|
||||||
|
List(list::Command),
|
||||||
|
|
||||||
|
/// Synchronize the given account locally
|
||||||
|
#[command()]
|
||||||
|
Sync(sync::Command),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
Self::Configure(cmd) => cmd.execute(printer, config).await,
|
||||||
|
Self::List(cmd) => cmd.execute(printer, config).await,
|
||||||
|
Self::Sync(cmd) => cmd.execute(printer, config).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
233
src/account/command/sync.rs
Normal file
233
src/account/command/sync.rs
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::{ArgAction, Parser};
|
||||||
|
use email::{
|
||||||
|
account::sync::{AccountSyncBuilder, AccountSyncProgressEvent},
|
||||||
|
folder::sync::FolderSyncStrategy,
|
||||||
|
};
|
||||||
|
use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle};
|
||||||
|
use log::info;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
sync::Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{backend::BackendBuilder, config::TomlConfig, printer::Printer};
|
||||||
|
|
||||||
|
const MAIN_PROGRESS_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
|
||||||
|
ProgressStyle::with_template(" {spinner:.dim} {msg:.dim}\n {wide_bar:.cyan/blue} \n").unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
const SUB_PROGRESS_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
|
||||||
|
ProgressStyle::with_template(
|
||||||
|
" {prefix:.bold} — {wide_msg:.dim} \n {wide_bar:.black/black} {percent}% ",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
const SUB_PROGRESS_DONE_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
|
||||||
|
ProgressStyle::with_template(" {prefix:.bold} \n {wide_bar:.green} {percent}% ").unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Command {
|
||||||
|
/// The name of the account that needs to be synchronized
|
||||||
|
///
|
||||||
|
/// The account names are taken from the table at the root level
|
||||||
|
/// of your TOML configuration file.
|
||||||
|
#[arg(value_name = "ACCOUNT")]
|
||||||
|
pub account_name: String,
|
||||||
|
|
||||||
|
/// Run the synchronization without applying changes
|
||||||
|
///
|
||||||
|
/// Instead, a report will be printed to stdout containing all the
|
||||||
|
/// changes the synchronization plan to do.
|
||||||
|
#[arg(long, short)]
|
||||||
|
pub dry_run: bool,
|
||||||
|
|
||||||
|
#[arg(long, short = 'f', value_name = "FOLDER", action = ArgAction::Append, conflicts_with = "exclude_folder", conflicts_with = "all_folders")]
|
||||||
|
pub include_folder: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(long, short = 'x', value_name = "FOLDER", action = ArgAction::Append, conflicts_with = "include_folder", conflicts_with = "all_folders")]
|
||||||
|
pub exclude_folder: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
short = 'A',
|
||||||
|
conflicts_with = "include_folder",
|
||||||
|
conflicts_with = "exclude_folder"
|
||||||
|
)]
|
||||||
|
pub all_folders: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
|
info!("executing account sync command");
|
||||||
|
|
||||||
|
let included_folders = HashSet::from_iter(self.include_folder);
|
||||||
|
let excluded_folders = HashSet::from_iter(self.exclude_folder);
|
||||||
|
|
||||||
|
let strategy = if !included_folders.is_empty() {
|
||||||
|
Some(FolderSyncStrategy::Include(included_folders))
|
||||||
|
} else if !excluded_folders.is_empty() {
|
||||||
|
Some(FolderSyncStrategy::Exclude(excluded_folders))
|
||||||
|
} else if self.all_folders {
|
||||||
|
Some(FolderSyncStrategy::All)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let (toml_account_config, account_config) = config
|
||||||
|
.clone()
|
||||||
|
.into_account_configs(Some(self.account_name.as_str()), true)?;
|
||||||
|
|
||||||
|
let backend_builder =
|
||||||
|
BackendBuilder::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
let sync_builder = AccountSyncBuilder::new(backend_builder.into())
|
||||||
|
.await?
|
||||||
|
.with_some_folders_strategy(strategy)
|
||||||
|
.with_dry_run(self.dry_run);
|
||||||
|
|
||||||
|
if self.dry_run {
|
||||||
|
let report = sync_builder.sync().await?;
|
||||||
|
let mut hunks_count = report.folders_patch.len();
|
||||||
|
|
||||||
|
if !report.folders_patch.is_empty() {
|
||||||
|
printer.print_log("Folders patch:")?;
|
||||||
|
for (hunk, _) in report.folders_patch {
|
||||||
|
printer.print_log(format!(" - {hunk}"))?;
|
||||||
|
}
|
||||||
|
printer.print_log("")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !report.emails_patch.is_empty() {
|
||||||
|
printer.print_log("Envelopes patch:")?;
|
||||||
|
for (hunk, _) in report.emails_patch {
|
||||||
|
hunks_count += 1;
|
||||||
|
printer.print_log(format!(" - {hunk}"))?;
|
||||||
|
}
|
||||||
|
printer.print_log("")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
printer.print(format!(
|
||||||
|
"Estimated patch length for account to be synchronized: {hunks_count}",
|
||||||
|
))?;
|
||||||
|
} else if printer.is_json() {
|
||||||
|
sync_builder.sync().await?;
|
||||||
|
printer.print("Account successfully synchronized!")?;
|
||||||
|
} else {
|
||||||
|
let multi = MultiProgress::new();
|
||||||
|
let sub_progresses = Mutex::new(HashMap::new());
|
||||||
|
let main_progress = multi.add(
|
||||||
|
ProgressBar::new(100)
|
||||||
|
.with_style(MAIN_PROGRESS_STYLE.clone())
|
||||||
|
.with_message("Synchronizing folders…"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Force the progress bar to show
|
||||||
|
main_progress.set_position(0);
|
||||||
|
|
||||||
|
let report = sync_builder
|
||||||
|
.with_on_progress(move |evt| {
|
||||||
|
use AccountSyncProgressEvent::*;
|
||||||
|
Ok(match evt {
|
||||||
|
ApplyFolderPatches(..) => {
|
||||||
|
main_progress.inc(3);
|
||||||
|
}
|
||||||
|
ApplyEnvelopePatches(patches) => {
|
||||||
|
let mut envelopes_progresses = sub_progresses.lock().unwrap();
|
||||||
|
let patches_len =
|
||||||
|
patches.values().fold(0, |sum, patch| sum + patch.len());
|
||||||
|
main_progress.set_length((110 * patches_len / 100) as u64);
|
||||||
|
main_progress.set_position((5 * patches_len / 100) as u64);
|
||||||
|
main_progress.set_message("Synchronizing envelopes…");
|
||||||
|
|
||||||
|
for (folder, patch) in patches {
|
||||||
|
let progress = ProgressBar::new(patch.len() as u64)
|
||||||
|
.with_style(SUB_PROGRESS_STYLE.clone())
|
||||||
|
.with_prefix(folder.clone())
|
||||||
|
.with_finish(ProgressFinish::AndClear);
|
||||||
|
let progress = multi.add(progress);
|
||||||
|
envelopes_progresses.insert(folder, progress.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplyEnvelopeHunk(hunk) => {
|
||||||
|
main_progress.inc(1);
|
||||||
|
let mut progresses = sub_progresses.lock().unwrap();
|
||||||
|
if let Some(progress) = progresses.get_mut(hunk.folder()) {
|
||||||
|
progress.inc(1);
|
||||||
|
if progress.position() == (progress.length().unwrap() - 1) {
|
||||||
|
progress.set_style(SUB_PROGRESS_DONE_STYLE.clone())
|
||||||
|
} else {
|
||||||
|
progress.set_message(format!("{hunk}…"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplyEnvelopeCachePatch(_patch) => {
|
||||||
|
main_progress.set_length(100);
|
||||||
|
main_progress.set_position(95);
|
||||||
|
main_progress.set_message("Saving cache database…");
|
||||||
|
}
|
||||||
|
ExpungeFolders(folders) => {
|
||||||
|
let mut progresses = sub_progresses.lock().unwrap();
|
||||||
|
for progress in progresses.values() {
|
||||||
|
progress.finish_and_clear()
|
||||||
|
}
|
||||||
|
progresses.clear();
|
||||||
|
|
||||||
|
main_progress.set_position(100);
|
||||||
|
main_progress
|
||||||
|
.set_message(format!("Expunging {} folders…", folders.len()));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.sync()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let folders_patch_err = report
|
||||||
|
.folders_patch
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(hunk, err)| err.as_ref().map(|err| (hunk, err)))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !folders_patch_err.is_empty() {
|
||||||
|
printer.print_log("")?;
|
||||||
|
printer.print_log("Errors occurred while applying the folders patch:")?;
|
||||||
|
folders_patch_err
|
||||||
|
.iter()
|
||||||
|
.try_for_each(|(hunk, err)| printer.print_log(format!(" - {hunk}: {err}")))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(err) = report.folders_cache_patch.1 {
|
||||||
|
printer.print_log("")?;
|
||||||
|
printer.print_log(format!(
|
||||||
|
"Error occurred while applying the folder cache patch: {err}"
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let envelopes_patch_err = report
|
||||||
|
.emails_patch
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(hunk, err)| err.as_ref().map(|err| (hunk, err)))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !envelopes_patch_err.is_empty() {
|
||||||
|
printer.print_log("")?;
|
||||||
|
printer.print_log("Errors occurred while applying the envelopes patch:")?;
|
||||||
|
for (hunk, err) in folders_patch_err {
|
||||||
|
printer.print_log(format!(" - {hunk}: {err}"))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(err) = report.emails_cache_patch.1 {
|
||||||
|
printer.print_log("")?;
|
||||||
|
printer.print_log(format!(
|
||||||
|
"Error occurred while applying the envelopes cache patch: {err}"
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
printer.print("Account successfully synchronized!")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,401 +0,0 @@
|
||||||
//! Account handlers module.
|
|
||||||
//!
|
|
||||||
//! This module gathers all account actions triggered by the CLI.
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use email::account::{
|
|
||||||
config::AccountConfig,
|
|
||||||
sync::{AccountSyncBuilder, AccountSyncProgressEvent},
|
|
||||||
};
|
|
||||||
#[cfg(feature = "imap")]
|
|
||||||
use email::imap::config::ImapAuthConfig;
|
|
||||||
#[cfg(feature = "smtp")]
|
|
||||||
use email::smtp::config::SmtpAuthConfig;
|
|
||||||
use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle};
|
|
||||||
use log::{debug, info, trace, warn};
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::{collections::HashMap, sync::Mutex};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
account::Accounts,
|
|
||||||
backend::BackendContextBuilder,
|
|
||||||
config::{
|
|
||||||
wizard::{prompt_passwd, prompt_secret},
|
|
||||||
TomlConfig,
|
|
||||||
},
|
|
||||||
printer::{PrintTableOpts, Printer},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::TomlAccountConfig;
|
|
||||||
|
|
||||||
const MAIN_PROGRESS_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
|
|
||||||
ProgressStyle::with_template(" {spinner:.dim} {msg:.dim}\n {wide_bar:.cyan/blue} \n").unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
const SUB_PROGRESS_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
|
|
||||||
ProgressStyle::with_template(
|
|
||||||
" {prefix:.bold} — {wide_msg:.dim} \n {wide_bar:.black/black} {percent}% ",
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
const SUB_PROGRESS_DONE_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
|
|
||||||
ProgressStyle::with_template(" {prefix:.bold} \n {wide_bar:.green} {percent}% ").unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Configure the current selected account
|
|
||||||
pub async fn configure(config: &TomlAccountConfig, reset: bool) -> Result<()> {
|
|
||||||
info!("entering the configure account handler");
|
|
||||||
|
|
||||||
if reset {
|
|
||||||
#[cfg(feature = "imap")]
|
|
||||||
if let Some(ref config) = config.imap {
|
|
||||||
let reset = match &config.auth {
|
|
||||||
ImapAuthConfig::Passwd(config) => config.reset(),
|
|
||||||
ImapAuthConfig::OAuth2(config) => config.reset(),
|
|
||||||
};
|
|
||||||
if let Err(err) = reset {
|
|
||||||
warn!("error while resetting imap secrets: {err}");
|
|
||||||
debug!("error while resetting imap secrets: {err:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "smtp")]
|
|
||||||
if let Some(ref config) = config.smtp {
|
|
||||||
let reset = match &config.auth {
|
|
||||||
SmtpAuthConfig::Passwd(config) => config.reset(),
|
|
||||||
SmtpAuthConfig::OAuth2(config) => config.reset(),
|
|
||||||
};
|
|
||||||
if let Err(err) = reset {
|
|
||||||
warn!("error while resetting smtp secrets: {err}");
|
|
||||||
debug!("error while resetting smtp secrets: {err:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "pgp")]
|
|
||||||
if let Some(ref config) = config.pgp {
|
|
||||||
config.pgp.reset().await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "imap")]
|
|
||||||
if let Some(ref config) = config.imap {
|
|
||||||
match &config.auth {
|
|
||||||
ImapAuthConfig::Passwd(config) => {
|
|
||||||
config.configure(|| prompt_passwd("IMAP password")).await
|
|
||||||
}
|
|
||||||
ImapAuthConfig::OAuth2(config) => {
|
|
||||||
config
|
|
||||||
.configure(|| prompt_secret("IMAP OAuth 2.0 client secret"))
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "smtp")]
|
|
||||||
if let Some(ref config) = config.smtp {
|
|
||||||
match &config.auth {
|
|
||||||
SmtpAuthConfig::Passwd(config) => {
|
|
||||||
config.configure(|| prompt_passwd("SMTP password")).await
|
|
||||||
}
|
|
||||||
SmtpAuthConfig::OAuth2(config) => {
|
|
||||||
config
|
|
||||||
.configure(|| prompt_secret("SMTP OAuth 2.0 client secret"))
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "pgp")]
|
|
||||||
if let Some(ref config) = config.pgp {
|
|
||||||
config
|
|
||||||
.pgp
|
|
||||||
.configure(&config.email, || prompt_passwd("PGP secret key password"))
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"Account successfully {}configured!",
|
|
||||||
if reset { "re" } else { "" }
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lists all accounts.
|
|
||||||
pub fn list<'a, P: Printer>(
|
|
||||||
max_width: Option<usize>,
|
|
||||||
config: &AccountConfig,
|
|
||||||
deserialized_config: &TomlConfig,
|
|
||||||
printer: &mut P,
|
|
||||||
) -> Result<()> {
|
|
||||||
info!("entering the list accounts handler");
|
|
||||||
|
|
||||||
let accounts: Accounts = deserialized_config.accounts.iter().into();
|
|
||||||
trace!("accounts: {:?}", accounts);
|
|
||||||
|
|
||||||
printer.print_table(
|
|
||||||
Box::new(accounts),
|
|
||||||
PrintTableOpts {
|
|
||||||
format: &config.email_reading_format,
|
|
||||||
max_width,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
info!("<< account list handler");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Synchronizes the account defined using argument `-a|--account`. If
|
|
||||||
/// no account given, synchronizes the default one.
|
|
||||||
pub async fn sync<P: Printer>(
|
|
||||||
printer: &mut P,
|
|
||||||
sync_builder: AccountSyncBuilder<BackendContextBuilder>,
|
|
||||||
dry_run: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
info!("entering the sync accounts handler");
|
|
||||||
trace!("dry run: {dry_run}");
|
|
||||||
|
|
||||||
if dry_run {
|
|
||||||
let report = sync_builder.sync().await?;
|
|
||||||
let mut hunks_count = report.folders_patch.len();
|
|
||||||
|
|
||||||
if !report.folders_patch.is_empty() {
|
|
||||||
printer.print_log("Folders patch:")?;
|
|
||||||
for (hunk, _) in report.folders_patch {
|
|
||||||
printer.print_log(format!(" - {hunk}"))?;
|
|
||||||
}
|
|
||||||
printer.print_log("")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !report.emails_patch.is_empty() {
|
|
||||||
printer.print_log("Envelopes patch:")?;
|
|
||||||
for (hunk, _) in report.emails_patch {
|
|
||||||
hunks_count += 1;
|
|
||||||
printer.print_log(format!(" - {hunk}"))?;
|
|
||||||
}
|
|
||||||
printer.print_log("")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
printer.print(format!(
|
|
||||||
"Estimated patch length for account to be synchronized: {hunks_count}",
|
|
||||||
))?;
|
|
||||||
} else if printer.is_json() {
|
|
||||||
sync_builder.sync().await?;
|
|
||||||
printer.print("Account successfully synchronized!")?;
|
|
||||||
} else {
|
|
||||||
let multi = MultiProgress::new();
|
|
||||||
let sub_progresses = Mutex::new(HashMap::new());
|
|
||||||
let main_progress = multi.add(
|
|
||||||
ProgressBar::new(100)
|
|
||||||
.with_style(MAIN_PROGRESS_STYLE.clone())
|
|
||||||
.with_message("Synchronizing folders…"),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Force the progress bar to show
|
|
||||||
main_progress.set_position(0);
|
|
||||||
|
|
||||||
let report = sync_builder
|
|
||||||
.with_on_progress(move |evt| {
|
|
||||||
use AccountSyncProgressEvent::*;
|
|
||||||
Ok(match evt {
|
|
||||||
ApplyFolderPatches(..) => {
|
|
||||||
main_progress.inc(3);
|
|
||||||
}
|
|
||||||
ApplyEnvelopePatches(patches) => {
|
|
||||||
let mut envelopes_progresses = sub_progresses.lock().unwrap();
|
|
||||||
let patches_len = patches.values().fold(0, |sum, patch| sum + patch.len());
|
|
||||||
main_progress.set_length((110 * patches_len / 100) as u64);
|
|
||||||
main_progress.set_position((5 * patches_len / 100) as u64);
|
|
||||||
main_progress.set_message("Synchronizing envelopes…");
|
|
||||||
|
|
||||||
for (folder, patch) in patches {
|
|
||||||
let progress = ProgressBar::new(patch.len() as u64)
|
|
||||||
.with_style(SUB_PROGRESS_STYLE.clone())
|
|
||||||
.with_prefix(folder.clone())
|
|
||||||
.with_finish(ProgressFinish::AndClear);
|
|
||||||
let progress = multi.add(progress);
|
|
||||||
envelopes_progresses.insert(folder, progress.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplyEnvelopeHunk(hunk) => {
|
|
||||||
main_progress.inc(1);
|
|
||||||
let mut progresses = sub_progresses.lock().unwrap();
|
|
||||||
if let Some(progress) = progresses.get_mut(hunk.folder()) {
|
|
||||||
progress.inc(1);
|
|
||||||
if progress.position() == (progress.length().unwrap() - 1) {
|
|
||||||
progress.set_style(SUB_PROGRESS_DONE_STYLE.clone())
|
|
||||||
} else {
|
|
||||||
progress.set_message(format!("{hunk}…"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ApplyEnvelopeCachePatch(_patch) => {
|
|
||||||
main_progress.set_length(100);
|
|
||||||
main_progress.set_position(95);
|
|
||||||
main_progress.set_message("Saving cache database…");
|
|
||||||
}
|
|
||||||
ExpungeFolders(folders) => {
|
|
||||||
let mut progresses = sub_progresses.lock().unwrap();
|
|
||||||
for progress in progresses.values() {
|
|
||||||
progress.finish_and_clear()
|
|
||||||
}
|
|
||||||
progresses.clear();
|
|
||||||
|
|
||||||
main_progress.set_position(100);
|
|
||||||
main_progress.set_message(format!("Expunging {} folders…", folders.len()));
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.sync()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let folders_patch_err = report
|
|
||||||
.folders_patch
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(hunk, err)| err.as_ref().map(|err| (hunk, err)))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if !folders_patch_err.is_empty() {
|
|
||||||
printer.print_log("")?;
|
|
||||||
printer.print_log("Errors occurred while applying the folders patch:")?;
|
|
||||||
folders_patch_err
|
|
||||||
.iter()
|
|
||||||
.try_for_each(|(hunk, err)| printer.print_log(format!(" - {hunk}: {err}")))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(err) = report.folders_cache_patch.1 {
|
|
||||||
printer.print_log("")?;
|
|
||||||
printer.print_log(format!(
|
|
||||||
"Error occurred while applying the folder cache patch: {err}"
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let envelopes_patch_err = report
|
|
||||||
.emails_patch
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(hunk, err)| err.as_ref().map(|err| (hunk, err)))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if !envelopes_patch_err.is_empty() {
|
|
||||||
printer.print_log("")?;
|
|
||||||
printer.print_log("Errors occurred while applying the envelopes patch:")?;
|
|
||||||
for (hunk, err) in folders_patch_err {
|
|
||||||
printer.print_log(format!(" - {hunk}: {err}"))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(err) = report.emails_cache_patch.1 {
|
|
||||||
printer.print_log("")?;
|
|
||||||
printer.print_log(format!(
|
|
||||||
"Error occurred while applying the envelopes cache patch: {err}"
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
printer.print("Account successfully synchronized!")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use email::{account::config::AccountConfig, imap::config::ImapConfig};
|
|
||||||
use std::{collections::HashMap, fmt::Debug, io};
|
|
||||||
use termcolor::ColorSpec;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
account::TomlAccountConfig,
|
|
||||||
backend::BackendKind,
|
|
||||||
printer::{Print, PrintTable, WriteColor},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_should_match_cmds_accounts() {
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
struct StringWriter {
|
|
||||||
content: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl io::Write for StringWriter {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
self.content
|
|
||||||
.push_str(&String::from_utf8(buf.to_vec()).unwrap());
|
|
||||||
Ok(buf.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
self.content = String::default();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl termcolor::WriteColor for StringWriter {
|
|
||||||
fn supports_color(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_color(&mut self, _spec: &ColorSpec) -> io::Result<()> {
|
|
||||||
io::Result::Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset(&mut self) -> io::Result<()> {
|
|
||||||
io::Result::Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WriteColor for StringWriter {}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct PrinterServiceTest {
|
|
||||||
pub writer: StringWriter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Printer for PrinterServiceTest {
|
|
||||||
fn print_table<T: Debug + PrintTable + erased_serde::Serialize + ?Sized>(
|
|
||||||
&mut self,
|
|
||||||
data: Box<T>,
|
|
||||||
opts: PrintTableOpts,
|
|
||||||
) -> Result<()> {
|
|
||||||
data.print_table(&mut self.writer, opts)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn print_log<T: Debug + Print>(&mut self, _data: T) -> Result<()> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
fn print<T: Debug + Print + serde::Serialize>(&mut self, _data: T) -> Result<()> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
fn is_json(&self) -> bool {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut printer = PrinterServiceTest::default();
|
|
||||||
let config = AccountConfig::default();
|
|
||||||
let deserialized_config = TomlConfig {
|
|
||||||
accounts: HashMap::from_iter([(
|
|
||||||
"account-1".into(),
|
|
||||||
TomlAccountConfig {
|
|
||||||
default: Some(true),
|
|
||||||
backend: Some(BackendKind::Imap),
|
|
||||||
imap: Some(ImapConfig::default()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)]),
|
|
||||||
..TomlConfig::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(list(None, &config, &deserialized_config, &mut printer).is_ok());
|
|
||||||
assert_eq!(
|
|
||||||
concat![
|
|
||||||
"\n",
|
|
||||||
"NAME │BACKEND │DEFAULT \n",
|
|
||||||
"account-1 │imap │yes \n",
|
|
||||||
"\n"
|
|
||||||
],
|
|
||||||
printer.writer.content
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
pub mod args;
|
pub mod command;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod handlers;
|
|
||||||
pub(crate) mod wizard;
|
pub(crate) mod wizard;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
@ -45,7 +44,7 @@ impl Table for Account {
|
||||||
fn head() -> Row {
|
fn head() -> Row {
|
||||||
Row::new()
|
Row::new()
|
||||||
.cell(Cell::new("NAME").shrinkable().bold().underline().white())
|
.cell(Cell::new("NAME").shrinkable().bold().underline().white())
|
||||||
.cell(Cell::new("BACKEND").bold().underline().white())
|
.cell(Cell::new("BACKENDS").bold().underline().white())
|
||||||
.cell(Cell::new("DEFAULT").bold().underline().white())
|
.cell(Cell::new("DEFAULT").bold().underline().white())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
113
src/cli.rs
Normal file
113
src/cli.rs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
account::command as account,
|
||||||
|
completion::command as completion,
|
||||||
|
config::{self, TomlConfig},
|
||||||
|
man::command as man,
|
||||||
|
output::{ColorFmt, OutputFmt},
|
||||||
|
printer::Printer,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(
|
||||||
|
name = "himalaya",
|
||||||
|
author,
|
||||||
|
version,
|
||||||
|
about,
|
||||||
|
propagate_version = true,
|
||||||
|
infer_subcommands = true
|
||||||
|
)]
|
||||||
|
pub struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: Command,
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
#[arg(long, short, value_name = "PATH", global = true, value_parser = config::path_parser)]
|
||||||
|
pub config: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Customize the output format
|
||||||
|
///
|
||||||
|
/// The output format determine how to display commands output to
|
||||||
|
/// the terminal.
|
||||||
|
///
|
||||||
|
/// The possible values are:
|
||||||
|
///
|
||||||
|
/// - json: output will be in a form of a JSON-compatible object
|
||||||
|
///
|
||||||
|
/// - plain: output will be in a form of either a plain text or
|
||||||
|
/// table, depending on the command
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
short,
|
||||||
|
value_name = "FORMAT",
|
||||||
|
global = true,
|
||||||
|
value_enum,
|
||||||
|
default_value_t = Default::default(),
|
||||||
|
)]
|
||||||
|
pub output: OutputFmt,
|
||||||
|
|
||||||
|
/// Control when to use colors
|
||||||
|
///
|
||||||
|
/// The default setting is 'auto', which means himalaya will try
|
||||||
|
/// to guess when to use colors. For example, if himalaya is
|
||||||
|
/// printing to a terminal, then it will use colors, but if it is
|
||||||
|
/// redirected to a file or a pipe, then it will suppress color
|
||||||
|
/// output. himalaya will suppress color output in some other
|
||||||
|
/// circumstances as well. For example, if the TERM environment
|
||||||
|
/// variable is not set or set to 'dumb', then himalaya will not
|
||||||
|
/// use colors.
|
||||||
|
///
|
||||||
|
/// The possible values are:
|
||||||
|
///
|
||||||
|
/// - never: colors will never be used
|
||||||
|
///
|
||||||
|
/// - always: colors will always be used regardless of where output is sent
|
||||||
|
///
|
||||||
|
/// - ansi: like 'always', but emits ANSI escapes (even in a Windows console)
|
||||||
|
///
|
||||||
|
/// - auto: himalaya tries to be smart
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
short = 'C',
|
||||||
|
value_name = "MODE",
|
||||||
|
global = true,
|
||||||
|
value_enum,
|
||||||
|
default_value_t = Default::default(),
|
||||||
|
)]
|
||||||
|
pub color: ColorFmt,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Top-level CLI commands.
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum Command {
|
||||||
|
/// Subcommand to manage accounts
|
||||||
|
#[command(subcommand)]
|
||||||
|
Account(account::Command),
|
||||||
|
|
||||||
|
/// Generate all man pages to the given directory
|
||||||
|
#[command(arg_required_else_help = true, alias = "mans")]
|
||||||
|
Man(man::Command),
|
||||||
|
|
||||||
|
/// Print completion script for the given shell to stdout
|
||||||
|
#[command(arg_required_else_help = true, aliases = ["completions", "compl", "comp"])]
|
||||||
|
Completion(completion::Command),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
Self::Account(cmd) => cmd.execute(printer, config).await,
|
||||||
|
Self::Man(cmd) => cmd.execute(printer).await,
|
||||||
|
Self::Completion(cmd) => cmd.execute(printer).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,32 @@
|
||||||
use clap::{value_parser, Parser};
|
use anyhow::Result;
|
||||||
|
use clap::{value_parser, CommandFactory, Parser};
|
||||||
use clap_complete::Shell;
|
use clap_complete::Shell;
|
||||||
|
use log::info;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use crate::{cli::Cli, printer::Printer};
|
||||||
|
|
||||||
/// Print completion script for the given shell to stdout
|
/// Print completion script for the given shell to stdout
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct Generate {
|
pub struct Command {
|
||||||
/// Shell that completion script should be generated for
|
/// Shell that completion script should be generated for
|
||||||
#[arg(value_parser = value_parser!(Shell))]
|
#[arg(value_parser = value_parser!(Shell))]
|
||||||
pub shell: Shell,
|
pub shell: Shell,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub async fn execute(self, printer: &mut impl Printer) -> Result<()> {
|
||||||
|
info!("executing completion generate command");
|
||||||
|
|
||||||
|
let mut cmd = Cli::command();
|
||||||
|
let name = cmd.get_name().to_string();
|
||||||
|
clap_complete::generate(self.shell, &mut cmd, name, &mut io::stdout());
|
||||||
|
|
||||||
|
printer.print(format!(
|
||||||
|
"Shell script successfully generated for shell {}!",
|
||||||
|
self.shell
|
||||||
|
))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use clap::Command;
|
|
||||||
use clap_complete::Shell;
|
|
||||||
use std::io::stdout;
|
|
||||||
|
|
||||||
use crate::printer::Printer;
|
|
||||||
|
|
||||||
pub fn generate(printer: &mut impl Printer, mut cmd: Command, shell: Shell) -> Result<()> {
|
|
||||||
let name = cmd.get_name().to_string();
|
|
||||||
|
|
||||||
clap_complete::generate(shell, &mut cmd, name, &mut stdout());
|
|
||||||
|
|
||||||
printer.print(format!(
|
|
||||||
"Shell script successfully generated for shell {shell}!"
|
|
||||||
))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,2 +1 @@
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod handler;
|
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
//! Deserialized config module.
|
|
||||||
//!
|
|
||||||
//! This module contains the raw deserialized representation of the
|
|
||||||
//! user configuration file.
|
|
||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
pub mod wizard;
|
pub mod wizard;
|
||||||
|
@ -16,6 +11,7 @@ use email::{
|
||||||
email::config::{EmailHooks, EmailTextPlainFormat},
|
email::config::{EmailHooks, EmailTextPlainFormat},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use shellexpand_utils::{canonicalize, expand};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs,
|
fs,
|
||||||
|
@ -162,12 +158,10 @@ impl TomlConfig {
|
||||||
.filter(|p| p.exists())
|
.filter(|p| p.exists())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build account configurations from a given account name.
|
pub fn into_toml_account_config(
|
||||||
pub fn into_account_configs(
|
&self,
|
||||||
self,
|
|
||||||
account_name: Option<&str>,
|
account_name: Option<&str>,
|
||||||
disable_cache: bool,
|
) -> Result<(String, TomlAccountConfig)> {
|
||||||
) -> Result<(TomlAccountConfig, AccountConfig)> {
|
|
||||||
let (account_name, mut toml_account_config) = match account_name {
|
let (account_name, mut toml_account_config) = match account_name {
|
||||||
Some("default") | Some("") | None => self
|
Some("default") | Some("") | None => self
|
||||||
.accounts
|
.accounts
|
||||||
|
@ -200,6 +194,18 @@ impl TomlConfig {
|
||||||
.replace_undefined_keyring_entries(&account_name);
|
.replace_undefined_keyring_entries(&account_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok((account_name, toml_account_config))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build account configurations from a given account name.
|
||||||
|
pub fn into_account_configs(
|
||||||
|
self,
|
||||||
|
account_name: Option<&str>,
|
||||||
|
disable_cache: bool,
|
||||||
|
) -> Result<(TomlAccountConfig, AccountConfig)> {
|
||||||
|
let (account_name, mut toml_account_config) =
|
||||||
|
self.into_toml_account_config(account_name)?;
|
||||||
|
|
||||||
if let Some(true) = toml_account_config.sync {
|
if let Some(true) = toml_account_config.sync {
|
||||||
if !disable_cache {
|
if !disable_cache {
|
||||||
toml_account_config.backend = Some(BackendKind::MaildirForSync);
|
toml_account_config.backend = Some(BackendKind::MaildirForSync);
|
||||||
|
@ -267,6 +273,15 @@ impl TomlConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a configuration file path as [`PathBuf`].
|
||||||
|
///
|
||||||
|
/// The path is shell-expanded then canonicalized (if applicable).
|
||||||
|
pub fn path_parser(path: &str) -> Result<PathBuf, String> {
|
||||||
|
expand::try_path(path)
|
||||||
|
.map(canonicalize::path)
|
||||||
|
.map_err(|err| err.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use email::{
|
use email::{
|
||||||
|
|
|
@ -121,11 +121,6 @@ pub fn all_arg(help: &'static str) -> Arg {
|
||||||
.conflicts_with(ARG_EXCLUDE)
|
.conflicts_with(ARG_EXCLUDE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the all folders argument parser.
|
|
||||||
pub fn parse_all_arg(m: &ArgMatches) -> bool {
|
|
||||||
m.get_flag(ARG_ALL)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the folders to include argument.
|
/// Represents the folders to include argument.
|
||||||
pub fn include_arg(help: &'static str) -> Arg {
|
pub fn include_arg(help: &'static str) -> Arg {
|
||||||
Arg::new(ARG_INCLUDE)
|
Arg::new(ARG_INCLUDE)
|
||||||
|
@ -141,14 +136,6 @@ pub fn include_arg(help: &'static str) -> Arg {
|
||||||
.conflicts_with(ARG_EXCLUDE)
|
.conflicts_with(ARG_EXCLUDE)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the folders to include argument parser.
|
|
||||||
pub fn parse_include_arg(m: &ArgMatches) -> HashSet<String> {
|
|
||||||
m.get_many::<String>(ARG_INCLUDE)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.map(ToOwned::to_owned)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the folders to exclude argument.
|
/// Represents the folders to exclude argument.
|
||||||
pub fn exclude_arg(help: &'static str) -> Arg {
|
pub fn exclude_arg(help: &'static str) -> Arg {
|
||||||
Arg::new(ARG_EXCLUDE)
|
Arg::new(ARG_EXCLUDE)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod account;
|
pub mod account;
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
|
pub mod cli;
|
||||||
pub mod completion;
|
pub mod completion;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod email;
|
pub mod email;
|
||||||
|
|
667
src/main.rs
667
src/main.rs
|
@ -1,361 +1,7 @@
|
||||||
use ::email::account::{config::DEFAULT_INBOX_FOLDER, sync::AccountSyncBuilder};
|
use anyhow::Result;
|
||||||
use anyhow::{anyhow, Context, Result};
|
use clap::Parser;
|
||||||
use clap::{Command, CommandFactory, Parser, Subcommand};
|
|
||||||
use env_logger::{Builder as LoggerBuilder, Env, DEFAULT_FILTER_ENV};
|
use env_logger::{Builder as LoggerBuilder, Env, DEFAULT_FILTER_ENV};
|
||||||
use log::{debug, warn};
|
use himalaya::{cli::Cli, config::TomlConfig, printer::StdoutPrinter};
|
||||||
use std::env;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use himalaya::{
|
|
||||||
account,
|
|
||||||
backend::{Backend, BackendBuilder},
|
|
||||||
cache, completion,
|
|
||||||
config::{self, TomlConfig},
|
|
||||||
envelope, flag, folder, man, message, output,
|
|
||||||
printer::StdoutPrinter,
|
|
||||||
template,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn _create_app() -> Command {
|
|
||||||
Command::new(env!("CARGO_PKG_NAME"))
|
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
|
||||||
.about(env!("CARGO_PKG_DESCRIPTION"))
|
|
||||||
.author(env!("CARGO_PKG_AUTHORS"))
|
|
||||||
.propagate_version(true)
|
|
||||||
.infer_subcommands(true)
|
|
||||||
.args(config::args::global_args())
|
|
||||||
.args(account::args::global_args())
|
|
||||||
.args(folder::args::global_args())
|
|
||||||
.args(cache::args::global_args())
|
|
||||||
.args(output::args::global_args())
|
|
||||||
.subcommand(account::args::subcmd())
|
|
||||||
.subcommand(folder::args::subcmd())
|
|
||||||
.subcommand(envelope::args::subcmd())
|
|
||||||
.subcommand(flag::args::subcmd())
|
|
||||||
.subcommand(message::args::subcmd())
|
|
||||||
.subcommand(template::args::subcmd())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn _old_main() -> Result<()> {
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
if let Err((_, err)) = coredump::register_panic_handler() {
|
|
||||||
warn!("cannot register custom panic handler: {err}");
|
|
||||||
debug!("cannot register custom panic handler: {err:?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
let default_env_filter = env_logger::DEFAULT_FILTER_ENV;
|
|
||||||
env_logger::init_from_env(env_logger::Env::default().filter_or(default_env_filter, "off"));
|
|
||||||
|
|
||||||
// check mailto command before app initialization
|
|
||||||
let raw_args: Vec<String> = env::args().collect();
|
|
||||||
if raw_args.len() > 1 && raw_args[1].starts_with("mailto:") {
|
|
||||||
let url = Url::parse(&raw_args[1])?;
|
|
||||||
let (toml_account_config, account_config) = TomlConfig::from_default_paths()
|
|
||||||
.await?
|
|
||||||
.into_account_configs(None, false)?;
|
|
||||||
let backend_builder =
|
|
||||||
BackendBuilder::new(toml_account_config, account_config.clone(), true).await?;
|
|
||||||
let backend = backend_builder.build().await?;
|
|
||||||
let mut printer = StdoutPrinter::default();
|
|
||||||
|
|
||||||
return message::handlers::mailto(&account_config, &backend, &mut printer, &url).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let app = _create_app();
|
|
||||||
let m = app.get_matches();
|
|
||||||
|
|
||||||
let some_config_path = config::args::parse_global_arg(&m);
|
|
||||||
let some_account_name = account::args::parse_global_arg(&m);
|
|
||||||
let disable_cache = cache::args::parse_disable_cache_arg(&m);
|
|
||||||
let folder = folder::args::parse_global_source_arg(&m);
|
|
||||||
|
|
||||||
let toml_config = TomlConfig::from_some_path_or_default(some_config_path).await?;
|
|
||||||
|
|
||||||
let mut printer = StdoutPrinter::try_from(&m)?;
|
|
||||||
|
|
||||||
// FIXME
|
|
||||||
// #[cfg(feature = "imap")]
|
|
||||||
// if let BackendConfig::Imap(imap_config) = &account_config.backend {
|
|
||||||
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
// match imap::args::matches(&m)? {
|
|
||||||
// Some(imap::args::Cmd::Notify(keepalive)) => {
|
|
||||||
// let backend =
|
|
||||||
// ImapBackend::new(account_config.clone(), imap_config.clone(), None).await?;
|
|
||||||
// imap::handlers::notify(&mut backend, &folder, keepalive).await?;
|
|
||||||
// return Ok(());
|
|
||||||
// }
|
|
||||||
// Some(imap::args::Cmd::Watch(keepalive)) => {
|
|
||||||
// let backend =
|
|
||||||
// ImapBackend::new(account_config.clone(), imap_config.clone(), None).await?;
|
|
||||||
// imap::handlers::watch(&mut backend, &folder, keepalive).await?;
|
|
||||||
// return Ok(());
|
|
||||||
// }
|
|
||||||
// _ => (),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
match account::args::matches(&m)? {
|
|
||||||
Some(account::args::Cmd::List(max_width)) => {
|
|
||||||
let (_, account_config) = toml_config
|
|
||||||
.clone()
|
|
||||||
.into_account_configs(some_account_name, disable_cache)?;
|
|
||||||
return account::handlers::list(max_width, &account_config, &toml_config, &mut printer);
|
|
||||||
}
|
|
||||||
Some(account::args::Cmd::Sync(strategy, dry_run)) => {
|
|
||||||
let (toml_account_config, account_config) = toml_config
|
|
||||||
.clone()
|
|
||||||
.into_account_configs(some_account_name, true)?;
|
|
||||||
let backend_builder =
|
|
||||||
BackendBuilder::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
let sync_builder = AccountSyncBuilder::new(backend_builder.into())
|
|
||||||
.await?
|
|
||||||
.with_some_folders_strategy(strategy)
|
|
||||||
.with_dry_run(dry_run);
|
|
||||||
return account::handlers::sync(&mut printer, sync_builder, dry_run).await;
|
|
||||||
}
|
|
||||||
Some(account::args::Cmd::Configure(reset)) => {
|
|
||||||
let (toml_account_config, _) = toml_config
|
|
||||||
.clone()
|
|
||||||
.into_account_configs(some_account_name, disable_cache)?;
|
|
||||||
return account::handlers::configure(&toml_account_config, reset).await;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
let (toml_account_config, account_config) = toml_config
|
|
||||||
.clone()
|
|
||||||
.into_account_configs(some_account_name, disable_cache)?;
|
|
||||||
|
|
||||||
// checks folder commands
|
|
||||||
match folder::args::matches(&m)? {
|
|
||||||
Some(folder::args::Cmd::Create) => {
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
let folder = folder
|
|
||||||
.ok_or_else(|| anyhow!("the folder argument is missing"))
|
|
||||||
.context("cannot create folder")?;
|
|
||||||
return folder::handlers::create(&mut printer, &backend, &folder).await;
|
|
||||||
}
|
|
||||||
Some(folder::args::Cmd::List(max_width)) => {
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return folder::handlers::list(&account_config, &mut printer, &backend, max_width)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Some(folder::args::Cmd::Expunge) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return folder::handlers::expunge(&mut printer, &backend, &folder).await;
|
|
||||||
}
|
|
||||||
Some(folder::args::Cmd::Delete) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return folder::handlers::delete(&mut printer, &backend, &folder).await;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
match envelope::args::matches(&m)? {
|
|
||||||
Some(envelope::args::Cmd::List(max_width, page_size, page)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return envelope::handlers::list(
|
|
||||||
&account_config,
|
|
||||||
&mut printer,
|
|
||||||
&backend,
|
|
||||||
&folder,
|
|
||||||
max_width,
|
|
||||||
page_size,
|
|
||||||
page,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
match flag::args::matches(&m)? {
|
|
||||||
Some(flag::args::Cmd::Set(ids, ref flags)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return flag::handlers::set(&mut printer, &backend, &folder, ids, flags).await;
|
|
||||||
}
|
|
||||||
Some(flag::args::Cmd::Add(ids, ref flags)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return flag::handlers::add(&mut printer, &backend, &folder, ids, flags).await;
|
|
||||||
}
|
|
||||||
Some(flag::args::Cmd::Remove(ids, ref flags)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return flag::handlers::remove(&mut printer, &backend, &folder, ids, flags).await;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
match message::args::matches(&m)? {
|
|
||||||
Some(message::args::Cmd::Attachments(ids)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return message::handlers::attachments(
|
|
||||||
&account_config,
|
|
||||||
&mut printer,
|
|
||||||
&backend,
|
|
||||||
&folder,
|
|
||||||
ids,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Some(message::args::Cmd::Copy(ids, to_folder)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return message::handlers::copy(&mut printer, &backend, &folder, to_folder, ids).await;
|
|
||||||
}
|
|
||||||
Some(message::args::Cmd::Delete(ids)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return message::handlers::delete(&mut printer, &backend, &folder, ids).await;
|
|
||||||
}
|
|
||||||
Some(message::args::Cmd::Forward(id, headers, body)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
|
|
||||||
return message::handlers::forward(
|
|
||||||
&account_config,
|
|
||||||
&mut printer,
|
|
||||||
&backend,
|
|
||||||
&folder,
|
|
||||||
id,
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Some(message::args::Cmd::Move(ids, to_folder)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return message::handlers::move_(&mut printer, &backend, &folder, to_folder, ids).await;
|
|
||||||
}
|
|
||||||
Some(message::args::Cmd::Read(ids, text_mime, raw, headers)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return message::handlers::read(
|
|
||||||
&account_config,
|
|
||||||
&mut printer,
|
|
||||||
&backend,
|
|
||||||
&folder,
|
|
||||||
ids,
|
|
||||||
text_mime,
|
|
||||||
raw,
|
|
||||||
headers,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Some(message::args::Cmd::Reply(id, all, headers, body)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
|
|
||||||
return message::handlers::reply(
|
|
||||||
&account_config,
|
|
||||||
&mut printer,
|
|
||||||
&backend,
|
|
||||||
&folder,
|
|
||||||
id,
|
|
||||||
all,
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Some(message::args::Cmd::Save(raw_email)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return message::handlers::save(&mut printer, &backend, &folder, raw_email).await;
|
|
||||||
}
|
|
||||||
Some(message::args::Cmd::Send(raw_email)) => {
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
|
|
||||||
return message::handlers::send(&account_config, &mut printer, &backend, raw_email)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Some(message::args::Cmd::Write(headers, body)) => {
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
|
|
||||||
return message::handlers::write(
|
|
||||||
&account_config,
|
|
||||||
&mut printer,
|
|
||||||
&backend,
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
match template::args::matches(&m)? {
|
|
||||||
Some(template::args::Cmd::Forward(id, headers, body)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return template::handlers::forward(
|
|
||||||
&account_config,
|
|
||||||
&mut printer,
|
|
||||||
&backend,
|
|
||||||
&folder,
|
|
||||||
id,
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Some(template::args::Cmd::Write(headers, body)) => {
|
|
||||||
return template::handlers::write(&account_config, &mut printer, headers, body).await;
|
|
||||||
}
|
|
||||||
Some(template::args::Cmd::Reply(id, all, headers, body)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return template::handlers::reply(
|
|
||||||
&account_config,
|
|
||||||
&mut printer,
|
|
||||||
&backend,
|
|
||||||
&folder,
|
|
||||||
id,
|
|
||||||
all,
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Some(template::args::Cmd::Save(template)) => {
|
|
||||||
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
|
||||||
return template::handlers::save(
|
|
||||||
&account_config,
|
|
||||||
&mut printer,
|
|
||||||
&backend,
|
|
||||||
&folder,
|
|
||||||
template,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Some(template::args::Cmd::Send(template)) => {
|
|
||||||
let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
|
|
||||||
return template::handlers::send(&account_config, &mut printer, &backend, template)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
#[command(name= "himalaya", author, version, about, long_about = None, propagate_version = true)]
|
|
||||||
struct Cli {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Commands,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
|
||||||
enum Commands {
|
|
||||||
Man(man::command::Generate),
|
|
||||||
#[command(aliases = ["completions", "compl", "comp"])]
|
|
||||||
Completion(completion::command::Generate),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
@ -364,12 +10,305 @@ async fn main() -> Result<()> {
|
||||||
.format_timestamp(None)
|
.format_timestamp(None)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let mut printer = StdoutPrinter::default();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
match Cli::parse().command {
|
let mut printer = StdoutPrinter::new(cli.output, cli.color);
|
||||||
Commands::Man(cmd) => man::handler::generate(&mut printer, Cli::command(), cmd.dir),
|
let config = TomlConfig::from_some_path_or_default(cli.config.as_ref()).await?;
|
||||||
Commands::Completion(cmd) => {
|
|
||||||
completion::handler::generate(&mut printer, Cli::command(), cmd.shell)
|
cli.command.execute(&mut printer, &config).await
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn create_app() -> clap::Command {
|
||||||
|
// clap::Command::new(env!("CARGO_PKG_NAME"))
|
||||||
|
// .version(env!("CARGO_PKG_VERSION"))
|
||||||
|
// .about(env!("CARGO_PKG_DESCRIPTION"))
|
||||||
|
// .author(env!("CARGO_PKG_AUTHORS"))
|
||||||
|
// .propagate_version(true)
|
||||||
|
// .infer_subcommands(true)
|
||||||
|
// .args(folder::args::global_args())
|
||||||
|
// .args(cache::args::global_args())
|
||||||
|
// .args(output::args::global_args())
|
||||||
|
// .subcommand(folder::args::subcmd())
|
||||||
|
// .subcommand(envelope::args::subcmd())
|
||||||
|
// .subcommand(flag::args::subcmd())
|
||||||
|
// .subcommand(message::args::subcmd())
|
||||||
|
// .subcommand(template::args::subcmd())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[tokio::main]
|
||||||
|
// async fn main() -> Result<()> {
|
||||||
|
// #[cfg(not(target_os = "windows"))]
|
||||||
|
// if let Err((_, err)) = coredump::register_panic_handler() {
|
||||||
|
// warn!("cannot register custom panic handler: {err}");
|
||||||
|
// debug!("cannot register custom panic handler: {err:?}");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let default_env_filter = env_logger::DEFAULT_FILTER_ENV;
|
||||||
|
// env_logger::init_from_env(env_logger::Env::default().filter_or(default_env_filter, "off"));
|
||||||
|
|
||||||
|
// // check mailto command before app initialization
|
||||||
|
// let raw_args: Vec<String> = env::args().collect();
|
||||||
|
// if raw_args.len() > 1 && raw_args[1].starts_with("mailto:") {
|
||||||
|
// let url = Url::parse(&raw_args[1])?;
|
||||||
|
// let (toml_account_config, account_config) = TomlConfig::from_default_paths()
|
||||||
|
// .await?
|
||||||
|
// .into_account_configs(None, false)?;
|
||||||
|
// let backend_builder =
|
||||||
|
// BackendBuilder::new(toml_account_config, account_config.clone(), true).await?;
|
||||||
|
// let backend = backend_builder.build().await?;
|
||||||
|
// let mut printer = StdoutPrinter::default();
|
||||||
|
|
||||||
|
// return message::handlers::mailto(&account_config, &backend, &mut printer, &url).await;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let app = _create_app();
|
||||||
|
// let m = app.get_matches();
|
||||||
|
|
||||||
|
// let some_config_path = config::args::parse_global_arg(&m);
|
||||||
|
// let some_account_name = account::command::parse_global_arg(&m);
|
||||||
|
// let disable_cache = cache::args::parse_disable_cache_arg(&m);
|
||||||
|
// let folder = folder::args::parse_global_source_arg(&m);
|
||||||
|
|
||||||
|
// let toml_config = TomlConfig::from_some_path_or_default(some_config_path).await?;
|
||||||
|
|
||||||
|
// let mut printer = StdoutPrinter::try_from(&m)?;
|
||||||
|
|
||||||
|
// #[cfg(feature = "imap")]
|
||||||
|
// if let BackendConfig::Imap(imap_config) = &account_config.backend {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// match imap::args::matches(&m)? {
|
||||||
|
// Some(imap::args::Cmd::Notify(keepalive)) => {
|
||||||
|
// let backend =
|
||||||
|
// ImapBackend::new(account_config.clone(), imap_config.clone(), None).await?;
|
||||||
|
// imap::handlers::notify(&mut backend, &folder, keepalive).await?;
|
||||||
|
// return Ok(());
|
||||||
|
// }
|
||||||
|
// Some(imap::args::Cmd::Watch(keepalive)) => {
|
||||||
|
// let backend =
|
||||||
|
// ImapBackend::new(account_config.clone(), imap_config.clone(), None).await?;
|
||||||
|
// imap::handlers::watch(&mut backend, &folder, keepalive).await?;
|
||||||
|
// return Ok(());
|
||||||
|
// }
|
||||||
|
// _ => (),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let (toml_account_config, account_config) = toml_config
|
||||||
|
// .clone()
|
||||||
|
// .into_account_configs(some_account_name, disable_cache)?;
|
||||||
|
|
||||||
|
// // checks folder commands
|
||||||
|
// match folder::args::matches(&m)? {
|
||||||
|
// Some(folder::args::Cmd::Create) => {
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// let folder = folder
|
||||||
|
// .ok_or_else(|| anyhow!("the folder argument is missing"))
|
||||||
|
// .context("cannot create folder")?;
|
||||||
|
// return folder::handlers::create(&mut printer, &backend, &folder).await;
|
||||||
|
// }
|
||||||
|
// Some(folder::args::Cmd::List(max_width)) => {
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return folder::handlers::list(&account_config, &mut printer, &backend, max_width)
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
// Some(folder::args::Cmd::Expunge) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return folder::handlers::expunge(&mut printer, &backend, &folder).await;
|
||||||
|
// }
|
||||||
|
// Some(folder::args::Cmd::Delete) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return folder::handlers::delete(&mut printer, &backend, &folder).await;
|
||||||
|
// }
|
||||||
|
// _ => (),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// match envelope::args::matches(&m)? {
|
||||||
|
// Some(envelope::args::Cmd::List(max_width, page_size, page)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return envelope::handlers::list(
|
||||||
|
// &account_config,
|
||||||
|
// &mut printer,
|
||||||
|
// &backend,
|
||||||
|
// &folder,
|
||||||
|
// max_width,
|
||||||
|
// page_size,
|
||||||
|
// page,
|
||||||
|
// )
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
// _ => (),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// match flag::args::matches(&m)? {
|
||||||
|
// Some(flag::args::Cmd::Set(ids, ref flags)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return flag::handlers::set(&mut printer, &backend, &folder, ids, flags).await;
|
||||||
|
// }
|
||||||
|
// Some(flag::args::Cmd::Add(ids, ref flags)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return flag::handlers::add(&mut printer, &backend, &folder, ids, flags).await;
|
||||||
|
// }
|
||||||
|
// Some(flag::args::Cmd::Remove(ids, ref flags)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return flag::handlers::remove(&mut printer, &backend, &folder, ids, flags).await;
|
||||||
|
// }
|
||||||
|
// _ => (),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// match message::args::matches(&m)? {
|
||||||
|
// Some(message::args::Cmd::Attachments(ids)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return message::handlers::attachments(
|
||||||
|
// &account_config,
|
||||||
|
// &mut printer,
|
||||||
|
// &backend,
|
||||||
|
// &folder,
|
||||||
|
// ids,
|
||||||
|
// )
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
// Some(message::args::Cmd::Copy(ids, to_folder)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return message::handlers::copy(&mut printer, &backend, &folder, to_folder, ids).await;
|
||||||
|
// }
|
||||||
|
// Some(message::args::Cmd::Delete(ids)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return message::handlers::delete(&mut printer, &backend, &folder, ids).await;
|
||||||
|
// }
|
||||||
|
// Some(message::args::Cmd::Forward(id, headers, body)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
|
||||||
|
// return message::handlers::forward(
|
||||||
|
// &account_config,
|
||||||
|
// &mut printer,
|
||||||
|
// &backend,
|
||||||
|
// &folder,
|
||||||
|
// id,
|
||||||
|
// headers,
|
||||||
|
// body,
|
||||||
|
// )
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
// Some(message::args::Cmd::Move(ids, to_folder)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return message::handlers::move_(&mut printer, &backend, &folder, to_folder, ids).await;
|
||||||
|
// }
|
||||||
|
// Some(message::args::Cmd::Read(ids, text_mime, raw, headers)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return message::handlers::read(
|
||||||
|
// &account_config,
|
||||||
|
// &mut printer,
|
||||||
|
// &backend,
|
||||||
|
// &folder,
|
||||||
|
// ids,
|
||||||
|
// text_mime,
|
||||||
|
// raw,
|
||||||
|
// headers,
|
||||||
|
// )
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
// Some(message::args::Cmd::Reply(id, all, headers, body)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
|
||||||
|
// return message::handlers::reply(
|
||||||
|
// &account_config,
|
||||||
|
// &mut printer,
|
||||||
|
// &backend,
|
||||||
|
// &folder,
|
||||||
|
// id,
|
||||||
|
// all,
|
||||||
|
// headers,
|
||||||
|
// body,
|
||||||
|
// )
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
// Some(message::args::Cmd::Save(raw_email)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return message::handlers::save(&mut printer, &backend, &folder, raw_email).await;
|
||||||
|
// }
|
||||||
|
// Some(message::args::Cmd::Send(raw_email)) => {
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
|
||||||
|
// return message::handlers::send(&account_config, &mut printer, &backend, raw_email)
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
// Some(message::args::Cmd::Write(headers, body)) => {
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
|
||||||
|
// return message::handlers::write(
|
||||||
|
// &account_config,
|
||||||
|
// &mut printer,
|
||||||
|
// &backend,
|
||||||
|
// headers,
|
||||||
|
// body,
|
||||||
|
// )
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
// _ => (),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// match template::args::matches(&m)? {
|
||||||
|
// Some(template::args::Cmd::Forward(id, headers, body)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return template::handlers::forward(
|
||||||
|
// &account_config,
|
||||||
|
// &mut printer,
|
||||||
|
// &backend,
|
||||||
|
// &folder,
|
||||||
|
// id,
|
||||||
|
// headers,
|
||||||
|
// body,
|
||||||
|
// )
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
// Some(template::args::Cmd::Write(headers, body)) => {
|
||||||
|
// return template::handlers::write(&account_config, &mut printer, headers, body).await;
|
||||||
|
// }
|
||||||
|
// Some(template::args::Cmd::Reply(id, all, headers, body)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return template::handlers::reply(
|
||||||
|
// &account_config,
|
||||||
|
// &mut printer,
|
||||||
|
// &backend,
|
||||||
|
// &folder,
|
||||||
|
// id,
|
||||||
|
// all,
|
||||||
|
// headers,
|
||||||
|
// body,
|
||||||
|
// )
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
// Some(template::args::Cmd::Save(template)) => {
|
||||||
|
// let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), false).await?;
|
||||||
|
// return template::handlers::save(
|
||||||
|
// &account_config,
|
||||||
|
// &mut printer,
|
||||||
|
// &backend,
|
||||||
|
// &folder,
|
||||||
|
// template,
|
||||||
|
// )
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
// Some(template::args::Cmd::Send(template)) => {
|
||||||
|
// let backend = Backend::new(toml_account_config, account_config.clone(), true).await?;
|
||||||
|
// return template::handlers::send(&account_config, &mut printer, &backend, template)
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
// _ => (),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
|
@ -1,16 +1,58 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::{CommandFactory, Parser};
|
||||||
|
use clap_mangen::Man;
|
||||||
|
use log::info;
|
||||||
use shellexpand_utils::{canonicalize, expand};
|
use shellexpand_utils::{canonicalize, expand};
|
||||||
use std::path::PathBuf;
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::{cli::Cli, printer::Printer};
|
||||||
|
|
||||||
/// Generate all man pages to the given directory
|
/// Generate all man pages to the given directory
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct Generate {
|
pub struct Command {
|
||||||
/// Directory where man files should be generated in
|
/// Directory where man files should be generated in
|
||||||
#[arg(value_parser = dir_parser)]
|
#[arg(value_parser = dir_parser)]
|
||||||
pub dir: PathBuf,
|
pub dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub async fn execute(self, printer: &mut impl Printer) -> Result<()> {
|
||||||
|
info!("executing man generate command");
|
||||||
|
|
||||||
|
let cmd = Cli::command();
|
||||||
|
let cmd_name = cmd.get_name().to_string();
|
||||||
|
let subcmds = cmd.get_subcommands().cloned().collect::<Vec<_>>();
|
||||||
|
let subcmds_len = subcmds.len() + 1;
|
||||||
|
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
Man::new(cmd).render(&mut buffer)?;
|
||||||
|
|
||||||
|
fs::create_dir_all(&self.dir)?;
|
||||||
|
printer.print_log(format!("Generating man page for command {cmd_name}…"))?;
|
||||||
|
fs::write(self.dir.join(format!("{}.1", cmd_name)), buffer)?;
|
||||||
|
|
||||||
|
for subcmd in subcmds {
|
||||||
|
let subcmd_name = subcmd.get_name().to_string();
|
||||||
|
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
Man::new(subcmd).render(&mut buffer)?;
|
||||||
|
|
||||||
|
printer.print_log(format!("Generating man page for subcommand {subcmd_name}…"))?;
|
||||||
|
fs::write(
|
||||||
|
self.dir.join(format!("{}-{}.1", cmd_name, subcmd_name)),
|
||||||
|
buffer,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
printer.print(format!(
|
||||||
|
"{subcmds_len} man page(s) successfully generated in {:?}!",
|
||||||
|
self.dir
|
||||||
|
))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse the given [`str`] as [`PathBuf`].
|
/// Parse the given [`str`] as [`PathBuf`].
|
||||||
///
|
///
|
||||||
/// The path is first shell expanded, then canonicalized (if
|
/// The path is first shell expanded, then canonicalized (if
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use clap::Command;
|
|
||||||
use clap_mangen::Man;
|
|
||||||
use std::{fs, path::PathBuf};
|
|
||||||
|
|
||||||
use crate::printer::Printer;
|
|
||||||
|
|
||||||
pub fn generate(printer: &mut impl Printer, cmd: Command, dir: PathBuf) -> Result<()> {
|
|
||||||
let cmd_name = cmd.get_name().to_string();
|
|
||||||
let subcmds = cmd.get_subcommands().cloned().collect::<Vec<_>>();
|
|
||||||
let subcmds_len = subcmds.len() + 1;
|
|
||||||
|
|
||||||
let mut buffer = Vec::new();
|
|
||||||
Man::new(cmd).render(&mut buffer)?;
|
|
||||||
|
|
||||||
fs::create_dir_all(&dir)?;
|
|
||||||
printer.print_log(format!("Generating man page for command {cmd_name}…"))?;
|
|
||||||
fs::write(dir.join(format!("{}.1", cmd_name)), buffer)?;
|
|
||||||
|
|
||||||
for subcmd in subcmds {
|
|
||||||
let subcmd_name = subcmd.get_name().to_string();
|
|
||||||
|
|
||||||
let mut buffer = Vec::new();
|
|
||||||
Man::new(subcmd).render(&mut buffer)?;
|
|
||||||
|
|
||||||
printer.print_log(format!("Generating man page for subcommand {subcmd_name}…"))?;
|
|
||||||
fs::write(dir.join(format!("{}-{}.1", cmd_name, subcmd_name)), buffer)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
printer.print(format!(
|
|
||||||
"Successfully generated {subcmds_len} man page(s) in {dir:?}!"
|
|
||||||
))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,2 +1 @@
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod handler;
|
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{anyhow, Error, Result};
|
||||||
use atty::Stream;
|
use atty::Stream;
|
||||||
|
use clap::ValueEnum;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{fmt, str::FromStr};
|
use std::{fmt, str::FromStr};
|
||||||
use termcolor::ColorChoice;
|
use termcolor::ColorChoice;
|
||||||
|
|
||||||
/// Represents the available output formats.
|
/// Represents the available output formats.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, ValueEnum)]
|
||||||
pub enum OutputFmt {
|
pub enum OutputFmt {
|
||||||
|
#[default]
|
||||||
Plain,
|
Plain,
|
||||||
Json,
|
Json,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for OutputFmt {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Plain
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for OutputFmt {
|
impl FromStr for OutputFmt {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
|
@ -52,20 +48,15 @@ impl<T: Serialize> OutputJson<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represent the available color configs.
|
/// Represent the available color configs.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, ValueEnum)]
|
||||||
pub enum ColorFmt {
|
pub enum ColorFmt {
|
||||||
Never,
|
Never,
|
||||||
Always,
|
Always,
|
||||||
Ansi,
|
Ansi,
|
||||||
|
#[default]
|
||||||
Auto,
|
Auto,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ColorFmt {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Auto
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for ColorFmt {
|
impl FromStr for ColorFmt {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue