summaryrefslogtreecommitdiff
path: root/src/switchboard.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/switchboard.rs')
-rw-r--r--src/switchboard.rs145
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()
+ }
+}
+
+