noshell_parser/
parser.rs

1//! A parser for collecting arguments from a token stream.
2
3use core::str::FromStr;
4
5use heapless::Vec;
6
7use crate::lexer;
8
9/// Defines the possible errors that may occur during parsing of arguments.
10#[derive(Debug, PartialEq, Eq, thiserror::Error)]
11#[cfg_attr(feature = "defmt", derive(defmt::Format))]
12#[non_exhaustive]
13pub enum Error {
14    /// The argument value is invalid, meaning that it cannot be converted to the destination
15    /// type. This could mean that there is a missing implementation for [`str::parse`] trait.
16    #[error("invalid argument")]
17    InvalidArgument,
18
19    /// The argument value is missing, which occurs when the flag is not boolean and expect a
20    /// value.
21    #[error("missing argument")]
22    MissingArgument,
23}
24
25/// Defines the result of argument parsing. This is a simple key-value store that offers a look-up
26/// over parsed arguments.
27#[derive(Debug, Default)]
28#[cfg_attr(feature = "defmt", derive(defmt::Format))]
29pub struct ParsedArgs<'a, const ARG_COUNT_MAX: usize = 8> {
30    args: Vec<lexer::Token<'a>, ARG_COUNT_MAX>,
31}
32
33impl<'a> ParsedArgs<'a> {
34    /// Parse the command line input from a token stream. The result is the set of found arguments.
35    pub fn parse<I>(tokens: I) -> Self
36    where
37        I: Iterator<Item = lexer::Token<'a>>,
38    {
39        let mut out = Self::default();
40
41        for token in tokens {
42            out.args.push(token).ok();
43        }
44
45        out
46    }
47
48    /// Try to get and parse the argument value if any.
49    pub fn get<'k, T>(&self, key: &'k str) -> Result<Option<T>, Error>
50    where
51        'a: 'k,
52        T: FromStr,
53    {
54        let mut wait_for_value = false;
55
56        for token in &self.args {
57            if !wait_for_value && match_key_with_token(key, token) {
58                wait_for_value = true;
59                continue;
60            }
61
62            if wait_for_value {
63                if let lexer::Token::Value(value) = token {
64                    let typed = value.parse::<T>().map_err(|_| Error::InvalidArgument)?;
65                    return Ok(Some(typed));
66                }
67
68                return Err(Error::MissingArgument);
69            }
70        }
71
72        // Not enough tokens.
73        if wait_for_value {
74            return Err(Error::MissingArgument);
75        }
76
77        Ok(None)
78    }
79
80    /// Check if there exists an argument with the given key (i.e. short or long flag).
81    pub fn is_enabled<'k>(&self, key: &'k str) -> bool
82    where
83        'a: 'k,
84    {
85        for token in &self.args {
86            if match_key_with_token(key, token) {
87                return true;
88            }
89        }
90
91        false
92    }
93}
94
95fn match_key_with_token<'a, 'k>(key: &'k str, token: &lexer::Token<'a>) -> bool
96where
97    'a: 'k,
98{
99    match token {
100        lexer::Token::ShortFlag(name) => key.len() == 1 && *name == key.chars().next().unwrap(),
101        lexer::Token::LongFlag(name) => key.len() > 1 && *name == key,
102        _ => false,
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use googletest::prelude::*;
109
110    use crate::Lexer;
111
112    use super::*;
113
114    #[test]
115    fn it_should_parse_valid_value() {
116        let lexer = Lexer::new(&["-v", "42"]);
117        let args = ParsedArgs::parse(lexer);
118        assert_that!(args.get::<u32>("v"), matches_pattern!(&Ok(Some(42))));
119    }
120
121    #[test]
122    fn it_should_parse_invalid_arg() {
123        let lexer = Lexer::new(&["-v", "-42"]);
124        let args = ParsedArgs::parse(lexer);
125        assert_that!(args.get::<u32>("v"), eq(&Err(Error::InvalidArgument)));
126    }
127
128    #[test]
129    fn it_should_parse_missing_arg() {
130        let lexer = Lexer::new(&["-v"]);
131        let args = ParsedArgs::parse(lexer);
132        assert_that!(args.get::<u32>("v"), eq(&Err(Error::MissingArgument)));
133    }
134
135    #[test]
136    fn it_should_parse_enabled_bool_arg() {
137        let lexer = Lexer::new(&["-v"]);
138        let args = ParsedArgs::parse(lexer);
139        assert_that!(args.is_enabled("v"), eq(true));
140        assert_that!(args.get::<bool>("v"), eq(&Err(Error::MissingArgument)));
141    }
142
143    #[test]
144    fn it_should_parse_enabled_bool_arg_with_value() {
145        let lexer = Lexer::new(&["-v", "true"]);
146        let args = ParsedArgs::parse(lexer);
147        assert_that!(args.is_enabled("v"), eq(true));
148        assert_that!(args.get::<bool>("v"), eq(&Ok(Some(true))));
149    }
150}