119625d8cSopenharmony_ci#[cfg(feature = "suggestions")] 219625d8cSopenharmony_ciuse std::cmp::Ordering; 319625d8cSopenharmony_ci 419625d8cSopenharmony_ci// Internal 519625d8cSopenharmony_ciuse crate::builder::Command; 619625d8cSopenharmony_ci 719625d8cSopenharmony_ci/// Find strings from an iterable of `possible_values` similar to a given value `v` 819625d8cSopenharmony_ci/// Returns a Vec of all possible values that exceed a similarity threshold 919625d8cSopenharmony_ci/// sorted by ascending similarity, most similar comes last 1019625d8cSopenharmony_ci#[cfg(feature = "suggestions")] 1119625d8cSopenharmony_cipub(crate) fn did_you_mean<T, I>(v: &str, possible_values: I) -> Vec<String> 1219625d8cSopenharmony_ciwhere 1319625d8cSopenharmony_ci T: AsRef<str>, 1419625d8cSopenharmony_ci I: IntoIterator<Item = T>, 1519625d8cSopenharmony_ci{ 1619625d8cSopenharmony_ci let mut candidates: Vec<(f64, String)> = possible_values 1719625d8cSopenharmony_ci .into_iter() 1819625d8cSopenharmony_ci // GH #4660: using `jaro` because `jaro_winkler` implementation in `strsim-rs` is wrong 1919625d8cSopenharmony_ci // causing strings with common prefix >=10 to be considered perfectly similar 2019625d8cSopenharmony_ci .map(|pv| (strsim::jaro(v, pv.as_ref()), pv.as_ref().to_owned())) 2119625d8cSopenharmony_ci // Confidence of 0.7 so that bar -> baz is suggested 2219625d8cSopenharmony_ci .filter(|(confidence, _)| *confidence > 0.7) 2319625d8cSopenharmony_ci .collect(); 2419625d8cSopenharmony_ci candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); 2519625d8cSopenharmony_ci candidates.into_iter().map(|(_, pv)| pv).collect() 2619625d8cSopenharmony_ci} 2719625d8cSopenharmony_ci 2819625d8cSopenharmony_ci#[cfg(not(feature = "suggestions"))] 2919625d8cSopenharmony_cipub(crate) fn did_you_mean<T, I>(_: &str, _: I) -> Vec<String> 3019625d8cSopenharmony_ciwhere 3119625d8cSopenharmony_ci T: AsRef<str>, 3219625d8cSopenharmony_ci I: IntoIterator<Item = T>, 3319625d8cSopenharmony_ci{ 3419625d8cSopenharmony_ci Vec::new() 3519625d8cSopenharmony_ci} 3619625d8cSopenharmony_ci 3719625d8cSopenharmony_ci/// Returns a suffix that can be empty, or is the standard 'did you mean' phrase 3819625d8cSopenharmony_cipub(crate) fn did_you_mean_flag<'a, 'help, I, T>( 3919625d8cSopenharmony_ci arg: &str, 4019625d8cSopenharmony_ci remaining_args: &[&std::ffi::OsStr], 4119625d8cSopenharmony_ci longs: I, 4219625d8cSopenharmony_ci subcommands: impl IntoIterator<Item = &'a mut Command>, 4319625d8cSopenharmony_ci) -> Option<(String, Option<String>)> 4419625d8cSopenharmony_ciwhere 4519625d8cSopenharmony_ci 'help: 'a, 4619625d8cSopenharmony_ci T: AsRef<str>, 4719625d8cSopenharmony_ci I: IntoIterator<Item = T>, 4819625d8cSopenharmony_ci{ 4919625d8cSopenharmony_ci use crate::mkeymap::KeyType; 5019625d8cSopenharmony_ci 5119625d8cSopenharmony_ci match did_you_mean(arg, longs).pop() { 5219625d8cSopenharmony_ci Some(candidate) => Some((candidate, None)), 5319625d8cSopenharmony_ci None => subcommands 5419625d8cSopenharmony_ci .into_iter() 5519625d8cSopenharmony_ci .filter_map(|subcommand| { 5619625d8cSopenharmony_ci subcommand._build_self(false); 5719625d8cSopenharmony_ci 5819625d8cSopenharmony_ci let longs = subcommand.get_keymap().keys().filter_map(|a| { 5919625d8cSopenharmony_ci if let KeyType::Long(v) = a { 6019625d8cSopenharmony_ci Some(v.to_string_lossy().into_owned()) 6119625d8cSopenharmony_ci } else { 6219625d8cSopenharmony_ci None 6319625d8cSopenharmony_ci } 6419625d8cSopenharmony_ci }); 6519625d8cSopenharmony_ci 6619625d8cSopenharmony_ci let subcommand_name = subcommand.get_name(); 6719625d8cSopenharmony_ci 6819625d8cSopenharmony_ci let candidate = some!(did_you_mean(arg, longs).pop()); 6919625d8cSopenharmony_ci let score = some!(remaining_args.iter().position(|x| subcommand_name == *x)); 7019625d8cSopenharmony_ci Some((score, (candidate, Some(subcommand_name.to_string())))) 7119625d8cSopenharmony_ci }) 7219625d8cSopenharmony_ci .min_by_key(|(x, _)| *x) 7319625d8cSopenharmony_ci .map(|(_, suggestion)| suggestion), 7419625d8cSopenharmony_ci } 7519625d8cSopenharmony_ci} 7619625d8cSopenharmony_ci 7719625d8cSopenharmony_ci#[cfg(all(test, feature = "suggestions"))] 7819625d8cSopenharmony_cimod test { 7919625d8cSopenharmony_ci use super::*; 8019625d8cSopenharmony_ci 8119625d8cSopenharmony_ci #[test] 8219625d8cSopenharmony_ci fn missing_letter() { 8319625d8cSopenharmony_ci let p_vals = ["test", "possible", "values"]; 8419625d8cSopenharmony_ci assert_eq!(did_you_mean("tst", p_vals.iter()), vec!["test"]); 8519625d8cSopenharmony_ci } 8619625d8cSopenharmony_ci 8719625d8cSopenharmony_ci #[test] 8819625d8cSopenharmony_ci fn ambiguous() { 8919625d8cSopenharmony_ci let p_vals = ["test", "temp", "possible", "values"]; 9019625d8cSopenharmony_ci assert_eq!(did_you_mean("te", p_vals.iter()), vec!["test", "temp"]); 9119625d8cSopenharmony_ci } 9219625d8cSopenharmony_ci 9319625d8cSopenharmony_ci #[test] 9419625d8cSopenharmony_ci fn unrelated() { 9519625d8cSopenharmony_ci let p_vals = ["test", "possible", "values"]; 9619625d8cSopenharmony_ci assert_eq!( 9719625d8cSopenharmony_ci did_you_mean("hahaahahah", p_vals.iter()), 9819625d8cSopenharmony_ci Vec::<String>::new() 9919625d8cSopenharmony_ci ); 10019625d8cSopenharmony_ci } 10119625d8cSopenharmony_ci 10219625d8cSopenharmony_ci #[test] 10319625d8cSopenharmony_ci fn best_fit() { 10419625d8cSopenharmony_ci let p_vals = [ 10519625d8cSopenharmony_ci "test", 10619625d8cSopenharmony_ci "possible", 10719625d8cSopenharmony_ci "values", 10819625d8cSopenharmony_ci "alignmentStart", 10919625d8cSopenharmony_ci "alignmentScore", 11019625d8cSopenharmony_ci ]; 11119625d8cSopenharmony_ci assert_eq!( 11219625d8cSopenharmony_ci did_you_mean("alignmentScorr", p_vals.iter()), 11319625d8cSopenharmony_ci vec!["alignmentStart", "alignmentScore"] 11419625d8cSopenharmony_ci ); 11519625d8cSopenharmony_ci } 11619625d8cSopenharmony_ci 11719625d8cSopenharmony_ci #[test] 11819625d8cSopenharmony_ci fn best_fit_long_common_prefix_issue_4660() { 11919625d8cSopenharmony_ci let p_vals = ["alignmentScore", "alignmentStart"]; 12019625d8cSopenharmony_ci assert_eq!( 12119625d8cSopenharmony_ci did_you_mean("alignmentScorr", p_vals.iter()), 12219625d8cSopenharmony_ci vec!["alignmentStart", "alignmentScore"] 12319625d8cSopenharmony_ci ); 12419625d8cSopenharmony_ci } 12519625d8cSopenharmony_ci 12619625d8cSopenharmony_ci #[test] 12719625d8cSopenharmony_ci fn flag_missing_letter() { 12819625d8cSopenharmony_ci let p_vals = ["test", "possible", "values"]; 12919625d8cSopenharmony_ci assert_eq!( 13019625d8cSopenharmony_ci did_you_mean_flag("tst", &[], p_vals.iter(), []), 13119625d8cSopenharmony_ci Some(("test".to_owned(), None)) 13219625d8cSopenharmony_ci ); 13319625d8cSopenharmony_ci } 13419625d8cSopenharmony_ci 13519625d8cSopenharmony_ci #[test] 13619625d8cSopenharmony_ci fn flag_ambiguous() { 13719625d8cSopenharmony_ci let p_vals = ["test", "temp", "possible", "values"]; 13819625d8cSopenharmony_ci assert_eq!( 13919625d8cSopenharmony_ci did_you_mean_flag("te", &[], p_vals.iter(), []), 14019625d8cSopenharmony_ci Some(("temp".to_owned(), None)) 14119625d8cSopenharmony_ci ); 14219625d8cSopenharmony_ci } 14319625d8cSopenharmony_ci 14419625d8cSopenharmony_ci #[test] 14519625d8cSopenharmony_ci fn flag_unrelated() { 14619625d8cSopenharmony_ci let p_vals = ["test", "possible", "values"]; 14719625d8cSopenharmony_ci assert_eq!( 14819625d8cSopenharmony_ci did_you_mean_flag("hahaahahah", &[], p_vals.iter(), []), 14919625d8cSopenharmony_ci None 15019625d8cSopenharmony_ci ); 15119625d8cSopenharmony_ci } 15219625d8cSopenharmony_ci 15319625d8cSopenharmony_ci #[test] 15419625d8cSopenharmony_ci fn flag_best_fit() { 15519625d8cSopenharmony_ci let p_vals = [ 15619625d8cSopenharmony_ci "test", 15719625d8cSopenharmony_ci "possible", 15819625d8cSopenharmony_ci "values", 15919625d8cSopenharmony_ci "alignmentStart", 16019625d8cSopenharmony_ci "alignmentScore", 16119625d8cSopenharmony_ci ]; 16219625d8cSopenharmony_ci assert_eq!( 16319625d8cSopenharmony_ci did_you_mean_flag("alignmentScorr", &[], p_vals.iter(), []), 16419625d8cSopenharmony_ci Some(("alignmentScore".to_owned(), None)) 16519625d8cSopenharmony_ci ); 16619625d8cSopenharmony_ci } 16719625d8cSopenharmony_ci} 168