Skip to main content

goreutils/
args.rs

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					// this is probably bad but I have absolutely no clue how to fix it
69					*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	// name
82	&'static str,
83	// short
84	Option<char>,
85	// mod
86	&'a dyn Fn(
87		// config
88		&mut T,
89		// params
90		&mut dyn FnMut() -> Result<&'a str, ()>,
91		// err
92		&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