1 use app::App; 2 // Third Party 3 #[cfg(feature = "suggestions")] 4 use strsim; 5 6 // Internal 7 use fmt::Format; 8 9 /// Produces a string from a given list of possible values which is similar to 10 /// the passed in value `v` with a certain confidence. 11 /// Thus in a list of possible values like ["foo", "bar"], the value "fop" will yield 12 /// `Some("foo")`, whereas "blark" would yield `None`. 13 #[cfg(feature = "suggestions")] 14 #[cfg_attr(feature = "lints", allow(needless_lifetimes))] 15 pub fn did_you_mean<'a, T: ?Sized, I>(v: &str, possible_values: I) -> Option<&'a str> 16 where 17 T: AsRef<str> + 'a, 18 I: IntoIterator<Item = &'a T>, 19 { 20 let mut candidate: Option<(f64, &str)> = None; 21 for pv in possible_values { 22 let confidence = strsim::jaro_winkler(v, pv.as_ref()); 23 if confidence > 0.8 && (candidate.is_none() || (candidate.as_ref().unwrap().0 < confidence)) 24 { 25 candidate = Some((confidence, pv.as_ref())); 26 } 27 } 28 match candidate { 29 None => None, 30 Some((_, candidate)) => Some(candidate), 31 } 32 } 33 34 #[cfg(not(feature = "suggestions"))] 35 pub fn did_you_mean<'a, T: ?Sized, I>(_: &str, _: I) -> Option<&'a str> 36 where 37 T: AsRef<str> + 'a, 38 I: IntoIterator<Item = &'a T>, 39 { 40 None 41 } 42 43 /// Returns a suffix that can be empty, or is the standard 'did you mean' phrase 44 #[cfg_attr(feature = "lints", allow(needless_lifetimes))] 45 pub fn did_you_mean_flag_suffix<'z, T, I>( 46 arg: &str, 47 args_rest: &'z [&str], 48 longs: I, 49 subcommands: &'z [App], 50 ) -> (String, Option<&'z str>) 51 where 52 T: AsRef<str> + 'z, 53 I: IntoIterator<Item = &'z T>, 54 { 55 if let Some(candidate) = did_you_mean(arg, longs) { 56 let suffix = format!( 57 "\n\tDid you mean {}{}?", 58 Format::Good("--"), 59 Format::Good(candidate) 60 ); 61 return (suffix, Some(candidate)); 62 } 63 64 subcommands 65 .into_iter() 66 .filter_map(|subcommand| { 67 let opts = subcommand 68 .p 69 .flags 70 .iter() 71 .filter_map(|f| f.s.long) 72 .chain(subcommand.p.opts.iter().filter_map(|o| o.s.long)); 73 74 let candidate = match did_you_mean(arg, opts) { 75 Some(candidate) => candidate, 76 None => return None, 77 }; 78 let score = match args_rest.iter().position(|x| *x == subcommand.get_name()) { 79 Some(score) => score, 80 None => return None, 81 }; 82 83 let suffix = format!( 84 "\n\tDid you mean to put '{}{}' after the subcommand '{}'?", 85 Format::Good("--"), 86 Format::Good(candidate), 87 Format::Good(subcommand.get_name()) 88 ); 89 90 Some((score, (suffix, Some(candidate)))) 91 }) 92 .min_by_key(|&(score, _)| score) 93 .map(|(_, suggestion)| suggestion) 94 .unwrap_or_else(|| (String::new(), None)) 95 } 96 97 /// Returns a suffix that can be empty, or is the standard 'did you mean' phrase 98 pub fn did_you_mean_value_suffix<'z, T, I>(arg: &str, values: I) -> (String, Option<&'z str>) 99 where 100 T: AsRef<str> + 'z, 101 I: IntoIterator<Item = &'z T>, 102 { 103 match did_you_mean(arg, values) { 104 Some(candidate) => { 105 let suffix = format!("\n\tDid you mean '{}'?", Format::Good(candidate)); 106 (suffix, Some(candidate)) 107 } 108 None => (String::new(), None), 109 } 110 } 111 112 #[cfg(all(test, features = "suggestions"))] 113 mod test { 114 use super::*; 115 116 #[test] 117 fn possible_values_match() { 118 let p_vals = ["test", "possible", "values"]; 119 assert_eq!(did_you_mean("tst", p_vals.iter()), Some("test")); 120 } 121 122 #[test] 123 fn possible_values_nomatch() { 124 let p_vals = ["test", "possible", "values"]; 125 assert!(did_you_mean("hahaahahah", p_vals.iter()).is_none()); 126 } 127 128 #[test] 129 fn suffix_long() { 130 let p_vals = ["test", "possible", "values"]; 131 let suffix = "\n\tDid you mean \'--test\'?"; 132 assert_eq!( 133 did_you_mean_flag_suffix("tst", p_vals.iter(), []), 134 (suffix, Some("test")) 135 ); 136 } 137 138 #[test] 139 fn suffix_enum() { 140 let p_vals = ["test", "possible", "values"]; 141 let suffix = "\n\tDid you mean \'test\'?"; 142 assert_eq!( 143 did_you_mean_value_suffix("tst", p_vals.iter()), 144 (suffix, Some("test")) 145 ); 146 } 147 } 148