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}