executable/
arguments_parser.rs

1use 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}