diff --git a/argh/Cargo.toml b/argh/Cargo.toml index 557706c..354d97d 100644 --- a/argh/Cargo.toml +++ b/argh/Cargo.toml @@ -12,6 +12,7 @@ readme = "README.md" [dependencies] argh_shared = { version = "0.1.12", path = "../argh_shared" } argh_derive = { version = "0.1.12", path = "../argh_derive" } +rust-fuzzy-search = "0.1.1" [dev-dependencies] once_cell = "1.10.0" diff --git a/argh/src/lib.rs b/argh/src/lib.rs index 46b1fc0..d56d1f0 100644 --- a/argh/src/lib.rs +++ b/argh/src/lib.rs @@ -332,6 +332,8 @@ pub type SubCommandInfo = argh_shared::SubCommandInfo<'static>; pub use argh_shared::{ErrorCodeInfo, FlagInfo, FlagInfoKind, Optionality, PositionalInfo}; +use rust_fuzzy_search::fuzzy_search_best_n; + /// Structured information about the command line arguments. pub trait ArgsInfo { /// Returns the argument info. @@ -976,7 +978,7 @@ impl<'a> ParseStructOptions<'a> { .arg_to_slot .iter() .find_map(|&(name, pos)| if name == arg { Some(pos) } else { None }) - .ok_or_else(|| unrecognized_argument(arg))?; + .ok_or_else(|| unrecognized_argument(arg, self.arg_to_slot, self.help_triggers))?; match self.slots[pos] { ParseStructOption::Flag(ref mut b) => b.set_flag(arg), @@ -996,8 +998,24 @@ impl<'a> ParseStructOptions<'a> { } } -fn unrecognized_argument(x: &str) -> String { - ["Unrecognized argument: ", x, "\n"].concat() +fn unrecognized_argument( + given: &str, + arg_to_slot: &[(&str, usize)], + extra_suggestions: &[&str], +) -> String { + // get the list of available arguments + let available = arg_to_slot + .iter() + .map(|(name, _pos)| *name) + .chain(extra_suggestions.iter().copied()) + .collect::>(); + + if available.is_empty() { + return format!("Unrecognized argument: \"{}\"\n", given); + } + + let suggestions = fuzzy_search_best_n(given, &available, 1); + format!("Unrecognized argument: \"{}\". Did you mean \"{}\"?\n", given, suggestions[0].0) } // `--` or `-` options, including a mutable reference to their value. diff --git a/argh/tests/lib.rs b/argh/tests/lib.rs index d6a2fef..6fed023 100644 --- a/argh/tests/lib.rs +++ b/argh/tests/lib.rs @@ -911,7 +911,7 @@ mod fuchsia_commandline_tools_rubric { let e = OneOption::from_args(&["cmdname"], &["--foo=bar"]) .expect_err("Parsing option value using `=` should fail"); - assert_eq!(e.output, "Unrecognized argument: --foo=bar\n"); + assert_eq!(e.output, "Unrecognized argument: \"--foo=bar\". Did you mean \"--foo\"?\n"); assert!(e.status.is_err()); }