diff --git a/src/lib.rs b/src/lib.rs index 99a24622..bfdda33e 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,12 +858,25 @@ 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() } + /// 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 f0774f3f..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 @@ -1188,3 +1188,53 @@ 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("e", "enact", "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 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]); +} + +#[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())]); +}