miniclap 0.1.1

a no-dependency command line parser
Documentation
use crate::arg::{Arg, ArgKind};
use crate::app::App;
use crate::matches::Matches;

#[must_use]
pub fn parse_app(app: &App, argv: &[&str], parents: &[&'static str]) -> Matches {
    if argv.iter().any(|&a| a == "--help" || a == "-h") {
        app.print_help_with_parents(parents);
        std::process::exit(0);
    }

    let mut matches = Matches::default();

    for arg in app.args() {
        match arg.kind {
            ArgKind::Flag => {
                matches.flags.insert(arg.name, false);
            }
            ArgKind::Count => {
                matches.counts.insert(arg.name, 0);
            }
            _ => {}
        }
    }

    let mut i = 0;

    while i < argv.len() {
        let token = argv[i];

        if !token.starts_with('-') {
            if let Some(sub) = app.find_subcommand(token) {
                let mut sub_parents = parents.to_vec();
                sub_parents.push(app.name);

                let sub_matches = parse_app(sub, &argv[i + 1..], &sub_parents);
                matches.matched_sub = Some((sub.name, Box::new(sub_matches)));
                return matches;
            }

            matches.positionals.push(token.to_string());
            i += 1;
            continue;
        }

        if let Some(rest) = token.strip_prefix("--") {
            let (long_name, inline_val) = match rest.split_once('=') {
                Some((k, v)) => (k, Some(v)),
                None => (rest, None),
            };

            if let Some(arg) = find_by_long(app, long_name) {
                match arg.kind {
                    ArgKind::Flag => {
                        matches.flags.insert(arg.name, true);
                    }
                    ArgKind::Count => {
                        *matches.counts.entry(arg.name).or_insert(0) += 1;
                    }
                    ArgKind::Value => {
                        let val = if let Some(v) = inline_val {
                            v.to_string()
                        } else {
                            i += 1;
                            argv.get(i).unwrap_or(&"").to_string()
                        };
                        matches.values.insert(arg.name, val);
                    }
                    ArgKind::Positional => {}
                }
            }
            i += 1;
            continue;
        }

        if let Some(chars_str) = token.strip_prefix('-') {
            let chars: Vec<char> = chars_str.chars().collect();
            let mut ci = 0;

            while ci < chars.len() {
                let ch = chars[ci];

                if let Some(arg) = find_by_short(app, ch) {
                    match arg.kind {
                        ArgKind::Flag => {
                            matches.flags.insert(arg.name, true);
                        }
                        ArgKind::Count => {
                            *matches.counts.entry(arg.name).or_insert(0) += 1;
                        }
                        ArgKind::Value => {
                            let val = if ci + 1 < chars.len() {
                                let v: String = chars[ci + 1..].iter().collect();
                                ci = chars.len();
                                v
                            } else {
                                i += 1;
                                argv.get(i).unwrap_or(&"").to_string()
                            };
                            matches.values.insert(arg.name, val);
                        }
                        ArgKind::Positional => {}
                    }
                }
                ci += 1;
            }

            i += 1;
            continue;
        }

        i += 1;
    }

    matches
}

#[must_use]
pub fn find_by_long(app: &App, name: &str) -> Option<Arg> {
    app.args()
        .find(|a| a.name == name && a.kind != ArgKind::Positional)
        .cloned()
}

#[must_use]
pub fn find_by_short(app: &App, ch: char) -> Option<Arg> {
    app.args().find(|a| a.short == Some(ch)).cloned()
}