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)]