• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Handles argument parsing.
6 //!
7 //! # Example
8 //!
9 //! ```
10 //! const ARGUMENTS: &'static [Argument] = &[
11 //!     Argument::positional("FILES", "files to operate on"),
12 //!     Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"),
13 //!     Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"),
14 //!     Argument::flag("unmount", "Unmount the root"),
15 //!     Argument::short_flag('h', "help", "Print help message."),
16 //! ];
17 //!
18 //! let match_res = set_arguments(args, ARGUMENTS, |name, value| {
19 //!     match name {
20 //!         "" => println!("positional arg! {}", value.unwrap()),
21 //!         "program" => println!("gonna use program {}", value.unwrap()),
22 //!         "cpus" => {
23 //!             let v: u32 = value.unwrap().parse().map_err(|_| {
24 //!                 Error::InvalidValue {
25 //!                     value: value.unwrap().to_owned(),
26 //!                     expected: "this value for `cpus` needs to be integer",
27 //!                 }
28 //!             })?;
29 //!         }
30 //!         "unmount" => println!("gonna unmount"),
31 //!         "help" => return Err(Error::PrintHelp),
32 //!         _ => unreachable!(),
33 //!     }
34 //! }
35 //!
36 //! match match_res {
37 //!     Ok(_) => println!("running with settings"),
38 //!     Err(Error::PrintHelp) => print_help("best_program", "FILES", ARGUMENTS),
39 //!     Err(e) => println!("{}", e),
40 //! }
41 //! ```
42 
43 use std::fmt::{self, Display};
44 use std::result;
45 
46 /// An error with argument parsing.
47 #[derive(Debug)]
48 pub enum Error {
49     /// There was a syntax error with the argument.
50     Syntax(String),
51     /// The argumen's name is unused.
52     UnknownArgument(String),
53     /// The argument was required.
54     ExpectedArgument(String),
55     /// The argument's given value is invalid.
56     InvalidValue {
57         value: String,
58         expected: &'static str,
59     },
60     /// The argument was already given and none more are expected.
61     TooManyArguments(String),
62     /// The argument expects a value.
63     ExpectedValue(String),
64     /// The help information was requested
65     PrintHelp,
66 }
67 
68 impl Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result69     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70         use self::Error::*;
71 
72         match self {
73             Syntax(s) => write!(f, "syntax error: {}", s),
74             UnknownArgument(s) => write!(f, "unknown argument: {}", s),
75             ExpectedArgument(s) => write!(f, "expected argument: {}", s),
76             InvalidValue { value, expected } => {
77                 write!(f, "invalid value {:?}: {}", value, expected)
78             }
79             TooManyArguments(s) => write!(f, "too many arguments: {}", s),
80             ExpectedValue(s) => write!(f, "expected parameter value: {}", s),
81             PrintHelp => write!(f, "help was requested"),
82         }
83     }
84 }
85 
86 /// Result of a argument parsing.
87 pub type Result<T> = result::Result<T, Error>;
88 
89 /// Information about an argument expected from the command line.
90 ///
91 /// # Examples
92 ///
93 /// To indicate a flag style argument:
94 ///
95 /// ```
96 /// Argument::short_flag('f', "flag", "enable awesome mode")
97 /// ```
98 ///
99 /// To indicate a parameter style argument that expects a value:
100 ///
101 /// ```
102 /// // "VALUE" and "NETMASK" are placeholder values displayed in the help message for these
103 /// // arguments.
104 /// Argument::short_value('v', "val", "VALUE", "how much do you value this usage information")
105 /// Argument::value("netmask", "NETMASK", "hides your netface")
106 /// ```
107 ///
108 /// To indicate an argument with no short version:
109 ///
110 /// ```
111 /// Argument::flag("verbose", "this option is hard to type quickly")
112 /// ```
113 ///
114 /// To indicate a positional argument:
115 ///
116 /// ```
117 /// Argument::positional("VALUES", "these are positional arguments")
118 /// ```
119 #[derive(Default)]
120 pub struct Argument {
121     /// The name of the value to display in the usage information. Use None to indicate that there
122     /// is no value expected for this argument.
123     pub value: Option<&'static str>,
124     /// Optional single character shortened argument name.
125     pub short: Option<char>,
126     /// The long name of this argument.
127     pub long: &'static str,
128     /// Helpfuly usage information for this argument to display to the user.
129     pub help: &'static str,
130 }
131 
132 impl Argument {
positional(value: &'static str, help: &'static str) -> Argument133     pub fn positional(value: &'static str, help: &'static str) -> Argument {
134         Argument {
135             value: Some(value),
136             long: "",
137             help,
138             ..Default::default()
139         }
140     }
141 
value(long: &'static str, value: &'static str, help: &'static str) -> Argument142     pub fn value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
143         Argument {
144             value: Some(value),
145             long,
146             help,
147             ..Default::default()
148         }
149     }
150 
short_value( short: char, long: &'static str, value: &'static str, help: &'static str, ) -> Argument151     pub fn short_value(
152         short: char,
153         long: &'static str,
154         value: &'static str,
155         help: &'static str,
156     ) -> Argument {
157         Argument {
158             value: Some(value),
159             short: Some(short),
160             long,
161             help,
162         }
163     }
164 
flag(long: &'static str, help: &'static str) -> Argument165     pub fn flag(long: &'static str, help: &'static str) -> Argument {
166         Argument {
167             long,
168             help,
169             ..Default::default()
170         }
171     }
172 
short_flag(short: char, long: &'static str, help: &'static str) -> Argument173     pub fn short_flag(short: char, long: &'static str, help: &'static str) -> Argument {
174         Argument {
175             short: Some(short),
176             long,
177             help,
178             ..Default::default()
179         }
180     }
181 }
182 
parse_arguments<I, R, F>(args: I, mut f: F) -> Result<()> where I: Iterator<Item = R>, R: AsRef<str>, F: FnMut(&str, Option<&str>) -> Result<()>,183 fn parse_arguments<I, R, F>(args: I, mut f: F) -> Result<()>
184 where
185     I: Iterator<Item = R>,
186     R: AsRef<str>,
187     F: FnMut(&str, Option<&str>) -> Result<()>,
188 {
189     enum State {
190         // Initial state at the start and after finishing a single argument/value.
191         Top,
192         // The remaining arguments are all positional.
193         Positional,
194         // The next string is the value for the argument `name`.
195         Value { name: String },
196     }
197     let mut s = State::Top;
198     for arg in args {
199         let arg = arg.as_ref();
200         s = match s {
201             State::Top => {
202                 if arg == "--" {
203                     State::Positional
204                 } else if arg.starts_with("--") {
205                     let param = arg.trim_start_matches('-');
206                     if param.contains('=') {
207                         let mut iter = param.splitn(2, '=');
208                         let name = iter.next().unwrap();
209                         let value = iter.next().unwrap();
210                         if name.is_empty() {
211                             return Err(Error::Syntax(
212                                 "expected parameter name before `=`".to_owned(),
213                             ));
214                         }
215                         if value.is_empty() {
216                             return Err(Error::Syntax(
217                                 "expected parameter value after `=`".to_owned(),
218                             ));
219                         }
220                         f(name, Some(value))?;
221                         State::Top
222                     } else if let Err(e) = f(param, None) {
223                         if let Error::ExpectedValue(_) = e {
224                             State::Value {
225                                 name: param.to_owned(),
226                             }
227                         } else {
228                             return Err(e);
229                         }
230                     } else {
231                         State::Top
232                     }
233                 } else if arg.starts_with('-') {
234                     if arg.len() == 1 {
235                         return Err(Error::Syntax(
236                             "expected argument short name after `-`".to_owned(),
237                         ));
238                     }
239                     let name = &arg[1..2];
240                     let value = if arg.len() > 2 { Some(&arg[2..]) } else { None };
241                     if let Err(e) = f(name, value) {
242                         if let Error::ExpectedValue(_) = e {
243                             State::Value {
244                                 name: name.to_owned(),
245                             }
246                         } else {
247                             return Err(e);
248                         }
249                     } else {
250                         State::Top
251                     }
252                 } else {
253                     f("", Some(&arg))?;
254                     State::Positional
255                 }
256             }
257             State::Positional => {
258                 f("", Some(&arg))?;
259                 State::Positional
260             }
261             State::Value { name } => {
262                 f(&name, Some(&arg))?;
263                 State::Top
264             }
265         };
266     }
267     Ok(())
268 }
269 
270 /// Parses the given `args` against the list of know arguments `arg_list` and calls `f` with each
271 /// present argument and value if required.
272 ///
273 /// This function guarantees that only valid long argument names from `arg_list` are sent to the
274 /// callback `f`. It is also guaranteed that if an arg requires a value (i.e.
275 /// `arg.value.is_some()`), the value will be `Some` in the callbacks arguments. If the callback
276 /// returns `Err`, this function will end parsing and return that `Err`.
277 ///
278 /// See the [module level](index.html) example for a usage example.
set_arguments<I, R, F>(args: I, arg_list: &[Argument], mut f: F) -> Result<()> where I: Iterator<Item = R>, R: AsRef<str>, F: FnMut(&str, Option<&str>) -> Result<()>,279 pub fn set_arguments<I, R, F>(args: I, arg_list: &[Argument], mut f: F) -> Result<()>
280 where
281     I: Iterator<Item = R>,
282     R: AsRef<str>,
283     F: FnMut(&str, Option<&str>) -> Result<()>,
284 {
285     parse_arguments(args, |name, value| {
286         let mut matches = None;
287         for arg in arg_list {
288             if let Some(short) = arg.short {
289                 if name.len() == 1 && name.starts_with(short) {
290                     if value.is_some() != arg.value.is_some() {
291                         return Err(Error::ExpectedValue(short.to_string()));
292                     }
293                     matches = Some(arg.long);
294                 }
295             }
296             if matches.is_none() && arg.long == name {
297                 if value.is_some() != arg.value.is_some() {
298                     return Err(Error::ExpectedValue(arg.long.to_owned()));
299                 }
300                 matches = Some(arg.long);
301             }
302         }
303         match matches {
304             Some(long) => f(long, value),
305             None => Err(Error::UnknownArgument(name.to_owned())),
306         }
307     })
308 }
309 
310 /// Prints command line usage information to stdout.
311 ///
312 /// Usage information is printed according to the help fields in `args` with a leading usage line.
313 /// The usage line is of the format "`program_name` [ARGUMENTS] `required_arg`".
print_help(program_name: &str, required_arg: &str, args: &[Argument])314 pub fn print_help(program_name: &str, required_arg: &str, args: &[Argument]) {
315     println!(
316         "Usage: {} {}{}\n",
317         program_name,
318         if args.is_empty() { "" } else { "[ARGUMENTS] " },
319         required_arg
320     );
321     if args.is_empty() {
322         return;
323     }
324     println!("Argument{}:", if args.len() > 1 { "s" } else { "" });
325     for arg in args {
326         match arg.short {
327             Some(s) => print!(" -{}, ", s),
328             None => print!("     "),
329         }
330         if arg.long.is_empty() {
331             print!("  ");
332         } else {
333             print!("--");
334         }
335         print!("{:<12}", arg.long);
336         if let Some(v) = arg.value {
337             if arg.long.is_empty() {
338                 print!(" ");
339             } else {
340                 print!("=");
341             }
342             print!("{:<10}", v);
343         } else {
344             print!("{:<11}", "");
345         }
346         println!("{}", arg.help);
347     }
348 }
349 
350 #[cfg(test)]
351 mod tests {
352     use super::*;
353 
354     #[test]
request_help()355     fn request_help() {
356         let arguments = [Argument::short_flag('h', "help", "Print help message.")];
357 
358         let match_res = set_arguments(["-h"].iter(), &arguments[..], |name, _| {
359             match name {
360                 "help" => return Err(Error::PrintHelp),
361                 _ => unreachable!(),
362             };
363         });
364         match match_res {
365             Err(Error::PrintHelp) => {}
366             _ => unreachable!(),
367         }
368     }
369 
370     #[test]
mixed_args()371     fn mixed_args() {
372         let arguments = [
373             Argument::positional("FILES", "files to operate on"),
374             Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"),
375             Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"),
376             Argument::flag("unmount", "Unmount the root"),
377             Argument::short_flag('h', "help", "Print help message."),
378         ];
379 
380         let mut unmount = false;
381         let match_res = set_arguments(
382             ["--cpus", "3", "--program", "hello", "--unmount", "file"].iter(),
383             &arguments[..],
384             |name, value| {
385                 match name {
386                     "" => assert_eq!(value.unwrap(), "file"),
387                     "program" => assert_eq!(value.unwrap(), "hello"),
388                     "cpus" => {
389                         let c: u32 = value.unwrap().parse().map_err(|_| Error::InvalidValue {
390                             value: value.unwrap().to_owned(),
391                             expected: "this value for `cpus` needs to be integer",
392                         })?;
393                         assert_eq!(c, 3);
394                     }
395                     "unmount" => unmount = true,
396                     "help" => return Err(Error::PrintHelp),
397                     _ => unreachable!(),
398                 };
399                 Ok(())
400             },
401         );
402         assert!(match_res.is_ok());
403         assert!(unmount);
404     }
405 
406     #[test]
name_value_pair()407     fn name_value_pair() {
408         let arguments = [Argument::short_value(
409             'c',
410             "cpus",
411             "N",
412             "Number of CPUs to use. (default: 1)",
413         )];
414         let match_res = set_arguments(
415             ["-c", "5", "--cpus", "5", "-c5", "--cpus=5"].iter(),
416             &arguments[..],
417             |name, value| {
418                 assert_eq!(name, "cpus");
419                 assert_eq!(value, Some("5"));
420                 Ok(())
421             },
422         );
423         assert!(match_res.is_ok());
424     }
425 }
426