ice 0.4.0

A simple CTF tool store
Documentation
use crate::app::cli::build_cli;

pub fn suggest_subcommand(typed: &str) -> Option<String> {
    let typed = typed.to_lowercase();
    let mut scored: Vec<(usize, String)> = Vec::new();

    for sub in build_cli().get_subcommands() {
        let d = levenshtein(&typed, &sub.get_name().to_lowercase());
        if within_threshold(typed.len(), d) {
            scored.push((d, sub.get_name().to_string()));
        }
        for alias in sub.get_all_aliases() {
            let d = levenshtein(&typed, &alias.to_lowercase());
            if within_threshold(typed.len(), d) && !scored.iter().any(|(_, n)| n == alias) {
                scored.push((d, alias.to_string()));
            }
        }
    }

    scored.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
    scored.truncate(5);
    if scored.is_empty() {
        None
    } else {
        Some(
            scored
                .iter()
                .map(|(_, n)| n.as_str())
                .collect::<Vec<_>>()
                .join(", "),
        )
    }
}

fn within_threshold(typed_len: usize, dist: usize) -> bool {
    dist <= 1 || (dist <= 2 && typed_len >= 5)
}

pub fn levenshtein(a: &str, b: &str) -> usize {
    let a: Vec<char> = a.chars().collect();
    let b: Vec<char> = b.chars().collect();
    let mut prev: Vec<usize> = (0..=b.len()).collect();
    let mut cur = vec![0usize; b.len() + 1];
    for (i, ca) in a.iter().enumerate() {
        cur[0] = i + 1;
        for (j, cb) in b.iter().enumerate() {
            let cost = if ca == cb { 0 } else { 1 };
            cur[j + 1] = (prev[j + 1] + 1).min(cur[j] + 1).min(prev[j] + cost);
        }
        std::mem::swap(&mut prev, &mut cur);
    }
    prev[b.len()]
}