From f84e91292135e0ae85fbf6a5e4f46a74c8df3a0b Mon Sep 17 00:00:00 2001 From: varkor Date: Sat, 27 Apr 2019 15:08:55 +0100 Subject: [PATCH 1/3] Add Matches::opt_positions --- src/lib.rs | 31 +++++++++++++++++++------------ src/tests/mod.rs | 22 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 99a24622..c9f2118d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -354,7 +354,7 @@ impl Options { let mut vals = (0..opts.len()) .map(|_| Vec::new()) - .collect::>>(); + .collect::>>(); let mut free: Vec = Vec::new(); let args = args .into_iter() @@ -365,6 +365,7 @@ impl Options { .map(|s| s.to_owned()) }).collect::<::std::result::Result, _>>()?; let mut args = args.into_iter().peekable(); + let mut arg_pos = 0; while let Some(cur) = args.next() { if !is_arg(&cur) { free.push(cur); @@ -440,7 +441,7 @@ impl Options { if name_pos == names.len() && i_arg.is_some() { return Err(UnexpectedArgument(nm.to_string())); } - vals[optid].push(Given); + vals[optid].push((arg_pos, Given)); } Maybe => { // Note that here we do not handle `--arg value`. @@ -450,21 +451,21 @@ impl Options { // option at the end of the arguments when // FloatingFrees is in use. if let Some(i_arg) = i_arg.take() { - vals[optid].push(Val(i_arg)); + vals[optid].push((arg_pos, Val(i_arg))); } else if was_long || name_pos < names.len() || args.peek().map_or(true, |n| is_arg(&n)) { - vals[optid].push(Given); + vals[optid].push((arg_pos, Given)); } else { - vals[optid].push(Val(args.next().unwrap())); + vals[optid].push((arg_pos, Val(args.next().unwrap()))); } } Yes => { if let Some(i_arg) = i_arg.take() { - vals[optid].push(Val(i_arg)); + vals[optid].push((arg_pos, Val(i_arg))); } else if let Some(n) = args.next() { - vals[optid].push(Val(n)); + vals[optid].push((arg_pos, Val(n))); } else { return Err(ArgumentMissing(nm.to_string())); } @@ -472,6 +473,7 @@ impl Options { } } } + arg_pos += 1; } debug_assert_eq!(vals.len(), opts.len()); for (vals, opt) in vals.iter().zip(opts.iter()) { @@ -701,8 +703,8 @@ enum Optval { pub struct Matches { /// Options that matched opts: Vec, - /// Values of the Options that matched - vals: Vec>, + /// Values of the Options that matched and their positions + vals: Vec>, /// Free string fragments pub free: Vec, } @@ -799,7 +801,7 @@ impl OptGroup { } impl Matches { - fn opt_vals(&self, nm: &str) -> Vec { + fn opt_vals(&self, nm: &str) -> Vec<(usize, Optval)> { match find_opt(&self.opts, &Name::from_str(nm)) { Some(id) => self.vals[id].clone(), None => panic!("No option '{}' defined", nm), @@ -807,7 +809,7 @@ impl Matches { } fn opt_val(&self, nm: &str) -> Option { - self.opt_vals(nm).into_iter().next() + self.opt_vals(nm).into_iter().map(|(_, o)| o).next() } /// Returns true if an option was defined pub fn opt_defined(&self, nm: &str) -> bool { @@ -824,6 +826,11 @@ impl Matches { self.opt_vals(nm).len() } + /// Returns a vector of all the positions in which an option was matched. + pub fn opt_positions(&self, nm: &str) -> Vec { + self.opt_vals(nm).into_iter().map(|(pos, _)| pos).collect() + } + /// Returns true if any of several options were matched. pub fn opts_present(&self, names: &[String]) -> bool { names @@ -851,7 +858,7 @@ impl Matches { pub fn opt_strs(&self, nm: &str) -> Vec { self.opt_vals(nm) .into_iter() - .filter_map(|v| match v { + .filter_map(|(_, v)| match v { Val(s) => Some(s), _ => None, }).collect() diff --git a/src/tests/mod.rs b/src/tests/mod.rs index f0774f3f..f6abd619 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1188,3 +1188,25 @@ fn test_opt_get_default() { let p_arg = matches.opt_get_default("p", 10.2); assert_eq!(p_arg, Ok(1.1)); } + +#[test] +fn test_opt_positions() { + let mut opts = Options::new(); + opts.optflagmulti("a", "act", "Description"); + opts.optflagmulti("r", "react", "Description"); + + let args: Vec = ["-a", "-a", "-r", "-a", "-r", "-r"] + .iter() + .map(|x| x.to_string()) + .collect(); + + let matches = &match opts.parse(&args) { + Ok(m) => m, + Err(e) => panic!("{}", e), + }; + + let a_pos = matches.opt_positions("a"); + assert_eq!(a_pos, vec![0, 1, 3]); + let b_pos = matches.opt_positions("r"); + assert_eq!(b_pos, vec![2, 4, 5]); +} From c8b554190a7d064d27582b63d5e097aeebd35112 Mon Sep 17 00:00:00 2001 From: varkor Date: Sat, 27 Apr 2019 15:33:36 +0100 Subject: [PATCH 2/3] Add a test for empty opt_positions --- src/tests/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tests/mod.rs b/src/tests/mod.rs index f6abd619..aa763a6d 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1193,6 +1193,7 @@ fn test_opt_get_default() { fn test_opt_positions() { let mut opts = Options::new(); opts.optflagmulti("a", "act", "Description"); + opts.optflagmulti("e", "enact", "Description"); opts.optflagmulti("r", "react", "Description"); let args: Vec = ["-a", "-a", "-r", "-a", "-r", "-r"] @@ -1207,6 +1208,8 @@ fn test_opt_positions() { let a_pos = matches.opt_positions("a"); assert_eq!(a_pos, vec![0, 1, 3]); - let b_pos = matches.opt_positions("r"); - assert_eq!(b_pos, vec![2, 4, 5]); + let e_pos = matches.opt_positions("e"); + assert_eq!(e_pos, vec![]); + let r_pos = matches.opt_positions("r"); + assert_eq!(r_pos, vec![2, 4, 5]); } From 4b605192daff595739f2ff4685d5bb5db9c0418a Mon Sep 17 00:00:00 2001 From: varkor Date: Sat, 27 Apr 2019 16:45:17 +0100 Subject: [PATCH 3/3] Add Matches::opt_strs_pos --- src/lib.rs | 13 +++++++++++++ src/tests/mod.rs | 27 ++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c9f2118d..bfdda33e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -864,6 +864,19 @@ impl Matches { }).collect() } + /// Returns a vector of the arguments provided to all matches of the given + /// option, together with their positions. + /// + /// Used when an option accepts multiple values. + pub fn opt_strs_pos(&self, nm: &str) -> Vec<(usize, String)> { + self.opt_vals(nm) + .into_iter() + .filter_map(|(p, v)| match v { + Val(s) => Some((p, s)), + _ => None, + }).collect() + } + /// Returns the string argument supplied to a matching option or `None`. pub fn opt_str(&self, nm: &str) -> Option { match self.opt_val(nm) { diff --git a/src/tests/mod.rs b/src/tests/mod.rs index aa763a6d..c61beaa5 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -909,7 +909,7 @@ Options: -c, --brûlée brûlée quite long description -k, --kiwi€ kiwi description -o, --orange‹ orange description - -r, --raspberry-but-making-this-option-way-too-long + -r, --raspberry-but-making-this-option-way-too-long\u{0020} raspberry description is also quite long indeed longer than every other piece of text we might encounter here and thus will be automatically broken up @@ -1213,3 +1213,28 @@ fn test_opt_positions() { let r_pos = matches.opt_positions("r"); assert_eq!(r_pos, vec![2, 4, 5]); } + +#[test] +fn test_opt_strs_pos() { + let mut opts = Options::new(); + opts.optmulti("a", "act", "Description", "NUM"); + opts.optmulti("e", "enact", "Description", "NUM"); + opts.optmulti("r", "react", "Description", "NUM"); + + let args: Vec = ["-a1", "-a2", "-r3", "-a4", "-r5", "-r6"] + .iter() + .map(|x| x.to_string()) + .collect(); + + let matches = &match opts.parse(&args) { + Ok(m) => m, + Err(e) => panic!("{}", e), + }; + + let a_pos = matches.opt_strs_pos("a"); + assert_eq!(a_pos, vec![(0, "1".to_string()), (1, "2".to_string()), (3, "4".to_string())]); + let e_pos = matches.opt_strs_pos("e"); + assert_eq!(e_pos, vec![]); + let r_pos = matches.opt_strs_pos("r"); + assert_eq!(r_pos, vec![(2, "3".to_string()), (4, "5".to_string()), (5, "6".to_string())]); +}