1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
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(&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()
}
}
|