Skip to main content

valargs/
lib.rs

1use std::{collections::HashMap, env};
2
3/// Build the [`Args`] object associated with the arguments
4/// that the program was started with.
5///
6/// #### Example:
7///
8/// ```
9/// # fn main() {
10/// let args = valargs::parse();
11/// if args.has_option("nevergonnaletyoudown") {
12///     println!("got rickrolled");
13/// }
14/// # }
15/// ```
16pub fn parse() -> Args {
17    Args::parse_raw(&env::args().collect::<Vec<_>>())
18}
19
20/// A struct representing parsed command-line arguments.
21///
22/// #### Example:
23///
24/// ```
25/// # fn main() {
26/// let args = valargs::parse();
27///
28/// if let Some(cat_name) = args.nth(1) {
29///     println!("the cat's name is {}", cat_name);
30/// }
31///
32/// if args.has_option("orange") {
33///     println!("the cat is an orange cat");
34/// }
35///
36/// if let Some(favorite_food) = args.option("fav-food") {
37///     println!("the cat likes {} a lot", favorite_food);
38/// } else {
39///     println!("no information about the cat's favorite food...");
40/// }
41/// # }
42/// ```
43#[derive(Debug, Clone)]
44pub struct Args {
45    args: Vec<String>,
46    options: HashMap<String, Option<String>>,
47}
48
49impl Args {
50    /// Gets the nth argument (including the executable name).
51    ///
52    /// #### Example:
53    ///
54    /// ```
55    /// let args = valargs::parse();
56    ///
57    /// let _ = args.nth(0); // executable name
58    /// let command = args.nth(1); // first argument
59    ///
60    /// match command {
61    ///   Some("hello") => {
62    ///     let name = args.nth(2); // second argument
63    ///
64    ///     if let Some(name) = name {
65    ///       println!("hello {} !!", name);
66    ///     } else {
67    ///       println!("hello !!");
68    ///     }
69    ///   }
70    ///   Some(_) => println!("unknown command"),
71    ///   None => {
72    ///     println!("please provide a command");
73    ///   }
74    /// }
75    /// ```
76    pub fn nth(&self, index: usize) -> Option<&str> {
77        self.args.get(index).map(|s| s.as_str())
78    }
79
80    /// Check if the given option name is present.
81    pub fn has_option(&self, option_name: &str) -> bool {
82        self.options.contains_key(option_name)
83    }
84
85    /// Get the option and it potential value as an `Option<Option<&str>>`,
86    /// the first option represents the presence of the option in the
87    /// argument array and the second one the presence of a value associated
88    /// with that option.
89    pub fn option<'a>(&'a self, option_name: &str) -> Option<Option<&'a str>> {
90        self.options
91            .get(option_name)
92            .map(|o| o.as_ref().map(|s| s.as_str()))
93    }
94
95    /// Get the value associated with the given option name if present.
96    pub fn option_value<'a>(&'a self, option_name: &str) -> Option<&'a str> {
97        self.option(option_name).flatten()
98    }
99
100    fn parse_raw(raw_args: &[String]) -> Args {
101        let l = raw_args.len();
102
103        let mut args = Vec::new();
104        let mut options = HashMap::new();
105
106        let mut i = 0;
107        while i < l {
108            let token = raw_args[i].clone();
109
110            // Process the current token correctly whether it is an option
111            // (starting with "--" or "-") or an argument.
112            if let Some(option) = parse_option(&token) {
113                // Check if the option has an associated value.
114                let param = raw_args.get(i + 1);
115
116                // Skip the next token (the next iteration) if the option has
117                // an associated value.
118                if param.is_some_and(|t| parse_option(t.as_str()).is_none()) {
119                    i += 1;
120                }
121
122                options.insert(option.to_string(), param.cloned());
123            } else {
124                args.push(token);
125            }
126            i += 1;
127        }
128
129        Args { args, options }
130    }
131}
132
133fn parse_option(token: &str) -> Option<&str> {
134    token
135        .strip_prefix("--")
136        .or(token.strip_prefix("-"))
137        .filter(|s| s.contains(|c: char| c.is_ascii_lowercase()))
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn parse_args() {
146        let args = Args::parse_raw(
147            &[
148                "exec",
149                "arg1",
150                "arg2",
151                "--option0",
152                "option0_value",
153                "arg3",
154                "-o",
155            ]
156            .map(|s| s.to_string()),
157        );
158        assert_eq!(Some("exec"), args.nth(0));
159        assert_eq!(Some("arg1"), args.nth(1));
160        assert_eq!(Some("arg2"), args.nth(2));
161        assert_eq!(Some("arg3"), args.nth(3));
162        assert_eq!(None, args.nth(4));
163
164        assert_eq!(Some("option0_value"), args.option_value("option0"));
165        assert!(args.has_option("o"));
166    }
167
168    #[test]
169    fn neg_number() {
170        let args = Args::parse_raw(
171            &[
172                "exec",
173                "-0.8563",
174                "--value",
175                "-53",
176                "--option",
177                "--option2",
178                "-o",
179            ]
180            .map(|s| s.to_string()),
181        );
182
183        assert_eq!(Some("exec"), args.nth(0));
184        assert_eq!(Some("-0.8563"), args.nth(1));
185        assert_eq!(Some("-53"), args.option_value("value"));
186
187        assert!(!args.has_option("53"));
188        assert!(!args.has_option("0.8563"));
189
190        assert!(args.has_option("option"));
191        assert!(args.has_option("option2"));
192        assert!(args.has_option("o"));
193    }
194}