executable/
arguments_parser.rs1use alloc::string::String;
2
3#[derive(Debug, PartialEq)]
4pub struct OptionArgument<'a>(&'a [String]);
5
6impl<'a> OptionArgument<'a> {
7 pub fn get_option_long(&self, name: &str) -> Option<&'a str> {
8 self.0
9 .iter()
10 .filter_map(|s| s.strip_prefix("--"))
11 .map(|s| s.split_once('=').unwrap_or((s, "")))
12 .find_map(|(key, value)| if key == name { Some(value) } else { None })
13 }
14
15 pub fn get_option_short(&self, name: char) -> Option<&'a str> {
16 self.0
17 .iter()
18 .filter_map(|s| s.strip_prefix('-'))
19 .map(|s| s.split_once('=').unwrap_or((s, "")))
20 .find_map(|(key, value)| {
21 if key.chars().next()? == name {
22 Some(value)
23 } else {
24 None
25 }
26 })
27 }
28
29 pub fn get_option(&self, name: &str) -> Option<&'a str> {
30 if let Some(first_char) = name.chars().next() {
31 self.get_option_long(name)
32 .or_else(|| self.get_option_short(first_char))
33 } else {
34 None
35 }
36 }
37}
38
39#[derive(Debug, PartialEq)]
40pub struct PositionalArgument<'a> {
41 pub value: Option<&'a str>,
42 pub options: OptionArgument<'a>,
43}
44
45#[derive(Debug, Clone)]
46pub struct ArgumentsParser<'a> {
47 arguments: &'a [String],
48}
49
50impl<'a> ArgumentsParser<'a> {
51 pub fn new(arguments: &'a [String]) -> Self {
52 Self { arguments }
53 }
54}
55
56impl<'a> Iterator for ArgumentsParser<'a> {
57 type Item = PositionalArgument<'a>;
58
59 fn next(&mut self) -> Option<Self::Item> {
60 if self.arguments.is_empty() {
61 return None;
62 }
63
64 let value = if let Some(item) = self.arguments.first()
65 && !item.starts_with('-')
66 {
67 self.arguments = &self.arguments[1..];
68 Some(item.as_str())
69 } else {
70 None
71 };
72
73 let options_end = self
74 .arguments
75 .iter()
76 .position(|arg| !arg.starts_with('-'))
77 .unwrap_or(self.arguments.len());
78
79 let options = &self.arguments[..options_end];
80 self.arguments = &self.arguments[options_end..];
81
82 Some(PositionalArgument {
83 value,
84 options: OptionArgument(options),
85 })
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use alloc::{string::ToString, vec, vec::Vec};
92 extern crate std;
93
94 use super::*;
95
96 #[test]
97 fn test_empty_arguments() {
98 let args: Vec<String> = vec![];
99 let mut parser = ArgumentsParser::new(&args);
100 assert!(parser.next().is_none());
101 }
102
103 #[test]
104 fn test_single_positional_argument() {
105 let args: Vec<String> = vec!["value1".to_string()];
106 let mut parser = ArgumentsParser::new(&args);
107 let result = parser.next().unwrap();
108 assert_eq!(result.value, Some("value1"));
109 assert!(result.options.get_option("test").is_none());
110 assert!(parser.next().is_none());
111 }
112
113 #[test]
114 fn test_positional_with_options() {
115 let args: Vec<String> = vec![
116 "value1".to_string(),
117 "--opt1=test".to_string(),
118 "-opt2".to_string(),
119 ];
120 let mut parser = ArgumentsParser::new(&args);
121 let result = parser.next().unwrap();
122 assert_eq!(result.value, Some("value1"));
123 assert_eq!(result.options.get_option("opt1"), Some("test"));
124 assert_eq!(result.options.get_option("opt2"), Some(""));
125 }
126
127 #[test]
128 fn test_multiple_positional_arguments() {
129 let args: Vec<String> = vec![
130 "value1".to_string(),
131 "value2".to_string(),
132 "value3".to_string(),
133 ];
134 let mut parser = ArgumentsParser::new(&args);
135 assert_eq!(
136 parser.next(),
137 Some(PositionalArgument {
138 value: Some("value1"),
139 options: OptionArgument(&[]),
140 })
141 );
142 assert_eq!(
143 parser.next(),
144 Some(PositionalArgument {
145 value: Some("value2"),
146 options: OptionArgument(&[]),
147 })
148 );
149 assert_eq!(
150 parser.next(),
151 Some(PositionalArgument {
152 value: Some("value3"),
153 options: OptionArgument(&[]),
154 })
155 );
156 assert!(parser.next().is_none());
157 }
158
159 #[test]
160 fn test_options_only() {
161 let args: Vec<String> = vec!["--opt1=value".to_string(), "-opt2".to_string()];
162 let mut parser = ArgumentsParser::new(&args);
163 let result = parser.next().unwrap();
164 assert_eq!(result.value, None);
165 assert_eq!(result.options.get_option("opt1"), Some("value"));
166 assert_eq!(result.options.get_option("opt2"), Some(""));
167 }
168
169 #[test]
170 fn test_mixed_arguments() {
171 let args: Vec<String> = vec![
172 "pos1".to_string(),
173 "--opt1=val1".to_string(),
174 "pos2".to_string(),
175 "-opt2".to_string(),
176 "--opt3=val3".to_string(),
177 "pos3".to_string(),
178 ];
179 let mut parser = ArgumentsParser::new(&args);
180
181 std::println!("{:?}", parser.clone().collect::<Vec<_>>());
182
183 let first = parser.next().unwrap();
184 assert_eq!(first.value, Some("pos1"));
185 assert_eq!(first.options.get_option("opt1"), Some("val1"));
186
187 let second = parser.next().unwrap();
188 assert_eq!(second.value, Some("pos2"));
189 assert_eq!(second.options.get_option("opt2"), Some(""));
190 assert_eq!(second.options.get_option("opt3"), Some("val3"));
191
192 let third = parser.next().unwrap();
193 assert_eq!(third.value, Some("pos3"));
194 }
195
196 #[test]
197 fn test_option_not_found() {
198 let args: Vec<String> = vec!["--opt1=value".to_string()];
199 let mut parser = ArgumentsParser::new(&args);
200 let result = parser.next().unwrap();
201 assert!(result.options.get_option("nonexistent").is_none());
202 }
203
204 #[test]
205 fn test_option_with_equals_in_value() {
206 let args: Vec<String> = vec!["--url=https://example.com?key=value".to_string()];
207 let mut parser = ArgumentsParser::new(&args);
208 let result = parser.next().unwrap();
209 assert_eq!(
210 result.options.get_option("url"),
211 Some("https://example.com?key=value")
212 );
213 }
214
215 #[test]
216 fn test_double_dash_prefix() {
217 let args: Vec<String> = vec!["--option=test".to_string()];
218 let mut parser = ArgumentsParser::new(&args);
219 let result = parser.next().unwrap();
220 assert_eq!(result.options.get_option("option"), Some("test"));
221 }
222}