nu_protocol/
did_you_mean.rs

1pub fn did_you_mean<'a, 'b, I, S>(possibilities: I, input: &'b str) -> Option<String>
2where
3    I: IntoIterator<Item = &'a S>,
4    S: AsRef<str> + 'a + ?Sized,
5{
6    let possibilities: Vec<&str> = possibilities.into_iter().map(|s| s.as_ref()).collect();
7    let suggestion =
8        crate::lev_distance::find_best_match_for_name_with_substrings(&possibilities, input, None)
9            .map(|s| s.to_string());
10    if let Some(suggestion) = &suggestion {
11        if suggestion.len() == 1 && !suggestion.eq_ignore_ascii_case(input) {
12            return None;
13        }
14    }
15    suggestion
16}
17
18#[cfg(test)]
19mod tests {
20
21    use super::did_you_mean;
22
23    #[test]
24    fn did_you_mean_examples() {
25        let all_cases = [
26            (
27                vec!["a", "b"],
28                vec![
29                    ("a", Some("a"), ""),
30                    ("A", Some("a"), ""),
31                    (
32                        "c",
33                        None,
34                        "Not helpful to suggest an arbitrary choice when none are close",
35                    ),
36                    (
37                        "ccccccccccccccccccccccc",
38                        None,
39                        "Not helpful to suggest an arbitrary choice when none are close",
40                    ),
41                ],
42            ),
43            (
44                vec!["OS", "PWD", "PWDPWDPWDPWD"],
45                vec![
46                    (
47                        "pwd",
48                        Some("PWD"),
49                        "Exact case insensitive match yields a match",
50                    ),
51                    (
52                        "pwdpwdpwdpwd",
53                        Some("PWDPWDPWDPWD"),
54                        "Exact case insensitive match yields a match",
55                    ),
56                    ("PWF", Some("PWD"), "One-letter typo yields a match"),
57                    ("pwf", None, "Case difference plus typo yields no match"),
58                    (
59                        "Xwdpwdpwdpwd",
60                        None,
61                        "Case difference plus typo yields no match",
62                    ),
63                ],
64            ),
65            (
66                vec!["foo", "bar", "baz"],
67                vec![
68                    ("fox", Some("foo"), ""),
69                    ("FOO", Some("foo"), ""),
70                    ("FOX", None, ""),
71                    (
72                        "ccc",
73                        None,
74                        "Not helpful to suggest an arbitrary choice when none are close",
75                    ),
76                    (
77                        "zzz",
78                        None,
79                        "'baz' does share a character, but rustc rule is edit distance must be <= 1/3 of the length of the user input",
80                    ),
81                ],
82            ),
83            (
84                vec!["aaaaaa"],
85                vec![
86                    (
87                        "XXaaaa",
88                        Some("aaaaaa"),
89                        "Distance of 2 out of 6 chars: close enough to meet rustc's rule",
90                    ),
91                    (
92                        "XXXaaa",
93                        None,
94                        "Distance of 3 out of 6 chars: not close enough to meet rustc's rule",
95                    ),
96                    (
97                        "XaaaaX",
98                        Some("aaaaaa"),
99                        "Distance of 2 out of 6 chars: close enough to meet rustc's rule",
100                    ),
101                    (
102                        "XXaaaaXX",
103                        None,
104                        "Distance of 4 out of 6 chars: not close enough to meet rustc's rule",
105                    ),
106                ],
107            ),
108        ];
109        for (possibilities, cases) in all_cases {
110            for (input, expected_suggestion, discussion) in cases {
111                let suggestion = did_you_mean(&possibilities, input);
112                assert_eq!(
113                    suggestion.as_deref(),
114                    expected_suggestion,
115                    "Expected the following reasoning to hold but it did not: '{discussion}'"
116                );
117            }
118        }
119    }
120}