• 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::convert::TryFrom;
47 use std::result;
48 use std::str::FromStr;
49 
50 use remain::sorted;
51 use thiserror::Error;
52 
53 /// An error with argument parsing.
54 #[sorted]
55 #[derive(Error, Debug)]
56 pub enum Error {
57     /// Free error for use with the `serde_keyvalue` crate parser.
58     #[error("failed to parse key-value arguments: {0}")]
59     ConfigParserError(String),
60     /// The argument was required.
61     #[error("expected argument: {0}")]
62     ExpectedArgument(String),
63     /// The argument expects a value.
64     #[error("expected parameter value: {0}")]
65     ExpectedValue(String),
66     /// The argument's given value is invalid.
67     #[error("invalid value {value:?}: {expected}")]
68     InvalidValue { value: String, expected: String },
69     /// The help information was requested
70     #[error("help was requested")]
71     PrintHelp,
72     /// There was a syntax error with the argument.
73     #[error("syntax error: {0}")]
74     Syntax(String),
75     /// The argument was already given and none more are expected.
76     #[error("too many arguments: {0}")]
77     TooManyArguments(String),
78     /// The argument does not expect a value.
79     #[error("unexpected parameter value: {0}")]
80     UnexpectedValue(String),
81     /// The argument's name is unused.
82     #[error("unknown argument: {0}")]
83     UnknownArgument(String),
84 }
85 
86 /// Result of a argument parsing.
87 pub type Result<T> = result::Result<T, Error>;
88 
89 #[derive(Debug, PartialEq)]
90 pub enum ArgumentValueMode {
91     /// Specifies that an argument requires a value and that an error should be generated if
92     /// no value is provided during parsing.
93     Required,
94 
95     /// Specifies that an argument does not allow a value and that an error should be returned
96     /// if a value is provided during parsing.
97     Disallowed,
98 
99     /// Specifies that an argument may have a value during parsing but is not required to.
100     Optional,
101 }
102 
103 /// Information about an argument expected from the command line.
104 ///
105 /// # Examples
106 ///
107 /// To indicate a flag style argument:
108 ///
109 /// ```
110 /// # use crosvm::argument::Argument;
111 /// Argument::short_flag('f', "flag", "enable awesome mode");
112 /// ```
113 ///
114 /// To indicate a parameter style argument that expects a value:
115 ///
116 /// ```
117 /// # use crosvm::argument::Argument;
118 /// // "VALUE" and "NETMASK" are placeholder values displayed in the help message for these
119 /// // arguments.
120 /// Argument::short_value('v', "val", "VALUE", "how much do you value this usage information");
121 /// Argument::value("netmask", "NETMASK", "hides your netface");
122 /// ```
123 ///
124 /// To indicate an argument with no short version:
125 ///
126 /// ```
127 /// # use crosvm::argument::Argument;
128 /// Argument::flag("verbose", "this option is hard to type quickly");
129 /// ```
130 ///
131 /// To indicate a positional argument:
132 ///
133 /// ```
134 /// # use crosvm::argument::Argument;
135 /// Argument::positional("VALUES", "these are positional arguments");
136 /// ```
137 pub struct Argument {
138     /// The name of the value to display in the usage information.
139     pub value: Option<&'static str>,
140     /// Specifies how values should be handled for this this argument.
141     pub value_mode: ArgumentValueMode,
142     /// Optional single character shortened argument name.
143     pub short: Option<char>,
144     /// The long name of this argument.
145     pub long: &'static str,
146     /// Helpfuly usage information for this argument to display to the user.
147     pub help: &'static str,
148 }
149 
150 impl Argument {
positional(value: &'static str, help: &'static str) -> Argument151     pub fn positional(value: &'static str, help: &'static str) -> Argument {
152         Argument {
153             value: Some(value),
154             value_mode: ArgumentValueMode::Required,
155             short: None,
156             long: "",
157             help,
158         }
159     }
160 
value(long: &'static str, value: &'static str, help: &'static str) -> Argument161     pub fn value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
162         Argument {
163             value: Some(value),
164             value_mode: ArgumentValueMode::Required,
165             short: None,
166             long,
167             help,
168         }
169     }
170 
short_value( short: char, long: &'static str, value: &'static str, help: &'static str, ) -> Argument171     pub fn short_value(
172         short: char,
173         long: &'static str,
174         value: &'static str,
175         help: &'static str,
176     ) -> Argument {
177         Argument {
178             value: Some(value),
179             value_mode: ArgumentValueMode::Required,
180             short: Some(short),
181             long,
182             help,
183         }
184     }
185 
flag(long: &'static str, help: &'static str) -> Argument186     pub fn flag(long: &'static str, help: &'static str) -> Argument {
187         Argument {
188             value: None,
189             value_mode: ArgumentValueMode::Disallowed,
190             short: None,
191             long,
192             help,
193         }
194     }
195 
short_flag(short: char, long: &'static str, help: &'static str) -> Argument196     pub fn short_flag(short: char, long: &'static str, help: &'static str) -> Argument {
197         Argument {
198             value: None,
199             value_mode: ArgumentValueMode::Disallowed,
200             short: Some(short),
201             long,
202             help,
203         }
204     }
205 
flag_or_value(long: &'static str, value: &'static str, help: &'static str) -> Argument206     pub fn flag_or_value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
207         Argument {
208             value: Some(value),
209             value_mode: ArgumentValueMode::Optional,
210             short: None,
211             long,
212             help,
213         }
214     }
215 }
216 
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<()>,217 fn parse_arguments<I, R, F>(args: I, mut f: F) -> Result<()>
218 where
219     I: Iterator<Item = R>,
220     R: AsRef<str>,
221     F: FnMut(&str, Option<&str>) -> Result<()>,
222 {
223     enum State {
224         // Initial state at the start and after finishing a single argument/value.
225         Top,
226         // The remaining arguments are all positional.
227         Positional,
228         // The next string is the value for the argument `name`.
229         Value { name: String },
230     }
231     let mut s = State::Top;
232     for arg in args {
233         let arg = arg.as_ref();
234         loop {
235             let mut arg_consumed = true;
236             s = match s {
237                 State::Top => {
238                     if arg == "--" {
239                         State::Positional
240                     } else if arg.starts_with("--") {
241                         let param = arg.trim_start_matches('-');
242                         if param.contains('=') {
243                             let mut iter = param.splitn(2, '=');
244                             let name = iter.next().unwrap();
245                             let value = iter.next().unwrap();
246                             if name.is_empty() {
247                                 return Err(Error::Syntax(
248                                     "expected parameter name before `=`".to_owned(),
249                                 ));
250                             }
251                             if value.is_empty() {
252                                 return Err(Error::Syntax(
253                                     "expected parameter value after `=`".to_owned(),
254                                 ));
255                             }
256                             f(name, Some(value))?;
257                             State::Top
258                         } else {
259                             State::Value {
260                                 name: param.to_owned(),
261                             }
262                         }
263                     } else if arg.starts_with('-') {
264                         if arg.len() == 1 {
265                             return Err(Error::Syntax(
266                                 "expected argument short name after `-`".to_owned(),
267                             ));
268                         }
269                         let name = &arg[1..2];
270                         let value = if arg.len() > 2 { Some(&arg[2..]) } else { None };
271                         if let Err(e) = f(name, value) {
272                             if let Error::ExpectedValue(_) = e {
273                                 State::Value {
274                                     name: name.to_owned(),
275                                 }
276                             } else {
277                                 return Err(e);
278                             }
279                         } else {
280                             State::Top
281                         }
282                     } else {
283                         f("", Some(arg))?;
284                         State::Positional
285                     }
286                 }
287                 State::Positional => {
288                     f("", Some(arg))?;
289                     State::Positional
290                 }
291                 State::Value { name } => {
292                     if arg.starts_with('-') {
293                         arg_consumed = false;
294                         f(&name, None)?;
295                     } else if let Err(e) = f(&name, Some(arg)) {
296                         arg_consumed = false;
297                         f(&name, None).map_err(|_| e)?;
298                     }
299                     State::Top
300                 }
301             };
302 
303             if arg_consumed {
304                 break;
305             }
306         }
307     }
308 
309     // If we ran out of arguments while parsing the last parameter, which may be either a
310     // value parameter or a flag, try to parse it as a flag. This will produce "missing value"
311     // error if the parameter is in fact a value parameter, which is the desired outcome.
312     match s {
313         State::Value { name } => f(&name, None),
314         _ => Ok(()),
315     }
316 }
317 
318 /// Parses the given `args` against the list of know arguments `arg_list` and calls `f` with each
319 /// present argument and value if required.
320 ///
321 /// This function guarantees that only valid long argument names from `arg_list` are sent to the
322 /// callback `f`. It is also guaranteed that if an arg requires a value (i.e.
323 /// `arg.value.is_some()`), the value will be `Some` in the callbacks arguments. If the callback
324 /// returns `Err`, this function will end parsing and return that `Err`.
325 ///
326 /// 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<()>,327 pub fn set_arguments<I, R, F>(args: I, arg_list: &[Argument], mut f: F) -> Result<()>
328 where
329     I: Iterator<Item = R>,
330     R: AsRef<str>,
331     F: FnMut(&str, Option<&str>) -> Result<()>,
332 {
333     parse_arguments(args, |name, value| {
334         let mut matches = None;
335         for arg in arg_list {
336             if let Some(short) = arg.short {
337                 if name.len() == 1 && name.starts_with(short) {
338                     if value.is_some() != arg.value.is_some() {
339                         return Err(Error::ExpectedValue(short.to_string()));
340                     }
341                     matches = Some(arg.long);
342                 }
343             }
344             if matches.is_none() && arg.long == name {
345                 if value.is_none() && arg.value_mode == ArgumentValueMode::Required {
346                     return Err(Error::ExpectedValue(arg.long.to_owned()));
347                 }
348                 if value.is_some() && arg.value_mode == ArgumentValueMode::Disallowed {
349                     return Err(Error::UnexpectedValue(arg.long.to_owned()));
350                 }
351                 matches = Some(arg.long);
352             }
353         }
354         match matches {
355             Some(long) => f(long, value),
356             None => Err(Error::UnknownArgument(name.to_owned())),
357         }
358     })
359 }
360 
361 /// Prints command line usage information to stdout.
362 ///
363 /// Usage information is printed according to the help fields in `args` with a leading usage line.
364 /// The usage line is of the format "`program_name` \[ARGUMENTS\] `required_arg`".
print_help(program_name: &str, required_arg: &str, args: &[Argument])365 pub fn print_help(program_name: &str, required_arg: &str, args: &[Argument]) {
366     println!(
367         "Usage: {} {}{}\n",
368         program_name,
369         if args.is_empty() { "" } else { "[ARGUMENTS] " },
370         required_arg
371     );
372     if args.is_empty() {
373         return;
374     }
375     println!("Argument{}:", if args.len() > 1 { "s" } else { "" });
376     for arg in args {
377         match arg.short {
378             Some(s) => print!(" -{}, ", s),
379             None => print!("     "),
380         }
381         if arg.long.is_empty() {
382             print!("  ");
383         } else {
384             print!("--");
385         }
386         print!("{:<12}", arg.long);
387         if let Some(v) = arg.value {
388             if arg.long.is_empty() {
389                 print!(" ");
390             } else {
391                 print!("=");
392             }
393             print!("{:<10}", v);
394         } else {
395             print!("{:<11}", "");
396         }
397         println!("{}", arg.help);
398     }
399 }
400 
parse_hex_or_decimal(maybe_hex_string: &str) -> Result<u64>401 pub fn parse_hex_or_decimal(maybe_hex_string: &str) -> Result<u64> {
402     // Parse string starting with 0x as hex and others as numbers.
403     if let Some(hex_string) = maybe_hex_string.strip_prefix("0x") {
404         u64::from_str_radix(hex_string, 16)
405     } else if let Some(hex_string) = maybe_hex_string.strip_prefix("0X") {
406         u64::from_str_radix(hex_string, 16)
407     } else {
408         u64::from_str(maybe_hex_string)
409     }
410     .map_err(|e| Error::InvalidValue {
411         value: maybe_hex_string.to_string(),
412         expected: e.to_string(),
413     })
414 }
415 
416 pub struct KeyValuePair<'a> {
417     context: &'a str,
418     key: &'a str,
419     value: Option<&'a str>,
420 }
421 
422 impl<'a> KeyValuePair<'a> {
handle_parse_err<T, E: std::error::Error>( &self, result: std::result::Result<T, E>, ) -> Result<T>423     fn handle_parse_err<T, E: std::error::Error>(
424         &self,
425         result: std::result::Result<T, E>,
426     ) -> Result<T> {
427         result.map_err(|e| {
428             self.invalid_value_err(format!(
429                 "Failed to parse parameter `{}` as {}: {}",
430                 self.key,
431                 std::any::type_name::<T>(),
432                 e
433             ))
434         })
435     }
436 
key(&self) -> &'a str437     pub fn key(&self) -> &'a str {
438         self.key
439     }
440 
value(&self) -> Result<&'a str>441     pub fn value(&self) -> Result<&'a str> {
442         self.value.ok_or(Error::ExpectedValue(format!(
443             "{}: parameter `{}` requires a value",
444             self.context, self.key
445         )))
446     }
447 
get_numeric<T>(&self, val: &str) -> Result<T> where T: TryFrom<u64>, <T as TryFrom<u64>>::Error: std::error::Error,448     fn get_numeric<T>(&self, val: &str) -> Result<T>
449     where
450         T: TryFrom<u64>,
451         <T as TryFrom<u64>>::Error: std::error::Error,
452     {
453         let num = parse_hex_or_decimal(val)?;
454         self.handle_parse_err(T::try_from(num))
455     }
456 
parse_numeric<T>(&self) -> Result<T> where T: TryFrom<u64>, <T as TryFrom<u64>>::Error: std::error::Error,457     pub fn parse_numeric<T>(&self) -> Result<T>
458     where
459         T: TryFrom<u64>,
460         <T as TryFrom<u64>>::Error: std::error::Error,
461     {
462         let val = self.value()?;
463         self.get_numeric(val)
464     }
465 
key_numeric<T>(&self) -> Result<T> where T: TryFrom<u64>, <T as TryFrom<u64>>::Error: std::error::Error,466     pub fn key_numeric<T>(&self) -> Result<T>
467     where
468         T: TryFrom<u64>,
469         <T as TryFrom<u64>>::Error: std::error::Error,
470     {
471         self.get_numeric(self.key())
472     }
473 
parse<T>(&self) -> Result<T> where T: FromStr, <T as FromStr>::Err: std::error::Error,474     pub fn parse<T>(&self) -> Result<T>
475     where
476         T: FromStr,
477         <T as FromStr>::Err: std::error::Error,
478     {
479         self.handle_parse_err(T::from_str(self.value()?))
480     }
481 
parse_or<T>(&self, default: T) -> Result<T> where T: FromStr, <T as FromStr>::Err: std::error::Error,482     pub fn parse_or<T>(&self, default: T) -> Result<T>
483     where
484         T: FromStr,
485         <T as FromStr>::Err: std::error::Error,
486     {
487         match self.value {
488             Some(v) => self.handle_parse_err(T::from_str(v)),
489             None => Ok(default),
490         }
491     }
492 
invalid_key_err(&self) -> Error493     pub fn invalid_key_err(&self) -> Error {
494         Error::UnknownArgument(format!(
495             "{}: Unknown parameter `{}`",
496             self.context, self.key
497         ))
498     }
499 
invalid_value_err(&self, description: String) -> Error500     pub fn invalid_value_err(&self, description: String) -> Error {
501         Error::InvalidValue {
502             value: self
503                 .value
504                 .expect("invalid value error without value")
505                 .to_string(),
506             expected: format!("{}: {}", self.context, description),
507         }
508     }
509 }
510 
511 /// Parse a string of delimiter-separated key-value options. This is intended to simplify parsing
512 /// of command-line options that take a bunch of parameters encoded into the argument, e.g. for
513 /// setting up an emulated hardware device. Returns an Iterator of KeyValuePair, which provides
514 /// convenience functions to parse numeric values and performs appropriate error handling.
515 ///
516 /// `flagname` - name of the command line parameter, used as context in error messages
517 /// `s` - the string to parse
518 /// `delimiter` - the character that separates individual pairs
519 ///
520 /// Usage example:
521 /// ```
522 /// # use crosvm::argument::{Result, parse_key_value_options};
523 ///
524 /// fn parse_turbo_button_parameters(s: &str) -> Result<(String, u32, bool)> {
525 ///     let mut color = String::new();
526 ///     let mut speed = 0u32;
527 ///     let mut turbo = false;
528 ///
529 ///     for opt in parse_key_value_options("turbo-button", s, ',') {
530 ///         match opt.key() {
531 ///             "color" => color = opt.value()?.to_string(),
532 ///             "speed" => speed = opt.parse_numeric::<u32>()?,
533 ///             "turbo" => turbo = opt.parse_or::<bool>(true)?,
534 ///             _ => return Err(opt.invalid_key_err()),
535 ///         }
536 ///     }
537 ///
538 ///     Ok((color, speed, turbo))
539 /// }
540 ///
541 /// assert_eq!(parse_turbo_button_parameters("color=red,speed=0xff,turbo").unwrap(),
542 ///            ("red".to_string(), 0xff, true))
543 /// ```
544 ///
545 /// TODO: upgrade `delimiter` to generic Pattern support once that has been stabilized
546 /// at <https://github.com/rust-lang/rust/issues/27721>.
parse_key_value_options<'a>( flagname: &'a str, s: &'a str, delimiter: char, ) -> impl Iterator<Item = KeyValuePair<'a>>547 pub fn parse_key_value_options<'a>(
548     flagname: &'a str,
549     s: &'a str,
550     delimiter: char,
551 ) -> impl Iterator<Item = KeyValuePair<'a>> {
552     s.split(delimiter)
553         .map(|frag| frag.splitn(2, '='))
554         .map(move |mut kv| KeyValuePair {
555             context: flagname,
556             key: kv.next().unwrap_or(""),
557             value: kv.next(),
558         })
559 }
560 
561 #[cfg(test)]
562 mod tests {
563     use super::*;
564 
565     #[test]
request_help()566     fn request_help() {
567         let arguments = [Argument::short_flag('h', "help", "Print help message.")];
568 
569         let match_res = set_arguments(["-h"].iter(), &arguments[..], |name, _| match name {
570             "help" => Err(Error::PrintHelp),
571             _ => unreachable!(),
572         });
573         match match_res {
574             Err(Error::PrintHelp) => {}
575             _ => unreachable!(),
576         }
577     }
578 
579     #[test]
mixed_args()580     fn mixed_args() {
581         let arguments = [
582             Argument::positional("FILES", "files to operate on"),
583             Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"),
584             Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"),
585             Argument::flag("unmount", "Unmount the root"),
586             Argument::short_flag('h', "help", "Print help message."),
587         ];
588 
589         let mut unmount = false;
590         let match_res = set_arguments(
591             ["--cpus", "3", "--program", "hello", "--unmount", "file"].iter(),
592             &arguments[..],
593             |name, value| {
594                 match name {
595                     "" => assert_eq!(value.unwrap(), "file"),
596                     "program" => assert_eq!(value.unwrap(), "hello"),
597                     "cpus" => {
598                         let c: u32 = value.unwrap().parse().map_err(|_| Error::InvalidValue {
599                             value: value.unwrap().to_owned(),
600                             expected: String::from("this value for `cpus` needs to be integer"),
601                         })?;
602                         assert_eq!(c, 3);
603                     }
604                     "unmount" => unmount = true,
605                     "help" => return Err(Error::PrintHelp),
606                     _ => unreachable!(),
607                 };
608                 Ok(())
609             },
610         );
611         assert!(match_res.is_ok());
612         assert!(unmount);
613     }
614 
615     #[test]
name_value_pair()616     fn name_value_pair() {
617         let arguments = [Argument::short_value(
618             'c',
619             "cpus",
620             "N",
621             "Number of CPUs to use. (default: 1)",
622         )];
623         let match_res = set_arguments(
624             ["-c", "5", "--cpus", "5", "-c5", "--cpus=5"].iter(),
625             &arguments[..],
626             |name, value| {
627                 assert_eq!(name, "cpus");
628                 assert_eq!(value, Some("5"));
629                 Ok(())
630             },
631         );
632         assert!(match_res.is_ok());
633         let not_match_res = set_arguments(
634             ["-c", "5", "--cpus"].iter(),
635             &arguments[..],
636             |name, value| {
637                 assert_eq!(name, "cpus");
638                 assert_eq!(value, Some("5"));
639                 Ok(())
640             },
641         );
642         assert!(not_match_res.is_err());
643     }
644 
645     #[test]
flag_or_value()646     fn flag_or_value() {
647         let run_case = |args| -> Option<String> {
648             let arguments = [
649                 Argument::positional("FILES", "files to operate on"),
650                 Argument::flag_or_value("gpu", "[2D|3D]", "Enable or configure gpu"),
651                 Argument::flag("foo", "Enable foo."),
652                 Argument::value("bar", "stuff", "Configure bar."),
653             ];
654 
655             let mut gpu_value: Option<String> = None;
656             let match_res =
657                 set_arguments(args, &arguments[..], |name: &str, value: Option<&str>| {
658                     match name {
659                         "" => assert_eq!(value.unwrap(), "file1"),
660                         "foo" => assert!(value.is_none()),
661                         "bar" => assert_eq!(value.unwrap(), "stuff"),
662                         "gpu" => match value {
663                             Some(v) => match v {
664                                 "2D" | "3D" => {
665                                     gpu_value = Some(v.to_string());
666                                 }
667                                 _ => {
668                                     return Err(Error::InvalidValue {
669                                         value: v.to_string(),
670                                         expected: String::from("2D or 3D"),
671                                     })
672                                 }
673                             },
674                             None => {
675                                 gpu_value = None;
676                             }
677                         },
678                         _ => unreachable!(),
679                     };
680                     Ok(())
681                 });
682 
683             assert!(match_res.is_ok());
684             gpu_value
685         };
686 
687         // Used as flag and followed by positional
688         assert_eq!(run_case(["--gpu", "file1"].iter()), None);
689         // Used as flag and followed by flag
690         assert_eq!(run_case(["--gpu", "--foo", "file1",].iter()), None);
691         // Used as flag and followed by value
692         assert_eq!(run_case(["--gpu", "--bar=stuff", "file1"].iter()), None);
693 
694         // Used as value and followed by positional
695         assert_eq!(run_case(["--gpu=2D", "file1"].iter()).unwrap(), "2D");
696         // Used as value and followed by flag
697         assert_eq!(run_case(["--gpu=2D", "--foo"].iter()).unwrap(), "2D");
698         // Used as value and followed by value
699         assert_eq!(
700             run_case(["--gpu=2D", "--bar=stuff", "file1"].iter()).unwrap(),
701             "2D"
702         );
703     }
704 
705     #[test]
parse_key_value_options_simple()706     fn parse_key_value_options_simple() {
707         let mut opts = parse_key_value_options("test", "fruit=apple,number=13,flag,hex=0x123", ',');
708 
709         let kv1 = opts.next().unwrap();
710         assert_eq!(kv1.key(), "fruit");
711         assert_eq!(kv1.value().unwrap(), "apple");
712 
713         let kv2 = opts.next().unwrap();
714         assert_eq!(kv2.key(), "number");
715         assert_eq!(kv2.parse::<u32>().unwrap(), 13);
716 
717         let kv3 = opts.next().unwrap();
718         assert_eq!(kv3.key(), "flag");
719         assert!(kv3.value().is_err());
720         assert!(kv3.parse_or::<bool>(true).unwrap());
721 
722         let kv4 = opts.next().unwrap();
723         assert_eq!(kv4.key(), "hex");
724         assert_eq!(kv4.parse_numeric::<u32>().unwrap(), 0x123);
725 
726         assert!(opts.next().is_none());
727     }
728 
729     #[test]
parse_key_value_options_overflow()730     fn parse_key_value_options_overflow() {
731         let mut opts = parse_key_value_options("test", "key=1000000000000000", ',');
732         let kv = opts.next().unwrap();
733         assert!(kv.parse::<u32>().is_err());
734         assert!(kv.parse_numeric::<u32>().is_err());
735     }
736 
737     #[test]
parse_hex_or_decimal_simple()738     fn parse_hex_or_decimal_simple() {
739         assert_eq!(parse_hex_or_decimal("15").unwrap(), 15);
740         assert_eq!(parse_hex_or_decimal("0x15").unwrap(), 0x15);
741         assert_eq!(parse_hex_or_decimal("0X15").unwrap(), 0x15);
742         assert!(parse_hex_or_decimal("0xz").is_err());
743         assert!(parse_hex_or_decimal("hello world").is_err());
744     }
745 
746     #[test]
parse_key_value_options_numeric_key()747     fn parse_key_value_options_numeric_key() {
748         let mut opts = parse_key_value_options("test", "0x30,0x100=value,nonnumeric=value", ',');
749         let kv = opts.next().unwrap();
750         assert_eq!(kv.key_numeric::<u32>().unwrap(), 0x30);
751 
752         let kv = opts.next().unwrap();
753         assert_eq!(kv.key_numeric::<u32>().unwrap(), 0x100);
754         assert_eq!(kv.value().unwrap(), "value");
755 
756         let kv = opts.next().unwrap();
757         assert!(kv.key_numeric::<u32>().is_err());
758         assert_eq!(kv.key(), "nonnumeric");
759     }
760 }
761