Skip to content

Commit

Permalink
feat: Add get/remove API for getting grouped values
Browse files Browse the repository at this point in the history
Change it to be more consistent with get_one and get_many and related
functions.

Relates-To: clap-rs#2924
  • Loading branch information
tmccombs committed Dec 10, 2022
1 parent 44dd8ea commit fb07dfe
Show file tree
Hide file tree
Showing 3 changed files with 325 additions and 11 deletions.
304 changes: 304 additions & 0 deletions src/parser/matches/arg_matches.rs
Expand Up @@ -216,6 +216,47 @@ impl ArgMatches {
MatchesError::unwrap(id, self.try_get_many(id))
}

/// Iterate over groups of values of a specific option.
///
/// specifically grouped by the occurrences of the options.
///
/// Each group is a `Vec<&T>` containing the arguments passed to a single occurrence
/// of the option.
///
/// If the option doesn't support multiple occurrences, or there was only a single occurrence,
/// the iterator will only contain a single item.
///
/// Returns `None` if the option wasn't present.
///
/// # Panics
///
/// If the argument definition and access mismatch. To handle this case programmatically, see
/// [`ArgMatches::try_get_groups`].
///
/// # Examples
/// ```rust
/// # use clap::{Command,Arg, ArgAction, value_parser};
/// let m = Command::new("myprog")
/// .arg(Arg::new("exec")
/// .short('x')
/// .num_args(1..)
/// .action(ArgAction::Append)
/// .value_parser(value_parser!(String))
/// .value_terminator(";"))
/// .get_matches_from(vec![
/// "myprog", "-x", "echo", "hi", ";", "-x", "echo", "bye"]);
/// let vals: Vec<Vec<&String>> = m.get_groups("exec").unwrap().collect();
/// assert_eq!(vals, [["echo", "hi"], ["echo", "bye"]]);
/// ```
#[cfg_attr(debug_assertions, track_caller)]
#[cfg(feature = "unstable-grouped")]
pub fn get_groups<T: Any + Clone + Send + Sync + 'static>(
&self,
id: &str,
) -> Option<GroupsRef<T>> {
MatchesError::unwrap(id, self.try_get_groups(id))
}

/// Iterate over the original argument values.
///
/// An `OsStr` on Unix-like systems is any series of bytes, regardless of whether or not they
Expand Down Expand Up @@ -262,6 +303,61 @@ impl ArgMatches {
MatchesError::unwrap(id, self.try_get_raw(id))
}

/// Iterate over the original argument values in groups.
///
/// Similar to [`ArgMatches::get_groups`] but returns raw values.
///
/// An `OsStr` on Unix-like systems is any series of bytes, regardless of whether or not they
/// contain valid UTF-8. Since [`String`]s in Rust are guaranteed to be valid UTF-8, a valid
/// filename on a Unix system as an argument value may contain invalid UTF-8.
///
/// Returns `None` if the option wasn't present.
///
/// # Panic
///
/// If the argument definition and access mismatch. To handle this case programmatically, see
/// [`ArgMatches::try_get_raw_groups`].
///
/// # Examples
///
#[cfg_attr(not(unix), doc = " ```ignore")]
#[cfg_attr(unix, doc = " ```")]
/// # use clap::{Command, arg, value_parser, ArgAction, Arg};
/// # use std::ffi::{OsStr,OsString};
/// # use std::os::unix::ffi::{OsStrExt,OsStringExt};
/// use std::path::PathBuf;
///
/// let m = Command::new("myprog")
/// .arg(Arg::new("exec")
/// .short('x')
/// .num_args(1..)
/// .action(ArgAction::Append)
/// .value_parser(value_parser!(PathBuf))
/// .value_terminator(";"))
/// .get_matches_from(vec![OsString::from("myprog"),
/// OsString::from("-x"),
/// OsString::from("echo"), OsString::from("hi"), OsString::from(";"),
/// OsString::from("-x"),
/// OsString::from("echo"),
/// // "{0xe9}!"
/// OsString::from_vec(vec![0xe9, b'!'])]);
/// let mut itr = m.get_raw_groups("exec")
/// .expect("`-x`is required")
/// .into_iter();
/// assert_eq!(itr.next(), Some(vec![OsStr::new("echo"), OsStr::new("hi")]));
/// assert_eq!(itr.next(), Some(vec![OsStr::new("echo"), OsStr::from_bytes(&[0xe9, b'!'])]));
/// assert_eq!(itr.next(), None);
/// ```
/// [`Iterator`]: std::iter::Iterator
/// [`OsStr`]: std::ffi::OsStr
/// [values]: OsValues
/// [`String`]: std::string::String
#[cfg(feature = "unstable-grouped")]
#[cfg_attr(debug_assertions, track_caller)]
pub fn get_raw_groups(&self, id: &str) -> Option<RawGroups<'_>> {
MatchesError::unwrap(id, self.try_get_raw_groups(id))
}

/// Returns the value of a specific option or positional argument.
///
/// i.e. an argument that [takes an additional value][crate::Arg::num_args] at runtime.
Expand Down Expand Up @@ -338,6 +434,48 @@ impl ArgMatches {
MatchesError::unwrap(id, self.try_remove_many(id))
}

/// Return groups of values of a specific option.
///
/// specifically grouped by the occurrences of the options.
///
/// Each group is a `Vec<T>` containing the arguments passed to a single occurrence of
/// the option.
///
/// If the option doesn't support multiple occurrences, or there was only a single occurrence,
/// the iterator will only contain a single item.
///
/// Returns `None` if the option wasn't present.
///
/// # Panic
///
/// If the argument definition and access mismatch. To handle this case programmatically, see
/// [`ArgMatches::try_remove_groups`].
///
/// # Examples
///
/// ```rust
/// # use clap::{Command, Arg, value_parser, ArgAction};
/// let mut m = Command::new("myprog")
/// .arg(Arg::new("exec")
/// .short('x')
/// .num_args(1..)
/// .action(ArgAction::Append)
/// .value_parser(value_parser!(String))
/// .value_terminator(";"))
/// .get_matches_from(vec![
/// "myprog", "-x", "echo", "hi", ";", "-x", "echo", "bye"]);
/// let vals: Vec<Vec<String>> = m.remove_groups("exec").unwrap().collect();
/// assert_eq!(vals, [["echo", "hi"], ["echo", "bye"]]);
/// ```
#[cfg(feature = "unstable-grouped")]
#[cfg_attr(debug_assertions, track_caller)]
pub fn remove_groups<T: Any + Clone + Send + Sync + 'static>(
&mut self,
id: &str,
) -> Option<Groups<T>> {
MatchesError::unwrap(id, self.try_remove_groups(id))
}

/// Check if values are present for the argument or group id
///
/// *NOTE:* This will always return `true` if [`default_value`] has been set.
Expand Down Expand Up @@ -453,6 +591,8 @@ impl ArgMatches {
/// [`Iterator`]: std::iter::Iterator
#[cfg(feature = "unstable-grouped")]
#[cfg_attr(debug_assertions, track_caller)]
#[deprecated = "Use get_groups, remove_groups, or get_raw_groups instead"]
#[allow(deprecated)]
pub fn grouped_values_of(&self, id: &str) -> Option<GroupedValues> {
let arg = some!(self.get_arg(id));
let v = GroupedValues {
Expand Down Expand Up @@ -973,6 +1113,26 @@ impl ArgMatches {
Ok(Some(values))
}

/// Non-panicking version of [`ArgMatches::get_groups`]
#[cfg(feature = "unstable-grouped")]
pub fn try_get_groups<T: Any + Clone + Send + Sync + 'static>(
&self,
id: &str,
) -> Result<Option<GroupsRef<T>>, MatchesError> {
let arg = match ok!(self.try_get_arg_t::<T>(id)) {
Some(arg) => arg,
None => return Ok(None),
};
let values = arg.vals();
Ok(Some(GroupsRef {
iter: values.map(|g| {
g.iter()
.map(|v| v.downcast_ref::<T>().expect(INTERNAL_ERROR_MSG))
.collect()
}),
}))
}

/// Non-panicking version of [`ArgMatches::get_raw`]
pub fn try_get_raw(&self, id: &str) -> Result<Option<RawValues<'_>>, MatchesError> {
let arg = match ok!(self.try_get_arg(id)) {
Expand All @@ -988,6 +1148,20 @@ impl ArgMatches {
Ok(Some(values))
}

/// Non-panicking version of [`ArgMatches::get_raw_groups`]
#[cfg(feature = "unstable-grouped")]
pub fn try_get_raw_groups(&self, id: &str) -> Result<Option<RawGroups<'_>>, MatchesError> {
let arg = match ok!(self.try_get_arg(id)) {
Some(arg) => arg,
None => return Ok(None),
};
let values = arg.raw_vals();
let groups = RawGroups {
iter: values.map(|g| g.iter().map(OsString::as_os_str).collect()),
};
Ok(Some(groups))
}

/// Non-panicking version of [`ArgMatches::remove_one`]
pub fn try_remove_one<T: Any + Clone + Send + Sync + 'static>(
&mut self,
Expand Down Expand Up @@ -1022,6 +1196,26 @@ impl ArgMatches {
Ok(Some(values))
}

/// Non-panicking version of [`ArgMatches::remove_groups`]
pub fn try_remove_groups<T: Any + Clone + Send + Sync + 'static>(
&mut self,
id: &str,
) -> Result<Option<Groups<T>>, MatchesError> {
let arg = match ok!(self.try_remove_arg_t::<T>(id)) {
Some(arg) => arg,
None => return Ok(None),
};
let values = arg.into_vals();
let groups = Groups {
iter: values.into_iter().map(|g| {
g.into_iter()
.map(|v| v.downcast_into::<T>().expect(INTERNAL_ERROR_MSG))
.collect()
}),
};
Ok(Some(groups))
}

/// Non-panicking version of [`ArgMatches::contains_id`]
pub fn try_contains_id(&self, id: &str) -> Result<bool, MatchesError> {
ok!(self.verify_arg(id));
Expand Down Expand Up @@ -1379,12 +1573,14 @@ impl Default for RawValues<'_> {

#[derive(Clone)]
#[allow(missing_debug_implementations)]
#[deprecated]
pub struct GroupedValues<'a> {
#[allow(clippy::type_complexity)]
iter: Map<Iter<'a, Vec<AnyValue>>, fn(&Vec<AnyValue>) -> Vec<&str>>,
len: usize,
}

#[allow(deprecated)]
impl<'a> Iterator for GroupedValues<'a> {
type Item = Vec<&'a str>;

Expand All @@ -1396,15 +1592,18 @@ impl<'a> Iterator for GroupedValues<'a> {
}
}

#[allow(deprecated)]
impl<'a> DoubleEndedIterator for GroupedValues<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back()
}
}

#[allow(deprecated)]
impl<'a> ExactSizeIterator for GroupedValues<'a> {}

/// Creates an empty iterator. Used for `unwrap_or_default()`.
#[allow(deprecated)]
impl<'a> Default for GroupedValues<'a> {
fn default() -> Self {
static EMPTY: [Vec<AnyValue>; 0] = [];
Expand All @@ -1415,6 +1614,111 @@ impl<'a> Default for GroupedValues<'a> {
}
}

#[derive(Clone)]
#[allow(missing_debug_implementations)]
pub struct GroupsRef<'a, T> {
#[allow(clippy::type_complexity)]
iter: Map<Iter<'a, Vec<AnyValue>>, fn(&Vec<AnyValue>) -> Vec<&T>>,
}

impl<'a, T> Iterator for GroupsRef<'a, T>
where
Self: 'a,
{
type Item = Vec<&'a T>; // should this be an iterator instead?

fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}

fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}

impl<'a, T> DoubleEndedIterator for GroupsRef<'a, T>
where
Self: 'a,
{
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back()
}
}

impl<'a, T> ExactSizeIterator for GroupsRef<'a, T> where Self: 'a {}

#[derive(Clone)]
#[allow(missing_debug_implementations)]
pub struct Groups<T> {
#[allow(clippy::type_complexity)]
iter: Map<std::vec::IntoIter<Vec<AnyValue>>, fn(Vec<AnyValue>) -> Vec<T>>,
}

impl<T> Iterator for Groups<T> {
type Item = Vec<T>;

fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}

fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}

impl<T> DoubleEndedIterator for Groups<T> {
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back()
}
}

impl<T> ExactSizeIterator for Groups<T> {}

impl<T> Default for Groups<T> {
fn default() -> Self {
let empty: Vec<Vec<AnyValue>> = Default::default();
Groups {
iter: empty.into_iter().map(|_| unreachable!()),
}
}
}

#[derive(Clone)]
#[allow(missing_debug_implementations)]
pub struct RawGroups<'a> {
#[allow(clippy::type_complexity)]
iter: Map<Iter<'a, Vec<OsString>>, fn(&Vec<OsString>) -> Vec<&OsStr>>,
}

impl<'a> Iterator for RawGroups<'a> {
type Item = Vec<&'a OsStr>;

fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}

impl<'a> DoubleEndedIterator for RawGroups<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back()
}
}

impl<'a> ExactSizeIterator for RawGroups<'a> {}

/// Creates an empty iterator. Used for `unwrap_or_default()`.
impl<'a> Default for RawGroups<'a> {
fn default() -> Self {
static EMPTY: [Vec<OsString>; 0] = [];
RawGroups {
iter: EMPTY[..].iter().map(|_| unreachable!()),
}
}
}

/// Iterate over indices for where an argument appeared when parsing, via [`ArgMatches::indices_of`]
///
/// # Examples
Expand Down

0 comments on commit fb07dfe

Please sign in to comment.