From 1b6a04ca5504955c571d1c97504fb45ea0befee4 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Mon, 8 Jan 2024 01:21:28 +0400 Subject: Initial vendor packages Signed-off-by: Valentin Popov --- .../src/parser/features/suggestions.rs | 167 +++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 vendor/clap_builder/src/parser/features/suggestions.rs (limited to 'vendor/clap_builder/src/parser/features/suggestions.rs') 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(v: &str, possible_values: I) -> Vec +where + T: AsRef, + I: IntoIterator, +{ + 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(_: &str, _: I) -> Vec +where + T: AsRef, + I: IntoIterator, +{ + 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, +) -> Option<(String, Option)> +where + 'help: 'a, + T: AsRef, + I: IntoIterator, +{ + 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::::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)) + ); + } +} -- cgit v1.2.3