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