From b05506b814beac2872a0c3c0591c0e0e1024359c Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Sun, 12 Nov 2023 14:25:25 -0800 Subject: [PATCH] feat: suggest fuzzy matches in case of unrecognized arguments --- argh/Cargo.toml | 1 + argh/src/lib.rs | 30 +++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) 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 3cbfa5f..cb7ff66 100644 --- a/argh/src/lib.rs +++ b/argh/src/lib.rs @@ -331,6 +331,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. @@ -972,7 +974,13 @@ 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, + &vec!["--help".to_owned(), "help".to_owned()], + ) + })?; match self.slots[pos] { ParseStructOption::Flag(ref mut b) => b.set_flag(arg), @@ -992,8 +1000,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: &[String], +) -> String { + // get the list of available arguments + let available = arg_to_slot + .iter() + .map(|(name, _pos)| *name) + .chain(extra_suggestions.iter().map(|s| s.as_str())) + .collect::>(); + + if available.is_empty() { + return format!("Unrecognized argument: \"{}\"", given); + } + + let suggestions = fuzzy_search_best_n(given, &available, 1); + format!("Unrecognized argument: \"{}\". Did you mean \"{}\"?", given, suggestions[0].0) } // `--` or `-` options, including a mutable reference to their value.