embedded_cli/
arguments.rs1use crate::token::{Tokens, TokensIter};
2
3#[derive(Debug, Eq, PartialEq)]
4pub enum Arg<'a> {
5 DoubleDash,
9
10 LongOption(&'a str),
15
16 ShortOption(char),
23
24 Value(&'a str),
29}
30
31#[derive(Clone, Debug, Eq)]
32pub struct ArgList<'a> {
33 tokens: Tokens<'a>,
34}
35
36impl<'a> ArgList<'a> {
37 pub fn new(tokens: Tokens<'a>) -> Self {
39 Self { tokens }
40 }
41
42 pub fn args(&self) -> ArgsIter<'a> {
43 ArgsIter::new(self.tokens.iter())
44 }
45}
46
47impl<'a> PartialEq for ArgList<'a> {
48 fn eq(&self, other: &Self) -> bool {
49 self.args().eq(other.args())
50 }
51}
52
53#[derive(Clone, Debug, Eq, PartialEq)]
54pub enum ArgError {
55 NonAsciiShortOption,
56}
57
58#[derive(Debug)]
59pub struct ArgsIter<'a> {
60 values_only: bool,
61
62 leftover: &'a [u8],
65
66 tokens: TokensIter<'a>,
67}
68
69impl<'a> ArgsIter<'a> {
70 fn new(tokens: TokensIter<'a>) -> Self {
71 Self {
72 values_only: false,
73 leftover: &[],
74 tokens,
75 }
76 }
77
78 pub fn into_args(self) -> ArgList<'a> {
83 ArgList::new(self.tokens.into_tokens())
84 }
85}
86
87impl<'a> Iterator for ArgsIter<'a> {
88 type Item = Result<Arg<'a>, ArgError>;
89
90 fn next(&mut self) -> Option<Self::Item> {
91 fn process_leftover<'a>(byte: u8) -> Result<Arg<'a>, ArgError> {
92 if byte.is_ascii_alphabetic() {
93 Ok(Arg::ShortOption(unsafe {
95 char::from_u32_unchecked(byte as u32)
96 }))
97 } else {
98 Err(ArgError::NonAsciiShortOption)
99 }
100 }
101
102 if !self.leftover.is_empty() {
103 let byte = self.leftover[0];
104 self.leftover = &self.leftover[1..];
105 return Some(process_leftover(byte));
106 }
107
108 let raw = self.tokens.next()?;
109 let bytes = raw.as_bytes();
110
111 if self.values_only {
112 return Some(Ok(Arg::Value(raw)));
113 }
114
115 let token = if bytes.len() > 1 && bytes[0] == b'-' {
116 if bytes[1] == b'-' {
117 if bytes.len() == 2 {
118 self.values_only = true;
119 Arg::DoubleDash
120 } else {
121 Arg::LongOption(unsafe { raw.get_unchecked(2..) })
122 }
123 } else {
124 self.leftover = &bytes[2..];
125 return Some(process_leftover(bytes[1]));
126 }
127 } else {
128 Arg::Value(raw)
129 };
130
131 Some(Ok(token))
132 }
133}
134
135#[derive(Debug)]
136pub struct FromArgumentError<'a> {
137 pub value: &'a str,
138 pub expected: &'static str,
139}
140
141pub trait FromArgument<'a> {
142 fn from_arg(arg: &'a str) -> Result<Self, FromArgumentError<'_>>
143 where
144 Self: Sized;
145}
146
147impl<'a> FromArgument<'a> for &'a str {
148 fn from_arg(arg: &'a str) -> Result<Self, FromArgumentError<'_>> {
149 Ok(arg)
150 }
151}
152
153macro_rules! impl_arg_fromstr {
154 ($id:ident) => (
155 impl<'a> FromArgument<'a> for $id {
156 fn from_arg(arg: &'a str) -> Result<Self, FromArgumentError<'_>> {
157 arg.parse().map_err(|_| FromArgumentError {
158 value: arg,
159 expected: stringify!($id),
160 })
161 }
162 }
163 );
164
165 ($id:ident, $($ids:ident),+) => (
166 impl_arg_fromstr!{$id}
167 impl_arg_fromstr!{$($ids),+}
168 )
169}
170
171impl_arg_fromstr! {char, bool, u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize, f32, f64}
172
173#[cfg(test)]
174mod tests {
175 use rstest::rstest;
176
177 use crate::{arguments::ArgList, token::Tokens};
178
179 use super::{Arg, ArgError};
180
181 #[rstest]
182 #[case("arg1 --option1 val1 -f val2 -vs", &[
183 Ok(Arg::Value("arg1")),
184 Ok(Arg::LongOption("option1")),
185 Ok(Arg::Value("val1")),
186 Ok(Arg::ShortOption('f')),
187 Ok(Arg::Value("val2")),
188 Ok(Arg::ShortOption('v')),
189 Ok(Arg::ShortOption('s')),
190 ])]
191 #[case("arg1 --option1 -- val1 -f val2 -vs", &[
192 Ok(Arg::Value("arg1")),
193 Ok(Arg::LongOption("option1")),
194 Ok(Arg::DoubleDash),
195 Ok(Arg::Value("val1")),
196 Ok(Arg::Value("-f")),
197 Ok(Arg::Value("val2")),
198 Ok(Arg::Value("-vs")),
199 ])]
200 #[case("arg1 -бjв", &[
201 Ok(Arg::Value("arg1")),
202 Err(ArgError::NonAsciiShortOption),
203 Err(ArgError::NonAsciiShortOption),
204 Ok(Arg::ShortOption('j')),
205 Err(ArgError::NonAsciiShortOption),
206 Err(ArgError::NonAsciiShortOption),
207 ])]
208 fn arg_tokens(#[case] input: &str, #[case] expected: &[Result<Arg<'_>, ArgError>]) {
209 let mut input = input.as_bytes().to_vec();
210 let input = core::str::from_utf8_mut(&mut input).unwrap();
211 let tokens = Tokens::new(input);
212 let args = ArgList::new(tokens);
213 let mut iter = args.args();
214
215 for arg in expected {
216 let actual = iter.next().unwrap();
217 assert_eq!(&actual, arg);
218 }
219 assert_eq!(iter.next(), None);
220 assert_eq!(iter.next(), None);
221 }
222
223 #[test]
224 fn test_eq() {
225 let mut input = b"arg1 arg2".to_vec();
226 let input = core::str::from_utf8_mut(&mut input).unwrap();
227 let tokens = Tokens::new(input);
228 let args1 = ArgList::new(tokens);
229
230 let mut input = b" arg1 arg2 ".to_vec();
231 let input = core::str::from_utf8_mut(&mut input).unwrap();
232 let tokens = Tokens::new(input);
233 let args2 = ArgList::new(tokens);
234
235 assert_eq!(args1, args2)
236 }
237}