mirror of
https://github.com/clap-rs/clap
synced 2024-09-20 14:31:58 +00:00
feat(did-you-mean): for subcommands
If an argument is not understood as subcommand, but has a high-confidence match in the list of all known subcommands, we will use this one to print a customized error message. Previously, it would say that a positional argument wasn't understood, now it will say that a subcommand was unknown, and if the user meant `high-confidence-candidate`. If the argument doesn't sufficiently match any subcommand, the default handling will take over and try to treat it as positional argument. * added dependency to `strsym` crate * new `did_you_mean` function uses `strsim::jaro_winkler(...)` to look for good candidates. Related to #103
This commit is contained in:
parent
d17dcb2920
commit
06e869b518
4 changed files with 44 additions and 0 deletions
|
@ -13,6 +13,9 @@ keywords = ["argument", "command", "arg", "parser", "parse"]
|
|||
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
strsim = "*"
|
||||
|
||||
[features]
|
||||
default=[]
|
||||
|
||||
|
|
|
@ -35,6 +35,11 @@ SUBCOMMANDS:
|
|||
help Prints this message
|
||||
subcmd tests subcommands'''
|
||||
|
||||
_sc_dym_usage = '''Subcommand "subcm" is unknown. Did you mean "subcmd" ?
|
||||
USAGE:
|
||||
\tclaptests [POSITIONAL] [FLAGS] [OPTIONS] [SUBCOMMANDS]
|
||||
For more information try --help'''
|
||||
|
||||
_excluded = '''The argument '--flag' cannot be used with '-F'
|
||||
USAGE:
|
||||
\tclaptests [positional2] -F --long-option-2 <option2>
|
||||
|
@ -220,6 +225,7 @@ cmds = {'help short: ': ['{} -h'.format(_bin), _help],
|
|||
'F(s),O(s),P: ': ['{} value -f -o some'.format(_bin), _fop],
|
||||
'F(l),O(l),P: ': ['{} value --flag --option some'.format(_bin), _fop],
|
||||
'F(l),O(l=),P: ': ['{} value --flag --option=some'.format(_bin), _fop],
|
||||
'sc dym: ': ['{} subcm'.format(_bin), _sc_dym_usage],
|
||||
'sc help short: ': ['{} subcmd -h'.format(_bin), _schelp],
|
||||
'sc help long: ': ['{} subcmd --help'.format(_bin), _schelp],
|
||||
'scF(l),O(l),P: ': ['{} subcmd value --flag --option some'.format(_bin), _scfop],
|
||||
|
|
34
src/app.rs
34
src/app.rs
|
@ -11,6 +11,30 @@ use args::{ ArgMatches, Arg, SubCommand, MatchedArg};
|
|||
use args::{ FlagBuilder, OptBuilder, PosBuilder};
|
||||
use args::ArgGroup;
|
||||
|
||||
use strsim;
|
||||
|
||||
/// Produces a string from a given list of possible values which is similar to
|
||||
/// the passed in value `v` with a certain confidence.
|
||||
/// Thus in a list of possible values like ["foo", "bar"], the value "fop" will yield
|
||||
/// `Some("foo")`, whereas "blark" would yield `None`.
|
||||
fn did_you_mean<'a, I, T>(v: &str, possible_values: I) -> Option<&'a str>
|
||||
where T: AsRef<str> + 'a,
|
||||
I: IntoIterator<Item=&'a T>{
|
||||
|
||||
let mut candidate: Option<(f64, &str)> = None;
|
||||
for pv in possible_values.into_iter() {
|
||||
let confidence = strsim::jaro_winkler(v, pv.as_ref());
|
||||
if confidence > 0.8 && (candidate.is_none() ||
|
||||
(candidate.as_ref().unwrap().0 < confidence)) {
|
||||
candidate = Some((confidence, pv.as_ref()));
|
||||
}
|
||||
}
|
||||
match candidate {
|
||||
None => None,
|
||||
Some((_, candidate)) => Some(candidate),
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to create a representation of a command line program and all possible command line
|
||||
/// arguments for parsing at runtime.
|
||||
///
|
||||
|
@ -1296,6 +1320,16 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
|
|||
break;
|
||||
}
|
||||
|
||||
if let Some(candidate_subcommand) = did_you_mean(&arg, self.subcommands.keys()) {
|
||||
self.report_error(
|
||||
format!("Subcommand \"{}\" is unknown. Did you mean \"{}\" ?",
|
||||
arg,
|
||||
candidate_subcommand),
|
||||
true,
|
||||
true,
|
||||
None);
|
||||
}
|
||||
|
||||
if self.positionals_idx.is_empty() {
|
||||
self.report_error(
|
||||
format!("Found argument \"{}\", but {} wasn't expecting any",
|
||||
|
|
|
@ -348,6 +348,7 @@
|
|||
//! - `Arg::new()` -> `Arg::with_name()`
|
||||
//! - `Arg::mutually_excludes()` -> `Arg::conflicts_with()`
|
||||
//! - `Arg::mutually_excludes_all()` -> `Arg::conflicts_with_all()`
|
||||
extern crate strsim;
|
||||
|
||||
pub use args::{Arg, SubCommand, ArgMatches, ArgGroup};
|
||||
pub use app::App;
|
||||
|
|
Loading…
Reference in a new issue