1#![no_std]
2
3#[cfg(feature = "std")]
10extern crate std;
11
12mod arg;
13mod populated_slice;
14
15use ::core::fmt::Debug;
16
17use populated_slice::PopulatedSlice;
18
19pub use crate::arg::Arg;
20
21pub trait Visitor<'arg> {
26 type Value;
27
28 fn visit_positional(self, argument: &'arg Arg) -> Self::Value;
30
31 fn visit_long_option(self, option: &'arg Arg, argument: &'arg Arg) -> Self::Value;
34
35 fn visit_long(self, option: &'arg Arg, arg: impl ArgAccess<'arg>) -> Self::Value;
37
38 fn visit_short(self, option: u8, arg: impl ArgAccess<'arg>) -> Self::Value;
40}
41
42pub trait ArgAccess<'arg>: Sized {
55 fn take(self) -> Option<&'arg Arg>;
65}
66
67#[derive(Debug, Clone)]
68enum State<'arg> {
69 Ready,
70 PositionalOnly,
71 ShortInProgress(&'arg PopulatedSlice<u8>),
73}
74
75#[derive(Debug, Clone)]
87pub struct ArgumentsParser<'arg, I> {
88 state: State<'arg>,
89 args: I,
90}
91
92pub fn parser<A: AsRef<[u8]>>(args: &[A]) -> ArgumentsParser<'_, impl Iterator<Item = &[u8]>> {
93 ArgumentsParser::new(args.iter().map(|arg| arg.as_ref()))
94}
95
96impl<'arg, I> ArgumentsParser<'arg, I>
97where
98 I: Iterator<Item = &'arg [u8]>,
99{
100 #[inline]
107 #[must_use]
108 pub fn new(args: impl IntoIterator<IntoIter = I>) -> Self {
109 Self {
110 state: State::Ready,
111 args: args.into_iter(),
112 }
113 }
114
115 #[inline]
116 #[must_use]
117 pub fn new_from_slice(
118 slice: &[impl AsBytes],
119 ) -> ArgumentsParser<'_, impl Iterator<Item = &'_ [u8]>> {
120 ArgumentsParser::new(slice.iter().map(|arg| arg.as_bytes()))
121 }
122
123 #[inline]
126 fn positional_only_arg<V>(&mut self, visitor: V) -> Option<V::Value>
127 where
128 V: Visitor<'arg>,
129 {
130 debug_assert!(!matches!(self.state, State::ShortInProgress(_)));
131
132 self.state = State::PositionalOnly;
133 self.args
134 .next()
135 .map(Arg::new)
136 .map(|arg| visitor.visit_positional(arg))
137 }
138
139 #[inline]
141 fn standard_arg(&mut self) -> StandardArgAccess<'_, 'arg, I> {
142 debug_assert!(!matches!(self.state, State::PositionalOnly));
143
144 self.state = State::Ready;
145 StandardArgAccess { parent: self }
146 }
147
148 #[inline]
151 fn short_arg(&mut self, short: &'arg PopulatedSlice<u8>) -> ShortArgAccess<'_, 'arg> {
152 debug_assert!(!matches!(self.state, State::PositionalOnly));
153
154 self.state = State::ShortInProgress(short);
155 ShortArgAccess {
156 short: short.get(),
157 state: &mut self.state,
158 }
159 }
160
161 #[inline]
165 fn handle_short_argument<V>(&mut self, short: &'arg PopulatedSlice<u8>, visitor: V) -> V::Value
166 where
167 V: Visitor<'arg>,
168 {
169 let (&option, short) = short.split_first();
170
171 match PopulatedSlice::new(short) {
172 None => visitor.visit_short(option, self.standard_arg()),
173 Some(short) => visitor.visit_short(option, self.short_arg(short)),
174 }
175 }
176
177 pub fn next_arg<V>(&mut self, visitor: V) -> Option<V::Value>
178 where
179 V: Visitor<'arg>,
180 {
181 match self.state {
182 State::Ready => match self.args.next()? {
183 b"--" => self.positional_only_arg(visitor),
184 argument => Some(match argument {
185 [b'-', b'-', option @ ..] => match split_once(option, b'=') {
186 Some((option, argument)) => {
187 visitor.visit_long_option(Arg::new(option), Arg::new(argument))
188 }
189 None => visitor.visit_long(Arg::new(option), self.standard_arg()),
190 },
191 [b'-', short @ ..] => match PopulatedSlice::new(short) {
192 None => visitor.visit_positional(Arg::new(b"-")),
193 Some(short) => self.handle_short_argument(short, visitor),
194 },
195 positional => visitor.visit_positional(Arg::new(positional)),
196 }),
197 },
198 State::PositionalOnly => self.positional_only_arg(visitor),
199 State::ShortInProgress(short) => Some(self.handle_short_argument(short, visitor)),
200 }
201 }
202}
203
204struct StandardArgAccess<'a, 'arg, I> {
207 parent: &'a mut ArgumentsParser<'arg, I>,
208}
209
210impl<'arg, I> ArgAccess<'arg> for StandardArgAccess<'_, 'arg, I>
211where
212 I: Iterator<Item = &'arg [u8]>,
213{
214 fn take(self) -> Option<&'arg Arg> {
215 match self.parent.args.next()? {
216 b"--" if !matches!(self.parent.state, State::PositionalOnly) => {
217 self.parent.state = State::PositionalOnly;
218 None
219 }
220 arg => Some(Arg::new(arg)),
221 }
222 }
223}
224
225struct ShortArgAccess<'a, 'arg> {
228 short: &'arg [u8],
229 state: &'a mut State<'arg>,
230}
231
232impl<'arg> ArgAccess<'arg> for ShortArgAccess<'_, 'arg> {
233 fn take(self) -> Option<&'arg Arg> {
234 debug_assert!(
235 matches!(*self.state, State::ShortInProgress(short) if short.get() == self.short)
236 );
237
238 *self.state = State::Ready;
239 Some(Arg::new(self.short))
240 }
241}
242
243fn split_once(input: &[u8], delimiter: u8) -> Option<(&[u8], &[u8])> {
244 memchr::memchr(delimiter, input).map(|i| (&input[..i], &input[i + 1..]))
245}
246
247pub trait AsBytes {
250 fn as_bytes(&self) -> &[u8];
251}
252
253impl AsBytes for [u8] {
254 fn as_bytes(&self) -> &[u8] {
255 self
256 }
257}
258
259impl AsBytes for str {
260 fn as_bytes(&self) -> &[u8] {
261 self.as_bytes()
262 }
263}
264
265impl<T: AsBytes> AsBytes for &T {
266 fn as_bytes(&self) -> &[u8] {
267 T::as_bytes(*self)
268 }
269}
270
271#[cfg(feature = "std")]
272mod std_impls {
273 use super::*;
274
275 use std::{
276 ffi::{OsStr, OsString},
277 string::String,
278 vec::Vec,
279 };
280
281 impl AsBytes for Vec<u8> {
282 fn as_bytes(&self) -> &[u8] {
283 self.as_slice()
284 }
285 }
286
287 impl AsBytes for String {
288 fn as_bytes(&self) -> &[u8] {
289 self.as_bytes()
290 }
291 }
292
293 impl AsBytes for OsString {
294 fn as_bytes(&self) -> &[u8] {
295 self.as_encoded_bytes()
296 }
297 }
298 impl AsBytes for OsStr {
299 fn as_bytes(&self) -> &[u8] {
300 self.as_encoded_bytes()
301 }
302 }
303}