• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #[cfg(feature = "suggestions")]
2 use std::cmp::Ordering;
3 
4 // Internal
5 use crate::builder::Command;
6 
7 /// Find strings from an iterable of `possible_values` similar to a given value `v`
8 /// Returns a Vec of all possible values that exceed a similarity threshold
9 /// sorted by ascending similarity, most similar comes last
10 #[cfg(feature = "suggestions")]
did_you_mean<T, I>(v: &str, possible_values: I) -> Vec<String> where T: AsRef<str>, I: IntoIterator<Item = T>,11 pub(crate) fn did_you_mean<T, I>(v: &str, possible_values: I) -> Vec<String>
12 where
13     T: AsRef<str>,
14     I: IntoIterator<Item = T>,
15 {
16     let mut candidates: Vec<(f64, String)> = possible_values
17         .into_iter()
18         // GH #4660: using `jaro` because `jaro_winkler` implementation in `strsim-rs` is wrong
19         // causing strings with common prefix >=10 to be considered perfectly similar
20         .map(|pv| (strsim::jaro(v, pv.as_ref()), pv.as_ref().to_owned()))
21         // Confidence of 0.7 so that bar -> baz is suggested
22         .filter(|(confidence, _)| *confidence > 0.7)
23         .collect();
24     candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
25     candidates.into_iter().map(|(_, pv)| pv).collect()
26 }
27 
28 #[cfg(not(feature = "suggestions"))]
did_you_mean<T, I>(_: &str, _: I) -> Vec<String> where T: AsRef<str>, I: IntoIterator<Item = T>,29 pub(crate) fn did_you_mean<T, I>(_: &str, _: I) -> Vec<String>
30 where
31     T: AsRef<str>,
32     I: IntoIterator<Item = T>,
33 {
34     Vec::new()
35 }
36 
37 /// Returns a suffix that can be empty, or is the standard 'did you mean' phrase
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>,38 pub(crate) fn did_you_mean_flag<'a, 'help, I, T>(
39     arg: &str,
40     remaining_args: &[&std::ffi::OsStr],
41     longs: I,
42     subcommands: impl IntoIterator<Item = &'a mut Command>,
43 ) -> Option<(String, Option<String>)>
44 where
45     'help: 'a,
46     T: AsRef<str>,
47     I: IntoIterator<Item = T>,
48 {
49     use crate::mkeymap::KeyType;
50 
51     match did_you_mean(arg, longs).pop() {
52         Some(candidate) => Some((candidate, None)),
53         None => subcommands
54             .into_iter()
55             .filter_map(|subcommand| {
56                 subcommand._build_self(false);
57 
58                 let longs = subcommand.get_keymap().keys().filter_map(|a| {
59                     if let KeyType::Long(v) = a {
60                         Some(v.to_string_lossy().into_owned())
61                     } else {
62                         None
63                     }
64                 });
65 
66                 let subcommand_name = subcommand.get_name();
67 
68                 let candidate = some!(did_you_mean(arg, longs).pop());
69                 let score = some!(remaining_args.iter().position(|x| subcommand_name == *x));
70                 Some((score, (candidate, Some(subcommand_name.to_string()))))
71             })
72             .min_by_key(|(x, _)| *x)
73             .map(|(_, suggestion)| suggestion),
74     }
75 }
76 
77 #[cfg(all(test, feature = "suggestions"))]
78 mod test {
79     use super::*;
80 
81     #[test]
missing_letter()82     fn missing_letter() {
83         let p_vals = ["test", "possible", "values"];
84         assert_eq!(did_you_mean("tst", p_vals.iter()), vec!["test"]);
85     }
86 
87     #[test]
ambiguous()88     fn ambiguous() {
89         let p_vals = ["test", "temp", "possible", "values"];
90         assert_eq!(did_you_mean("te", p_vals.iter()), vec!["test", "temp"]);
91     }
92 
93     #[test]
unrelated()94     fn unrelated() {
95         let p_vals = ["test", "possible", "values"];
96         assert_eq!(
97             did_you_mean("hahaahahah", p_vals.iter()),
98             Vec::<String>::new()
99         );
100     }
101 
102     #[test]
best_fit()103     fn best_fit() {
104         let p_vals = [
105             "test",
106             "possible",
107             "values",
108             "alignmentStart",
109             "alignmentScore",
110         ];
111         assert_eq!(
112             did_you_mean("alignmentScorr", p_vals.iter()),
113             vec!["alignmentStart", "alignmentScore"]
114         );
115     }
116 
117     #[test]
best_fit_long_common_prefix_issue_4660()118     fn best_fit_long_common_prefix_issue_4660() {
119         let p_vals = ["alignmentScore", "alignmentStart"];
120         assert_eq!(
121             did_you_mean("alignmentScorr", p_vals.iter()),
122             vec!["alignmentStart", "alignmentScore"]
123         );
124     }
125 
126     #[test]
flag_missing_letter()127     fn flag_missing_letter() {
128         let p_vals = ["test", "possible", "values"];
129         assert_eq!(
130             did_you_mean_flag("tst", &[], p_vals.iter(), []),
131             Some(("test".to_owned(), None))
132         );
133     }
134 
135     #[test]
flag_ambiguous()136     fn flag_ambiguous() {
137         let p_vals = ["test", "temp", "possible", "values"];
138         assert_eq!(
139             did_you_mean_flag("te", &[], p_vals.iter(), []),
140             Some(("temp".to_owned(), None))
141         );
142     }
143 
144     #[test]
flag_unrelated()145     fn flag_unrelated() {
146         let p_vals = ["test", "possible", "values"];
147         assert_eq!(
148             did_you_mean_flag("hahaahahah", &[], p_vals.iter(), []),
149             None
150         );
151     }
152 
153     #[test]
flag_best_fit()154     fn flag_best_fit() {
155         let p_vals = [
156             "test",
157             "possible",
158             "values",
159             "alignmentStart",
160             "alignmentScore",
161         ];
162         assert_eq!(
163             did_you_mean_flag("alignmentScorr", &[], p_vals.iter(), []),
164             Some(("alignmentScore".to_owned(), None))
165         );
166     }
167 }
168