From 49bad6c5cafc8be75d8fedb68f6b37c1f9215c0b Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 28 Aug 2023 18:42:58 +0200 Subject: [PATCH] Allow to mark arguments as to be ignored by suggestions. As pointed out in #4853, it might be useful to mark arguments to not be considered by the suggestions feature. In particular with the introduction of UnknownArgumentValueParser in #5075, one might want explicitly handle some common confusion (say handle `--silent` and print `tip: did you mean --quiet`), but not have those in the suggestion engine. I'm guessing one might want to have an `arg` be visible but not in suggestion for deprecated flags as well. I'm trying to do so by adding a `.didyoumena(false)`, that will mark the argument as to not be considered by suggestion. There is also some mention of completion of hidden command in #4853, that might be relevant as well. -- I'm not too familiar with clap internals, and fairly new to rust, but in particular : - I've tried to implement that only for args - I guess we will need to extend to subcommands if this goes further - I'm not happy with the setting negations nor the naming `didyoumean`/`ExcludeDidYouMean` - I'm not super happy about the iteration over MKeyMap keys and items. --- clap_builder/src/builder/arg.rs | 45 ++++++++++++++++++++++++ clap_builder/src/builder/arg_settings.rs | 1 + clap_builder/src/parser/parser.rs | 20 +++++++---- tests/builder/opts.rs | 16 +++++++++ 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/clap_builder/src/builder/arg.rs b/clap_builder/src/builder/arg.rs index d067dec1dcd2..cfe05c1f2003 100644 --- a/clap_builder/src/builder/arg.rs +++ b/clap_builder/src/builder/arg.rs @@ -2349,6 +2349,46 @@ impl Arg { } } + /// Do not include the argument in didyoumean suggestions. + /// + /// # Examples + /// + /// Setting `ExcludeDidYouMean` will not consider the argument + /// when making suggestions for mistyped arguments. + /// + /// ```rust + /// # use clap_builder as clap; + /// # use clap::{Command, Arg}; + /// let m = Command::new("prog") + /// .arg(Arg::new("cfg") + /// .long("config") + /// .didyoumean(false) + /// .get_matches_from(vec![ + /// "prog", "--conf" + /// ]); + /// # } + /// ``` + /// + /// The above example will not display the usual `a similar argument ...` + /// message + /// + /// ```text + /// error: unexpected argument '--conf' found + /// + /// Usage: prog [OPTIONS] + /// + /// For more information, try '--help'. + /// ``` + #[inline] + #[must_use] + pub fn didyoumean(self, yes: bool) -> Self { + if yes { + self.unset_setting(ArgSettings::ExcludeDidYouMean) + } else { + self.setting(ArgSettings::ExcludeDidYouMean) + } + } + /// Do not display the [possible values][crate::builder::ValueParser::possible_values] in the help message. /// /// This is useful for args with many values, or ones which are explained elsewhere in the @@ -4153,6 +4193,11 @@ impl Arg { self.is_set(ArgSettings::HideEnv) } + /// dont did you mean + pub fn is_didyoumean_set(&self) -> bool { + !self.is_set(ArgSettings::ExcludeDidYouMean) + } + /// Report whether [`Arg::hide_env_values`] is set #[cfg(feature = "env")] pub fn is_hide_env_values_set(&self) -> bool { diff --git a/clap_builder/src/builder/arg_settings.rs b/clap_builder/src/builder/arg_settings.rs index fd47504047fc..c42d8625e8a8 100644 --- a/clap_builder/src/builder/arg_settings.rs +++ b/clap_builder/src/builder/arg_settings.rs @@ -61,6 +61,7 @@ pub(crate) enum ArgSettings { HiddenShortHelp, HiddenLongHelp, Exclusive, + ExcludeDidYouMean, } impl ArgSettings { diff --git a/clap_builder/src/parser/parser.rs b/clap_builder/src/parser/parser.rs index 98b28eeb73db..90a55ddf7971 100644 --- a/clap_builder/src/parser/parser.rs +++ b/clap_builder/src/parser/parser.rs @@ -1503,13 +1503,21 @@ impl<'cmd> Parser<'cmd> { ) -> ClapError { debug!("Parser::did_you_mean_error: arg={arg}"); // Didn't match a flag or option - let longs = self - .cmd - .get_keymap() + let keymap = self.cmd.get_keymap(); + let longs = keymap .keys() - .filter_map(|x| match x { - KeyType::Long(l) => Some(l.to_string_lossy().into_owned()), - _ => None, + .filter_map(|key| { + let arg = keymap.get(key)?; + match (key, arg) { + (KeyType::Long(l), arg) => { + if arg.is_didyoumean_set() { + Some(l.to_string_lossy().into_owned()) + } else { + None + } + } + (_, _) => None, + } }) .collect::>(); debug!("Parser::did_you_mean_error: longs={longs:?}"); diff --git a/tests/builder/opts.rs b/tests/builder/opts.rs index 501bb469c3dd..a9346a0e2ed2 100644 --- a/tests/builder/opts.rs +++ b/tests/builder/opts.rs @@ -458,6 +458,22 @@ For more information, try '--help'. utils::assert_output(utils::complex_app(), "clap-test --optio=foo", DYM, true); } +#[test] +#[cfg(feature = "suggestions")] +#[cfg(feature = "error-context")] +fn did_you_mean_disabled() { + static DYM: &str = "\ +error: unexpected argument '--conf' found + +Usage: clap-test [OPTIONS] + +For more information, try '--help'. +"; + + let c = Command::new("clap-test").arg(Arg::new("cfg").long("config").didyoumean(false)); + utils::assert_output(c, "clap-test --conf", DYM, true); +} + #[test] fn issue_1047_min_zero_vals_default_val() { let m = Command::new("foo")