• 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 //! # use crosvm::argument::{Argument, Error, print_help, set_arguments};
11 //! # let args: std::slice::Iter<String> = [].iter();
12 //! let arguments = &[
13 //!     Argument::positional("FILES", "files to operate on"),
14 //!     Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"),
15 //!     Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"),
16 //!     Argument::flag("unmount", "Unmount the root"),
17 //!     Argument::short_flag('h', "help", "Print help message."),
18 //! ];
19 //!
20 //! let match_res = set_arguments(args, arguments, |name, value| {
21 //!     match name {
22 //!         "" => println!("positional arg! {}", value.unwrap()),
23 //!         "program" => println!("gonna use program {}", value.unwrap()),
24 //!         "cpus" => {
25 //!             let v: u32 = value.unwrap().parse().map_err(|_| {
26 //!                 Error::InvalidValue {
27 //!                     value: value.unwrap().to_owned(),
28 //!                     expected: String::from("this value for `cpus` needs to be integer"),
29 //!                 }
30 //!             })?;
31 //!         }
32 //!         "unmount" => println!("gonna unmount"),
33 //!         "help" => return Err(Error::PrintHelp),
34 //!         _ => unreachable!(),
35 //!     }
36 //!     unreachable!();
37 //! });
38 //!
39 //! match match_res {
40 //!     Ok(_) => println!("running with settings"),
41 //!     Err(Error::PrintHelp) => print_help("best_program", "FILES", arguments),
42 //!     Err(e) => println!("{}", e),
43 //! }
44 //! ```
45 
46 use std::fmt::{self, Display};
47 use std::result;
48 
49 /// An error with argument parsing.
50 #[derive(Debug)]
51 pub enum Error {
52     /// There was a syntax error with the argument.
53     Syntax(String),
54     /// The argument's name is unused.
55     UnknownArgument(String),
56     /// The argument was required.
57     ExpectedArgument(String),
58     /// The argument's given value is invalid.
59     InvalidValue { value: String, expected: String },
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 argument does not expect a value.
65     UnexpectedValue(String),
66     /// The help information was requested
67     PrintHelp,
68 }
69 
70 impl Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result71     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72         use self::Error::*;
73 
74         match self {
75             Syntax(s) => write!(f, "syntax error: {}", s),
76             UnknownArgument(s) => write!(f, "unknown argument: {}", s),
77             ExpectedArgument(s) => write!(f, "expected argument: {}", s),
78             InvalidValue { value, expected } => {
79                 write!(f, "invalid value {:?}: {}", value, expected)
80             }
81             TooManyArguments(s) => write!(f, "too many arguments: {}", s),
82             ExpectedValue(s) => write!(f, "expected parameter value: {}", s),
83             UnexpectedValue(s) => write!(f, "unexpected parameter value: {}", s),
84             PrintHelp => write!(f, "help was requested"),
85         }
86     }
87 }
88 
89 /// Result of a argument parsing.
90 pub type Result<T> = result::Result<T, Error>;
91 
92 #[derive(Debug, PartialEq)]
93 pub enum ArgumentValueMode {
94     /// Specifies that an argument requires a value and that an error should be generated if
95     /// no value is provided during parsing.
96     Required,
97 
98     /// Specifies that an argument does not allow a value and that an error should be returned
99     /// if a value is provided during parsing.
100     Disallowed,
101 
102     /// Specifies that an argument may have a value during parsing but is not required to.
103     Optional,
104 }
105 
106 /// Information about an argument expected from the command line.
107 ///
108 /// # Examples
109 ///
110 /// To indicate a flag style argument:
111 ///
112 /// ```
113 /// # use crosvm::argument::Argument;
114 /// Argument::short_flag('f', "flag", "enable awesome mode");
115 /// ```
116 ///
117 /// To indicate a parameter style argument that expects a value:
118 ///
119 /// ```
120 /// # use crosvm::argument::Argument;
121 /// // "VALUE" and "NETMASK" are placeholder values displayed in the help message for these
122 /// // arguments.
123 /// Argument::short_value('v', "val", "VALUE", "how much do you value this usage information");
124 /// Argument::value("netmask", "NETMASK", "hides your netface");
125 /// ```
126 ///
127 /// To indicate an argument with no short version:
128 ///
129 /// ```
130 /// # use crosvm::argument::Argument;
131 /// Argument::flag("verbose", "this option is hard to type quickly");
132 /// ```
133 ///
134 /// To indicate a positional argument:
135 ///
136 /// ```
137 /// # use crosvm::argument::Argument;
138 /// Argument::positional("VALUES", "these are positional arguments");
139 /// ```
140 pub struct Argument {
141     /// The name of the value to display in the usage information.
142     pub value: Option<&'static str>,
143     /// Specifies how values should be handled for this this argument.
144     pub value_mode: ArgumentValueMode,
145     /// Optional single character shortened argument name.
146     pub short: Option<char>,
147     /// The long name of this argument.
148     pub long: &'static str,
149     /// Helpfuly usage information for this argument to display to the user.
150     pub help: &'static str,
151 }
152 
153 impl Argument {
positional(value: &'static str, help: &'static str) -> Argument154     pub fn positional(value: &'static str, help: &'static str) -> Argument {
155         Argument {
156             value: Some(value),
157             value_mode: ArgumentValueMode::Required,
158             short: None,
159             long: "",
160             help,
161         }
162     }
163 
value(long: &'static str, value: &'static str, help: &'static str) -> Argument164     pub fn value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
165         Argument {
166             value: Some(value),
167             value_mode: ArgumentValueMode::Required,
168             short: None,
169             long,
170             help,
171         }
172     }
173 
short_value( short: char, long: &'static str, value: &'static str, help: &'static str, ) -> Argument174     pub fn short_value(
175         short: char,
176         long: &'static str,
177         value: &'static str,
178         help: &'static str,
179     ) -> Argument {
180         Argument {
181             value: Some(value),
182             value_mode: ArgumentValueMode::Required,
183             short: Some(short),
184             long,
185             help,
186         }
187     }
188 
flag(long: &'static str, help: &'static str) -> Argument189     pub fn flag(long: &'static str, help: &'static str) -> Argument {
190         Argument {
191             value: None,
192             value_mode: ArgumentValueMode::Disallowed,
193             short: None,
194             long,
195             help,
196         }
197     }
198 
short_flag(short: char, long: &'static str, help: &'static str) -> Argument199     pub fn short_flag(short: char, long: &'static str, help: &'static str) -> Argument {
200         Argument {
201             value: None,
202             value_mode: ArgumentValueMode::Disallowed,
203             short: Some(short),
204             long,
205             help,
206         }
207     }
208 
flag_or_value(long: &'static str, value: &'static str, help: &'static str) -> Argument209     pub fn flag_or_value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
210         Argument {
211             value: Some(value),
212             value_mode: ArgumentValueMode::Optional,
213             short: None,
214             long,
215             help,
216         }
217     }
218 }
219 
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<()>,220 fn parse_arguments<I, R, F>(args: I, mut f: F) -> Result<()>
221 where
222     I: Iterator<Item = R>,
223     R: AsRef<str>,
224     F: FnMut(&str, Option<&str>) -> Result<()>,
225 {
226     enum State {
227         // Initial state at the start and after finishing a single argument/value.
228         Top,
229         // The remaining arguments are all positional.
230         Positional,
231         // The next string is the value for the argument `name`.
232         Value { name: String },
233     }
234     let mut s = State::Top;
235     for arg in args {
236         let arg = arg.as_ref();
237         loop {
238             let mut arg_consumed = true;
239             s = match s {
240                 State::Top => {
241                     if arg == "--" {
242                         State::Positional
243                     } else if arg.starts_with("--") {
244                         let param = arg.trim_start_matches('-');
245                         if param.contains('=') {
246                             let mut iter = param.splitn(2, '=');
247                             let name = iter.next().unwrap();
248                             let value = iter.next().unwrap();
249                             if name.is_empty() {
250                                 return Err(Error::Syntax(
251                                     "expected parameter name before `=`".to_owned(),
252                                 ));
253                             }
254                             if value.is_empty() {
255                                 return Err(Error::Syntax(
256                                     "expected parameter value after `=`".to_owned(),
257                                 ));
258                             }
259                             f(name, Some(value))?;
260                             State::Top
261                         } else {
262                             State::Value {
263                                 name: param.to_owned(),
264                             }
265                         }
266                     } else if arg.starts_with('-') {
267                         if arg.len() == 1 {
268                             return Err(Error::Syntax(
269                                 "expected argument short name after `-`".to_owned(),
270                             ));
271                         }
272                         let name = &arg[1..2];
273                         let value = if arg.len() > 2 { Some(&arg[2..]) } else { None };
274                         if let Err(e) = f(name, value) {
275                             if let Error::ExpectedValue(_) = e {
276                                 State::Value {
277                                     name: name.to_owned(),
278                                 }
279                             } else {
280                                 return Err(e);
281                             }
282                         } else {
283                             State::Top
284                         }
285                     } else {
286                         f("", Some(&arg))?;
287                         State::Positional
288                     }
289                 }
290                 State::Positional => {
291                     f("", Some(&arg))?;
292                     State::Positional
293                 }
294                 State::Value { name } => {
295                     if arg.starts_with('-') {
296                         arg_consumed = false;
297                         f(&name, None)?;
298                     } else if let Err(e) = f(&name, Some(&arg)) {
299                         arg_consumed = false;
300                         f(&name, None).map_err(|_| e)?;
301                     }
302                     State::Top
303                 }
304             };
305 
306             if arg_consumed {
307                 break;
308             }
309         }
310     }
311 
312     // If we ran out of arguments while parsing the last parameter, which may be either a
313     // value parameter or a flag, try to parse it as a flag. This will produce "missing value"
314     // error if the parameter is in fact a value parameter, which is the desired outcome.
315     match s {
316         State::Value { name } => f(&name, None),
317         _ => Ok(()),
318     }
319 }
320 
321 /// Parses the given `args` against the list of know arguments `arg_list` and calls `f` with each
322 /// present argument and value if required.
323 ///
324 /// This function guarantees that only valid long argument names from `arg_list` are sent to the
325 /// callback `f`. It is also guaranteed that if an arg requires a value (i.e.
326 /// `arg.value.is_some()`), the value will be `Some` in the callbacks arguments. If the callback
327 /// returns `Err`, this function will end parsing and return that `Err`.
328 ///
329 /// 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<()>,330 pub fn set_arguments<I, R, F>(args: I, arg_list: &[Argument], mut f: F) -> Result<()>
331 where
332     I: Iterator<Item = R>,
333     R: AsRef<str>,
334     F: FnMut(&str, Option<&str>) -> Result<()>,
335 {
336     parse_arguments(args, |name, value| {
337         let mut matches = None;
338         for arg in arg_list {
339             if let Some(short) = arg.short {
340                 if name.len() == 1 && name.starts_with(short) {
341                     if value.is_some() != arg.value.is_some() {
342                         return Err(Error::ExpectedValue(short.to_string()));
343                     }
344                     matches = Some(arg.long);
345                 }
346             }
347             if matches.is_none() && arg.long == name {
348                 if value.is_none() && arg.value_mode == ArgumentValueMode::Required {
349                     return Err(Error::ExpectedValue(arg.long.to_owned()));
350                 }
351                 if value.is_some() && arg.value_mode == ArgumentValueMode::Disallowed {
352                     return Err(Error::UnexpectedValue(arg.long.to_owned()));
353                 }
354                 matches = Some(arg.long);
355             }
356         }
357         match matches {
358             Some(long) => f(long, value),
359             None => Err(Error::UnknownArgument(name.to_owned())),
360         }
361     })
362 }
363 
364 /// Prints command line usage information to stdout.
365 ///
366 /// Usage information is printed according to the help fields in `args` with a leading usage line.
367 /// The usage line is of the format "`program_name` \[ARGUMENTS\] `required_arg`".
print_help(program_name: &str, required_arg: &str, args: &[Argument])368 pub fn print_help(program_name: &str, required_arg: &str, args: &[Argument]) {
369     println!(
370         "Usage: {} {}{}\n",
371         program_name,
372         if args.is_empty() { "" } else { "[ARGUMENTS] " },
373         required_arg
374     );
375     if args.is_empty() {
376         return;
377     }
378     println!("Argument{}:", if args.len() > 1 { "s" } else { "" });
379     for arg in args {
380         match arg.short {
381             Some(s) => print!(" -{}, ", s),
382             None => print!("     "),
383         }
384         if arg.long.is_empty() {
385             print!("  ");
386         } else {
387             print!("--");
388         }
389         print!("{:<12}", arg.long);
390         if let Some(v) = arg.value {
391             if arg.long.is_empty() {
392                 print!(" ");
393             } else {
394                 print!("=");
395             }
396             print!("{:<10}", v);
397         } else {
398             print!("{:<11}", "");
399         }
400         println!("{}", arg.help);
401     }
402 }
403 
404 #[cfg(test)]
405 mod tests {
406     use super::*;
407 
408     #[test]
request_help()409     fn request_help() {
410         let arguments = [Argument::short_flag('h', "help", "Print help message.")];
411 
412         let match_res = set_arguments(["-h"].iter(), &arguments[..], |name, _| {
413             match name {
414                 "help" => return Err(Error::PrintHelp),
415                 _ => unreachable!(),
416             };
417         });
418         match match_res {
419             Err(Error::PrintHelp) => {}
420             _ => unreachable!(),
421         }
422     }
423 
424     #[test]
mixed_args()425     fn mixed_args() {
426         let arguments = [
427             Argument::positional("FILES", "files to operate on"),
428             Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"),
429             Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"),
430             Argument::flag("unmount", "Unmount the root"),
431             Argument::short_flag('h', "help", "Print help message."),
432         ];
433 
434         let mut unmount = false;
435         let match_res = set_arguments(
436             ["--cpus", "3", "--program", "hello", "--unmount", "file"].iter(),
437             &arguments[..],
438             |name, value| {
439                 match name {
440                     "" => assert_eq!(value.unwrap(), "file"),
441                     "program" => assert_eq!(value.unwrap(), "hello"),
442                     "cpus" => {
443                         let c: u32 = value.unwrap().parse().map_err(|_| Error::InvalidValue {
444                             value: value.unwrap().to_owned(),
445                             expected: String::from("this value for `cpus` needs to be integer"),
446                         })?;
447                         assert_eq!(c, 3);
448                     }
449                     "unmount" => unmount = true,
450                     "help" => return Err(Error::PrintHelp),
451                     _ => unreachable!(),
452                 };
453                 Ok(())
454             },
455         );
456         assert!(match_res.is_ok());
457         assert!(unmount);
458     }
459 
460     #[test]
name_value_pair()461     fn name_value_pair() {
462         let arguments = [Argument::short_value(
463             'c',
464             "cpus",
465             "N",
466             "Number of CPUs to use. (default: 1)",
467         )];
468         let match_res = set_arguments(
469             ["-c", "5", "--cpus", "5", "-c5", "--cpus=5"].iter(),
470             &arguments[..],
471             |name, value| {
472                 assert_eq!(name, "cpus");
473                 assert_eq!(value, Some("5"));
474                 Ok(())
475             },
476         );
477         assert!(match_res.is_ok());
478         let not_match_res = set_arguments(
479             ["-c", "5", "--cpus"].iter(),
480             &arguments[..],
481             |name, value| {
482                 assert_eq!(name, "cpus");
483                 assert_eq!(value, Some("5"));
484                 Ok(())
485             },
486         );
487         assert!(not_match_res.is_err());
488     }
489 
490     #[test]
flag_or_value()491     fn flag_or_value() {
492         let run_case = |args| -> Option<String> {
493             let arguments = [
494                 Argument::positional("FILES", "files to operate on"),
495                 Argument::flag_or_value("gpu", "[2D|3D]", "Enable or configure gpu"),
496                 Argument::flag("foo", "Enable foo."),
497                 Argument::value("bar", "stuff", "Configure bar."),
498             ];
499 
500             let mut gpu_value: Option<String> = None;
501             let match_res =
502                 set_arguments(args, &arguments[..], |name: &str, value: Option<&str>| {
503                     match name {
504                         "" => assert_eq!(value.unwrap(), "file1"),
505                         "foo" => assert!(value.is_none()),
506                         "bar" => assert_eq!(value.unwrap(), "stuff"),
507                         "gpu" => match value {
508                             Some(v) => match v {
509                                 "2D" | "3D" => {
510                                     gpu_value = Some(v.to_string());
511                                 }
512                                 _ => {
513                                     return Err(Error::InvalidValue {
514                                         value: v.to_string(),
515                                         expected: String::from("2D or 3D"),
516                                     })
517                                 }
518                             },
519                             None => {
520                                 gpu_value = None;
521                             }
522                         },
523                         _ => unreachable!(),
524                     };
525                     Ok(())
526                 });
527 
528             assert!(match_res.is_ok());
529             gpu_value
530         };
531 
532         // Used as flag and followed by positional
533         assert_eq!(run_case(["--gpu", "file1"].iter()), None);
534         // Used as flag and followed by flag
535         assert_eq!(run_case(["--gpu", "--foo", "file1",].iter()), None);
536         // Used as flag and followed by value
537         assert_eq!(run_case(["--gpu", "--bar=stuff", "file1"].iter()), None);
538 
539         // Used as value and followed by positional
540         assert_eq!(run_case(["--gpu=2D", "file1"].iter()).unwrap(), "2D");
541         // Used as value and followed by flag
542         assert_eq!(run_case(["--gpu=2D", "--foo"].iter()).unwrap(), "2D");
543         // Used as value and followed by value
544         assert_eq!(
545             run_case(["--gpu=2D", "--bar=stuff", "file1"].iter()).unwrap(),
546             "2D"
547         );
548     }
549 }
550