use crate::*; use std::collections::{HashMap, VecDeque}; 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>, // All queried values. pub values: HashMap, // All query errors pub errors: Vec, // Total number of popped positional arguments pub i: usize, } impl Switchboard { pub fn parse(args: Vec) -> Self { let mut positional = Vec::new(); let mut switches = Vec::new(); let mut unprocessed = None; let mut args = VecDeque::from(args); let mut errors = Vec::new(); while let Some(arg) = args.pop_front() { if arg.is_empty() { continue; } else if arg == "--" { unprocessed = Some(Vec::from(args)); break; } else if let Some(stripped) = arg.strip_prefix("--") { let (name, value) = match stripped.split_once("=") { Some((name, "")) => (name, None), Some((name, value)) => (name, Some(value.to_string())), None => (stripped, None), }; match !name.is_empty() { true => switches.push((SwitchName::Long(name.to_string()), value)), false => errors.push(SwitchboardError::Malformed(arg.to_string())), } } else if let Some(stripped) = arg.strip_prefix("-") { let (name, value) = match stripped.split_once("=") { Some((name, "")) => (name, None), Some((name, value)) => (name, Some(value.to_string())), None => (stripped, None), }; let chars: Vec = name.chars().collect(); match chars.len() == 1 { true => switches.push((SwitchName::Short(chars[0]), value)), false => errors.push(SwitchboardError::Malformed(arg.to_string())), } } else { positional.push(arg) } } // Reverse order of positional arguments and move program name. positional.reverse(); let program = positional.pop().unwrap(); let values = HashMap::new(); let i = 0; Self { program, positional, switches, unprocessed, values, errors, i } } 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, quick: None, doc: None, committed: false, } } pub fn positional(&mut self, name: &str) -> PositionalSwitchQuery { validate_name(name); PositionalSwitchQuery { switchboard: self, name: name.to_string(), doc: None, committed: false, } } pub fn raise_errors(&self) { if self.errors.is_empty() && self.switches.is_empty() && self.positional.is_empty(){ return; } for (name, _value) in &self.switches { error!("The switch {name} was not recognised") } if !self.positional.is_empty() { error!("Too many positional arguments provided") } for error in &self.errors { match error { SwitchboardError::MissingNamed(name) => { error!("The {name} switch is required") } SwitchboardError::MissingPositional(name) => { error!("The <{name}> argument is required") } SwitchboardError::Repeated(name) => { error!("The {name} switch was passed multiple times") } SwitchboardError::Malformed(string) => { error!("The '{string}' argument is malformed") } } } std::process::exit(1); } pub fn get(&mut self, name: &str) -> QueriedValue { match self.values.remove(name) { Some(value) => value, None => panic!("Name has not been defined: {name:?}"), } } /// 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) -> Option { self.i += 1; self.positional.pop() } }