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