feat(parser): Report what arg ids are present

For now, we are focusing only on iterating over the argument ids and not
the values.

This provides a building block for more obscure use cases like iterating
over argument values, in order.  We are not providing it out of the box
at the moment both to not overly incentize a less common case, because
it would abstract away a performance hit, and because we want to let
people experiment with this and if a common path emerges we can consider
it then if there is enough users.

Fixes #1206
This commit is contained in:
Ed Page 2022-08-15 10:00:42 -05:00
parent c45bd64941
commit 7486a0b4b9
4 changed files with 143 additions and 1 deletions

View file

@ -303,6 +303,35 @@ impl ArgMatches {
MatchesError::unwrap(id, self.try_contains_id(id))
}
/// Iterate over [`Arg`][crate::Arg] and [`ArgGroup`][crate::ArgGroup] [`Id`][crate::Id]s via [`ArgMatches::ids`].
///
/// # Examples
///
/// ```
/// # use clap::{Command, arg, value_parser};
///
/// let m = Command::new("myprog")
/// .arg(arg!(--color <when>)
/// .value_parser(["auto", "always", "never"])
/// .required(false))
/// .arg(arg!(--config <path>)
/// .value_parser(value_parser!(std::path::PathBuf))
/// .required(false))
/// .get_matches_from(["myprog", "--config=config.toml", "--color=auto"]);
/// assert_eq!(m.ids().len(), 2);
/// assert_eq!(
/// m.ids()
/// .map(|id| id.as_str())
/// .collect::<Vec<_>>(),
/// ["config", "color"]
/// );
/// ```
pub fn ids(&self) -> IdsRef<'_> {
IdsRef {
iter: self.args.keys(),
}
}
/// Check if any args were present on the command line
///
/// # Examples
@ -1066,6 +1095,52 @@ pub(crate) struct SubCommand {
pub(crate) matches: ArgMatches,
}
/// Iterate over [`Arg`][crate::Arg] and [`ArgGroup`][crate::ArgGroup] [`Id`][crate::Id]s via [`ArgMatches::ids`].
///
/// # Examples
///
/// ```
/// # use clap::{Command, arg, value_parser};
///
/// let m = Command::new("myprog")
/// .arg(arg!(--color <when>)
/// .value_parser(["auto", "always", "never"])
/// .required(false))
/// .arg(arg!(--config <path>)
/// .value_parser(value_parser!(std::path::PathBuf))
/// .required(false))
/// .get_matches_from(["myprog", "--config=config.toml", "--color=auto"]);
/// assert_eq!(
/// m.ids()
/// .map(|id| id.as_str())
/// .collect::<Vec<_>>(),
/// ["config", "color"]
/// );
/// ```
#[derive(Clone, Debug)]
pub struct IdsRef<'a> {
iter: std::slice::Iter<'a, Id>,
}
impl<'a> Iterator for IdsRef<'a> {
type Item = &'a Id;
fn next(&mut self) -> Option<&'a Id> {
self.iter.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl<'a> DoubleEndedIterator for IdsRef<'a> {
fn next_back(&mut self) -> Option<&'a Id> {
self.iter.next_back()
}
}
impl<'a> ExactSizeIterator for IdsRef<'a> {}
/// Iterate over multiple values for an argument via [`ArgMatches::remove_many`].
///
/// # Examples

View file

@ -4,6 +4,7 @@ mod matched_arg;
mod value_source;
pub use any_value::AnyValueId;
pub use arg_matches::IdsRef;
pub use arg_matches::RawValues;
pub use arg_matches::Values;
pub use arg_matches::ValuesRef;

View file

@ -19,6 +19,7 @@ pub(crate) use self::parser::{ParseState, Parser};
pub(crate) use self::validator::get_possible_values_cli;
pub(crate) use self::validator::Validator;
pub use self::matches::IdsRef;
pub use self::matches::RawValues;
pub use self::matches::Values;
pub use self::matches::ValuesRef;

View file

@ -1,5 +1,70 @@
use clap::{arg, value_parser, Command};
#[cfg(debug_assertions)]
use clap::{Arg, ArgAction, Command};
use clap::{Arg, ArgAction};
#[test]
fn ids() {
let m = Command::new("test")
.arg(
arg!(--color <when>)
.value_parser(["auto", "always", "never"])
.required(false),
)
.arg(
arg!(--config <path>)
.value_parser(value_parser!(std::path::PathBuf))
.required(false),
)
.try_get_matches_from(["test", "--config=config.toml", "--color=auto"])
.unwrap();
assert_eq!(
m.ids().map(|id| id.as_str()).collect::<Vec<_>>(),
["config", "color"]
);
assert_eq!(m.ids().len(), 2);
}
#[test]
fn ids_ignore_unused() {
let m = Command::new("test")
.arg(
arg!(--color <when>)
.value_parser(["auto", "always", "never"])
.required(false),
)
.arg(
arg!(--config <path>)
.value_parser(value_parser!(std::path::PathBuf))
.required(false),
)
.try_get_matches_from(["test", "--config=config.toml"])
.unwrap();
assert_eq!(
m.ids().map(|id| id.as_str()).collect::<Vec<_>>(),
["config"]
);
assert_eq!(m.ids().len(), 1);
}
#[test]
fn ids_ignore_overridden() {
let m = Command::new("test")
.arg(
arg!(--color <when>)
.value_parser(["auto", "always", "never"])
.required(false),
)
.arg(
arg!(--config <path>)
.value_parser(value_parser!(std::path::PathBuf))
.required(false)
.overrides_with("color"),
)
.try_get_matches_from(["test", "--config=config.toml", "--color=auto"])
.unwrap();
assert_eq!(m.ids().map(|id| id.as_str()).collect::<Vec<_>>(), ["color"]);
assert_eq!(m.ids().len(), 1);
}
#[test]
#[cfg(debug_assertions)]