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<SwitchboardError>,
// 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);
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<char> = 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(&self, name: &str) -> &QueriedValue {
match self.values.get(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()
}
}