From 5277fd9c56619d1fcd4776968b851ea534526435 Mon Sep 17 00:00:00 2001 From: Ben Bridle Date: Mon, 3 Feb 2025 17:50:10 +1300 Subject: Initial commit --- src/lib.rs | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/lib.rs (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e25725d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,113 @@ +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:?}"); + } +} -- cgit v1.2.3-70-g09d2