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