meap 0.8.1

Minimal Extensible Argument Parser
Documentation
use crate::parser::{Name, ParseError, SpecError};
use std::collections::HashMap;
use std::error;
use std::vec;

#[derive(Clone, Copy, PartialEq, Eq)]
pub enum HasParam {
    Yes,
    No,
}

#[derive(Clone, Copy)]
struct LowLevelArgRef {
    index: usize,
    has_param: HasParam,
}

pub struct LowLevelParser {
    program_name: String,
    instance_name_to_arg_ref: HashMap<Name, LowLevelArgRef>,
    flag_count: usize,
    opt_count: usize,
    has_positional_multi: bool,
    has_extra: bool,
}

pub struct LowLevelParserOutput {
    program_name: String,
    instance_name_to_arg_ref: HashMap<Name, LowLevelArgRef>,
    flags: Vec<usize>,
    opts: Vec<Vec<String>>,
    frees: vec::IntoIter<String>,
    extra: Vec<String>,
}

enum Token {
    Name(Name),
    Word(String),
    LongAssignment { long: String, value: String },
    ShortSequence { first: char, rest: String },
    Separator,
}

impl Token {
    fn parse(s: String) -> Self {
        if s == "--" {
            Token::Separator
        } else if let Some(long) = s.strip_prefix("--") {
            let assignment_split = long.splitn(2, "=").collect::<Vec<_>>();
            if assignment_split.len() == 1 {
                Token::Name(Name::Long(long.to_string()))
            } else {
                Token::LongAssignment {
                    long: assignment_split[0].to_string(),
                    value: assignment_split[1].to_string(),
                }
            }
        } else if let Some(shorts) = s.strip_prefix("-") {
            match shorts.len() {
                0 => Token::Word("-".to_string()),
                1 => Token::Name(Name::Short(shorts.chars().next().unwrap())),
                _ => {
                    let (first, rest) = shorts.split_at(1);
                    Token::ShortSequence {
                        first: first.chars().next().unwrap(),
                        rest: rest.to_string(),
                    }
                }
            }
        } else {
            Token::Word(s)
        }
    }
}

#[derive(Debug)]
pub enum Unique {
    PositionalMulti,
    Extra,
}

impl LowLevelParser {
    pub fn new(program_name: String) -> Self {
        Self {
            program_name,
            instance_name_to_arg_ref: HashMap::default(),
            flag_count: 0,
            opt_count: 0,
            has_positional_multi: false,
            has_extra: false,
        }
    }

    pub fn register(&mut self, names: &[Name], has_param: HasParam) -> Result<(), SpecError> {
        let index = match has_param {
            HasParam::No => &mut self.flag_count,
            HasParam::Yes => &mut self.opt_count,
        };
        let arg_ref = LowLevelArgRef {
            index: *index,
            has_param,
        };
        for name in names {
            if self.instance_name_to_arg_ref.contains_key(name) {
                return Err(SpecError::NameUsedMultipleTimes(name.clone()));
            }
            self.instance_name_to_arg_ref.insert(name.clone(), arg_ref);
        }
        *index += 1;
        Ok(())
    }

    pub fn register_anonymous_unique(&mut self, unique: Unique) -> Result<(), SpecError> {
        match unique {
            Unique::PositionalMulti => {
                if self.has_positional_multi {
                    return Err(SpecError::RepeatedUnique(Unique::PositionalMulti));
                }
                self.has_positional_multi = true;
            }
            Unique::Extra => {
                if self.has_extra {
                    return Err(SpecError::RepeatedUnique(Unique::Extra));
                }
                self.has_extra = true;
            }
        }
        Ok(())
    }

    pub fn parse<A: IntoIterator<Item = String>>(
        self,
        args: A,
    ) -> Result<LowLevelParserOutput, Box<dyn error::Error>> {
        let LowLevelParser {
            program_name,
            instance_name_to_arg_ref,
            flag_count,
            opt_count,
            has_extra,
            has_positional_multi: _,
        } = self;
        let mut flags = vec![0; flag_count];
        let mut opts = Vec::with_capacity(opt_count);
        opts.resize_with(opt_count, Vec::new);
        let mut frees = Vec::new();
        let mut args_iter = args.into_iter();
        let mut extra = None;
        while let Some(token) = args_iter.next().map(Token::parse) {
            match token {
                Token::Separator => {
                    let extra_args = args_iter.collect();
                    if has_extra {
                        extra = Some(extra_args);
                    } else {
                        return Err(Box::new(ParseError::UnexpectedSeparator {
                            extra: extra_args,
                        }));
                    }
                    break;
                }
                Token::Word(word) => frees.push(word),
                Token::ShortSequence { first, rest } => {
                    let LowLevelArgRef { index, has_param } = instance_name_to_arg_ref
                        .get(&Name::Short(first))
                        .ok_or_else(|| ParseError::UnknownName(Name::Short(first)))?;
                    match has_param {
                        HasParam::No => {
                            flags[*index] += 1;
                            for short in rest.chars() {
                                let LowLevelArgRef { index, has_param } = instance_name_to_arg_ref
                                    .get(&Name::Short(short))
                                    .ok_or_else(|| ParseError::UnknownName(Name::Short(short)))?;
                                match has_param {
                                    HasParam::No => flags[*index] += 1,
                                    HasParam::Yes => {
                                        return Err(ParseError::ArgumentLacksParameter(
                                            Name::Short(short),
                                        )
                                        .into())
                                    }
                                }
                            }
                        }
                        HasParam::Yes => {
                            opts[*index].push(rest);
                        }
                    }
                }
                Token::Name(name) => {
                    let LowLevelArgRef { index, has_param } =
                        instance_name_to_arg_ref
                            .get(&name)
                            .ok_or_else(|| ParseError::UnknownName(name.clone()))?;
                    match has_param {
                        HasParam::No => flags[*index] += 1,
                        HasParam::Yes => {
                            match Token::parse(
                                args_iter.next().ok_or_else(|| {
                                    ParseError::ArgumentLacksParameter(name.clone())
                                })?,
                            ) {
                                Token::Word(word) => opts[*index].push(word),
                                _ => return Err(ParseError::ArgumentLacksParameter(name).into()),
                            }
                        }
                    }
                }
                Token::LongAssignment { long, value } => {
                    let name = Name::Long(long);
                    let LowLevelArgRef { index, has_param } =
                        instance_name_to_arg_ref
                            .get(&name)
                            .ok_or_else(|| ParseError::UnknownName(name.clone()))?;
                    match has_param {
                        HasParam::No => {
                            return Err(ParseError::UnexpectedArgumentParam {
                                name: name.clone(),
                                value,
                            }
                            .into())
                        }
                        HasParam::Yes => opts[*index].push(value),
                    }
                }
            }
        }
        let extra = extra.unwrap_or_else(Vec::new);
        Ok(LowLevelParserOutput {
            program_name,
            instance_name_to_arg_ref,
            flags,
            opts,
            frees: frees.into_iter(),
            extra,
        })
    }
}

impl LowLevelParserOutput {
    pub fn program_name(&self) -> &str {
        self.program_name.as_str()
    }

    pub fn get_flag_count(&self, names: &[Name]) -> usize {
        let LowLevelArgRef { index, has_param } =
            self.instance_name_to_arg_ref.get(&names[0]).unwrap();
        assert!(*has_param == HasParam::No);
        self.flags[*index]
    }

    pub fn get_opt_values(&self, names: &[Name]) -> &[String] {
        let LowLevelArgRef { index, has_param } =
            self.instance_name_to_arg_ref.get(&names[0]).unwrap();
        assert!(*has_param == HasParam::Yes);
        &self.opts[*index]
    }

    pub fn free_iter(&mut self) -> &mut vec::IntoIter<String> {
        &mut self.frees
    }

    pub fn extra(&self) -> &[String] {
        &self.extra
    }
}