From 74e7270aa6b34cd17fc50ab30ef3562e54d8bc8f Mon Sep 17 00:00:00 2001 From: debris Date: Fri, 11 Aug 2017 22:29:23 +0200 Subject: [PATCH] pattern suggestion for commands --- src/dopt.rs | 7 ++-- src/parse.rs | 73 ++++++++++++++++++++++++++++++++++++----- src/test/suggestions.rs | 21 ++++++++++++ 3 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/dopt.rs b/src/dopt.rs index 58c3647..5f3f21f 100644 --- a/src/dopt.rs +++ b/src/dopt.rs @@ -9,7 +9,7 @@ use regex::{Captures, Regex}; use serde::de; use serde::de::IntoDeserializer; -use parse::Parser; +use parse::{Parser, Matches}; use synonym::SynonymMap; use self::Value::{Switch, Counted, Plain, List}; @@ -235,8 +235,9 @@ impl Docopt { .map_err(|s| self.err_with_usage(Argv(s))) .and_then(|argv| match self.p.matches(&argv) { - Some(m) => Ok(ArgvMap { map: m }), - None => Err(self.err_with_usage(NoMatch)), + Matches::Some(m) => Ok(ArgvMap { map: m }), + Matches::None => Err(self.err_with_usage(NoMatch)), + Matches::Suggestion(s) => Err(self.err_with_usage(Argv(s))), })?; if self.help && vals.get_bool("--help") { return Err(self.err_with_full_doc(Help)); diff --git a/src/parse.rs b/src/parse.rs index d3abcdf..527c4e0 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -69,6 +69,21 @@ pub struct Parser { last_atom_added: Option, // context for [default: ...] } +pub enum Matches { + Some(SynonymMap), + Suggestion(String), + None, +} + +impl From> for Matches { + fn from(o: Option) -> Self { + match o { + Some(s) => Matches::Suggestion(s), + None => Matches::None, + } + } +} + impl Parser { pub fn new(doc: &str) -> Result { let mut d = Parser { @@ -83,14 +98,16 @@ impl Parser { Ok(d) } - pub fn matches(&self, argv: &Argv) -> Option> { + pub fn matches(&self, argv: &Argv) -> Matches { + let mut suggestion = None; for usage in &self.usages { match Matcher::matches(argv, usage) { - None => continue, - Some(vals) => return Some(vals), + Matches::None => continue, + Matches::Some(vals) => return Matches::Some(vals), + Matches::Suggestion(s) => suggestion = Some(s), } } - None + suggestion.into() } pub fn parse_argv(&self, argv: Vec, options_first: bool) @@ -1176,8 +1193,7 @@ impl MState { } impl<'a, 'b> Matcher<'a, 'b> { - fn matches(argv: &'a Argv, pat: &Pattern) - -> Option> { + fn matches(argv: &'a Argv, pat: &Pattern) -> Matches { let m = Matcher { argv: argv }; let init = MState { argvi: 0, @@ -1185,7 +1201,7 @@ impl<'a, 'b> Matcher<'a, 'b> { max_counts: HashMap::new(), vals: HashMap::new(), }; - m.states(pat, &init) + let matches = m.states(pat, &init) .into_iter() .filter(|s| m.state_consumed_all_argv(s)) .filter(|s| m.state_has_valid_flags(s)) @@ -1209,7 +1225,12 @@ impl<'a, 'b> Matcher<'a, 'b> { } } synmap - }) + }); + + match matches { + Some(matches) => Matches::Some(matches), + None => m.pattern_suggestion(pat).into(), + } } fn token_from(&self, state: &MState) -> Option<&ArgvToken> { @@ -1268,6 +1289,42 @@ impl<'a, 'b> Matcher<'a, 'b> { } } + fn pattern_suggestion(&self, pattern: &Pattern) -> Option { + fn pattern_suggestion_for_arg(arg: &str, pattern: &Pattern) -> Option { + match *pattern { + Pattern::PatAtom(ref atom) => match *atom { + Atom::Short(short) if &short.to_string() == arg => Some(format!("-{}", short)), + Atom::Long(ref long) if long == arg => Some(format!("--{}", long)), + _ => None, + }, + Pattern::Optional(ref optional) => { + for pat in optional { + let suggestion = pattern_suggestion_for_arg(arg, pat); + if suggestion.is_some() { + return suggestion; + } + } + None + }, + _ => None, + } + } + + if let Pattern::Sequence(ref seq) = *pattern { + for (arg, pat) in self.argv.positional.iter().zip(seq.iter()) { + let arg_str = match arg.atom { + Atom::Positional(ref s) => s, + _ => unreachable!(), + }; + + if let Some(suggestion) = pattern_suggestion_for_arg(arg_str, pat) { + return Some(format!("Unknown command: '{}'. Did you mean '{}'?", arg_str, suggestion)); + } + } + } + None + } + fn state_consumed_all_argv(&self, state: &MState) -> bool { self.argv.positional.len() == state.argvi } diff --git a/src/test/suggestions.rs b/src/test/suggestions.rs index 77bf47f..166f3fb 100644 --- a/src/test/suggestions.rs +++ b/src/test/suggestions.rs @@ -70,3 +70,24 @@ test_suggest!(test_suggest_5, ", &["--import", "--complte"], "Unknown flag: '--complte'. Did you mean '--complete'?"); +test_suggest!(test_suggest_6, +"Usage: cargo owner [options] + + Options: + -h, --help Print this message + -a, --add LOGIN Name of a user or team to add as an owner + -r, --remove LOGIN Name of a user or team to remove as an owner + -l, --list List owners of a crate +", +&["owner", "add"], "Unknown command: 'add'. Did you mean '--add'?"); + +test_suggest!(test_suggest_7, +"Usage: cargo owner [options] + + Options: + -h, --help Print this message + -a, --add LOGIN Name of a user or team to add as an owner + -r, --remove LOGIN Name of a user or team to remove as an owner + -l, --list List owners of a crate +", +&["owner", "list"], "Unknown command: 'list'. Did you mean '--list'?");