From 5e1a03e3c1facff6dcded573ac5c2c9bfd68d62c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Thu, 7 Dec 2023 10:10:18 +0100 Subject: [PATCH] refactor flag with clap derive api --- src/cli.rs | 12 ++- src/email/envelope/flag/arg/ids_and_flags.rs | 51 +++++++++++++ src/email/envelope/flag/arg/mod.rs | 1 + src/email/envelope/flag/command/add.rs | 48 ++++++++++++ src/email/envelope/flag/command/mod.rs | 39 ++++++++++ src/email/envelope/flag/command/remove.rs | 48 ++++++++++++ src/email/envelope/flag/command/set.rs | 48 ++++++++++++ src/email/envelope/flag/mod.rs | 4 +- src/main.rs | 79 ++------------------ 9 files changed, 254 insertions(+), 76 deletions(-) create mode 100644 src/email/envelope/flag/arg/ids_and_flags.rs create mode 100644 src/email/envelope/flag/arg/mod.rs create mode 100644 src/email/envelope/flag/command/add.rs create mode 100644 src/email/envelope/flag/command/mod.rs create mode 100644 src/email/envelope/flag/command/remove.rs create mode 100644 src/email/envelope/flag/command/set.rs diff --git a/src/cli.rs b/src/cli.rs index 0670587..5745387 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -7,6 +7,7 @@ use crate::{ completion::command::CompletionGenerateCommand, config::{self, TomlConfig}, envelope::command::EnvelopeSubcommand, + flag::command::FlagSubcommand, folder::command::FolderSubcommand, manual::command::ManualGenerateCommand, output::{ColorFmt, OutputFmt}, @@ -90,17 +91,21 @@ pub struct Cli { #[derive(Subcommand, Debug)] pub enum HimalayaCommand { /// Subcommand to manage accounts - #[command(subcommand)] + #[command(subcommand, alias = "accounts")] Account(AccountSubcommand), /// Subcommand to manage folders - #[command(subcommand)] + #[command(subcommand, alias = "folders")] Folder(FolderSubcommand), /// Subcommand to manage envelopes - #[command(subcommand)] + #[command(subcommand, alias = "envelopes")] Envelope(EnvelopeSubcommand), + /// Subcommand to manage flags + #[command(subcommand, alias = "flags")] + Flag(FlagSubcommand), + /// Generate manual pages to a directory #[command(arg_required_else_help = true)] Manual(ManualGenerateCommand), @@ -116,6 +121,7 @@ impl HimalayaCommand { Self::Account(cmd) => cmd.execute(printer, config).await, Self::Folder(cmd) => cmd.execute(printer, config).await, Self::Envelope(cmd) => cmd.execute(printer, config).await, + Self::Flag(cmd) => cmd.execute(printer, config).await, Self::Manual(cmd) => cmd.execute(printer).await, Self::Completion(cmd) => cmd.execute(printer).await, } diff --git a/src/email/envelope/flag/arg/ids_and_flags.rs b/src/email/envelope/flag/arg/ids_and_flags.rs new file mode 100644 index 0000000..f1eb40f --- /dev/null +++ b/src/email/envelope/flag/arg/ids_and_flags.rs @@ -0,0 +1,51 @@ +use clap::Parser; +use email::flag::{Flag, Flags}; +use log::debug; + +/// The ids and/or flags arguments parser +#[derive(Debug, Parser)] +pub struct IdsAndFlagsArgs { + /// The list of ids and/or flags + /// + /// Every argument that can be parsed as an integer is considered + /// an id, otherwise it is considered as a flag. + #[arg(value_name = "ID-OR-FLAG", required = true)] + pub ids_and_flags: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub enum IdOrFlag { + Id(String), + Flag(Flag), +} + +impl From<&str> for IdOrFlag { + fn from(value: &str) -> Self { + value + .parse::() + .map(|_| Self::Id(value.to_owned())) + .unwrap_or_else(|err| { + let flag = Flag::from(value); + debug!("cannot parse {value} as usize, parsing it as flag {flag}"); + debug!("{err:?}"); + Self::Flag(flag) + }) + } +} + +pub fn to_tuple<'a>(ids_and_flags: &'a [IdOrFlag]) -> (Vec<&'a str>, Flags) { + ids_and_flags.iter().fold( + (Vec::default(), Flags::default()), + |(mut ids, mut flags), arg| { + match arg { + IdOrFlag::Id(id) => { + ids.push(id.as_str()); + } + IdOrFlag::Flag(flag) => { + flags.insert(flag.to_owned()); + } + }; + (ids, flags) + }, + ) +} diff --git a/src/email/envelope/flag/arg/mod.rs b/src/email/envelope/flag/arg/mod.rs new file mode 100644 index 0000000..c98b755 --- /dev/null +++ b/src/email/envelope/flag/arg/mod.rs @@ -0,0 +1 @@ +pub mod ids_and_flags; diff --git a/src/email/envelope/flag/command/add.rs b/src/email/envelope/flag/command/add.rs new file mode 100644 index 0000000..c09538e --- /dev/null +++ b/src/email/envelope/flag/command/add.rs @@ -0,0 +1,48 @@ +use anyhow::Result; +use clap::Parser; +use log::info; + +use crate::{ + account::arg::name::AccountNameFlag, + backend::Backend, + cache::arg::disable::DisableCacheFlag, + config::TomlConfig, + flag::arg::ids_and_flags::{to_tuple, IdsAndFlagsArgs}, + folder::arg::name::FolderNameArg, + printer::Printer, +}; + +/// Add flag(s) to an envelope +#[derive(Debug, Parser)] +pub struct FlagAddCommand { + #[command(flatten)] + pub folder: FolderNameArg, + + #[command(flatten)] + pub args: IdsAndFlagsArgs, + + #[command(flatten)] + pub account: AccountNameFlag, + + #[command(flatten)] + pub cache: DisableCacheFlag, +} + +impl FlagAddCommand { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { + info!("executing flag add command"); + + let folder = &self.folder.name; + let account = self.account.name.as_ref().map(String::as_str); + let cache = self.cache.disable; + + let (toml_account_config, account_config) = + config.clone().into_account_configs(account, cache)?; + let backend = Backend::new(toml_account_config, account_config.clone(), false).await?; + + let (ids, flags) = to_tuple(&self.args.ids_and_flags); + backend.add_flags(folder, &ids, &flags).await?; + + printer.print(format!("Flag(s) {flags} successfully added!")) + } +} diff --git a/src/email/envelope/flag/command/mod.rs b/src/email/envelope/flag/command/mod.rs new file mode 100644 index 0000000..05c7a2f --- /dev/null +++ b/src/email/envelope/flag/command/mod.rs @@ -0,0 +1,39 @@ +mod add; +mod remove; +mod set; + +use anyhow::Result; +use clap::Subcommand; + +use crate::{config::TomlConfig, printer::Printer}; + +use self::{add::FlagAddCommand, remove::FlagRemoveCommand, set::FlagSetCommand}; + +/// Subcommand to manage flags +#[derive(Debug, Subcommand)] +pub enum FlagSubcommand { + /// Add flag(s) to an envelope + #[command(arg_required_else_help = true)] + #[command(alias = "create")] + Add(FlagAddCommand), + + /// Replace flag(s) of an envelope + #[command(arg_required_else_help = true)] + #[command(aliases = ["update", "change", "replace"])] + Set(FlagSetCommand), + + /// Remove flag(s) from an envelope + #[command(arg_required_else_help = true)] + #[command(aliases = ["rm", "delete", "del"])] + Remove(FlagRemoveCommand), +} + +impl FlagSubcommand { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { + match self { + Self::Add(cmd) => cmd.execute(printer, config).await, + Self::Set(cmd) => cmd.execute(printer, config).await, + Self::Remove(cmd) => cmd.execute(printer, config).await, + } + } +} diff --git a/src/email/envelope/flag/command/remove.rs b/src/email/envelope/flag/command/remove.rs new file mode 100644 index 0000000..fae6c22 --- /dev/null +++ b/src/email/envelope/flag/command/remove.rs @@ -0,0 +1,48 @@ +use anyhow::Result; +use clap::Parser; +use log::info; + +use crate::{ + account::arg::name::AccountNameFlag, + backend::Backend, + cache::arg::disable::DisableCacheFlag, + config::TomlConfig, + flag::arg::ids_and_flags::{to_tuple, IdsAndFlagsArgs}, + folder::arg::name::FolderNameArg, + printer::Printer, +}; + +/// Remove flag(s) from an envelope +#[derive(Debug, Parser)] +pub struct FlagRemoveCommand { + #[command(flatten)] + pub folder: FolderNameArg, + + #[command(flatten)] + pub args: IdsAndFlagsArgs, + + #[command(flatten)] + pub account: AccountNameFlag, + + #[command(flatten)] + pub cache: DisableCacheFlag, +} + +impl FlagRemoveCommand { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { + info!("executing flag remove command"); + + let folder = &self.folder.name; + let account = self.account.name.as_ref().map(String::as_str); + let cache = self.cache.disable; + + let (toml_account_config, account_config) = + config.clone().into_account_configs(account, cache)?; + let backend = Backend::new(toml_account_config, account_config.clone(), false).await?; + + let (ids, flags) = to_tuple(&self.args.ids_and_flags); + backend.remove_flags(folder, &ids, &flags).await?; + + printer.print(format!("Flag(s) {flags} successfully removed!")) + } +} diff --git a/src/email/envelope/flag/command/set.rs b/src/email/envelope/flag/command/set.rs new file mode 100644 index 0000000..f93edae --- /dev/null +++ b/src/email/envelope/flag/command/set.rs @@ -0,0 +1,48 @@ +use anyhow::Result; +use clap::Parser; +use log::info; + +use crate::{ + account::arg::name::AccountNameFlag, + backend::Backend, + cache::arg::disable::DisableCacheFlag, + config::TomlConfig, + flag::arg::ids_and_flags::{to_tuple, IdsAndFlagsArgs}, + folder::arg::name::FolderNameArg, + printer::Printer, +}; + +/// Replace flag(s) of an envelope +#[derive(Debug, Parser)] +pub struct FlagSetCommand { + #[command(flatten)] + pub folder: FolderNameArg, + + #[command(flatten)] + pub args: IdsAndFlagsArgs, + + #[command(flatten)] + pub account: AccountNameFlag, + + #[command(flatten)] + pub cache: DisableCacheFlag, +} + +impl FlagSetCommand { + pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { + info!("executing flag set command"); + + let folder = &self.folder.name; + let account = self.account.name.as_ref().map(String::as_str); + let cache = self.cache.disable; + + let (toml_account_config, account_config) = + config.clone().into_account_configs(account, cache)?; + let backend = Backend::new(toml_account_config, account_config.clone(), false).await?; + + let (ids, flags) = to_tuple(&self.args.ids_and_flags); + backend.set_flags(folder, &ids, &flags).await?; + + printer.print(format!("Flag(s) {flags} successfully set!")) + } +} diff --git a/src/email/envelope/flag/mod.rs b/src/email/envelope/flag/mod.rs index 2416abc..bd3e0ec 100644 --- a/src/email/envelope/flag/mod.rs +++ b/src/email/envelope/flag/mod.rs @@ -1,4 +1,6 @@ +pub mod arg; pub mod args; +pub mod command; pub mod config; pub mod handlers; @@ -6,7 +8,7 @@ use serde::Serialize; use std::{collections::HashSet, ops}; /// Represents the flag variants. -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize)] pub enum Flag { Seen, Answered, diff --git a/src/main.rs b/src/main.rs index 8a2a343..cbba050 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,16 @@ use anyhow::Result; use clap::Parser; use env_logger::{Builder as LoggerBuilder, Env, DEFAULT_FILTER_ENV}; use himalaya::{cli::Cli, config::TomlConfig, printer::StdoutPrinter}; +use log::{debug, warn}; #[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!("{err:?}"); + } + LoggerBuilder::new() .parse_env(Env::new().filter_or(DEFAULT_FILTER_ENV, "warn")) .format_timestamp(None) @@ -20,30 +27,12 @@ async fn main() -> Result<()> { // 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(cache::args::global_args()) -// .args(output::args::global_args()) -// .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 = env::args().collect(); // if raw_args.len() > 1 && raw_args[1].starts_with("mailto:") { @@ -59,58 +48,6 @@ async fn main() -> Result<()> { // 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 toml_config = TomlConfig::from_some_path_or_default(some_config_path).await?; - -// let mut printer = StdoutPrinter::try_from(&m)?; - -// let (toml_account_config, account_config) = toml_config -// .clone() -// .into_account_configs(some_account_name, disable_cache)?; - -// 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); @@ -259,6 +196,4 @@ async fn main() -> Result<()> { // } // _ => (), // } - -// Ok(()) // }