Skip to content

Commit

Permalink
Merge pull request #5574 from zanieb/zb/try-help-custom
Browse files Browse the repository at this point in the history
feat: Show user defined help flags in hints
  • Loading branch information
epage authored Jul 9, 2024
2 parents 866d7d1 + 2eb842c commit da1093a
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 8 deletions.
31 changes: 26 additions & 5 deletions clap_builder/src/error/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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<Cow<'static, str>> {
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<String> {
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 _;
Expand Down
4 changes: 2 additions & 2 deletions clap_builder/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ struct ErrorInner {
context: FlatMap<ContextKind, ContextValue>,
message: Option<Message>,
source: Option<Box<dyn error::Error + Send + Sync>>,
help_flag: Option<&'static str>,
help_flag: Option<Cow<'static, str>>,
styles: Styles,
color_when: ColorChoice,
color_help_when: ColorChoice,
Expand Down Expand Up @@ -319,7 +319,7 @@ impl<F: ErrorFormatter> Error<F> {
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<Cow<'static, str>>) -> Self {
self.inner.help_flag = help_flag;
self
}
Expand Down
156 changes: 155 additions & 1 deletion tests/builder/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -351,6 +353,158 @@ 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
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),
)
.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
For more information, try '-h'.
";

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
For more information, try '--help'.
";

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() {
Expand Down

0 comments on commit da1093a

Please sign in to comment.