summaryrefslogtreecommitdiff
path: root/src/switchboard.rs
diff options
context:
space:
mode:
authorBen Bridle <ben@derelict.engineering>2025-03-03 20:51:01 +1300
committerBen Bridle <ben@derelict.engineering>2025-03-03 20:54:45 +1300
commit8a43a02b6950455aedbbdbee737bee1654aa91ef (patch)
tree64e31ff1cfbbdce22e104adcb1ad81f051019ca1 /src/switchboard.rs
parentea70fa89659e5cf1a9d4ca6ea31fb67f7a2cc633 (diff)
downloadswitchboard-8a43a02b6950455aedbbdbee737bee1654aa91ef.zip
Implement error reporting
The library has been redesigned so that all queries can be entered before any values are parsed. This allows all errors unrelated to value parsing to be displayed as a batch.
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()
+ }
+}
+
+