1
2#[derive(Debug)]
3pub enum Arg<'a> {
4 Long(&'a str),
5 Short(&'a str),
6 Value(&'a str),
7}
8impl<'a> Arg<'a> {
9 pub fn as_str(&self) -> &'a str {
10 match self {
11 Arg::Long(x) => *x,
12 Arg::Short(x) => *x,
13 Arg::Value(x) => *x,
14 }
15 }
16}
17
18enum ParseState<'a> {
19 Normal,
20 Short(&'a str),
21}
22
23pub struct Parse<'a, I: Iterator<Item = &'a str>> {
24 args: I,
25 state: ParseState<'a>,
26 rest: bool,
27}
28impl<'a, I: Iterator<Item = &'a str>> Parse<'a, I> {
29 pub fn new(args: I) -> Self {
30 Self {
31 args,
32 state: ParseState::Normal,
33 rest: false,
34 }
35 }
36}
37impl<'a, I: Iterator<Item = &'a str>> Iterator for Parse<'a, I> {
38 type Item = Arg<'a>;
39 fn next(&mut self) -> Option<Self::Item> {
40 match self.state {
41 ParseState::Normal => {
42 let item = self.args.next()?;
43
44 if self.rest {
45 return Some(Arg::Value(item));
46 }
47
48 if item.starts_with("--") {
49 if item == "--" {
50 self.rest = true;
51 self.next()
52 } else {
53 let item = item.trim_start_matches("--");
54 Some(Arg::Long(item))
55 }
56 } else if item.starts_with("-") {
57 let item = item.trim_start_matches("-");
58 self.state = ParseState::Short(item);
59 self.next()
60 } else {
61 Some(Arg::Value(item))
62 }
63 }
64 ParseState::Short(ref mut item) => {
65 let c = item.split_inclusive(|_| true).nth(0);
66
67 if let Some(c) = c {
68 *item = &item[1..];
70 Some(Arg::Short(c))
71 } else {
72 self.state = ParseState::Normal;
73 self.next()
74 }
75 }
76 }
77 }
78}
79
80pub type Rule<'a, T> = (
81 &'static str,
83 Option<char>,
85 &'a dyn Fn(
87 &mut T,
89 &mut dyn FnMut() -> Result<&'a str, ()>,
91 &mut dyn std::fmt::Write
93 ) -> Result<(), ()>,
94);
95
96pub fn construct<'a, T: Default>(
97 mut parse: impl Iterator<Item = Arg<'a>>,
98 rules: &[Rule<'a, T>],
99 err: &mut impl std::fmt::Write,
100) -> Result<(T, Vec<&'a str>), ()> {
101 let mut config = T::default();
102
103 let mut values = Vec::new();
104
105 while let Some(arg) = parse.next() {
106 match arg {
107 Arg::Long(x) => {
108 let Some(rule) = rules.iter().find(|a| a.0 == x) else {
109 write!(err, "unrecognized option '--{}'", x).map_err(|_| ())?;
110 return Err(());
111 };
112
113 rule.2(
114 &mut config,
115 &mut || {
116 if let Some(param) = parse.next() {
117 Ok(param.as_str())
118 } else {
119 Err(())
120 }
121 },
122 err,
123 )?;
124 }
125 Arg::Short(x) => {
126 let Some(rule) = rules.iter().find(|a| a.1 == x.chars().nth(0)) else {
127 write!(err, "invalid option -- '-{}'", x).map_err(|_| ())?;
128 return Err(());
129 };
130
131 rule.2(
132 &mut config,
133 &mut || {
134 if let Some(param) = parse.next() {
135 Ok(param.as_str())
136 } else {
137 Err(())
138 }
139 },
140 err,
141 )?;
142 }
143 Arg::Value(x) => {
144 values.push(x);
145 }
146 }
147 }
148
149 Ok((config, values))
150}
151
152pub fn quick<'a, T: Default>(rules: &[Rule<'a, T>]) -> Result<(T, Vec<String>), String> {
153 let mut args = argv::iter().filter_map(|x| x.to_str());
154 args.next();
155
156 let mut err = String::new();
157
158 let config = construct(
159 Parse::new(args),
160 rules,
161 &mut err,
162 );
163
164 config
165 .map(|x| (x.0, x.1.iter().map(|x| x.to_string()).collect()))
166 .map_err(|_| err)
167}
168