rust-arguments 0.1.0

A Flexible Command-line argument parser
Documentation
use std::collections::HashMap;                             use std::sync::Arc;
use std::fmt;

#[derive(Clone)]
pub struct Arg {
    pub name: String,
    pub short: Option<char>,
    pub long: Option<String>,
    pub takes_value: bool,
    pub required: bool,
    pub default: Option<String>,
    pub validator: Option<Arc<dyn Fn(&str) -> bool + Send + Sync>>,
}

impl fmt::Debug for Arg {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Arg")
            .field("name", &self.name)
            .field("short", &self.short)
            .field("long", &self.long)
            .field("takes_value", &self.takes_value)
            .field("required", &self.required)
            .field("default", &self.default)
            .finish()
    }
}

#[derive(Debug)]
pub struct ArgMatches {
    pub values: HashMap<String, String>,
    pub flags: HashMap<String, bool>,
    pub positionals: Vec<String>,
}

pub struct ArgParser {
    args: Vec<Arg>,
    subcommands: HashMap<String, ArgParser>,
}

impl ArgParser {
    pub fn new() -> Self {
        Self {
            args: Vec::new(),
            subcommands: HashMap::new(),
        }
    }

    pub fn arg(mut self, name: &str) -> Self {
        self.args.push(Arg {
            name: name.to_string(),
            short: None,
            long: None,
            takes_value: false,
            required: false,
            default: None,
            validator: None,
        });
        self
    }

    pub fn short(mut self, name: &str, short: char) -> Self {
        if let Some(arg) = self.args.iter_mut().find(|a| a.name == name) {
            arg.short = Some(short);
        }
        self
    }

    pub fn long(mut self, name: &str, long: &str) -> Self {
        if let Some(arg) = self.args.iter_mut().find(|a| a.name == name) {
            arg.long = Some(long.to_string());
        }
        self
    }

    pub fn takes_value(mut self, name: &str) -> Self {
        if let Some(arg) = self.args.iter_mut().find(|a| a.name == name) {
            arg.takes_value = true;
        }
        self
    }

    pub fn required(mut self, name: &str) -> Self {
        if let Some(arg) = self.args.iter_mut().find(|a| a.name == name) {
            arg.required = true;
        }
        self
    }

    pub fn default(mut self, name: &str, default: &str) -> Self {
        if let Some(arg) = self.args.iter_mut().find(|a| a.name == name) {
            arg.default = Some(default.to_string());
        }
        self
    }

    pub fn validator<F>(mut self, name: &str, validator: F) -> Self
    where
        F: 'static + Fn(&str) -> bool + Send + Sync,
    {
        if let Some(arg) = self.args.iter_mut().find(|a| a.name == name) {
            arg.validator = Some(Arc::new(validator));
        }
        self
    }

    pub fn subcommand(mut self, name: &str, parser: ArgParser) -> Self {
        self.subcommands.insert(name.to_string(), parser);
        self
    }

    pub fn parse(mut self, args: &[String]) -> ArgMatches {
        let mut values = HashMap::new();
        let mut flags = HashMap::new();
        let mut positionals = Vec::new();
        let mut iter = args.iter().skip(1).peekable();

        while let Some(arg) = iter.next() {
            if arg.starts_with("--") {
                let name = &arg[2..];
                if let Some(a) = self.args.iter().find(|a| a.long.as_deref() == Some(name)) {
                    if a.takes_value {
                        if let Some(value) = iter.next() {
                            if let Some(validator) = &a.validator {
                                if !validator(value) {
                                    panic!("Invalid value for argument: {}", name);
                                }
                            }
                            values.insert(a.name.clone(), value.clone());
                        }
                    } else {
                        flags.insert(a.name.clone(), true);
                    }
                }
            } else if arg.starts_with('-') {
                let chars: Vec<char> = arg.chars().skip(1).collect();
                for &c in &chars {
                    if let Some(a) = self.args.iter().find(|a| a.short == Some(c)) {
                        if a.takes_value {
                            if let Some(value) = iter.next() {
                                if let Some(validator) = &a.validator {
                                    if !validator(value) {
                                        panic!("Invalid value for argument: -{}", c);
                                    }
                                }
                                values.insert(a.name.clone(), value.clone());
                            }
                        } else {
                            flags.insert(a.name.clone(), true);
                        }
                    }
                }
            } else if self.subcommands.contains_key(arg) {
                let sub = self.subcommands.remove(arg).unwrap();
                return sub.parse(&args[1..]);
            } else {
                positionals.push(arg.clone());
            }
        }

        for arg in &self.args {
            if arg.required && !values.contains_key(&arg.name) {
                if let Some(default) = &arg.default {
                    values.insert(arg.name.clone(), default.clone());
                } else {
                    panic!("Missing required argument: {}", arg.name);
                }
            }
        }

        ArgMatches {
            values,
            flags,
            positionals,
        }
    }
}