Skip to content

Commit

Permalink
feat(Arg Indices): adds the ability to query argument value indices
Browse files Browse the repository at this point in the history
Adds the abiltiy to query the matches struct for the indices of values or flags. The index
is similar to that of an argv index, but not exactly a 1:1.

For flags (i.e. those arguments which don't have an associated value), indices refer
to occurrence of the switch, such as `-f`, or `--flag`. However, for options the indices
refer to the *values* `-o val` would therefore not represent two distinct indices, only the
index for `val` would be recorded. This is by design.

Besides the flag/option descrepancy, the primary difference between an argv index and clap
index, is that clap continues counting once all arguments have properly seperated, whereas
an argv index does not.

The examples should clear this up.

*NOTE:* If an argument is allowed multiple times, this method will only give the *first*
index.

The argv indices are listed in the comments below. See how they correspond to the clap
indices. Note that if it's not listed in a clap index, this is becuase it's not saved in
in an `ArgMatches` struct for querying.

```rust
let m = App::new("myapp")
    .arg(Arg::with_name("flag")
        .short("f"))
    .arg(Arg::with_name("option")
        .short("o")
        .takes_value(true))
    .get_matches_from(vec!["myapp", "-f", "-o", "val"]);
            // ARGV idices: ^0       ^1    ^2    ^3
            // clap idices:          ^1          ^3

assert_eq!(m.index_of("flag"), Some(1));
assert_eq!(m.index_of("option"), Some(3));
```

Now notice, if we use one of the other styles of options:

```rust
let m = App::new("myapp")
    .arg(Arg::with_name("flag")
        .short("f"))
    .arg(Arg::with_name("option")
        .short("o")
        .takes_value(true))
    .get_matches_from(vec!["myapp", "-f", "-o=val"]);
            // ARGV idices: ^0       ^1    ^2
            // clap idices:          ^1       ^3

assert_eq!(m.index_of("flag"), Some(1));
assert_eq!(m.index_of("option"), Some(3));
```

Things become much more complicated, or clear if we look at a more complex combination of
flags. Let's also throw in the final option style for good measure.

```rust
let m = App::new("myapp")
    .arg(Arg::with_name("flag")
        .short("f"))
    .arg(Arg::with_name("flag2")
        .short("F"))
    .arg(Arg::with_name("flag3")
        .short("z"))
    .arg(Arg::with_name("option")
        .short("o")
        .takes_value(true))
    .get_matches_from(vec!["myapp", "-fzF", "-oval"]);
            // ARGV idices: ^0      ^1       ^2
            // clap idices:         ^1,2,3    ^5
            //
            // clap sees the above as 'myapp -f -z -F -o val'
            //                         ^0    ^1 ^2 ^3 ^4 ^5
assert_eq!(m.index_of("flag"), Some(1));
assert_eq!(m.index_of("flag2"), Some(3));
assert_eq!(m.index_of("flag3"), Some(2));
assert_eq!(m.index_of("option"), Some(5));
```

One final combination of flags/options to see how they combine:

```rust
let m = App::new("myapp")
    .arg(Arg::with_name("flag")
        .short("f"))
    .arg(Arg::with_name("flag2")
        .short("F"))
    .arg(Arg::with_name("flag3")
        .short("z"))
    .arg(Arg::with_name("option")
        .short("o")
        .takes_value(true)
        .multiple(true))
    .get_matches_from(vec!["myapp", "-fzFoval"]);
            // ARGV idices: ^0       ^1
            // clap idices:          ^1,2,3^5
            //
            // clap sees the above as 'myapp -f -z -F -o val'
            //                         ^0    ^1 ^2 ^3 ^4 ^5
assert_eq!(m.index_of("flag"), Some(1));
assert_eq!(m.index_of("flag2"), Some(3));
assert_eq!(m.index_of("flag3"), Some(2));
assert_eq!(m.index_of("option"), Some(5));
```

The last part to mention is when values are sent in multiple groups with a [delimiter].

```rust
let m = App::new("myapp")
    .arg(Arg::with_name("option")
        .short("o")
        .takes_value(true)
        .multiple(true))
    .get_matches_from(vec!["myapp", "-o=val1,val2,val3"]);
            // ARGV idices: ^0       ^1
            // clap idices:             ^2   ^3   ^4
            //
            // clap sees the above as 'myapp -o val1 val2 val3'
            //                         ^0    ^1 ^2   ^3   ^4
assert_eq!(m.index_of("option"), Some(2));
```
  • Loading branch information
kbknapp committed Mar 4, 2018
1 parent 47ade6c commit f58d057
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 15 deletions.
21 changes: 21 additions & 0 deletions src/app/parser.rs
Expand Up @@ -8,6 +8,7 @@ use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf;
use std::slice::Iter;
use std::iter::Peekable;
use std::cell::Cell;

// Internal
use INTERNAL_ERROR_MSG;
Expand Down Expand Up @@ -67,6 +68,7 @@ where
cache: Option<&'a str>,
pub help_message: Option<&'a str>,
pub version_message: Option<&'a str>,
cur_idx: Cell<usize>,
}

impl<'a, 'b> Parser<'a, 'b>
Expand All @@ -77,6 +79,7 @@ where
Parser {
meta: AppMeta::with_name(n),
g_settings: AppFlags::zeroed(),
cur_idx: Cell::new(0),
..Default::default()
}
}
Expand Down Expand Up @@ -1552,6 +1555,10 @@ where
) -> ClapResult<ParseResult<'a>> {
// maybe here lifetime should be 'a
debugln!("Parser::parse_long_arg;");

// Update the curent index
self.cur_idx.set(self.cur_idx.get() + 1);

let mut val = None;
debug!("Parser::parse_long_arg: Does it contain '='...");
let arg = if full_arg.contains_byte(b'=') {
Expand Down Expand Up @@ -1635,6 +1642,10 @@ where
let mut ret = ParseResult::NotFound;
for c in arg.chars() {
debugln!("Parser::parse_short_arg:iter:{}", c);

// update each index because `-abcd` is four indices to clap
self.cur_idx.set(self.cur_idx.get() + 1);

// Check for matching short options, and return the name if there is no trailing
// concatenated value: -oval
// Option: -o
Expand Down Expand Up @@ -1810,12 +1821,20 @@ where
{
debugln!("Parser::add_single_val_to_arg;");
debugln!("Parser::add_single_val_to_arg: adding val...{:?}", v);

// update the current index because each value is a distinct index to clap
self.cur_idx.set(self.cur_idx.get() + 1);

// @TODO @docs @p4: docs for indices should probably note that a terminator isn't a value
// and therefore not reported in indices
if let Some(t) = arg.val_terminator() {
if t == v {
return Ok(ParseResult::ValuesDone);
}
}

matcher.add_val_to(arg.name(), v);
matcher.add_index_to(arg.name(), self.cur_idx.get());

// Increment or create the group "args"
if let Some(grps) = self.groups_for_arg(arg.name()) {
Expand All @@ -1838,6 +1857,8 @@ where
debugln!("Parser::parse_flag;");

matcher.inc_occurrence_of(flag.b.name);
matcher.add_index_to(flag.b.name, self.cur_idx.get());

// Increment or create the group "args"
self.groups_for_arg(flag.b.name)
.and_then(|vec| Some(matcher.inc_occurrences_of(&*vec)));
Expand Down
12 changes: 11 additions & 1 deletion src/args/arg_matcher.rs
Expand Up @@ -59,7 +59,7 @@ impl<'a> ArgMatcher<'a> {
if !aa.has_switch() || aa.is_set(ArgSettings::Multiple) {
// positional args can't override self or else we would never advance to the next

// Also flags with --multiple set are ignored otherwise we could never have more
// Also flags with --multiple set are ignored otherwise we could never have more
// than one
return;
}
Expand Down Expand Up @@ -172,11 +172,21 @@ impl<'a> ArgMatcher<'a> {
pub fn add_val_to(&mut self, arg: &'a str, val: &OsStr) {
let ma = self.entry(arg).or_insert(MatchedArg {
occurs: 0,
indices: Vec::with_capacity(1),
vals: Vec::with_capacity(1),
});
ma.vals.push(val.to_owned());
}

pub fn add_index_to(&mut self, arg: &'a str, idx: usize) {
let ma = self.entry(arg).or_insert(MatchedArg {
occurs: 0,
indices: Vec::with_capacity(1),
vals: Vec::new(),
});
ma.indices.push(idx);
}

pub fn needs_more_vals<'b, A>(&self, o: &A) -> bool
where
A: AnyArg<'a, 'b>,
Expand Down
38 changes: 25 additions & 13 deletions src/args/arg_matches.rs
Expand Up @@ -485,17 +485,22 @@ impl<'a> ArgMatches<'a> {
/// .takes_value(true)
/// .multiple(true))
/// .get_matches_from(vec!["myapp", "-o=val1,val2,val3"]);
/// // ARGV idices: ^0 ^1
/// // ARGV idices: ^0 ^1
/// // clap idices: ^2 ^3 ^4
/// //
/// //
/// // clap sees the above as 'myapp -o val1 val2 val3'
/// // ^0 ^1 ^2 ^3 ^4
/// assert_eq!(m.index_of("option"), Some(2));
/// ```
/// [`ArgMatches`]: ./struct.ArgMatches.html
/// [delimiter]: ./struct.Arg.html#method.value_delimiter
pub fn index_of<S: AsRef<str>>(&self, name: S) -> Option<&str> {
unimplemented!()
pub fn index_of<S: AsRef<str>>(&self, name: S) -> Option<usize> {
if let Some(arg) = self.args.get(name.as_ref()) {
if let Some(i) = arg.indices.get(0) {
return Some(*i);
}
}
None
}

/// Gets all indices of the argument in respect to all other arguments. Indices are
Expand Down Expand Up @@ -571,8 +576,15 @@ impl<'a> ArgMatches<'a> {
/// [`ArgMatches`]: ./struct.ArgMatches.html
/// [`ArgMatches::index_of`]: ./struct.ArgMatches.html#method.index_of
/// [delimiter]: ./struct.Arg.html#method.value_delimiter
pub fn indices_of<S: AsRef<str>>(&self, name: S) -> Option<&str> {
unimplemented!()
pub fn indices_of<S: AsRef<str>>(&'a self, name: S) -> Option<Indices<'a>> {
if let Some(arg) = self.args.get(name.as_ref()) {
fn to_usize(i: &usize) -> usize { *i }
let to_usize: fn(&usize) -> usize = to_usize; // coerce to fn pointer
return Some(Indices {
iter: arg.indices.iter().map(to_usize),
});
}
None
}

/// Because [`Subcommand`]s are essentially "sub-[`App`]s" they have their own [`ArgMatches`]
Expand Down Expand Up @@ -878,19 +890,19 @@ impl<'a> Default for OsValues<'a> {
/// [`ArgMatches::indices_of`]: ./struct.ArgMatches.html#method.indices_of
#[derive(Clone)]
#[allow(missing_debug_implementations)]
pub struct Indices<'a> {
iter: Map<Iter<'a, OsString>, fn(&'a OsString) -> &'a usize>,
pub struct Indices<'a> { // would rather use '_, but: https://github.com/rust-lang/rust/issues/48469
iter: Map<Iter<'a, usize>, fn(&'a usize) -> usize>,
}

impl<'a> Iterator for Indices<'a> {
type Item = &'a usize;
type Item = usize;

fn next(&mut self) -> Option<&'a str> { self.iter.next() }
fn next(&mut self) -> Option<usize> { self.iter.next() }
fn size_hint(&self) -> (usize, Option<usize>) { self.iter.size_hint() }
}

impl<'a> DoubleEndedIterator for Indices<'a> {
fn next_back(&mut self) -> Option<&'a usize> { self.iter.next_back() }
fn next_back(&mut self) -> Option<usize> { self.iter.next_back() }
}

impl<'a> ExactSizeIterator for Indices<'a> {}
Expand All @@ -900,9 +912,9 @@ impl<'a> Default for Indices<'a> {
fn default() -> Self {
static EMPTY: [usize; 0] = [];
// This is never called because the iterator is empty:
fn to_usize_ref(_: &OsString) -> &usize { unreachable!() };
fn to_usize(_: &usize) -> usize { unreachable!() };
Indices {
iter: EMPTY[..].iter().map(to_usize_ref),
iter: EMPTY[..].iter().map(to_usize),
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/args/matched_arg.rs
Expand Up @@ -5,14 +5,16 @@ use std::ffi::OsString;
#[derive(Debug, Clone)]
pub struct MatchedArg {
#[doc(hidden)] pub occurs: u64,
#[doc(hidden)] pub indices: Vec<usize>,
#[doc(hidden)] pub vals: Vec<OsString>,
}

impl Default for MatchedArg {
fn default() -> Self {
MatchedArg {
occurs: 1,
vals: Vec::with_capacity(1),
indices: Vec::new(),
vals: Vec::new(),
}
}
}
Expand Down

0 comments on commit f58d057

Please sign in to comment.