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, // Named arguments, stored in forward order. pub switches: Vec<(SwitchName, Option)>, // All arguments following a '--' token, stored in forward order. pub unprocessed: Option>, } impl Switchboard { pub fn parse(mut args: Vec) -> 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 = 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:?}"); } }