From 1b527570f98f7a5a7828368dd3bb076402a7f90f Mon Sep 17 00:00:00 2001 From: Zdenek Crha Date: Fri, 24 Jul 2020 20:30:55 +0200 Subject: [PATCH] Add support for detecting args after double-dash The crate provides support for detecting and ignoring double-dash ("--") argument passed from command line, but it does not provide any information about its presence or position to user. For applications that may want to pass arguments after double-dash to child process is such indication critical. Add optional field `args_end` into Matches structure storing position of first argument after double-dash (if there is any) and method to provide user access to the data. When checking for double-dash positin, it is important to make sure that there actually are some arguments after it. Otherwise the position would point to non-existent index in `free` list and would require additional unnecessary checks on user side. --- src/lib.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++- src/tests/mod.rs | 29 +++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 51ba7787..89354be0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -347,6 +347,8 @@ impl Options { .map(|_| Vec::new()) .collect::>>(); let mut free: Vec = Vec::new(); + let mut args_end = None; + let args = args .into_iter() .map(|i| { @@ -369,6 +371,7 @@ impl Options { } } } else if cur == "--" { + args_end = Some(free.len()); free.extend(args); break; } else { @@ -471,7 +474,12 @@ impl Options { return Err(OptionDuplicated(opt.name.to_string())); } } - Ok(Matches { opts, vals, free }) + + // Note that if "--" is last argument on command line, then index stored + // in option does not exist in `free` and must be replaced with `None` + args_end = args_end.filter(|pos| pos != &free.len()); + + Ok(Matches { opts, vals, free, args_end }) } /// Derive a short one-line usage summary from a set of long options. @@ -692,8 +700,12 @@ pub struct Matches { opts: Vec, /// Values of the Options that matched and their positions vals: Vec>, + /// Free string fragments pub free: Vec, + + /// Index of first free fragment after "--" separator + args_end: Option, } /// The type returned when the command line does not conform to the @@ -1018,6 +1030,43 @@ impl Matches { None => Ok(def), } } + + /// Returns index of first free argument after "--". + /// + /// If double-dash separator is present and there are some args after it in + /// the argument list then the method returns index into `free` vector + /// indicating first argument after it. + /// behind it. + /// + /// # Examples + /// + /// ``` + /// # use getopts::Options; + /// let mut opts = Options::new(); + /// + /// let matches = opts.parse(&vec!["arg1", "--", "arg2"]).unwrap(); + /// let end_pos = matches.free_trailing_start().unwrap(); + /// assert_eq!(end_pos, 1); + /// assert_eq!(matches.free[end_pos], "arg2".to_owned()); + /// ``` + /// + /// If the double-dash is missing from argument list or if there are no + /// arguments after it: + /// + /// ``` + /// # use getopts::Options; + /// let mut opts = Options::new(); + /// + /// let matches = opts.parse(&vec!["arg1", "--"]).unwrap(); + /// assert_eq!(matches.free_trailing_start(), None); + /// + /// let matches = opts.parse(&vec!["arg1", "arg2"]).unwrap(); + /// assert_eq!(matches.free_trailing_start(), None); + /// ``` + /// + pub fn free_trailing_start(&self) -> Option { + self.args_end + } } fn is_arg(arg: &str) -> bool { diff --git a/src/tests/mod.rs b/src/tests/mod.rs index d578e843..f1eb9410 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -207,12 +207,24 @@ fn test_optflag_missing() { } #[test] -fn test_opt_end() { +fn test_free_trailing_missing() { + let args = vec![] as Vec; + match Options::new().parse(&args) { + Ok(ref m) => { + assert_eq!(m.free_trailing_start(), None); + } + _ => panic!(), + } +} + +#[test] +fn test_free_trailing() { let args = vec!["--".to_owned(), "-t".to_owned()]; match Options::new().optflag("t", "test", "testing").parse(&args) { Ok(ref m) => { assert!(!m.opt_present("test")); assert!(!m.opt_present("t")); + assert_eq!(m.free_trailing_start(), Some(0)); assert_eq!(m.free.len(), 1); assert_eq!(m.free[0], "-t"); } @@ -221,18 +233,31 @@ fn test_opt_end() { } #[test] -fn test_opt_only_end() { +fn test_free_trailing_only() { let args = vec!["--".to_owned()]; match Options::new().optflag("t", "test", "testing").parse(&args) { Ok(ref m) => { assert!(!m.opt_present("test")); assert!(!m.opt_present("t")); + assert_eq!(m.free_trailing_start(), None); assert_eq!(m.free.len(), 0); } _ => panic!(), } } +#[test] +fn test_free_trailing_args() { + let args = vec!["pre".to_owned(), "--".to_owned(), "post".to_owned() ]; + match Options::new().parse(&args) { + Ok(ref m) => { + assert_eq!(m.free_trailing_start(), Some(1)); + assert_eq!(m.free.len(), 2); + } + _ => panic!(), + } +} + #[test] fn test_optflag_long_arg() { let args = vec!["--test=20".to_string()];