Skip to content

Commit

Permalink
feat(did-you-mean): mark args to be ignored
Browse files Browse the repository at this point in the history
As pointed out in clap-rs#4853, it might be useful to mark arguments to not be
considered by the suggestions feature.

In particular with the introduction of UnknownArgumentValueParser in clap-rs#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 clap-rs#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.
  • Loading branch information
Carreau committed Aug 28, 2023
1 parent 7126f78 commit 79864a8
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 6 deletions.
45 changes: 45 additions & 0 deletions clap_builder/src/builder/arg.rs
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions clap_builder/src/builder/arg_settings.rs
Expand Up @@ -61,6 +61,7 @@ pub(crate) enum ArgSettings {
HiddenShortHelp,
HiddenLongHelp,
Exclusive,
ExcludeDidYouMean,
}

impl ArgSettings {
Expand Down
20 changes: 14 additions & 6 deletions clap_builder/src/parser/parser.rs
Expand Up @@ -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::<Vec<_>>();
debug!("Parser::did_you_mean_error: longs={longs:?}");
Expand Down
16 changes: 16 additions & 0 deletions tests/builder/opts.rs
Expand Up @@ -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")
Expand Down

0 comments on commit 79864a8

Please sign in to comment.