mirror of
https://github.com/clap-rs/clap
synced 2024-11-15 00:57:15 +00:00
Implement nested vals
This commit is contained in:
parent
9c52e454e8
commit
f1e9b82584
5 changed files with 108 additions and 44 deletions
|
@ -130,6 +130,11 @@ impl ArgMatcher {
|
|||
ma.push_val(val);
|
||||
}
|
||||
|
||||
pub(crate) fn add_vals_to(&mut self, arg: &Id, vals: Vec<OsString>, ty: ValueType) {
|
||||
let ma = self.entry(arg).or_insert(MatchedArg::new(ty));
|
||||
ma.push_vals(vals);
|
||||
}
|
||||
|
||||
pub(crate) fn add_index_to(&mut self, arg: &Id, idx: usize, ty: ValueType) {
|
||||
let ma = self.entry(arg).or_insert(MatchedArg::new(ty));
|
||||
ma.push_index(idx);
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
|||
borrow::Cow,
|
||||
ffi::{OsStr, OsString},
|
||||
fmt::{Debug, Display},
|
||||
iter::{Cloned, Map},
|
||||
iter::{Cloned, Flatten, Map},
|
||||
slice::Iter,
|
||||
str::FromStr,
|
||||
};
|
||||
|
@ -234,14 +234,18 @@ impl ArgMatches {
|
|||
fn to_str_slice(o: &OsString) -> &str {
|
||||
o.to_str().expect(INVALID_UTF8)
|
||||
}
|
||||
let to_str_slice: fn(&OsString) -> &str = to_str_slice; // coerce to fn pointer
|
||||
|
||||
Values {
|
||||
iter: arg.vals().map(to_str_slice),
|
||||
iter: arg.vals_flatten().map(to_str_slice),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Placeholder documentation.
|
||||
pub fn grouped_values_of<T: Key>(&self, id: T) -> Option<Iter<Vec<OsString>>> {
|
||||
self.args.get(&Id::from(id)).map(|arg| arg.vals())
|
||||
}
|
||||
|
||||
/// Gets the lossy values of a specific argument. If the option wasn't present at runtime
|
||||
/// it returns `None`. A lossy value is one where if it contains invalid UTF-8 code points,
|
||||
/// those invalid points will be replaced with `\u{FFFD}`
|
||||
|
@ -268,7 +272,7 @@ impl ArgMatches {
|
|||
/// ```
|
||||
pub fn values_of_lossy<T: Key>(&self, id: T) -> Option<Vec<String>> {
|
||||
self.args.get(&Id::from(id)).map(|arg| {
|
||||
arg.vals()
|
||||
arg.vals_flatten()
|
||||
.map(|v| v.to_string_lossy().into_owned())
|
||||
.collect()
|
||||
})
|
||||
|
@ -305,14 +309,13 @@ impl ArgMatches {
|
|||
/// [`Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
|
||||
/// [`OsString`]: https://doc.rust-lang.org/std/ffi/struct.OsString.html
|
||||
/// [`String`]: https://doc.rust-lang.org/std/string/struct.String.html
|
||||
pub fn values_of_os<'a, T: Key>(&'a self, id: T) -> Option<OsValues<'a>> {
|
||||
pub fn values_of_os<T: Key>(&self, id: T) -> Option<OsValues> {
|
||||
fn to_str_slice(o: &OsString) -> &OsStr {
|
||||
&*o
|
||||
o
|
||||
}
|
||||
let to_str_slice: fn(&'a OsString) -> &'a OsStr = to_str_slice; // coerce to fn pointer
|
||||
|
||||
self.args.get(&Id::from(id)).map(|arg| OsValues {
|
||||
iter: arg.vals().map(to_str_slice),
|
||||
iter: arg.vals_flatten().map(to_str_slice),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -987,7 +990,8 @@ impl ArgMatches {
|
|||
#[derive(Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Values<'a> {
|
||||
iter: Map<Iter<'a, OsString>, fn(&'a OsString) -> &'a str>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
iter: Map<Flatten<std::slice::Iter<'a, Vec<OsString>>>, for<'r> fn(&'r OsString) -> &'r str>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Values<'a> {
|
||||
|
@ -1012,13 +1016,13 @@ impl<'a> ExactSizeIterator for Values<'a> {}
|
|||
/// Creates an empty iterator.
|
||||
impl<'a> Default for Values<'a> {
|
||||
fn default() -> Self {
|
||||
static EMPTY: [OsString; 0] = [];
|
||||
static EMPTY: [Vec<OsString>; 0] = [];
|
||||
// This is never called because the iterator is empty:
|
||||
fn to_str_slice(_: &OsString) -> &str {
|
||||
unreachable!()
|
||||
}
|
||||
Values {
|
||||
iter: EMPTY[..].iter().map(to_str_slice),
|
||||
iter: EMPTY[..].iter().flatten().map(to_str_slice),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1047,7 +1051,8 @@ impl<'a> Default for Values<'a> {
|
|||
#[derive(Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct OsValues<'a> {
|
||||
iter: Map<Iter<'a, OsString>, fn(&'a OsString) -> &'a OsStr>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
iter: Map<Flatten<std::slice::Iter<'a, Vec<OsString>>>, fn(&OsString) -> &OsStr>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for OsValues<'a> {
|
||||
|
@ -1072,13 +1077,13 @@ impl<'a> ExactSizeIterator for OsValues<'a> {}
|
|||
/// Creates an empty iterator.
|
||||
impl Default for OsValues<'_> {
|
||||
fn default() -> Self {
|
||||
static EMPTY: [OsString; 0] = [];
|
||||
static EMPTY: [Vec<OsString>; 0] = [];
|
||||
// This is never called because the iterator is empty:
|
||||
fn to_str_slice(_: &OsString) -> &OsStr {
|
||||
unreachable!()
|
||||
}
|
||||
OsValues {
|
||||
iter: EMPTY[..].iter().map(to_str_slice),
|
||||
iter: EMPTY[..].iter().flatten().map(to_str_slice),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
// Std
|
||||
use std::{ffi::{OsStr, OsString}, slice::Iter, iter::Cloned};
|
||||
use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
iter::{Cloned, Flatten},
|
||||
slice::Iter,
|
||||
};
|
||||
|
||||
// TODO: Maybe make this public?
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -14,7 +18,7 @@ pub(crate) struct MatchedArg {
|
|||
pub(crate) occurs: u64,
|
||||
pub(crate) ty: ValueType,
|
||||
indices: Vec<usize>,
|
||||
vals: Vec<OsString>,
|
||||
vals: Vec<Vec<OsString>>,
|
||||
}
|
||||
|
||||
impl MatchedArg {
|
||||
|
@ -39,16 +43,24 @@ impl MatchedArg {
|
|||
self.indices.push(index)
|
||||
}
|
||||
|
||||
pub(crate) fn vals(&self) -> Iter<'_, OsString> {
|
||||
pub(crate) fn vals(&self) -> Iter<Vec<OsString>> {
|
||||
self.vals.iter()
|
||||
}
|
||||
|
||||
pub(crate) fn vals_flatten(&self) -> Flatten<Iter<Vec<OsString>>> {
|
||||
self.vals.iter().flatten()
|
||||
}
|
||||
|
||||
pub(crate) fn get_val(&self, index: usize) -> Option<&OsString> {
|
||||
self.vals.get(index)
|
||||
self.vals.get(0)?.get(index)
|
||||
}
|
||||
|
||||
pub(crate) fn push_val(&mut self, val: OsString) {
|
||||
self.vals.push(val)
|
||||
self.vals.push(vec![val])
|
||||
}
|
||||
|
||||
pub(crate) fn push_vals(&mut self, vals: Vec<OsString>) {
|
||||
self.vals.push(vals)
|
||||
}
|
||||
|
||||
pub(crate) fn num_vals(&self) -> usize {
|
||||
|
@ -64,8 +76,7 @@ impl MatchedArg {
|
|||
}
|
||||
|
||||
pub(crate) fn contains_val(&self, val: &str) -> bool {
|
||||
self.vals
|
||||
.iter()
|
||||
self.vals_flatten()
|
||||
.any(|v| OsString::as_os_str(v) == OsStr::new(val))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1226,46 +1226,88 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
);
|
||||
if !(self.is_set(AS::TrailingValues) && self.is_set(AS::DontDelimitTrailingValues)) {
|
||||
if let Some(delim) = arg.val_delim {
|
||||
let mut iret = ParseResult::ValuesDone;
|
||||
for v in val.split(delim) {
|
||||
iret = self.add_single_val_to_arg(arg, &v, matcher, ty);
|
||||
}
|
||||
let arg_split = val.split(delim);
|
||||
let vals = if let Some(t) = arg.terminator {
|
||||
let mut vals = vec![];
|
||||
for val in arg_split {
|
||||
if t == val {
|
||||
break;
|
||||
}
|
||||
vals.push(val);
|
||||
}
|
||||
vals
|
||||
} else {
|
||||
arg_split.into_iter().collect()
|
||||
};
|
||||
let mut parse_result = if vals.is_empty() {
|
||||
ParseResult::ValuesDone
|
||||
} else {
|
||||
self.add_multiple_val_to_arg(arg, vals, matcher, ty)
|
||||
};
|
||||
// If there was a delimiter used or we must use the delimiter to
|
||||
// separate the values, we're not looking for more values.
|
||||
if val.contains_char(delim) || arg.is_set(ArgSettings::RequireDelimiter) {
|
||||
iret = ParseResult::ValuesDone;
|
||||
parse_result = ParseResult::ValuesDone;
|
||||
}
|
||||
return iret;
|
||||
return parse_result;
|
||||
}
|
||||
}
|
||||
if let Some(t) = arg.terminator {
|
||||
if t == val {
|
||||
return ParseResult::ValuesDone;
|
||||
}
|
||||
}
|
||||
self.add_single_val_to_arg(arg, val, matcher, ty)
|
||||
}
|
||||
|
||||
fn add_single_val_to_arg(
|
||||
fn add_multiple_val_to_arg(
|
||||
&self,
|
||||
arg: &Arg<'help>,
|
||||
v: &ArgStr,
|
||||
vals: Vec<ArgStr>,
|
||||
matcher: &mut ArgMatcher,
|
||||
ty: ValueType,
|
||||
) -> ParseResult {
|
||||
debug!("Parser::add_single_val_to_arg: adding val...{:?}", v);
|
||||
debug!("Parser::add_multiple_val_to_arg: adding vals...{:?}", vals);
|
||||
|
||||
let num_vals = vals.len();
|
||||
let begin_index = self.cur_idx.get() + 1;
|
||||
self.cur_idx.set(self.cur_idx.get() + num_vals);
|
||||
for i in begin_index..begin_index + num_vals {
|
||||
matcher.add_index_to(&arg.id, i, ty);
|
||||
}
|
||||
let vals: Vec<_> = vals.into_iter().map(|x| x.to_os_string()).collect();
|
||||
// Increment or create the group "args"
|
||||
for val in vals.iter() {
|
||||
for group in self.app.groups_for_arg(&arg.id) {
|
||||
matcher.add_val_to(&group, val.clone(), ty);
|
||||
}
|
||||
}
|
||||
matcher.add_vals_to(&arg.id, vals, ty);
|
||||
if matcher.needs_more_vals(arg) {
|
||||
ParseResult::Opt(arg.id.clone())
|
||||
} else {
|
||||
ParseResult::ValuesDone
|
||||
}
|
||||
}
|
||||
|
||||
fn add_single_val_to_arg(
|
||||
&self,
|
||||
arg: &Arg<'help>,
|
||||
val: &ArgStr,
|
||||
matcher: &mut ArgMatcher,
|
||||
ty: ValueType,
|
||||
) -> ParseResult {
|
||||
debug!("Parser::add_single_val_to_arg: adding val...{:?}", val);
|
||||
|
||||
// update the current index because each value is a distinct index to clap
|
||||
self.cur_idx.set(self.cur_idx.get() + 1);
|
||||
|
||||
// @TODO @docs @p4 docs should probably note that terminator doesn't get an index
|
||||
if let Some(t) = arg.terminator {
|
||||
if t == v {
|
||||
return ParseResult::ValuesDone;
|
||||
}
|
||||
}
|
||||
|
||||
matcher.add_val_to(&arg.id, v.to_os_string(), ty);
|
||||
matcher.add_val_to(&arg.id, val.to_os_string(), ty);
|
||||
matcher.add_index_to(&arg.id, self.cur_idx.get(), ty);
|
||||
|
||||
// Increment or create the group "args"
|
||||
for group in self.app.groups_for_arg(&arg.id) {
|
||||
matcher.add_val_to(&group, v.to_os_string(), ty);
|
||||
matcher.add_val_to(&group, val.to_os_string(), ty);
|
||||
}
|
||||
|
||||
if matcher.needs_more_vals(arg) {
|
||||
|
@ -1379,7 +1421,7 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
for (id, val, default) in arg.default_vals_ifs.values() {
|
||||
let add = if let Some(a) = matcher.get(&id) {
|
||||
if let Some(v) = val {
|
||||
a.vals().any(|value| v == value)
|
||||
a.vals_flatten().any(|value| v == value)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
|
|||
matcher: &ArgMatcher,
|
||||
) -> ClapResult<()> {
|
||||
debug!("Validator::validate_arg_values: arg={:?}", arg.name);
|
||||
for val in ma.vals() {
|
||||
for val in ma.vals_flatten() {
|
||||
if self.p.is_set(AS::StrictUtf8) && val.to_str().is_none() {
|
||||
debug!(
|
||||
"Validator::validate_arg_values: invalid UTF-8 found in val {:?}",
|
||||
|
@ -399,7 +399,8 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
|
|||
for (name, ma) in matcher.iter() {
|
||||
debug!(
|
||||
"Validator::validate_matched_args:iter:{:?}: vals={:#?}",
|
||||
name, ma.vals()
|
||||
name,
|
||||
ma.vals_flatten()
|
||||
);
|
||||
if let Some(arg) = self.p.app.find(name) {
|
||||
self.validate_arg_num_vals(arg, ma)?;
|
||||
|
@ -467,7 +468,7 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
|
|||
if (ma.num_vals() as u64) > num {
|
||||
debug!("Validator::validate_arg_num_vals: Sending error TooManyValues");
|
||||
return Err(Error::too_many_values(
|
||||
ma.vals()
|
||||
ma.vals_flatten()
|
||||
.last()
|
||||
.expect(INTERNAL_ERROR_MSG)
|
||||
.to_str()
|
||||
|
@ -517,7 +518,7 @@ impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
|
|||
for (val, name) in &a.requires {
|
||||
if let Some(val) = val {
|
||||
let missing_req = |v| v == val && !matcher.contains(&name);
|
||||
if ma.vals().any(missing_req) {
|
||||
if ma.vals_flatten().any(missing_req) {
|
||||
return self.missing_required_error(matcher, vec![a.id.clone()]);
|
||||
}
|
||||
} else if !matcher.contains(&name) {
|
||||
|
|
Loading…
Reference in a new issue