trivial_argument_parser/
lib.rs

1pub mod argument;
2
3use std::{borrow::BorrowMut, env, iter::Peekable};
4
5use argument::{legacy_argument::Argument, parsable_argument::HandleableArgument};
6
7///
8/// Acumulates arguments into list which then can be fed to parse.
9///
10/// # Examples
11/// ```
12/// use trivial_argument_parser::{ArgumentList, argument::legacy_argument::*};
13/// let mut args_list = ArgumentList::new();
14/// args_list.append_arg(Argument::new(Some('d'), None, ArgType::Flag).unwrap());
15/// args_list.append_arg(Argument::new(Some('p'), None, ArgType::Value).unwrap());
16/// args_list.append_arg(Argument::new(Some('l'), Some("an-list"), ArgType::ValueList).unwrap());
17/// ```
18pub struct ArgumentList<'a> {
19    pub dangling_values: Vec<String>,
20    pub arguments: Vec<Argument>,
21    pub parsable_arguments: Vec<&'a mut (dyn HandleableArgument<'a> + 'a)>,
22}
23
24impl<'a> ArgumentList<'a> {
25    pub fn arguments(&self) -> &Vec<Argument> {
26        &self.arguments
27    }
28    /**
29    Create ArgumentList with empty vector of arguments.
30    */
31    pub fn new() -> ArgumentList<'a> {
32        ArgumentList {
33            dangling_values: Vec::new(),
34            arguments: Vec::new(),
35            parsable_arguments: Vec::new(),
36        }
37    }
38
39    /**
40    Append argument to the end of the list.
41    */
42    pub fn append_arg(&mut self, argument: Argument) {
43        self.arguments.push(argument);
44    }
45
46    /**
47    Append dangling values.
48    */
49    pub fn append_dangling_value(&mut self, value: &str) {
50        self.dangling_values.push(String::from(value));
51    }
52
53    /**
54    Search arguments by short name.
55    */
56    pub fn search_by_short_name(&self, name: char) -> Option<&Argument> {
57        for x in &self.arguments {
58            match x.short() {
59                Some(symbol) => {
60                    if symbol == &name {
61                        return Some(x);
62                    }
63                }
64                None => (),
65            };
66        }
67        Option::None
68    }
69
70    /**
71    Search arguments by short name.
72    */
73    pub fn search_by_short_name_mut(&mut self, name: char) -> Option<&mut Argument> {
74        for x in &mut self.arguments {
75            match x.short() {
76                Some(symbol) => {
77                    if symbol == &name {
78                        return Some(x);
79                    }
80                }
81                None => (),
82            };
83        }
84        Option::None
85    }
86
87    fn handle_parsable_short_name(
88        &mut self,
89        name: char,
90        input_iter: &mut Peekable<&mut std::slice::Iter<'_, String>>,
91    ) -> Result<bool, String> {
92        for x in &mut self.parsable_arguments {
93            if x.is_by_short(name) {
94                x.handle(input_iter)?;
95                return Result::Ok(true);
96            }
97        }
98        return Result::Ok(false);
99    }
100
101    fn handle_parsable_long_name(
102        &mut self,
103        name: &str,
104        input_iter: &mut Peekable<&mut std::slice::Iter<'_, String>>,
105    ) -> Result<bool, String> {
106        for x in &mut self.parsable_arguments {
107            if x.is_by_long(name) {
108                x.handle(input_iter)?;
109                return Result::Ok(true);
110            }
111        }
112        return Result::Ok(false);
113    }
114
115    pub fn search_by_long_name(&self, name: &str) -> Option<&Argument> {
116        for x in &self.arguments {
117            match x.long() {
118                Some(ref long_name) => {
119                    if long_name == name {
120                        return Option::Some(x);
121                    }
122                }
123                None => (),
124            }
125        }
126        Option::None
127    }
128
129    /**
130    Search arguments by long name.
131    */
132    pub fn search_by_long_name_mut(&mut self, name: &str) -> Option<&mut Argument> {
133        for x in &mut self.arguments {
134            match x.long() {
135                Some(ref long_name) => {
136                    if long_name == name {
137                        return Option::Some(x);
138                    }
139                }
140                None => (),
141            }
142        }
143        Option::None
144    }
145
146    /// Returns vector of all generated dangling values (values not attached to any argument)
147    pub fn get_dangling_values(&self) -> &Vec<String> {
148        &self.dangling_values
149    }
150
151    /// Function that does all the parsing. You need to feed user input as an argument. Handles both
152    /// legacy type arguments and parsable value arguments. When used with mixed type arguments, parsable
153    /// arguments cannot be accessed before all borrows to ArgumentList are released or it gets dropped.
154    ///
155    /// # Examples
156    /// ```
157    /// use trivial_argument_parser::{
158    ///     ArgumentList, args_to_string_vector,
159    ///     argument::{
160    ///         legacy_argument::*,
161    ///         parsable_argument::ParsableValueArgument,
162    ///         ArgumentIdentification
163    ///     }
164    /// };
165    ///
166    /// let mut args_list = ArgumentList::new();
167    /// args_list.append_arg(Argument::new(Some('d'), None, ArgType::Flag).unwrap());
168    /// let mut argument_str = ParsableValueArgument::new_string(ArgumentIdentification::Long(String::from("hello")));
169    /// args_list.register_parsable(&mut argument_str);
170    /// args_list.parse_args(args_to_string_vector(std::env::args())).unwrap();
171    /// // First read legacy arguments.
172    /// args_list.search_by_short_name('n');
173    /// // Then access parsable value arguments since last reference was used.
174    /// argument_str.first_value();
175    /// ```
176    pub fn parse_args(&mut self, input: Vec<String>) -> Result<(), String> {
177        let mut iter = input.iter();
178        let mut input_iter = iter.borrow_mut().peekable();
179        while let Some(word) = input_iter.next() {
180            // Check if word is a short argument, long argument or dangling value
181            let word_length = word.chars().count();
182            if word_length == 2 {
183                if word.chars().nth(0).expect("first letter") == '-'
184                    && word
185                        .chars()
186                        .nth(1)
187                        .expect(&format!("{}", word_length))
188                        .is_alphabetic()
189                {
190                    // Add value to argument identified by short name
191                    match self.search_by_short_name_mut(word.chars().nth(1).unwrap()) {
192                        Some(argument) => {
193                            argument.add_value(&mut input_iter)?;
194                        }
195                        None => {
196                            if !self.handle_parsable_short_name(
197                                word.chars().nth(1).unwrap(),
198                                &mut input_iter,
199                            )? {
200                                return Err(format!(
201                                    "Could not find argument identified by {}.",
202                                    word
203                                ));
204                            }
205                        }
206                    };
207                } else {
208                    // Add as dangling value
209                    self.append_dangling_value(word);
210                }
211            } else if word_length > 2 {
212                if word.chars().nth(0).unwrap() == '-'
213                    && word.chars().nth(1).unwrap() == '-'
214                    && word.chars().nth(2).unwrap().is_alphabetic()
215                {
216                    // Add value to argument identified by long name
217                    match self.search_by_long_name_mut(&word[2..word.len()]) {
218                        Some(argument) => {
219                            argument.add_value(&mut input_iter)?;
220                        }
221                        Option::None => {
222                            if !self
223                                .handle_parsable_long_name(&word[2..word.len()], &mut input_iter)?
224                            {
225                                return Err(format!(
226                                    "Could not find argument identified by {}.",
227                                    word
228                                ));
229                            }
230                        }
231                    };
232                } else {
233                    // Add as dangling value
234                    self.append_dangling_value(word);
235                }
236            } else {
237                // Add as dangling value
238                self.append_dangling_value(word);
239            }
240        }
241
242        // return arguments list with filled parsed values
243        Ok(())
244    }
245
246    /**
247     * Registers argument mutable borrow to be used while parsing.
248     */
249    pub fn register_parsable(&mut self, arg: &'a mut impl HandleableArgument<'a>) {
250        self.parsable_arguments.push(arg);
251    }
252}
253
254/**
255Helper function to transform arguments given by user from Args to vector of String.
256*/
257pub fn args_to_string_vector(args: env::Args) -> Vec<String> {
258    let mut arguments = Vec::new();
259
260    for x in args {
261        arguments.push(String::from(x));
262    }
263    arguments
264}
265
266#[cfg(test)]
267mod tests {
268    use crate::argument::{
269        legacy_argument::{ArgResult, ArgType},
270        parsable_argument::ParsableValueArgument,
271    };
272
273    use super::{argument::ArgumentIdentification, *};
274
275    #[test]
276    fn parse_works() {
277        let args = vec![
278            String::from("-d"),
279            String::from("-p"),
280            String::from("/file"),
281            String::from("--an-list"),
282            String::from("Marcin"),
283            String::from("-l"),
284            String::from("Mazgaj"),
285        ];
286
287        let mut args_list = ArgumentList::new();
288        args_list.append_arg(Argument::new(Some('d'), None, ArgType::Flag).expect("append 1"));
289        args_list.append_arg(Argument::new(Some('p'), None, ArgType::Value).expect("append 2"));
290        args_list.append_arg(
291            Argument::new(Some('l'), Some("an-list"), ArgType::ValueList).expect("append 3"),
292        );
293        args_list.parse_args(args).unwrap();
294        assert_eq!(args_list.arguments()[0].arg_result, Some(ArgResult::Flag));
295        assert_eq!(
296            args_list.arguments[1].arg_result,
297            Some(ArgResult::Value(String::from("/file")))
298        );
299        assert_eq!(
300            args_list.arguments[2].arg_result,
301            Some(ArgResult::ValueList(vec![
302                String::from("Marcin"),
303                String::from("Mazgaj")
304            ]))
305        );
306
307        assert_eq!(
308            args_list
309                .search_by_short_name('d')
310                .unwrap()
311                .get_flag()
312                .unwrap(),
313            true
314        );
315        assert_eq!(
316            args_list
317                .search_by_long_name("an-list")
318                .unwrap()
319                .get_values()
320                .unwrap(),
321            &vec![String::from("Marcin"), String::from("Mazgaj")]
322        );
323    }
324
325    #[test]
326    fn get_dangling_values_works() {
327        let args = vec![
328            String::from("-d"),
329            String::from("-p"),
330            String::from("/file"),
331            String::from("--an-list"),
332            String::from("Marcin"),
333            String::from("-l"),
334            String::from("Mazgaj"),
335            String::from("dangling"),
336        ];
337
338        let mut args_list = ArgumentList::new();
339
340        args_list.append_arg(Argument::new(Some('d'), None, ArgType::Flag).expect("append 1"));
341        args_list.append_arg(Argument::new(Some('p'), None, ArgType::Value).expect("append 2"));
342        args_list.append_arg(
343            Argument::new(Some('l'), Some("an-list"), ArgType::ValueList).expect("append 3"),
344        );
345
346        args_list.parse_args(args).unwrap();
347
348        let dangling = args_list.get_dangling_values();
349
350        assert_eq!("dangling", dangling[0]);
351    }
352
353    #[test]
354    fn values_with_spaces_work() {
355        let args = vec![
356            String::from("-n"),
357            String::from("Marcin Mazgaj"),
358            String::from("--hello"),
359            String::from("Hello World!"),
360            String::from("--hello"),
361            String::from("Witaj Świecie!"),
362        ];
363
364        let mut args_list = ArgumentList::new();
365
366        args_list.append_arg(Argument::new(Some('n'), None, ArgType::Value).unwrap());
367        args_list.append_arg(Argument::new(None, Some("hello"), ArgType::ValueList).unwrap());
368
369        args_list.parse_args(args).unwrap();
370
371        assert_eq!(
372            args_list
373                .search_by_short_name('n')
374                .unwrap()
375                .get_value()
376                .unwrap(),
377            "Marcin Mazgaj"
378        );
379        assert_eq!(
380            args_list
381                .search_by_long_name("hello")
382                .unwrap()
383                .get_values()
384                .unwrap(),
385            &vec![String::from("Hello World!"), String::from("Witaj Świecie!")]
386        );
387    }
388
389    #[test]
390    fn parse_with_parsable_arguments_works() {
391        let args = vec![
392            String::from("-n"),
393            String::from("5"),
394            String::from("--hello"),
395            String::from("Hello World!"),
396            String::from("--hello"),
397            String::from("Witaj Świecie!"),
398        ];
399
400        let mut args_list = ArgumentList::new();
401        let mut argument_int =
402            ParsableValueArgument::new_integer(ArgumentIdentification::Short('n'));
403        let mut argument_str =
404            ParsableValueArgument::new_string(ArgumentIdentification::Long(String::from("hello")));
405        args_list.register_parsable(&mut argument_int);
406        args_list.register_parsable(&mut argument_str);
407        args_list
408            .parse_args(args)
409            .expect("Failed while parsing arguments");
410        assert_eq!(argument_int.first_value().unwrap(), &5);
411        assert_eq!(argument_str.first_value().unwrap(), "Hello World!");
412        assert_eq!(argument_str.values().get(1).unwrap(), "Witaj Świecie!");
413    }
414
415    #[test]
416    fn parse_drop_parser_works() {
417        let args = vec![
418            String::from("-n"),
419            String::from("5"),
420            String::from("--hello"),
421            String::from("Hello World!"),
422            String::from("--hello"),
423            String::from("Witaj Świecie!"),
424        ];
425        let mut argument_int =
426            ParsableValueArgument::new_integer(ArgumentIdentification::Short('n'));
427        let mut argument_str =
428            ParsableValueArgument::new_string(ArgumentIdentification::Long(String::from("hello")));
429        {
430            let mut args_list = ArgumentList::new();
431            args_list.register_parsable(&mut argument_int);
432            args_list.register_parsable(&mut argument_str);
433            args_list
434                .parse_args(args)
435                .expect("Failed while parsing arguments");
436        }
437        assert_eq!(argument_int.first_value().unwrap(), &5);
438        assert_eq!(argument_str.first_value().unwrap(), "Hello World!");
439        assert_eq!(argument_str.values().get(1).unwrap(), "Witaj Świecie!");
440    }
441
442    #[test]
443    fn parse_with_mixed_arguments_works() {
444        let args = vec![
445            String::from("-n"),
446            String::from("5"),
447            String::from("--hello"),
448            String::from("Hello World!"),
449            String::from("--hello"),
450            String::from("Witaj Świecie!"),
451        ];
452
453        let mut args_list = ArgumentList::new();
454        let mut argument_str =
455            ParsableValueArgument::new_string(ArgumentIdentification::Long(String::from("hello")));
456        args_list.register_parsable(&mut argument_str);
457        args_list.append_arg(Argument::new(Some('n'), None, ArgType::Value).unwrap());
458        args_list
459            .parse_args(args)
460            .expect("Failed while parsing arguments");
461        assert_eq!(
462            args_list
463                .search_by_short_name('n')
464                .unwrap()
465                .get_value()
466                .unwrap(),
467            "5"
468        );
469        assert_eq!(argument_str.first_value().unwrap(), "Hello World!");
470        assert_eq!(argument_str.values().get(1).unwrap(), "Witaj Świecie!");
471    }
472}