Skip to content

Commit

Permalink
feat(parser): Report what arg ids are present
Browse files Browse the repository at this point in the history
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 clap-rs#1206
  • Loading branch information
epage committed Aug 15, 2022
1 parent c45bd64 commit 7486a0b
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 1 deletion.
75 changes: 75 additions & 0 deletions src/parser/matches/arg_matches.rs
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/parser/matches/mod.rs
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/parser/mod.rs
Expand Up @@ -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;
Expand Down
67 changes: 66 additions & 1 deletion 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 <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)]
Expand Down

0 comments on commit 7486a0b

Please sign in to comment.