mirror of
https://github.com/nushell/nushell
synced 2024-12-27 21:43:09 +00:00
0f600bc3f5
# Description Add an extension trait `IgnoreCaseExt` to nu_utils which adds some case insensitivity helpers, and use them throughout nu to improve the handling of case insensitivity. Proper case folding is done via unicase, which is already a dependency via mime_guess from nu-command. In actuality a lot of code still does `to_lowercase`, because unicase only provides immediate comparison and doesn't expose a `to_folded_case` yet. And since we do a lot of `contains`/`starts_with`/`ends_with`, it's not sufficient to just have `eq_ignore_case`. But if we get access in the future, this makes us ready to use it with a change in one place. Plus, it's clearer what the purpose is at the call site to call `to_folded_case` instead of `to_lowercase` if it's exclusively for the purpose of case insensitive comparison, even if it just does `to_lowercase` still. # User-Facing Changes - Some commands that were supposed to be case insensitive remained only insensitive to ASCII case (a-z), and now are case insensitive w.r.t. non-ASCII characters as well. # Tests + Formatting - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib` --------- Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
88 lines
3.4 KiB
Rust
88 lines
3.4 KiB
Rust
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 {
|
|
if 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}'"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|