Skip to content

Commit b96d6e6

Browse files
authored
Merge pull request #79 from varkor/opt_positions
Add methods to `Matches` to support getting the index of an argument
2 parents 87e60a0 + 4b60519 commit b96d6e6

File tree

2 files changed

+83
-13
lines changed

2 files changed

+83
-13
lines changed

src/lib.rs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ impl Options {
354354

355355
let mut vals = (0..opts.len())
356356
.map(|_| Vec::new())
357-
.collect::<Vec<Vec<Optval>>>();
357+
.collect::<Vec<Vec<(usize, Optval)>>>();
358358
let mut free: Vec<String> = Vec::new();
359359
let args = args
360360
.into_iter()
@@ -365,6 +365,7 @@ impl Options {
365365
.map(|s| s.to_owned())
366366
}).collect::<::std::result::Result<Vec<_>, _>>()?;
367367
let mut args = args.into_iter().peekable();
368+
let mut arg_pos = 0;
368369
while let Some(cur) = args.next() {
369370
if !is_arg(&cur) {
370371
free.push(cur);
@@ -440,7 +441,7 @@ impl Options {
440441
if name_pos == names.len() && i_arg.is_some() {
441442
return Err(UnexpectedArgument(nm.to_string()));
442443
}
443-
vals[optid].push(Given);
444+
vals[optid].push((arg_pos, Given));
444445
}
445446
Maybe => {
446447
// Note that here we do not handle `--arg value`.
@@ -450,28 +451,29 @@ impl Options {
450451
// option at the end of the arguments when
451452
// FloatingFrees is in use.
452453
if let Some(i_arg) = i_arg.take() {
453-
vals[optid].push(Val(i_arg));
454+
vals[optid].push((arg_pos, Val(i_arg)));
454455
} else if was_long
455456
|| name_pos < names.len()
456457
|| args.peek().map_or(true, |n| is_arg(&n))
457458
{
458-
vals[optid].push(Given);
459+
vals[optid].push((arg_pos, Given));
459460
} else {
460-
vals[optid].push(Val(args.next().unwrap()));
461+
vals[optid].push((arg_pos, Val(args.next().unwrap())));
461462
}
462463
}
463464
Yes => {
464465
if let Some(i_arg) = i_arg.take() {
465-
vals[optid].push(Val(i_arg));
466+
vals[optid].push((arg_pos, Val(i_arg)));
466467
} else if let Some(n) = args.next() {
467-
vals[optid].push(Val(n));
468+
vals[optid].push((arg_pos, Val(n)));
468469
} else {
469470
return Err(ArgumentMissing(nm.to_string()));
470471
}
471472
}
472473
}
473474
}
474475
}
476+
arg_pos += 1;
475477
}
476478
debug_assert_eq!(vals.len(), opts.len());
477479
for (vals, opt) in vals.iter().zip(opts.iter()) {
@@ -701,8 +703,8 @@ enum Optval {
701703
pub struct Matches {
702704
/// Options that matched
703705
opts: Vec<Opt>,
704-
/// Values of the Options that matched
705-
vals: Vec<Vec<Optval>>,
706+
/// Values of the Options that matched and their positions
707+
vals: Vec<Vec<(usize, Optval)>>,
706708
/// Free string fragments
707709
pub free: Vec<String>,
708710
}
@@ -799,15 +801,15 @@ impl OptGroup {
799801
}
800802

801803
impl Matches {
802-
fn opt_vals(&self, nm: &str) -> Vec<Optval> {
804+
fn opt_vals(&self, nm: &str) -> Vec<(usize, Optval)> {
803805
match find_opt(&self.opts, &Name::from_str(nm)) {
804806
Some(id) => self.vals[id].clone(),
805807
None => panic!("No option '{}' defined", nm),
806808
}
807809
}
808810

809811
fn opt_val(&self, nm: &str) -> Option<Optval> {
810-
self.opt_vals(nm).into_iter().next()
812+
self.opt_vals(nm).into_iter().map(|(_, o)| o).next()
811813
}
812814
/// Returns true if an option was defined
813815
pub fn opt_defined(&self, nm: &str) -> bool {
@@ -824,6 +826,11 @@ impl Matches {
824826
self.opt_vals(nm).len()
825827
}
826828

829+
/// Returns a vector of all the positions in which an option was matched.
830+
pub fn opt_positions(&self, nm: &str) -> Vec<usize> {
831+
self.opt_vals(nm).into_iter().map(|(pos, _)| pos).collect()
832+
}
833+
827834
/// Returns true if any of several options were matched.
828835
pub fn opts_present(&self, names: &[String]) -> bool {
829836
names
@@ -851,12 +858,25 @@ impl Matches {
851858
pub fn opt_strs(&self, nm: &str) -> Vec<String> {
852859
self.opt_vals(nm)
853860
.into_iter()
854-
.filter_map(|v| match v {
861+
.filter_map(|(_, v)| match v {
855862
Val(s) => Some(s),
856863
_ => None,
857864
}).collect()
858865
}
859866

867+
/// Returns a vector of the arguments provided to all matches of the given
868+
/// option, together with their positions.
869+
///
870+
/// Used when an option accepts multiple values.
871+
pub fn opt_strs_pos(&self, nm: &str) -> Vec<(usize, String)> {
872+
self.opt_vals(nm)
873+
.into_iter()
874+
.filter_map(|(p, v)| match v {
875+
Val(s) => Some((p, s)),
876+
_ => None,
877+
}).collect()
878+
}
879+
860880
/// Returns the string argument supplied to a matching option or `None`.
861881
pub fn opt_str(&self, nm: &str) -> Option<String> {
862882
match self.opt_val(nm) {

src/tests/mod.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,7 @@ Options:
909909
-c, --brûlée brûlée quite long description
910910
-k, --kiwi€ kiwi description
911911
-o, --orange‹ orange description
912-
-r, --raspberry-but-making-this-option-way-too-long
912+
-r, --raspberry-but-making-this-option-way-too-long\u{0020}
913913
raspberry description is also quite long indeed longer
914914
than every other piece of text we might encounter here
915915
and thus will be automatically broken up
@@ -1188,3 +1188,53 @@ fn test_opt_get_default() {
11881188
let p_arg = matches.opt_get_default("p", 10.2);
11891189
assert_eq!(p_arg, Ok(1.1));
11901190
}
1191+
1192+
#[test]
1193+
fn test_opt_positions() {
1194+
let mut opts = Options::new();
1195+
opts.optflagmulti("a", "act", "Description");
1196+
opts.optflagmulti("e", "enact", "Description");
1197+
opts.optflagmulti("r", "react", "Description");
1198+
1199+
let args: Vec<String> = ["-a", "-a", "-r", "-a", "-r", "-r"]
1200+
.iter()
1201+
.map(|x| x.to_string())
1202+
.collect();
1203+
1204+
let matches = &match opts.parse(&args) {
1205+
Ok(m) => m,
1206+
Err(e) => panic!("{}", e),
1207+
};
1208+
1209+
let a_pos = matches.opt_positions("a");
1210+
assert_eq!(a_pos, vec![0, 1, 3]);
1211+
let e_pos = matches.opt_positions("e");
1212+
assert_eq!(e_pos, vec![]);
1213+
let r_pos = matches.opt_positions("r");
1214+
assert_eq!(r_pos, vec![2, 4, 5]);
1215+
}
1216+
1217+
#[test]
1218+
fn test_opt_strs_pos() {
1219+
let mut opts = Options::new();
1220+
opts.optmulti("a", "act", "Description", "NUM");
1221+
opts.optmulti("e", "enact", "Description", "NUM");
1222+
opts.optmulti("r", "react", "Description", "NUM");
1223+
1224+
let args: Vec<String> = ["-a1", "-a2", "-r3", "-a4", "-r5", "-r6"]
1225+
.iter()
1226+
.map(|x| x.to_string())
1227+
.collect();
1228+
1229+
let matches = &match opts.parse(&args) {
1230+
Ok(m) => m,
1231+
Err(e) => panic!("{}", e),
1232+
};
1233+
1234+
let a_pos = matches.opt_strs_pos("a");
1235+
assert_eq!(a_pos, vec![(0, "1".to_string()), (1, "2".to_string()), (3, "4".to_string())]);
1236+
let e_pos = matches.opt_strs_pos("e");
1237+
assert_eq!(e_pos, vec![]);
1238+
let r_pos = matches.opt_strs_pos("r");
1239+
assert_eq!(r_pos, vec![(2, "3".to_string()), (4, "5".to_string()), (5, "6".to_string())]);
1240+
}

0 commit comments

Comments
 (0)