use crate::{
args::Arg,
item::{Item, ShortLong},
meta_help::{Long, Short},
Args, Error, Meta,
};
pub(crate) fn should_suggest(err: &Error) -> bool {
match err {
Error::Stdout(_) | Error::Stderr(_) => true,
Error::Missing(xs) => {
let mut hi = crate::meta_help::HelpItems::default();
for x in xs.iter() {
hi.classify_item(x);
}
hi.flgs.is_empty() && hi.psns.is_empty()
}
}
}
pub(crate) fn suggest(args: &Args, meta: &Meta) -> Result<(), Error> {
let arg = match args.peek() {
Some(arg) => arg,
None => return Ok(()),
};
if args.items.iter().filter(|&a| a == arg).count() > 1 {
return Ok(());
}
let mut variants = Vec::new();
inner(arg, meta, &mut variants);
variants.sort_by(|a, b| b.0.cmp(&a.0));
if let Some((l, best)) = variants.pop() {
if l > 0 {
return Err(Error::Stderr(best));
}
}
Ok(())
}
#[derive(Copy, Clone)]
enum I<'a> {
ShortFlag(char),
LongFlag(&'a str),
ShortCmd(char),
LongCmd(&'a str),
}
impl std::fmt::Debug for I<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ShortFlag(s) => write!(f, "flag: `{}`", w_err!(Short(*s))),
Self::LongFlag(s) => write!(f, "flag: `{}`", w_err!(Long(s))),
Self::ShortCmd(s) => write!(f, "command alias: `{}`", w_err!(s)),
Self::LongCmd(s) => write!(f, "command: `{}`", w_err!(s)),
}
}
}
impl std::fmt::Display for I<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::fmt::Write;
match self {
Self::ShortFlag(s) => write!(f, "-{}", s),
Self::LongFlag(s) => write!(f, "--{}", s),
Self::ShortCmd(s) => f.write_char(*s),
Self::LongCmd(s) => f.write_str(s),
}
}
}
fn ins(expected: I, actual: I, variants: &mut Vec<(usize, String)>) {
variants.push((
levenshtein(&expected.to_string(), &actual.to_string()),
format!(
"No such {:?}, did you mean `{}`?",
actual,
w_flag!(expected)
),
));
}
fn inner_item(arg: &Arg, item: &Item, variants: &mut Vec<(usize, String)>) {
let actual: I = match arg {
Arg::Short(s, _, _) => I::ShortFlag(*s),
Arg::Long(s, _, _) => I::LongFlag(s.as_str()),
Arg::Word(w) | Arg::PosWord(w) => match &w.to_str() {
Some(s) => I::LongCmd(s),
None => return,
},
Arg::Ambiguity(_, _) => return,
};
match item {
Item::Positional { .. } => {}
Item::Command { name, short, .. } => {
ins(I::LongCmd(name), actual, variants);
if let Some(s) = short {
ins(I::ShortCmd(*s), actual, variants);
}
}
Item::Flag { name, .. } | Item::Argument { name, .. } => match name {
ShortLong::Short(s) => ins(I::ShortFlag(*s), actual, variants),
ShortLong::Long(l) => ins(I::LongFlag(l), actual, variants),
ShortLong::ShortLong(s, l) => {
ins(I::ShortFlag(*s), actual, variants);
ins(I::LongFlag(l), actual, variants);
}
},
}
}
fn inner(arg: &Arg, meta: &Meta, variants: &mut Vec<(usize, String)>) {
match meta {
Meta::And(xs) | Meta::Or(xs) => {
for x in xs {
inner(arg, x, variants);
}
}
Meta::Item(item) => inner_item(arg, item, variants),
Meta::HideUsage(meta)
| Meta::Optional(meta)
| Meta::Many(meta)
| Meta::Decorated(meta, _) => {
inner(arg, meta, variants);
}
Meta::Skip => {}
}
}
fn levenshtein(a: &str, b: &str) -> usize {
let mut result = 0;
let mut cache = a.chars().enumerate().map(|i| i.0 + 1).collect::<Vec<_>>();
let mut distance_a;
let mut distance_b;
for (index_b, code_b) in b.chars().enumerate() {
result = index_b;
distance_a = index_b;
for (index_a, code_a) in a.chars().enumerate() {
distance_b = if code_a == code_b {
distance_a
} else {
distance_a + 1
};
distance_a = cache[index_a];
result = if distance_a > result {
if distance_b > result {
result + 1
} else {
distance_b
}
} else if distance_b > distance_a {
distance_a + 1
} else {
distance_b
};
cache[index_a] = result;
}
}
result
}