From d2308221d7ee5ddd8a3d5ac3a1fd44c13aeb391a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Tue, 5 Dec 2023 22:38:08 +0100 Subject: [PATCH] refactor man and completion with clap derive api --- Cargo.lock | 30 ++++++++++-- Cargo.toml | 3 +- src/account/args.rs | 64 +++++++++++++++--------- src/cache/args.rs | 17 ++++--- src/completion/args.rs | 39 --------------- src/completion/command.rs | 10 ++++ src/completion/handler.rs | 18 +++++++ src/completion/handlers.rs | 15 ------ src/completion/mod.rs | 10 +--- src/config/args.rs | 15 ++++-- src/email/envelope/args.rs | 3 +- src/email/envelope/flag/args.rs | 3 +- src/email/message/args.rs | 3 +- src/email/message/template/args.rs | 3 +- src/folder/args.rs | 29 ++++++++--- src/main.rs | 79 +++++++++++++++++------------- src/man/args.rs | 43 ---------------- src/man/command.rs | 22 +++++++++ src/man/handler.rs | 35 +++++++++++++ src/man/handlers.rs | 29 ----------- src/man/mod.rs | 4 +- src/output/args.rs | 29 +++++------ 22 files changed, 270 insertions(+), 233 deletions(-) delete mode 100644 src/completion/args.rs create mode 100644 src/completion/command.rs create mode 100644 src/completion/handler.rs delete mode 100644 src/completion/handlers.rs delete mode 100644 src/man/args.rs create mode 100644 src/man/command.rs create mode 100644 src/man/handler.rs delete mode 100644 src/man/handlers.rs diff --git a/Cargo.lock b/Cargo.lock index 2c12a90..1e66c7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -470,6 +470,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -493,6 +494,18 @@ dependencies = [ "clap", ] +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "clap_lex" version = "0.6.0" @@ -1063,12 +1076,12 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.8.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ - "atty", "humantime", + "is-terminal", "log", "regex", "termcolor", @@ -2371,6 +2384,17 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi 0.3.3", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "itoa" version = "1.0.9" diff --git a/Cargo.toml b/Cargo.toml index 9759e3f..2803579 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ version = "0.4.24" [dependencies.clap] version = "4.0" +features = ["derive"] [dependencies.clap_complete] version = "4.0" @@ -80,7 +81,7 @@ version = "4.0.0" version = "0.2.4" [dependencies.env_logger] -version = "0.8" +version = "0.10" [dependencies.erased-serde] version = "0.3" diff --git a/src/account/args.rs b/src/account/args.rs index 0917fb1..da2264c 100644 --- a/src/account/args.rs +++ b/src/account/args.rs @@ -33,12 +33,20 @@ pub enum Cmd { /// Represents the account command matcher. pub fn matches(m: &ArgMatches) -> Result> { let cmd = if let Some(m) = m.subcommand_matches(CMD_ACCOUNT) { - if let Some(m) = m.subcommand_matches(CMD_SYNC) { + 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_source_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() ]))) @@ -52,17 +60,8 @@ pub fn matches(m: &ArgMatches) -> Result> { None }; Some(Cmd::Sync(folders_strategy, dry_run)) - } 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_CONFIGURE) { - info!("configure account subcommand matched"); - let reset = parse_reset_flag(m); - Some(Cmd::Configure(reset)) } else { - info!("no account subcommand matched, falling back to subcommand list"); - Some(Cmd::List(None)) + None } } else { None @@ -75,10 +74,26 @@ pub fn matches(m: &ArgMatches) -> Result> { pub fn subcmd() -> Command { Command::new(CMD_ACCOUNT) .about("Subcommand to manage accounts") - .subcommands([ + .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 from the config file") + .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")) @@ -89,26 +104,27 @@ pub fn subcmd() -> Command { "Synchronize all folders except the given ones", )) .arg(dry_run()), - Command::new(CMD_CONFIGURE) - .about("Configure the current selected account") - .aliases(["config", "conf", "cfg"]) - .arg(reset_flag()), - ]) + ) } /// Represents the user account name argument. This argument allows /// the user to select a different account than the default one. -pub fn arg() -> Arg { - Arg::new(ARG_ACCOUNT) - .help("Set the account") +pub fn global_args() -> impl IntoIterator { + [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("STRING") + .value_name("name")] } /// Represents the user account name argument parser. -pub fn parse_arg(matches: &ArgMatches) -> Option<&str> { +pub fn parse_global_arg(matches: &ArgMatches) -> Option<&str> { matches.get_one::(ARG_ACCOUNT).map(String::as_str) } diff --git a/src/cache/args.rs b/src/cache/args.rs index cd40a09..c32f6e4 100644 --- a/src/cache/args.rs +++ b/src/cache/args.rs @@ -6,19 +6,24 @@ const ARG_DISABLE_CACHE: &str = "disable-cache"; /// Represents the disable cache flag argument. This argument allows /// the user to disable any sort of cache. -pub fn arg() -> Arg { - Arg::new(ARG_DISABLE_CACHE) +pub fn global_args() -> impl IntoIterator { + [Arg::new(ARG_DISABLE_CACHE) .help("Disable any sort of cache") .long_help( - "Disable any sort of cache. The action depends on -the command it applies on.", + "Disable any sort of cache. + +The action depends on commands it apply on. For example, when listing +envelopes using the IMAP backend, this flag will ensure that envelopes +are fetched from the IMAP server and not from the synchronized local +Maildir.", ) .long("disable-cache") + .alias("no-cache") .global(true) - .action(ArgAction::SetTrue) + .action(ArgAction::SetTrue)] } /// Represents the disable cache flag parser. -pub fn parse_disable_cache_flag(m: &ArgMatches) -> bool { +pub fn parse_disable_cache_arg(m: &ArgMatches) -> bool { m.get_flag(ARG_DISABLE_CACHE) } diff --git a/src/completion/args.rs b/src/completion/args.rs deleted file mode 100644 index 00c79e9..0000000 --- a/src/completion/args.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Module related to completion CLI. -//! -//! This module provides subcommands and a command matcher related to completion. - -use anyhow::Result; -use clap::{value_parser, Arg, ArgMatches, Command}; -use clap_complete::Shell; -use log::debug; - -const ARG_SHELL: &str = "shell"; -const CMD_COMPLETION: &str = "completion"; - -type SomeShell = Shell; - -/// Completion commands. -pub enum Cmd { - /// Generate completion script for the given shell. - Generate(SomeShell), -} - -/// Completion command matcher. -pub fn matches(m: &ArgMatches) -> Result> { - if let Some(m) = m.subcommand_matches(CMD_COMPLETION) { - let shell = m.get_one::(ARG_SHELL).cloned().unwrap(); - debug!("shell: {:?}", shell); - return Ok(Some(Cmd::Generate(shell))); - }; - - Ok(None) -} - -/// Completion subcommands. -pub fn subcmd() -> Command { - Command::new(CMD_COMPLETION) - .about("Generate the completion script for the given shell") - .args(&[Arg::new(ARG_SHELL) - .value_parser(value_parser!(Shell)) - .required(true)]) -} diff --git a/src/completion/command.rs b/src/completion/command.rs new file mode 100644 index 0000000..83c4a48 --- /dev/null +++ b/src/completion/command.rs @@ -0,0 +1,10 @@ +use clap::{value_parser, Parser}; +use clap_complete::Shell; + +/// Print completion script for the given shell to stdout +#[derive(Debug, Parser)] +pub struct Generate { + /// Shell that completion script should be generated for + #[arg(value_parser = value_parser!(Shell))] + pub shell: Shell, +} diff --git a/src/completion/handler.rs b/src/completion/handler.rs new file mode 100644 index 0000000..b804a47 --- /dev/null +++ b/src/completion/handler.rs @@ -0,0 +1,18 @@ +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(()) +} diff --git a/src/completion/handlers.rs b/src/completion/handlers.rs deleted file mode 100644 index 5b36791..0000000 --- a/src/completion/handlers.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Module related to completion handling. -//! -//! This module gathers all completion commands. - -use anyhow::Result; -use clap::Command; -use clap_complete::Shell; -use std::io::stdout; - -/// Generates completion script from the given [`clap::App`] for the given shell slice. -pub fn generate<'a>(mut cmd: Command, shell: Shell) -> Result<()> { - let name = cmd.get_name().to_string(); - clap_complete::generate(shell, &mut cmd, name, &mut stdout()); - Ok(()) -} diff --git a/src/completion/mod.rs b/src/completion/mod.rs index 12d2685..a2f4939 100644 --- a/src/completion/mod.rs +++ b/src/completion/mod.rs @@ -1,8 +1,2 @@ -//! Module related to shell completion. -//! -//! This module allows users to generate autocompletion scripts for -//! their shells. You can see the list of available shells directly on -//! the clap's [docs.rs](https://docs.rs/clap/2.33.3/clap/enum.Shell.html). - -pub mod args; -pub mod handlers; +pub mod command; +pub mod handler; diff --git a/src/config/args.rs b/src/config/args.rs index ec425f5..655ba7c 100644 --- a/src/config/args.rs +++ b/src/config/args.rs @@ -6,16 +6,21 @@ const ARG_CONFIG: &str = "config"; /// Represents the config file path argument. This argument allows the /// user to customize the config file path. -pub fn arg() -> Arg { - Arg::new(ARG_CONFIG) - .help("Set a custom configuration file path") +pub fn global_args() -> impl IntoIterator { + [Arg::new(ARG_CONFIG) + .help("Override the configuration file path") + .long_help( + "Override the configuration file path + +If the file under the given path does not exist, the wizard will propose to create it.", + ) .long("config") .short('c') .global(true) - .value_name("PATH") + .value_name("path")] } /// Represents the config file path argument parser. -pub fn parse_arg(matches: &ArgMatches) -> Option<&str> { +pub fn parse_global_arg(matches: &ArgMatches) -> Option<&str> { matches.get_one::(ARG_CONFIG).map(String::as_str) } diff --git a/src/email/envelope/args.rs b/src/email/envelope/args.rs index 717e079..8a7e8ce 100644 --- a/src/email/envelope/args.rs +++ b/src/email/envelope/args.rs @@ -43,7 +43,8 @@ pub fn matches(m: &ArgMatches) -> Result> { /// Represents the envelope subcommand. pub fn subcmd() -> Command { Command::new(CMD_ENVELOPE) - .about("Manage envelopes") + .about("Subcommand to manage envelopes") + .long_about("Subcommand to manage envelopes like list") .subcommands([Command::new(CMD_LIST) .alias("lst") .about("List envelopes") diff --git a/src/email/envelope/flag/args.rs b/src/email/envelope/flag/args.rs index fd80935..7eb528a 100644 --- a/src/email/envelope/flag/args.rs +++ b/src/email/envelope/flag/args.rs @@ -57,7 +57,8 @@ pub fn matches(m: &ArgMatches) -> Result> { /// Represents the flag subcommand. pub fn subcmd() -> Command { Command::new(CMD_FLAG) - .about("Manage flags") + .about("Subcommand to manage flags") + .long_about("Subcommand to manage flags like add, set or remove") .subcommand_required(true) .arg_required_else_help(true) .subcommand( diff --git a/src/email/message/args.rs b/src/email/message/args.rs index 92eb099..65448c0 100644 --- a/src/email/message/args.rs +++ b/src/email/message/args.rs @@ -120,7 +120,8 @@ pub fn matches(m: &ArgMatches) -> Result> { /// Represents the email subcommands. pub fn subcmd() -> Command { Command::new(CMD_MESSAGE) - .about("Manage messages") + .about("Subcommand to manage messages") + .long_about("Subcommand to manage messages like read, write, reply or send") .aliases(["msg"]) .subcommand_required(true) .arg_required_else_help(true) diff --git a/src/email/message/template/args.rs b/src/email/message/template/args.rs index 7664aae..a64e7b7 100644 --- a/src/email/message/template/args.rs +++ b/src/email/message/template/args.rs @@ -73,7 +73,8 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result>> { pub fn subcmd() -> Command { Command::new(CMD_TPL) .alias("tpl") - .about("Manage templates") + .about("Subcommand to manage templates") + .long_about("Subcommand to manage templates like write, reply, send or save") .subcommand_required(true) .arg_required_else_help(true) .subcommand( diff --git a/src/folder/args.rs b/src/folder/args.rs index 98f57aa..f4bb7bf 100644 --- a/src/folder/args.rs +++ b/src/folder/args.rs @@ -14,6 +14,7 @@ use crate::ui::table; const ARG_ALL: &str = "all"; const ARG_EXCLUDE: &str = "exclude"; const ARG_INCLUDE: &str = "include"; +const ARG_GLOBAL_SOURCE: &str = "global-source"; const ARG_SOURCE: &str = "source"; const ARG_TARGET: &str = "target"; const CMD_CREATE: &str = "create"; @@ -61,7 +62,8 @@ pub fn matches(m: &ArgMatches) -> Result> { /// Represents the folder subcommand. pub fn subcmd() -> Command { Command::new(CMD_FOLDER) - .about("Manage folders") + .about("Subcommand to manage folders") + .long_about("Subcommand to manage folders like list, expunge or delete") .subcommands([ Command::new(CMD_EXPUNGE).about("Delete emails marked for deletion"), Command::new(CMD_CREATE) @@ -77,16 +79,31 @@ pub fn subcmd() -> Command { } /// Represents the source folder argument. -pub fn source_arg() -> Arg { - Arg::new(ARG_SOURCE) - .help("Set the source folder") +pub fn global_args() -> impl IntoIterator { + [Arg::new(ARG_GLOBAL_SOURCE) + .help("Override the default INBOX folder") + .long_help( + "Override the default INBOX folder. + +The given folder will be used by default for all other commands (when +applicable).", + ) .long("folder") .short('f') .global(true) - .value_name("SOURCE") + .value_name("name")] +} + +pub fn parse_global_source_arg(matches: &ArgMatches) -> Option<&str> { + matches + .get_one::(ARG_GLOBAL_SOURCE) + .map(String::as_str) +} + +pub fn source_arg(help: &'static str) -> Arg { + Arg::new(ARG_SOURCE).help(help).value_name("name") } -/// Represents the source folder argument parser. pub fn parse_source_arg(matches: &ArgMatches) -> Option<&str> { matches.get_one::(ARG_SOURCE).map(String::as_str) } diff --git a/src/main.rs b/src/main.rs index b399239..a4d7330 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use ::email::account::{config::DEFAULT_INBOX_FOLDER, sync::AccountSyncBuilder}; use anyhow::{anyhow, Context, Result}; -use clap::Command; +use clap::{Command, CommandFactory, Parser, Subcommand}; +use env_logger::{Builder as LoggerBuilder, Env, DEFAULT_FILTER_ENV}; use log::{debug, warn}; use std::env; use url::Url; @@ -15,20 +16,18 @@ use himalaya::{ template, }; -fn create_app() -> Command { +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) - .arg(config::args::arg()) - .arg(account::args::arg()) - .arg(cache::args::arg()) - .args(output::args::args()) - .arg(folder::args::source_arg()) - .subcommand(completion::args::subcmd()) - .subcommand(man::args::subcmd()) + .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()) @@ -38,7 +37,7 @@ fn create_app() -> Command { } #[tokio::main] -async fn main() -> Result<()> { +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}"); @@ -63,32 +62,13 @@ async fn main() -> Result<()> { return message::handlers::mailto(&account_config, &backend, &mut printer, &url).await; } - let app = create_app(); + let app = _create_app(); let m = app.get_matches(); - // check completionetion command before configs - // https://github.com/soywod/himalaya/issues/115 - #[allow(clippy::single_match)] - match completion::args::matches(&m)? { - Some(completion::args::Cmd::Generate(shell)) => { - return completion::handlers::generate(create_app(), shell); - } - _ => (), - } - - // check also man command before configs - #[allow(clippy::single_match)] - match man::args::matches(&m)? { - Some(man::args::Cmd::GenerateAll(dir)) => { - return man::handlers::generate(dir, create_app()); - } - _ => (), - } - - let some_config_path = config::args::parse_arg(&m); - let some_account_name = account::args::parse_arg(&m); - let disable_cache = cache::args::parse_disable_cache_flag(&m); - let folder = folder::args::parse_source_arg(&m); + 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?; @@ -362,3 +342,34 @@ async fn main() -> Result<()> { 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] +async fn main() -> Result<()> { + LoggerBuilder::new() + .parse_env(Env::new().filter_or(DEFAULT_FILTER_ENV, "warn")) + .format_timestamp(None) + .init(); + + let mut printer = StdoutPrinter::default(); + + match Cli::parse().command { + Commands::Man(cmd) => man::handler::generate(&mut printer, Cli::command(), cmd.dir), + Commands::Completion(cmd) => { + completion::handler::generate(&mut printer, Cli::command(), cmd.shell) + } + } +} diff --git a/src/man/args.rs b/src/man/args.rs deleted file mode 100644 index a85f953..0000000 --- a/src/man/args.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! Module related to man CLI. -//! -//! This module provides subcommands and a command matcher related to -//! man. - -use anyhow::Result; -use clap::{Arg, ArgMatches, Command}; -use log::debug; - -const ARG_DIR: &str = "dir"; -const CMD_MAN: &str = "man"; - -/// Man commands. -pub enum Cmd<'a> { - /// Generates all man pages to the specified directory. - GenerateAll(&'a str), -} - -/// Man command matcher. -pub fn matches(m: &ArgMatches) -> Result> { - if let Some(m) = m.subcommand_matches(CMD_MAN) { - let dir = m.get_one::(ARG_DIR).map(String::as_str).unwrap(); - debug!("directory: {}", dir); - return Ok(Some(Cmd::GenerateAll(dir))); - }; - - Ok(None) -} - -/// Man subcommands. -pub fn subcmd() -> Command { - Command::new(CMD_MAN) - .about("Generate all man pages to the given directory") - .arg( - Arg::new(ARG_DIR) - .help("Directory to generate man files in") - .long_help( - "Represents the directory where all man files of -all commands and subcommands should be generated in.", - ) - .required(true), - ) -} diff --git a/src/man/command.rs b/src/man/command.rs new file mode 100644 index 0000000..7316b8e --- /dev/null +++ b/src/man/command.rs @@ -0,0 +1,22 @@ +use anyhow::Result; +use clap::Parser; +use shellexpand_utils::{canonicalize, expand}; +use std::path::PathBuf; + +/// Generate all man pages to the given directory +#[derive(Debug, Parser)] +pub struct Generate { + /// Directory where man files should be generated in + #[arg(value_parser = dir_parser)] + pub dir: PathBuf, +} + +/// Parse the given [`str`] as [`PathBuf`]. +/// +/// The path is first shell expanded, then canonicalized (if +/// applicable). +fn dir_parser(path: &str) -> Result { + expand::try_path(path) + .map(canonicalize::path) + .map_err(|err| err.to_string()) +} diff --git a/src/man/handler.rs b/src/man/handler.rs new file mode 100644 index 0000000..069e45e --- /dev/null +++ b/src/man/handler.rs @@ -0,0 +1,35 @@ +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::>(); + 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(()) +} diff --git a/src/man/handlers.rs b/src/man/handlers.rs deleted file mode 100644 index bb0e7b6..0000000 --- a/src/man/handlers.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Module related to man handling. -//! -//! This module gathers all man commands. - -use anyhow::Result; -use clap::Command; -use clap_mangen::Man; -use std::{fs, path::PathBuf}; - -/// Generates all man pages of all subcommands in the given directory. -pub fn generate(dir: &str, cmd: Command) -> Result<()> { - let mut buffer = Vec::new(); - let cmd_name = cmd.get_name().to_string(); - let subcmds = cmd.get_subcommands().cloned().collect::>(); - Man::new(cmd).render(&mut buffer)?; - fs::write(PathBuf::from(dir).join(format!("{}.1", cmd_name)), buffer)?; - - for subcmd in subcmds { - let mut buffer = Vec::new(); - let subcmd_name = subcmd.get_name().to_string(); - Man::new(subcmd).render(&mut buffer)?; - fs::write( - PathBuf::from(dir).join(format!("{}-{}.1", cmd_name, subcmd_name)), - buffer, - )?; - } - - Ok(()) -} diff --git a/src/man/mod.rs b/src/man/mod.rs index b0b957b..a2f4939 100644 --- a/src/man/mod.rs +++ b/src/man/mod.rs @@ -1,2 +1,2 @@ -pub mod args; -pub mod handlers; +pub mod command; +pub mod handler; diff --git a/src/output/args.rs b/src/output/args.rs index d5a98a1..812052b 100644 --- a/src/output/args.rs +++ b/src/output/args.rs @@ -8,27 +8,28 @@ pub(crate) const ARG_COLOR: &str = "color"; pub(crate) const ARG_OUTPUT: &str = "output"; /// Output arguments. -pub fn args() -> Vec { - vec![ +pub fn global_args() -> impl IntoIterator { + [ Arg::new(ARG_OUTPUT) - .help("Set the output format") + .help("Define the output format") .long("output") .short('o') .global(true) - .value_name("FMT") + .value_name("format") .value_parser(["plain", "json"]) .default_value("plain"), Arg::new(ARG_COLOR) - .help("Control when to use colors.") + .help("Control when to use colors") .long_help( - "This flag controls 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. + "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 for this flag are: @@ -42,6 +43,6 @@ ansi Like 'always', but emits ANSI escapes (even in a Windows console).", .global(true) .value_parser(["never", "auto", "always", "ansi"]) .default_value("auto") - .value_name("WHEN"), + .value_name("mode"), ] }