Repository: kbknapp/clap-rs
Pull request #1196 (2018-02-26 03:05:30)

Indices
Adds the scaffolding to add index querying methods to `ArgMatches`, namely `index_of` and `indices_of`

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/kbknapp/clap-rs/1196)
<!-- Reviewable:end -->


mention-bot: @kbknapp, thanks for your PR! By analyzing the history of the files in this pull request, we identified @kbknapp to be a potential reviewer.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 501271cd..c950611c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,28 @@
+<a name="v2.31.0"></a>
+## v2.31.0 (2018-03-04)
+
+
+#### Features
+
+* **Arg Indices:**  adds the ability to query argument value indices ([f58d0576](https://github.com/kbknapp/clap-rs/commit/f58d05767ec8133c8eb2de117cb642b9ae29ccbc))
+* **Indices:**  implements an Indices<Item=&usize> iterator ([1e67be44](https://github.com/kbknapp/clap-rs/commit/1e67be44f0ccf161cc84c4e6082382072e89c302))
+* **Raw Args** adds a convenience function to `Arg` that allows implying all of `Arg::last` `Arg::allow_hyphen_values` and `Arg::multiple(true)` ([66a78f29](https://github.com/kbknapp/clap-rs/commit/66a78f2972786f5fe7c07937a1ac23da2542afd2))
+
+#### Documentation
+
+*   Fix some typos and markdown issues. ([935ba0dd](https://github.com/kbknapp/clap-rs/commit/935ba0dd547a69c3f636c5486795012019408794))
+* **Arg Indices:**  adds the documentation for the arg index querying methods ([50bc0047](https://github.com/kbknapp/clap-rs/commit/50bc00477afa64dc6cdc5de161d3de3ba1d105a7))
+* **CONTRIBUTING.md:**  fix url to clippy upstream repo to point to https://github.com/rust-lang-nursery/rust-clippy instead of https://github.com/Manishearth/rust-clippy ([42407d7f](https://github.com/kbknapp/clap-rs/commit/42407d7f21d794103cda61f49d2615aae0a4bcd9))
+* **Values:**  improves the docs example of the Values iterator ([74075d65](https://github.com/kbknapp/clap-rs/commit/74075d65e8db1ddb5e2a4558009a5729d749d1b6))
+* Updates readme to hint that the `wrap_help` feature is a thing ([fc7ab227](https://github.com/kbknapp/clap-rs/commit/66a78f2972786f5fe7c07937a1ac23da2542afd2))
+
+### Improvements
+
+*  Cargo.toml: use codegen-units = 1 in release and bench profiles ([19f425ea](https://github.com/kbknapp/clap-rs/commit/66a78f2972786f5fe7c07937a1ac23da2542afd2))
+*  Adds WASM support (clap now compiles on WASM!) ([689949e5](https://github.com/kbknapp/clap-rs/commit/689949e57d390bb61bc69f3ed91f60a2105738d0))
+*  Uses the short help tool-tip for PowerShell completion scripts ([ecda22ce](https://github.com/kbknapp/clap-rs/commit/ecda22ce7210ce56d7b2d1a5445dd1b8a2959656))
+
+
 <a name="v2.30.0"></a>
 ## v2.30.0 (2018-02-13)
 
diff --git a/Cargo.toml b/Cargo.toml
index 15607fe4..1d3c5f88 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 
 name = "clap"
-version = "2.30.0"
+version = "2.31.0"
 authors = ["Kevin K. <kbknapp@gmail.com>"]
 exclude = ["examples/*", "clap-test/*", "tests/*", "benches/*", "*.png", "clap-perf/*", "*.dot"]
 repository = "https://github.com/kbknapp/clap-rs"
diff --git a/README.md b/README.md
index 21cfd2f3..0c091857 100644
--- a/README.md
+++ b/README.md
@@ -42,12 +42,26 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)
 
 ## What's New
 
-Here's whats new in 2.30.0:
-
-* **Bash Completions:**  instead of completing a generic option name, all bash completions fall back to file completions UNLESS `Arg::possible_values` was used 
-* **YAML:** Adds a missing conversion from  `Arg::last` when instantiating from a YAML file 
-* **Deps:**  No longer needlessly compiles `ansi_term` on Windows since its not used 
-* **Help Message:** changes the `[values: foo bar baz]` array to `[possible values: foo bar baz]` for consistency with the API 
+Here's whats new in 2.31.0:
+
+* **Arg Indices:**  adds the ability to query argument value indices
+  * implements an `Indices<Item=usize>` iterator
+  * adds the documentation for the arg index querying methods
+* **Improves PowerShell completions** - Uses the short help tool-tip for PowerShell completion scripts
+* Adds WASM support (clap now compiles on WASM!)
+* **Raw Args** adds a convenience function to `Arg` that allows implying all of `Arg::last` `Arg::allow_hyphen_values` and `Arg::multiple(true)`
+* **CONTRIBUTING.md:**  fix url to clippy upstream repo
+* **Values Documentation:**  improves the docs example of the Values iterator
+* Updates README.md to hint that the `wrap_help` feature is a thing
+* Use `codegen-units = 1` in release and bench profiles to improve bench performance
+* Fix some typos and markdown issues in the docs
+
+Here's whats new in 2.30.x:
+
+* **Bash Completions:**  instead of completing a generic option name, all bash completions fall back to file completions UNLESS `Arg::possible_values` was used
+* **YAML:** Adds a missing conversion from  `Arg::last` when instantiating from a YAML file
+* **Deps:**  No longer needlessly compiles `ansi_term` on Windows since its not used
+* **Help Message:** changes the `[values: foo bar baz]` array to `[possible values: foo bar baz]` for consistency with the API
 
 Here's whats new in 2.29.x:
 
@@ -309,7 +323,7 @@ subcommands:
 
 Since this feature requires additional dependencies that not everyone may want, it is *not* compiled in by default and we need to enable a feature flag in Cargo.toml:
 
-Simply change your `clap = "2.30"` to `clap = {version = "2.30", features = ["yaml"]}`.
+Simply change your `clap = "2.31"` to `clap = {version = "2.31", features = ["yaml"]}`.
 
 Finally we create our `main.rs` file just like we would have with the previous two examples:
 
@@ -429,7 +443,7 @@ For full usage, add `clap` as a dependency in your `Cargo.toml` () to use from c
 
 ```toml
 [dependencies]
-clap = "~2.30"
+clap = "~2.31"
 ```
 
 (**note**: If you are concerned with supporting a minimum version of Rust that is *older* than the current stable Rust minus 2 stable releases, it's recommended to use the `~major.minor.patch` style versions in your `Cargo.toml` which will only update the patch version automatically. For more information see the [Compatibility Policy](#compatibility-policy))
@@ -452,7 +466,7 @@ To disable these, add this to your `Cargo.toml`:
 
 ```toml
 [dependencies.clap]
-version = "2.30"
+version = "2.31"
 default-features = false
 ```
 
@@ -460,7 +474,7 @@ You can also selectively enable only the features you'd like to include, by addi
 
 ```toml
 [dependencies.clap]
-version = "2.30"
+version = "2.31"
 default-features = false
 
 # Cherry-pick the features you'd like to use
@@ -509,7 +523,7 @@ In order to keep from being surprised of breaking changes, it is **highly** reco
 
 ```toml
 [dependencies]
-clap = "~2.30"
+clap = "~2.31"
 ```
 
 This will cause *only* the patch version to be updated upon a `cargo update` call, and therefore cannot break due to new features, or bumped minimum versions of Rust.
@@ -526,11 +540,11 @@ Right now Cargo's version resolution is pretty naive, it's just a brute-force se
 
 # In one Cargo.toml
 [dependencies]
-clap = "~2.30.0"
+clap = "~2.31.0"
 
 # In another Cargo.toml
 [dependencies]
-clap = "2.30"
+clap = "2.31"
 ```
 
 This is inherently an unresolvable crate graph in Cargo right now. Cargo requires there's only one major version of a crate, and being in the same workspace these two crates must share a version. This is impossible in this location, though, as these version constraints cannot be met.
diff --git a/src/app/parser.rs b/src/app/parser.rs
index 0228f75b..d4b7d961 100644
--- a/src/app/parser.rs
+++ b/src/app/parser.rs
@@ -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;
@@ -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>
@@ -77,6 +79,7 @@ where
         Parser {
             meta: AppMeta::with_name(n),
             g_settings: AppFlags::zeroed(),
+            cur_idx: Cell::new(0),
             ..Default::default()
         }
     }
@@ -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'=') {
@@ -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
@@ -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()) {
@@ -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)));
diff --git a/src/args/arg_matcher.rs b/src/args/arg_matcher.rs
index 32b62829..e1d8067e 100644
--- a/src/args/arg_matcher.rs
+++ b/src/args/arg_matcher.rs
@@ -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;
             }
@@ -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>,
diff --git a/src/args/arg_matches.rs b/src/args/arg_matches.rs
index 770067a3..5617b226 100644
--- a/src/args/arg_matches.rs
+++ b/src/args/arg_matches.rs
@@ -364,6 +364,229 @@ impl<'a> ArgMatches<'a> {
         self.args.get(name.as_ref()).map_or(0, |a| a.occurs)
     }
 
+    /// Gets the starting index of the argument in respect to all other arguments. Indices are
+    /// similar to argv indices, but are not exactly 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.
+    ///
+    /// # Examples
+    ///
+    /// 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
+    /// # use clap::{App, Arg};
+    /// 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
+    /// # use clap::{App, Arg};
+    /// 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
+    /// # use clap::{App, Arg};
+    /// 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
+    /// # use clap::{App, Arg};
+    /// 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
+    /// # use clap::{App, Arg};
+    /// 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));
+    /// ```
+    /// [`ArgMatches`]: ./struct.ArgMatches.html
+    /// [delimiter]: ./struct.Arg.html#method.value_delimiter
+    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
+    /// similar to argv indices, but are not exactly 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.
+    ///
+    /// *NOTE:* For more information about how clap indices compare to argv indices, see
+    /// [`ArgMatches::index_of`]
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// # use clap::{App, Arg};
+    /// let m = App::new("myapp")
+    ///     .arg(Arg::with_name("option")
+    ///         .short("o")
+    ///         .takes_value(true)
+    ///         .use_delimiter(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.indices_of("option").unwrap().collect::<Vec<_>>(), &[2, 3, 4]);
+    /// ```
+    ///
+    /// Another quick example is when flags and options are used together
+    ///
+    /// ```rust
+    /// # use clap::{App, Arg};
+    /// let m = App::new("myapp")
+    ///     .arg(Arg::with_name("option")
+    ///         .short("o")
+    ///         .takes_value(true)
+    ///         .multiple(true))
+    ///     .arg(Arg::with_name("flag")
+    ///         .short("f")
+    ///         .multiple(true))
+    ///     .get_matches_from(vec!["myapp", "-o", "val1", "-f", "-o", "val2", "-f"]);
+    ///             // ARGV idices: ^0       ^1    ^2      ^3    ^4    ^5      ^6
+    ///             // clap idices:                ^2      ^3          ^5      ^6
+    ///
+    /// assert_eq!(m.indices_of("option").unwrap().collect::<Vec<_>>(), &[2, 5]);
+    /// assert_eq!(m.indices_of("flag").unwrap().collect::<Vec<_>>(), &[3, 6]);
+    /// ```
+    ///
+    /// One final example, which is an odd case; if we *don't* use  value delimiter as we did with
+    /// the first example above instead of `val1`, `val2` and `val3` all being distinc values, they
+    /// would all be a single value of `val1,val2,val3`, in which case case they'd only receive a
+    /// single index.
+    ///
+    /// ```rust
+    /// # use clap::{App, Arg};
+    /// 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
+    ///             //
+    ///             // clap sees the above as 'myapp -o "val1,val2,val3"'
+    ///             //                         ^0    ^1  ^2
+    /// assert_eq!(m.indices_of("option").unwrap().collect::<Vec<_>>(), &[2]);
+    /// ```
+    /// [`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>>(&'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`]
     /// as well. This method returns the [`ArgMatches`] for a particular subcommand or `None` if
     /// the subcommand wasn't present at runtime.
@@ -551,10 +774,16 @@ impl<'a> ArgMatches<'a> {
 /// # use clap::{App, Arg};
 /// let m = App::new("myapp")
 ///     .arg(Arg::with_name("output")
+///         .short("o")
+///         .multiple(true)
 ///         .takes_value(true))
-///     .get_matches_from(vec!["myapp", "something"]);
+///     .get_matches_from(vec!["myapp", "-o", "val1", "val2"]);
 ///
-/// assert_eq!(m.value_of("output"), Some("something"));
+/// let mut values = m.values_of("output").unwrap();
+///
+/// assert_eq!(values.next(), Some("val1"));
+/// assert_eq!(values.next(), Some("val2"));
+/// assert_eq!(values.next(), None);
 /// ```
 /// [`ArgMatches::values_of`]: ./struct.ArgMatches.html#method.values_of
 #[derive(Clone)]
@@ -588,19 +817,6 @@ impl<'a> Default for Values<'a> {
     }
 }
 
-#[test]
-fn test_default_values() {
-    let mut values: Values = Values::default();
-    assert_eq!(values.next(), None);
-}
-
-#[test]
-fn test_default_values_with_shorter_lifetime() {
-    let matches = ArgMatches::new();
-    let mut values = matches.values_of("").unwrap_or_default();
-    assert_eq!(values.next(), None);
-}
-
 /// An iterator for getting multiple values out of an argument via the [`ArgMatches::values_of_os`]
 /// method. Usage of this iterator allows values which contain invalid UTF-8 code points unlike
 /// [`Values`].
@@ -651,15 +867,98 @@ impl<'a> Default for OsValues<'a> {
     }
 }
 
-#[test]
-fn test_default_osvalues() {
-    let mut values: OsValues = OsValues::default();
-    assert_eq!(values.next(), None);
+/// An iterator for getting multiple indices out of an argument via the [`ArgMatches::indices_of`]
+/// method.
+///
+/// # Examples
+///
+/// ```rust
+/// # use clap::{App, Arg};
+/// let m = App::new("myapp")
+///     .arg(Arg::with_name("output")
+///         .short("o")
+///         .multiple(true)
+///         .takes_value(true))
+///     .get_matches_from(vec!["myapp", "-o", "val1", "val2"]);
+///
+/// let mut indices = m.indices_of("output").unwrap();
+///
+/// assert_eq!(indices.next(), Some(2));
+/// assert_eq!(indices.next(), Some(3));
+/// assert_eq!(indices.next(), None);
+/// ```
+/// [`ArgMatches::indices_of`]: ./struct.ArgMatches.html#method.indices_of
+#[derive(Clone)]
+#[allow(missing_debug_implementations)]
+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 = usize;
+
+    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<usize> { self.iter.next_back() }
+}
+
+impl<'a> ExactSizeIterator for Indices<'a> {}
+
+/// Creates an empty iterator.
+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(_: &usize) -> usize { unreachable!() };
+        Indices {
+            iter: EMPTY[..].iter().map(to_usize),
+        }
+    }
 }
 
-#[test]
-fn test_default_osvalues_with_shorter_lifetime() {
-    let matches = ArgMatches::new();
-    let mut values = matches.values_of_os("").unwrap_or_default();
-    assert_eq!(values.next(), None);
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_default_values() {
+        let mut values: Values = Values::default();
+        assert_eq!(values.next(), None);
+    }
+
+    #[test]
+    fn test_default_values_with_shorter_lifetime() {
+        let matches = ArgMatches::new();
+        let mut values = matches.values_of("").unwrap_or_default();
+        assert_eq!(values.next(), None);
+    }
+
+    #[test]
+    fn test_default_osvalues() {
+        let mut values: OsValues = OsValues::default();
+        assert_eq!(values.next(), None);
+    }
+
+    #[test]
+    fn test_default_osvalues_with_shorter_lifetime() {
+        let matches = ArgMatches::new();
+        let mut values = matches.values_of_os("").unwrap_or_default();
+        assert_eq!(values.next(), None);
+    }
+
+    #[test]
+    fn test_default_indices() {
+        let mut indices: Indices = Indices::default();
+        assert_eq!(indices.next(), None);
+    }
+
+    #[test]
+    fn test_default_indices_with_shorter_lifetime() {
+        let matches = ArgMatches::new();
+        let mut indices = matches.indices_of("").unwrap_or_default();
+        assert_eq!(indices.next(), None);
+    }
 }
diff --git a/src/args/matched_arg.rs b/src/args/matched_arg.rs
index 4847718c..eeda2611 100644
--- a/src/args/matched_arg.rs
+++ b/src/args/matched_arg.rs
@@ -5,6 +5,7 @@ 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>,
 }
 
@@ -12,7 +13,8 @@ impl Default for MatchedArg {
     fn default() -> Self {
         MatchedArg {
             occurs: 1,
-            vals: Vec::with_capacity(1),
+            indices: Vec::new(),
+            vals: Vec::new(),
         }
     }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 0c1738ed..1a76d6f6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -513,7 +513,7 @@
 //! this repository for more information.
 
 #![crate_type = "lib"]
-#![doc(html_root_url = "https://docs.rs/clap/2.30.0")]
+#![doc(html_root_url = "https://docs.rs/clap/2.31.0")]
 #![deny(missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
         unused_import_braces, unused_allocation)]
 // Lints we'd like to deny but are currently failing for upstream crates


Test patch:
diff --git a/tests/indices.rs b/tests/indices.rs
new file mode 100644
index 00000000..ebc7a529
--- /dev/null
+++ b/tests/indices.rs
@@ -0,0 +1,175 @@
+extern crate clap;
+extern crate regex;
+
+include!("../clap-test.rs");
+
+use clap::{App, Arg};
+
+#[test]
+fn indices_mult_opts() {
+	let m = App::new("ind")
+		.arg(Arg::with_name("exclude")
+			.short("e")
+			.takes_value(true)
+			.multiple(true))
+		.arg(Arg::with_name("include")
+			.short("i")
+			.takes_value(true)
+			.multiple(true))
+		.get_matches_from(vec!["ind", "-e", "A", "B", "-i", "B", "C", "-e", "C"]);
+
+		assert_eq!(m.indices_of("exclude").unwrap().collect::<Vec<_>>(), &[2, 3, 8]);
+		assert_eq!(m.indices_of("include").unwrap().collect::<Vec<_>>(), &[5, 6]);
+}
+
+#[test]
+fn index_mult_opts() {
+	let m = App::new("ind")
+		.arg(Arg::with_name("exclude")
+			.short("e")
+			.takes_value(true)
+			.multiple(true))
+		.arg(Arg::with_name("include")
+			.short("i")
+			.takes_value(true)
+			.multiple(true))
+		.get_matches_from(vec!["ind", "-e", "A", "B", "-i", "B", "C", "-e", "C"]);
+
+		assert_eq!(m.index_of("exclude"), Some(2));
+		assert_eq!(m.index_of("include"), Some(5));
+}
+
+#[test]
+fn index_flag() {
+	let m = App::new("ind")
+		.arg(Arg::with_name("exclude")
+			.short("e"))
+		.arg(Arg::with_name("include")
+			.short("i"))
+		.get_matches_from(vec!["ind", "-e", "-i"]);
+
+		assert_eq!(m.index_of("exclude"), Some(1));
+		assert_eq!(m.index_of("include"), Some(2));
+}
+
+#[test]
+fn index_flags() {
+	let m = App::new("ind")
+		.arg(Arg::with_name("exclude")
+			.short("e")
+			.multiple(true))
+		.arg(Arg::with_name("include")
+			.short("i")
+			.multiple(true))
+		.get_matches_from(vec!["ind", "-e", "-i", "-e", "-e", "-i"]);
+
+		assert_eq!(m.index_of("exclude"), Some(1));
+		assert_eq!(m.index_of("include"), Some(2));
+}
+
+#[test]
+fn indices_mult_flags() {
+	let m = App::new("ind")
+		.arg(Arg::with_name("exclude")
+			.short("e")
+			.multiple(true))
+		.arg(Arg::with_name("include")
+			.short("i")
+			.multiple(true))
+		.get_matches_from(vec!["ind", "-e", "-i", "-e", "-e", "-i"]);
+
+		assert_eq!(m.indices_of("exclude").unwrap().collect::<Vec<_>>(), &[1, 3, 4]);
+		assert_eq!(m.indices_of("include").unwrap().collect::<Vec<_>>(), &[2, 5]);
+}
+
+#[test]
+fn indices_mult_flags_combined() {
+	let m = App::new("ind")
+		.arg(Arg::with_name("exclude")
+			.short("e")
+			.multiple(true))
+		.arg(Arg::with_name("include")
+			.short("i")
+			.multiple(true))
+		.get_matches_from(vec!["ind", "-eieei"]);
+
+		assert_eq!(m.indices_of("exclude").unwrap().collect::<Vec<_>>(), &[1, 3, 4]);
+		assert_eq!(m.indices_of("include").unwrap().collect::<Vec<_>>(), &[2, 5]);
+}
+
+#[test]
+fn indices_mult_flags_opt_combined() {
+	let m = App::new("ind")
+		.arg(Arg::with_name("exclude")
+			.short("e")
+			.multiple(true))
+		.arg(Arg::with_name("include")
+			.short("i")
+			.multiple(true))
+		.arg(Arg::with_name("option")
+			.short("o")
+			.takes_value(true))
+		.get_matches_from(vec!["ind", "-eieeio", "val"]);
+
+		assert_eq!(m.indices_of("exclude").unwrap().collect::<Vec<_>>(), &[1, 3, 4]);
+		assert_eq!(m.indices_of("include").unwrap().collect::<Vec<_>>(), &[2, 5]);
+		assert_eq!(m.indices_of("option").unwrap().collect::<Vec<_>>(), &[7]);
+}
+
+#[test]
+fn indices_mult_flags_opt_combined_eq() {
+	let m = App::new("ind")
+		.arg(Arg::with_name("exclude")
+			.short("e")
+			.multiple(true))
+		.arg(Arg::with_name("include")
+			.short("i")
+			.multiple(true))
+		.arg(Arg::with_name("option")
+			.short("o")
+			.takes_value(true))
+		.get_matches_from(vec!["ind", "-eieeio=val"]);
+
+		assert_eq!(m.indices_of("exclude").unwrap().collect::<Vec<_>>(), &[1, 3, 4]);
+		assert_eq!(m.indices_of("include").unwrap().collect::<Vec<_>>(), &[2, 5]);
+		assert_eq!(m.indices_of("option").unwrap().collect::<Vec<_>>(), &[7]);
+}
+
+#[test]
+fn indices_mult_opt_value_delim_eq() {
+    let m = App::new("myapp")
+	    .arg(Arg::with_name("option")
+		    .short("o")
+		    .takes_value(true)
+		    .use_delimiter(true)
+		    .multiple(true))
+	    .get_matches_from(vec!["myapp", "-o=val1,val2,val3"]);
+    assert_eq!(m.indices_of("option").unwrap().collect::<Vec<_>>(), &[2, 3, 4]);
+}
+
+#[test]
+fn indices_mult_opt_value_no_delim_eq() {
+    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"]);
+    assert_eq!(m.indices_of("option").unwrap().collect::<Vec<_>>(), &[2]);
+}
+
+#[test]
+fn indices_mult_opt_mult_flag() {
+    let m = App::new("myapp")
+    .arg(Arg::with_name("option")
+	    .short("o")
+	    .takes_value(true)
+	    .multiple(true))
+    .arg(Arg::with_name("flag")
+	    .short("f")
+	    .multiple(true))
+    .get_matches_from(vec!["myapp", "-o", "val1", "-f", "-o", "val2", "-f"]);
+
+    assert_eq!(m.indices_of("option").unwrap().collect::<Vec<_>>(), &[2, 5]);
+    assert_eq!(m.indices_of("flag").unwrap().collect::<Vec<_>>(), &[3, 6]);
+}
\ No newline at end of file