From 7486a0b4b9fb131f19bb32fb3dddfb5708aa617d Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 15 Aug 2022 10:00:42 -0500 Subject: [PATCH] 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 --- src/parser/matches/arg_matches.rs | 75 +++++++++++++++++++++++++++++++ src/parser/matches/mod.rs | 1 + src/parser/mod.rs | 1 + tests/builder/arg_matches.rs | 67 ++++++++++++++++++++++++++- 4 files changed, 143 insertions(+), 1 deletion(-) diff --git a/src/parser/matches/arg_matches.rs b/src/parser/matches/arg_matches.rs index 6c61e775ad2..0742a4aa185 100644 --- a/src/parser/matches/arg_matches.rs +++ b/src/parser/matches/arg_matches.rs @@ -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 ) + /// .value_parser(["auto", "always", "never"]) + /// .required(false)) + /// .arg(arg!(--config ) + /// .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::>(), + /// ["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 ) +/// .value_parser(["auto", "always", "never"]) +/// .required(false)) +/// .arg(arg!(--config ) +/// .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::>(), +/// ["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) { + 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 diff --git a/src/parser/matches/mod.rs b/src/parser/matches/mod.rs index 6e06a1eeb75..0e3474fb3fe 100644 --- a/src/parser/matches/mod.rs +++ b/src/parser/matches/mod.rs @@ -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; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index efc3d6bf9c5..c99e74f95d3 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -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; diff --git a/tests/builder/arg_matches.rs b/tests/builder/arg_matches.rs index 1af4af81569..036d8966679 100644 --- a/tests/builder/arg_matches.rs +++ b/tests/builder/arg_matches.rs @@ -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 ) + .value_parser(["auto", "always", "never"]) + .required(false), + ) + .arg( + arg!(--config ) + .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::>(), + ["config", "color"] + ); + assert_eq!(m.ids().len(), 2); +} + +#[test] +fn ids_ignore_unused() { + let m = Command::new("test") + .arg( + arg!(--color ) + .value_parser(["auto", "always", "never"]) + .required(false), + ) + .arg( + arg!(--config ) + .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::>(), + ["config"] + ); + assert_eq!(m.ids().len(), 1); +} + +#[test] +fn ids_ignore_overridden() { + let m = Command::new("test") + .arg( + arg!(--color ) + .value_parser(["auto", "always", "never"]) + .required(false), + ) + .arg( + arg!(--config ) + .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::>(), ["color"]); + assert_eq!(m.ids().len(), 1); +} #[test] #[cfg(debug_assertions)]