diff options
Diffstat (limited to 'src/switchboard.rs')
-rw-r--r-- | src/switchboard.rs | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/src/switchboard.rs b/src/switchboard.rs new file mode 100644 index 0000000..f19db13 --- /dev/null +++ b/src/switchboard.rs @@ -0,0 +1,145 @@ +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<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>>, + // All queried values. + pub values: HashMap<String, QueriedValue>, + // All query errors + pub errors: Vec<QueryError>, + // Total number of popped positional arguments + pub i: usize, +} + +impl Switchboard { + pub fn parse(args: Vec<String>) -> Self { + let mut positional = Vec::new(); + let mut switches = Vec::new(); + let mut unprocessed = None; + let mut args = VecDeque::from(args); + + 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(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(); + let values = HashMap::new(); + let errors = Vec::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 { + QueryError::MissingNamed(name) => { + error!("The {name} switch is required") + } + QueryError::MissingPositional(name) => { + error!("The <{name}> argument is required") + } + QueryError::Repeated(name) => { + error!("The {name} switch was passed multiple times") + } + } + } + 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<String> { + self.i += 1; + self.positional.pop() + } +} + + |