knott 0.1.4

Fast Rust package manager helper for Arch Linux repos and the AUR
Documentation
use std::collections::BTreeSet;

pub fn parse_selection(input: &str, max: usize, names: &[String]) -> Vec<usize> {
    let mut include = BTreeSet::new();
    let mut exclude = BTreeSet::new();
    let mut saw_include = false;

    for token in input.split(|ch: char| ch.is_whitespace() || ch == ',') {
        let token = token.trim();
        if token.is_empty() {
            continue;
        }

        let (is_exclude, token) = token
            .strip_prefix('^')
            .map(|value| (true, value))
            .unwrap_or((false, token));

        let target = if is_exclude {
            &mut exclude
        } else {
            saw_include = true;
            &mut include
        };

        if let Some((start, end)) = token.split_once('-') {
            if let (Ok(start), Ok(end)) = (start.parse::<usize>(), end.parse::<usize>()) {
                for index in start.min(end)..=start.max(end) {
                    if index > 0 && index <= max {
                        target.insert(index - 1);
                    }
                }
            }
            continue;
        }

        if let Ok(index) = token.parse::<usize>() {
            if index > 0 && index <= max {
                target.insert(index - 1);
            }
            continue;
        }

        if let Some(index) = names.iter().position(|name| name == token) {
            target.insert(index);
        }
    }

    let mut selected = if saw_include {
        include
    } else if !exclude.is_empty() {
        (0..max).collect()
    } else {
        BTreeSet::new()
    };

    for index in exclude {
        selected.remove(&index);
    }

    selected.into_iter().collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_ranges_and_exclusions() {
        let names = vec!["a".into(), "b".into(), "c".into(), "d".into()];
        assert_eq!(parse_selection("1-3 ^2", 4, &names), vec![0, 2]);
        assert_eq!(parse_selection("^4", 4, &names), vec![0, 1, 2]);
        assert_eq!(parse_selection("b d", 4, &names), vec![1, 3]);
    }
}