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