summaryrefslogblamecommitdiff
path: root/src/lib.rs
blob: e25725d5b4a5ba64beb141dee05d21ae87a6cd43 (plain) (tree)















































































































                                                                                    
mod query;
pub use query::*;


pub enum SwitchName {
    Short(char),
    Long(String),
}


pub struct Switchboard {
    // First positional argument.
    pub program: String,
    // Positional arguments, stored in reverse order.
    pub positional: Vec<String>,
    // Named arguments, stored in forward order.
    pub switches: Vec<(SwitchName, Option<String>)>,
    // All arguments following a '--' token, stored in forward order.
    pub unprocessed: Option<Vec<String>>,
}

impl Switchboard {
    pub fn parse(mut args: Vec<String>) -> Self {
        let mut positional = Vec::new();
        let mut switches = Vec::new();
        let mut unprocessed = None;
        args.reverse();

        while let Some(arg) = args.pop() {
            if arg.is_empty() {
                continue;
            } else if arg == "--" {
                args.reverse();
                unprocessed = Some(args);
                break;
            } else if let Some(arg) = arg.strip_prefix("--") {
                let (name, value) = match arg.split_once("=") {
                    Some((name, "")) => (name, None),
                    Some((name, value)) => (name, Some(value.to_string())),
                    None => (arg, None),
                };
                if name.is_empty() { continue }
                switches.push((SwitchName::Long(name.to_string()), value));
            } else if let Some(arg) = arg.strip_prefix("-") {
                let (name, value) = match arg.split_once("=") {
                    Some((name, "")) => (name, None),
                    Some((name, value)) => (name, Some(value.to_string())),
                    None => (arg, None),
                };
                let chars: Vec<char> = name.chars().collect();
                if chars.len() != 1 { continue }
                switches.push((SwitchName::Short(chars[0]), value));
            } else {
                positional.push(arg)
            }
        }
        // Reverse order of positional arguments and move program name.
        positional.reverse();
        let program = positional.pop().unwrap();
        Self {
            program, positional, switches, unprocessed,
        }
    }

    pub fn from_env() -> Self {
        let mut args = Vec::new();
        for arg_os in std::env::args_os() {
            args.push(arg_os.to_string_lossy().to_string());
        }
        Self::parse(args)
    }

    pub fn named(&mut self, name: &str) -> NamedSwitchQuery {
        validate_name(name);
        NamedSwitchQuery {
            switchboard: self,
            name: name.to_string(),
            short: None,
            default: None,
            quick: None,
        }
    }

    pub fn positional(&mut self, name: &str) -> PositionalSwitchQuery {
        validate_name(name);
        PositionalSwitchQuery {
            switchboard: self,
            name: name.to_string(),
            default: None,
        }
    }

    /// Check the next positional argument without consuming it.
    pub fn peek(&self) -> Option<&str> {
        self.positional.last().map(|p| p.as_str())
    }

    /// Consume the next positional argument.
    pub fn pop(&mut self) {
        self.positional.pop();
    }
}


pub enum SwitchboardError {
}


fn validate_name(name: &str) {
    if !name.chars().all(|c| c.is_ascii_lowercase() || c.is_digit(10) || c == '-') {
        panic!("Invalid name for argument: {name:?}");
    }
}