From b24deb101f7e12660b8b19d6b3979df87ffe065d Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 8 Jul 2024 17:15:54 -0500 Subject: [PATCH 1/2] test: Add coverage for help flag hints --- tests/builder/help.rs | 150 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) diff --git a/tests/builder/help.rs b/tests/builder/help.rs index a66f5f0d8d5..0f722bb19b4 100644 --- a/tests/builder/help.rs +++ b/tests/builder/help.rs @@ -81,7 +81,9 @@ fn help_multi_subcommand_error() { .try_get_matches_from(["ctest", "help", "subcmd", "multi", "foo"]) .unwrap_err(); - assert_data_eq!(err.to_string(), str![[r#" + assert_data_eq!( + err.to_string(), + str![[r#" error: unrecognized subcommand 'foo' Usage: ctest subcmd multi [OPTIONS] @@ -351,6 +353,152 @@ Options: utils::assert_output(cmd, "ctest --help", DEFAULT_HELP, false); } +#[test] +fn try_help_default() { + static DEFAULT_HELP: &str = "\ +error: unexpected argument 'bar' found + +Usage: ctest + +For more information, try '--help'. +"; + + let cmd = Command::new("ctest").version("1.0").term_width(0); + utils::assert_output(cmd, "ctest bar", DEFAULT_HELP, true); +} + +#[test] +fn try_help_custom_flag() { + static EXPECTED_HELP: &str = "\ +error: unexpected argument 'bar' found + +Usage: ctest +"; + + let cmd = Command::new("ctest") + .version("1.0") + .disable_help_flag(true) + .arg( + Arg::new("help") + .long("help") + .short('h') + .action(ArgAction::Help), + ) + .term_width(0); + utils::assert_output(cmd, "ctest bar", EXPECTED_HELP, true); +} + +#[test] +fn try_help_custom_flag_short() { + static EXPECTED_HELP: &str = "\ +error: unexpected argument 'bar' found + +Usage: ctest +"; + + let cmd = Command::new("ctest") + .version("1.0") + .disable_help_flag(true) + .arg(Arg::new("help").short('h').action(ArgAction::HelpShort)) + .term_width(0); + utils::assert_output(cmd, "ctest bar", EXPECTED_HELP, true); +} + +#[test] +fn try_help_custom_flag_long() { + static EXPECTED_HELP: &str = "\ +error: unexpected argument 'bar' found + +Usage: ctest +"; + + let cmd = Command::new("ctest") + .version("1.0") + .disable_help_flag(true) + .arg(Arg::new("help").long("help").action(ArgAction::HelpShort)) + .term_width(0); + utils::assert_output(cmd, "ctest bar", EXPECTED_HELP, true); +} + +#[test] +fn try_help_custom_flag_no_action() { + static EXPECTED_HELP: &str = "\ +error: unexpected argument 'bar' found + +Usage: ctest +"; + + let cmd = Command::new("ctest") + .version("1.0") + .disable_help_flag(true) + // Note `ArgAction::Help` is excluded + .arg(Arg::new("help").long("help").global(true)) + .term_width(0); + utils::assert_output(cmd, "ctest bar", EXPECTED_HELP, true); +} + +#[test] +fn try_help_subcommand_default() { + static DEFAULT_HELP: &str = "\ +error: unrecognized subcommand 'bar' + +Usage: ctest [COMMAND] + +For more information, try '--help'. +"; + + let cmd = Command::new("ctest") + .version("1.0") + .subcommand(Command::new("foo")) + .term_width(0); + utils::assert_output(cmd, "ctest bar", DEFAULT_HELP, true); +} + +#[test] +fn try_help_subcommand_custom_flag() { + static EXPECTED_HELP: &str = "\ +error: unrecognized subcommand 'bar' + +Usage: ctest [COMMAND] + +For more information, try 'help'. +"; + + let cmd = Command::new("ctest") + .version("1.0") + .disable_help_flag(true) + .arg( + Arg::new("help") + .long("help") + .short('h') + .action(ArgAction::Help) + .global(true), + ) + .subcommand(Command::new("foo")) + .term_width(0); + utils::assert_output(cmd, "ctest bar", EXPECTED_HELP, true); +} + +#[test] +fn try_help_subcommand_custom_flag_no_action() { + static EXPECTED_HELP: &str = "\ +error: unrecognized subcommand 'bar' + +Usage: ctest [COMMAND] + +For more information, try 'help'. +"; + + let cmd = Command::new("ctest") + .version("1.0") + .disable_help_flag(true) + // Note `ArgAction::Help` is excluded + .arg(Arg::new("help").long("help").global(true)) + .subcommand(Command::new("foo")) + .term_width(0); + utils::assert_output(cmd, "ctest bar", EXPECTED_HELP, true); +} + #[test] #[cfg(feature = "wrap_help")] fn wrapped_help() { From 2eb842cc3bc1fb5f04156efa816003ef803d5254 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 8 Jul 2024 17:29:42 -0500 Subject: [PATCH 2/2] feat: Show user defined help flags in hints This allows display of user-defined help flags during "try 'help'" style messages --- clap_builder/src/error/format.rs | 31 ++++++++++++++++++++++++++----- clap_builder/src/error/mod.rs | 4 ++-- tests/builder/help.rs | 8 +++++++- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/clap_builder/src/error/format.rs b/clap_builder/src/error/format.rs index 0f8644f6adc..8696c79be6d 100644 --- a/clap_builder/src/error/format.rs +++ b/clap_builder/src/error/format.rs @@ -3,6 +3,8 @@ #![cfg_attr(not(feature = "error-context"), allow(dead_code))] #![cfg_attr(not(feature = "error-context"), allow(unused_imports))] +use std::borrow::Cow; + use crate::builder::Command; use crate::builder::StyledStr; use crate::builder::Styles; @@ -12,6 +14,7 @@ use crate::error::ContextKind; use crate::error::ContextValue; use crate::error::ErrorKind; use crate::output::TAB; +use crate::ArgAction; /// Defines how to format an error for displaying to the user pub trait ErrorFormatter: Sized { @@ -120,7 +123,7 @@ impl ErrorFormatter for RichFormatter { put_usage(&mut styled, usage); } - try_help(&mut styled, styles, error.inner.help_flag); + try_help(&mut styled, styles, error.inner.help_flag.as_deref()); styled } @@ -461,7 +464,7 @@ pub(crate) fn format_error_message( put_usage(&mut styled, usage); } if let Some(cmd) = cmd { - try_help(&mut styled, styles, get_help_flag(cmd)); + try_help(&mut styled, styles, get_help_flag(cmd).as_deref()); } styled } @@ -480,16 +483,34 @@ fn put_usage(styled: &mut StyledStr, usage: &StyledStr) { styled.push_styled(usage); } -pub(crate) fn get_help_flag(cmd: &Command) -> Option<&'static str> { +pub(crate) fn get_help_flag(cmd: &Command) -> Option> { if !cmd.is_disable_help_flag_set() { - Some("--help") + Some(Cow::Borrowed("--help")) + } else if let Some(flag) = get_user_help_flag(cmd) { + Some(Cow::Owned(flag)) } else if cmd.has_subcommands() && !cmd.is_disable_help_subcommand_set() { - Some("help") + Some(Cow::Borrowed("help")) } else { None } } +fn get_user_help_flag(cmd: &Command) -> Option { + let arg = cmd.get_arguments().find(|arg| match arg.get_action() { + ArgAction::Help | ArgAction::HelpShort | ArgAction::HelpLong => true, + ArgAction::Append + | ArgAction::Count + | ArgAction::SetTrue + | ArgAction::SetFalse + | ArgAction::Set + | ArgAction::Version => false, + })?; + + arg.get_long() + .map(|long| format!("--{long}")) + .or_else(|| arg.get_short().map(|short| format!("-{short}"))) +} + fn try_help(styled: &mut StyledStr, styles: &Styles, help: Option<&str>) { if let Some(help) = help { use std::fmt::Write as _; diff --git a/clap_builder/src/error/mod.rs b/clap_builder/src/error/mod.rs index 1e8fcd57bab..cfba5a818b4 100644 --- a/clap_builder/src/error/mod.rs +++ b/clap_builder/src/error/mod.rs @@ -69,7 +69,7 @@ struct ErrorInner { context: FlatMap, message: Option, source: Option>, - help_flag: Option<&'static str>, + help_flag: Option>, styles: Styles, color_when: ColorChoice, color_help_when: ColorChoice, @@ -319,7 +319,7 @@ impl Error { self } - pub(crate) fn set_help_flag(mut self, help_flag: Option<&'static str>) -> Self { + pub(crate) fn set_help_flag(mut self, help_flag: Option>) -> Self { self.inner.help_flag = help_flag; self } diff --git a/tests/builder/help.rs b/tests/builder/help.rs index 0f722bb19b4..79afc396ef2 100644 --- a/tests/builder/help.rs +++ b/tests/builder/help.rs @@ -373,6 +373,8 @@ fn try_help_custom_flag() { error: unexpected argument 'bar' found Usage: ctest + +For more information, try '--help'. "; let cmd = Command::new("ctest") @@ -394,6 +396,8 @@ fn try_help_custom_flag_short() { error: unexpected argument 'bar' found Usage: ctest + +For more information, try '-h'. "; let cmd = Command::new("ctest") @@ -410,6 +414,8 @@ fn try_help_custom_flag_long() { error: unexpected argument 'bar' found Usage: ctest + +For more information, try '--help'. "; let cmd = Command::new("ctest") @@ -461,7 +467,7 @@ error: unrecognized subcommand 'bar' Usage: ctest [COMMAND] -For more information, try 'help'. +For more information, try '--help'. "; let cmd = Command::new("ctest")