diff options
Diffstat (limited to 'vendor/clap_builder/src/parser/features')
-rw-r--r-- | vendor/clap_builder/src/parser/features/mod.rs | 1 | ||||
-rw-r--r-- | vendor/clap_builder/src/parser/features/suggestions.rs | 167 |
2 files changed, 168 insertions, 0 deletions
diff --git a/vendor/clap_builder/src/parser/features/mod.rs b/vendor/clap_builder/src/parser/features/mod.rs new file mode 100644 index 0000000..bdeb766 --- /dev/null +++ b/vendor/clap_builder/src/parser/features/mod.rs @@ -0,0 +1 @@ +pub(crate) mod suggestions; diff --git a/vendor/clap_builder/src/parser/features/suggestions.rs b/vendor/clap_builder/src/parser/features/suggestions.rs new file mode 100644 index 0000000..b8bb7ad --- /dev/null +++ b/vendor/clap_builder/src/parser/features/suggestions.rs @@ -0,0 +1,167 @@ +#[cfg(feature = "suggestions")] +use std::cmp::Ordering; + +// Internal +use crate::builder::Command; + +/// Find strings from an iterable of `possible_values` similar to a given value `v` +/// Returns a Vec of all possible values that exceed a similarity threshold +/// sorted by ascending similarity, most similar comes last +#[cfg(feature = "suggestions")] +pub(crate) fn did_you_mean<T, I>(v: &str, possible_values: I) -> Vec<String> +where + T: AsRef<str>, + I: IntoIterator<Item = T>, +{ + let mut candidates: Vec<(f64, String)> = possible_values + .into_iter() + // GH #4660: using `jaro` because `jaro_winkler` implementation in `strsim-rs` is wrong + // causing strings with common prefix >=10 to be considered perfectly similar + .map(|pv| (strsim::jaro(v, pv.as_ref()), pv.as_ref().to_owned())) + // Confidence of 0.7 so that bar -> baz is suggested + .filter(|(confidence, _)| *confidence > 0.7) + .collect(); + candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); + candidates.into_iter().map(|(_, pv)| pv).collect() +} + +#[cfg(not(feature = "suggestions"))] +pub(crate) fn did_you_mean<T, I>(_: &str, _: I) -> Vec<String> +where + T: AsRef<str>, + I: IntoIterator<Item = T>, +{ + Vec::new() +} + +/// Returns a suffix that can be empty, or is the standard 'did you mean' phrase +pub(crate) fn did_you_mean_flag<'a, 'help, I, T>( + arg: &str, + remaining_args: &[&std::ffi::OsStr], + longs: I, + subcommands: impl IntoIterator<Item = &'a mut Command>, +) -> Option<(String, Option<String>)> +where + 'help: 'a, + T: AsRef<str>, + I: IntoIterator<Item = T>, +{ + use crate::mkeymap::KeyType; + + match did_you_mean(arg, longs).pop() { + Some(candidate) => Some((candidate, None)), + None => subcommands + .into_iter() + .filter_map(|subcommand| { + subcommand._build_self(false); + + let longs = subcommand.get_keymap().keys().filter_map(|a| { + if let KeyType::Long(v) = a { + Some(v.to_string_lossy().into_owned()) + } else { + None + } + }); + + let subcommand_name = subcommand.get_name(); + + let candidate = some!(did_you_mean(arg, longs).pop()); + let score = some!(remaining_args.iter().position(|x| subcommand_name == *x)); + Some((score, (candidate, Some(subcommand_name.to_string())))) + }) + .min_by_key(|(x, _)| *x) + .map(|(_, suggestion)| suggestion), + } +} + +#[cfg(all(test, feature = "suggestions"))] +mod test { + use super::*; + + #[test] + fn missing_letter() { + let p_vals = ["test", "possible", "values"]; + assert_eq!(did_you_mean("tst", p_vals.iter()), vec!["test"]); + } + + #[test] + fn ambiguous() { + let p_vals = ["test", "temp", "possible", "values"]; + assert_eq!(did_you_mean("te", p_vals.iter()), vec!["test", "temp"]); + } + + #[test] + fn unrelated() { + let p_vals = ["test", "possible", "values"]; + assert_eq!( + did_you_mean("hahaahahah", p_vals.iter()), + Vec::<String>::new() + ); + } + + #[test] + fn best_fit() { + let p_vals = [ + "test", + "possible", + "values", + "alignmentStart", + "alignmentScore", + ]; + assert_eq!( + did_you_mean("alignmentScorr", p_vals.iter()), + vec!["alignmentStart", "alignmentScore"] + ); + } + + #[test] + fn best_fit_long_common_prefix_issue_4660() { + let p_vals = ["alignmentScore", "alignmentStart"]; + assert_eq!( + did_you_mean("alignmentScorr", p_vals.iter()), + vec!["alignmentStart", "alignmentScore"] + ); + } + + #[test] + fn flag_missing_letter() { + let p_vals = ["test", "possible", "values"]; + assert_eq!( + did_you_mean_flag("tst", &[], p_vals.iter(), []), + Some(("test".to_owned(), None)) + ); + } + + #[test] + fn flag_ambiguous() { + let p_vals = ["test", "temp", "possible", "values"]; + assert_eq!( + did_you_mean_flag("te", &[], p_vals.iter(), []), + Some(("temp".to_owned(), None)) + ); + } + + #[test] + fn flag_unrelated() { + let p_vals = ["test", "possible", "values"]; + assert_eq!( + did_you_mean_flag("hahaahahah", &[], p_vals.iter(), []), + None + ); + } + + #[test] + fn flag_best_fit() { + let p_vals = [ + "test", + "possible", + "values", + "alignmentStart", + "alignmentScore", + ]; + assert_eq!( + did_you_mean_flag("alignmentScorr", &[], p_vals.iter(), []), + Some(("alignmentScore".to_owned(), None)) + ); + } +} |