mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 06:12:40 +00:00
Auto merge of #800 - kbknapp:issue-782-take2, r=kbknapp
Issue 782 take2
This commit is contained in:
commit
446ec981d1
12 changed files with 276 additions and 125 deletions
|
@ -21,7 +21,7 @@ unicode-width = "=0.1.4"
|
|||
unicode-segmentation = "1.0.1"
|
||||
strsim = { version = "=0.6.0", optional = true }
|
||||
ansi_term = { version = "=0.9.0", optional = true }
|
||||
term_size = { version = "=0.2.0", optional = true }
|
||||
term_size = { version = "=0.2.1", optional = true }
|
||||
libc = { version = "=0.2.18", optional = true }
|
||||
yaml-rust = { version = "=0.3.5", optional = true }
|
||||
clippy = { version = "~0.0.104", optional = true }
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
extern crate clap;
|
||||
extern crate test;
|
||||
|
||||
use clap::{App, Arg, SubCommand};
|
||||
use clap::{App, Arg, SubCommand, AppSettings};
|
||||
|
||||
use test::Bencher;
|
||||
|
||||
|
@ -219,6 +219,10 @@ fn parse_complex2(b: &mut Bencher) {
|
|||
b.iter(|| create_app!().get_matches_from(vec!["myprog", "arg1", "-f", "arg2", "--long-option-2", "some", "-O", "slow", "--multvalsmo", "one", "two", "--minvals2", "3", "2", "1"]));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn parse_complex2_with_args_negate_scs(b: &mut Bencher) {
|
||||
b.iter(|| create_app!().setting(AppSettings::ArgsNegateSubcommands).get_matches_from(vec!["myprog", "arg1", "-f", "arg2", "--long-option-2", "some", "-O", "slow", "--multvalsmo", "one", "two", "--minvals2", "3", "2", "1"]));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn parse_sc_complex(b: &mut Bencher) {
|
||||
|
|
|
@ -486,7 +486,7 @@ impl<'a, 'b> App<'a, 'b> {
|
|||
/// [`AppSettings`]: ./enum.AppSettings.html
|
||||
pub fn global_setting(mut self, setting: AppSettings) -> Self {
|
||||
self.p.set(setting);
|
||||
self.p.g_settings.push(setting);
|
||||
self.p.g_settings.set(setting);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -510,7 +510,7 @@ impl<'a, 'b> App<'a, 'b> {
|
|||
pub fn global_settings(mut self, settings: &[AppSettings]) -> Self {
|
||||
for s in settings {
|
||||
self.p.set(*s);
|
||||
self.p.g_settings.push(*s)
|
||||
self.p.g_settings.set(*s)
|
||||
}
|
||||
self
|
||||
}
|
||||
|
@ -1517,6 +1517,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> {
|
|||
fn required_unless(&self) -> Option<&[&'e str]> { None }
|
||||
fn val_names(&self) -> Option<&VecMap<&'e str>> { None }
|
||||
fn is_set(&self, _: ArgSettings) -> bool { false }
|
||||
fn val_terminator(&self) -> Option<&'e str> {None}
|
||||
fn set(&mut self, _: ArgSettings) {
|
||||
unreachable!("App struct does not support AnyArg::set, this is a bug!")
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ pub struct Parser<'a, 'b>
|
|||
help_short: Option<char>,
|
||||
version_short: Option<char>,
|
||||
settings: AppFlags,
|
||||
pub g_settings: Vec<AppSettings>,
|
||||
pub g_settings: AppFlags,
|
||||
pub meta: AppMeta<'b>,
|
||||
pub id: usize,
|
||||
trailing_vals: bool,
|
||||
|
@ -83,7 +83,7 @@ impl<'a, 'b> Default for Parser<'a, 'b> {
|
|||
groups: HashMap::new(),
|
||||
global_args: vec![],
|
||||
overrides: vec![],
|
||||
g_settings: vec![],
|
||||
g_settings: AppFlags::new(),
|
||||
settings: AppFlags::new(),
|
||||
meta: AppMeta::new(),
|
||||
trailing_vals: false,
|
||||
|
@ -272,10 +272,8 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
sc.p.set(AppSettings::GlobalVersion);
|
||||
sc.p.meta.version = Some(self.meta.version.unwrap());
|
||||
}
|
||||
for s in &self.g_settings {
|
||||
sc.p.set(*s);
|
||||
sc.p.g_settings.push(*s);
|
||||
}
|
||||
sc.p.settings = sc.p.settings | self.g_settings;
|
||||
sc.p.g_settings = sc.p.settings | self.g_settings;
|
||||
}
|
||||
sc.p.propogate_settings();
|
||||
}
|
||||
|
@ -553,14 +551,17 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
&& p.v.num_vals.is_none()).map(|_| 1).sum::<u64>() <= 1,
|
||||
"Only one positional argument with .multiple(true) set is allowed per command");
|
||||
|
||||
debug_assert!(self.positionals.values()
|
||||
.rev()
|
||||
.next()
|
||||
.unwrap()
|
||||
.is_set(ArgSettings::Required),
|
||||
debug_assert!({
|
||||
let mut it = self.positionals.values().rev();
|
||||
// Either the final positional is required
|
||||
it.next().unwrap().is_set(ArgSettings::Required)
|
||||
// Or the second to last has a terminator set
|
||||
|| it.next().unwrap().v.terminator.is_some()
|
||||
},
|
||||
"When using a positional argument with .multiple(true) that is *not the last* \
|
||||
positional argument, the last positional argument (i.e the one with the highest \
|
||||
index) *must* have .required(true) set.");
|
||||
index) *must* have .required(true) set."
|
||||
);
|
||||
|
||||
debug_assert!({
|
||||
let num = self.positionals.len() - 1;
|
||||
|
@ -611,7 +612,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
// Checks if the arg matches a subcommand name, or any of it's aliases (if defined)
|
||||
#[inline]
|
||||
fn possible_subcommand(&self, arg_os: &OsStr) -> bool {
|
||||
debugln!("Parser::possible_subcommand;");
|
||||
debugln!("Parser::possible_subcommand: arg={:?}", arg_os);
|
||||
if self.is_set(AppSettings::ArgsNegateSubcommands) && self.valid_arg {
|
||||
return false;
|
||||
}
|
||||
|
@ -691,9 +692,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
pb.b.help = Some("The subcommand whose help message to display");
|
||||
pb.set(ArgSettings::Multiple);
|
||||
sc.positionals.insert(1, pb);
|
||||
for s in self.g_settings.clone() {
|
||||
sc.set(s);
|
||||
}
|
||||
sc.settings = sc.settings | self.g_settings;
|
||||
} else {
|
||||
sc.create_help_and_version();
|
||||
}
|
||||
|
@ -704,8 +703,8 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn check_is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: Option<&'a str>) -> bool {
|
||||
debugln!("Parser::check_is_new_arg: Needs Val of={:?}", needs_val_of);
|
||||
fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: Option<&'a str>) -> bool {
|
||||
debugln!("Parser::is_new_arg: arg={:?}, Needs Val of={:?}", arg_os, needs_val_of);
|
||||
let app_wide_settings = if self.is_set(AppSettings::AllowLeadingHyphen) {
|
||||
true
|
||||
} else if self.is_set(AppSettings::AllowNegativeNumbers) {
|
||||
|
@ -721,19 +720,19 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
};
|
||||
let arg_allows_tac = if let Some(name) = needs_val_of {
|
||||
if let Some(o) = find_by_name!(self, &name, opts, iter) {
|
||||
!(o.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings)
|
||||
} else if let Some(p) = find_by_name!(self, &name, opts, iter) {
|
||||
!(p.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings)
|
||||
(o.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings)
|
||||
} else if let Some(p) = find_by_name!(self, &name, positionals, values) {
|
||||
(p.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings)
|
||||
} else {
|
||||
true
|
||||
false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
false
|
||||
};
|
||||
debugln!("Parser::check_is_new_arg: Does arg allow leading hyphen...{:?}", !arg_allows_tac);
|
||||
debugln!("Parser::is_new_arg: Does arg allow leading hyphen...{:?}", arg_allows_tac);
|
||||
|
||||
// Is this a new argument, or values from a previous option?
|
||||
debug!("Parser::check_is_new_arg: Starts new arg...");
|
||||
debug!("Parser::is_new_arg: Starts new arg...");
|
||||
let mut ret = if arg_os.starts_with(b"--") {
|
||||
sdebugln!("--");
|
||||
if arg_os.len_() == 2 {
|
||||
|
@ -749,9 +748,9 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
false
|
||||
};
|
||||
|
||||
ret = ret && arg_allows_tac;
|
||||
ret = ret && !arg_allows_tac;
|
||||
|
||||
debugln!("Parser::check_is_new_arg: Starts new arg...{:?}", ret);
|
||||
debugln!("Parser::is_new_arg: Starts new arg...{:?}", ret);
|
||||
ret
|
||||
}
|
||||
|
||||
|
@ -781,94 +780,102 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
|
||||
self.valid_neg_num = false;
|
||||
// Is this a new argument, or values from a previous option?
|
||||
let starts_new_arg = self.check_is_new_arg(&arg_os, needs_val_of);
|
||||
let starts_new_arg = self.is_new_arg(&arg_os, needs_val_of);
|
||||
|
||||
// Has the user already passed '--'? Meaning only positional args follow
|
||||
if !self.trailing_vals {
|
||||
// Does the arg match a subcommand name, or any of it's aliases (if defined)
|
||||
let pos_sc = self.possible_subcommand(&arg_os);
|
||||
|
||||
// If the arg doesn't start with a `-` (except numbers, or AllowLeadingHyphen) and
|
||||
// isn't a subcommand
|
||||
if !starts_new_arg && !pos_sc {
|
||||
// Check to see if parsing a value from an option
|
||||
if let Some(name) = needs_val_of {
|
||||
// get the OptBuilder so we can check the settings
|
||||
if let Some(arg) = find_by_name!(self, &name, opts, iter) {
|
||||
needs_val_of = try!(self.add_val_to_arg(&*arg, &arg_os, matcher));
|
||||
// get the next value from the iterator
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if arg_os.starts_with(b"--") {
|
||||
if arg_os.len_() == 2 {
|
||||
// The user has passed '--' which means only positional args follow no
|
||||
// matter what they start with
|
||||
self.trailing_vals = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
needs_val_of = try!(self.parse_long_arg(matcher, &arg_os));
|
||||
if !(needs_val_of.is_none() && self.is_set(AppSettings::AllowLeadingHyphen)) {
|
||||
continue;
|
||||
}
|
||||
} else if arg_os.starts_with(b"-") && arg_os.len_() != 1 {
|
||||
// Try to parse short args like normal, if AllowLeadingHyphen or
|
||||
// AllowNegativeNumbers is set, parse_short_arg will *not* throw
|
||||
// an error, and instead return Ok(None)
|
||||
needs_val_of = try!(self.parse_short_arg(matcher, &arg_os));
|
||||
// If it's None, we then check if one of those two AppSettings was set
|
||||
if needs_val_of.is_none() {
|
||||
if self.is_set(AppSettings::AllowNegativeNumbers) {
|
||||
if !(arg_os.to_string_lossy().parse::<i64>().is_ok() ||
|
||||
arg_os.to_string_lossy().parse::<f64>().is_ok()) {
|
||||
return Err(Error::unknown_argument(&*arg_os.to_string_lossy(),
|
||||
"",
|
||||
&*self.create_current_usage(matcher),
|
||||
self.color()));
|
||||
}
|
||||
} else if !self.is_set(AppSettings::AllowLeadingHyphen) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if pos_sc {
|
||||
if self.possible_subcommand(&arg_os) {
|
||||
if &*arg_os == "help" && self.is_set(AppSettings::NeedsSubcommandHelp) {
|
||||
try!(self.parse_help_subcommand(it));
|
||||
}
|
||||
subcmd_name = Some(arg_os.to_str().expect(INVALID_UTF8).to_owned());
|
||||
break;
|
||||
} else if self.is_set(AppSettings::ArgsNegateSubcommands) {
|
||||
();
|
||||
} else if let Some(cdate) =
|
||||
}
|
||||
|
||||
if !starts_new_arg {
|
||||
if let Some(name) = needs_val_of {
|
||||
// Check to see if parsing a value from a previous arg
|
||||
if let Some(arg) = find_by_name!(self, &name, opts, iter) {
|
||||
// get the OptBuilder so we can check the settings
|
||||
needs_val_of = try!(self.add_val_to_arg(&*arg, &arg_os, matcher));
|
||||
// get the next value from the iterator
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if arg_os.starts_with(b"--") {
|
||||
if arg_os.len_() == 2 {
|
||||
// The user has passed '--' which means only positional args follow no
|
||||
// matter what they start with
|
||||
self.trailing_vals = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
needs_val_of = try!(self.parse_long_arg(matcher, &arg_os));
|
||||
if !(needs_val_of.is_none() && self.is_set(AppSettings::AllowLeadingHyphen)) {
|
||||
continue;
|
||||
}
|
||||
} else if arg_os.starts_with(b"-") && arg_os.len_() != 1 {
|
||||
// Try to parse short args like normal, if AllowLeadingHyphen or
|
||||
// AllowNegativeNumbers is set, parse_short_arg will *not* throw
|
||||
// an error, and instead return Ok(None)
|
||||
needs_val_of = try!(self.parse_short_arg(matcher, &arg_os));
|
||||
// If it's None, we then check if one of those two AppSettings was set
|
||||
if needs_val_of.is_none() {
|
||||
if self.is_set(AppSettings::AllowNegativeNumbers) {
|
||||
if !(arg_os.to_string_lossy().parse::<i64>().is_ok() ||
|
||||
arg_os.to_string_lossy().parse::<f64>().is_ok()) {
|
||||
return Err(Error::unknown_argument(&*arg_os.to_string_lossy(),
|
||||
"",
|
||||
&*self.create_current_usage(matcher),
|
||||
self.color()));
|
||||
}
|
||||
} else if !self.is_set(AppSettings::AllowLeadingHyphen) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !self.is_set(AppSettings::ArgsNegateSubcommands) {
|
||||
if let Some(cdate) =
|
||||
suggestions::did_you_mean(&*arg_os.to_string_lossy(),
|
||||
self.subcommands
|
||||
.iter()
|
||||
.map(|s| &s.p.meta.name)) {
|
||||
return Err(Error::invalid_subcommand(arg_os.to_string_lossy().into_owned(),
|
||||
cdate,
|
||||
self.meta
|
||||
.bin_name
|
||||
.as_ref()
|
||||
.unwrap_or(&self.meta.name),
|
||||
&*self.create_current_usage(matcher),
|
||||
self.color()));
|
||||
return Err(Error::invalid_subcommand(arg_os.to_string_lossy().into_owned(),
|
||||
cdate,
|
||||
self.meta
|
||||
.bin_name
|
||||
.as_ref()
|
||||
.unwrap_or(&self.meta.name),
|
||||
&*self.create_current_usage(matcher),
|
||||
self.color()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let low_index_mults = self.is_set(AppSettings::LowIndexMultiplePositional) &&
|
||||
!self.positionals.is_empty() &&
|
||||
pos_counter == (self.positionals.len() - 1);
|
||||
debugln!("Parser::get_matches_with: Low index multiples...{:?}", low_index_mults);
|
||||
debugln!("Parser::get_matches_with: Positional counter...{}", pos_counter);
|
||||
debug!("Parser::get_matches_with: Checking for low index multiples...");
|
||||
if self.is_set(AppSettings::LowIndexMultiplePositional) &&
|
||||
!self.positionals.is_empty() &&
|
||||
pos_counter == (self.positionals.len() - 1) {
|
||||
sdebugln!("Found");
|
||||
if low_index_mults {
|
||||
if let Some(na) = it.peek() {
|
||||
let n = (*na).clone().into();
|
||||
if self.check_is_new_arg(&n, needs_val_of) || self.possible_subcommand(&n) ||
|
||||
needs_val_of = if let None = needs_val_of {
|
||||
if let Some(p) = self.positionals.get(pos_counter) {
|
||||
Some(p.b.name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if self.is_new_arg(&n, needs_val_of) || self.possible_subcommand(&n) ||
|
||||
suggestions::did_you_mean(&n.to_string_lossy(),
|
||||
self.subcommands
|
||||
.iter()
|
||||
|
@ -881,8 +888,6 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
debugln!("Parser::get_matches_with: Bumping the positional counter...");
|
||||
pos_counter += 1;
|
||||
}
|
||||
} else {
|
||||
sdebugln!("None");
|
||||
}
|
||||
if let Some(p) = self.positionals.get(pos_counter) {
|
||||
parse_positional!(self, p, arg_os, pos_counter, matcher);
|
||||
|
@ -932,14 +937,15 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
let sc_name = &*self.subcommands
|
||||
.iter()
|
||||
.filter(|sc| sc.p.meta.aliases.is_some())
|
||||
.filter(|sc| sc.p
|
||||
.meta
|
||||
.aliases
|
||||
.as_ref()
|
||||
.expect(INTERNAL_ERROR_MSG)
|
||||
.iter()
|
||||
.any(|&(a, _)| &a == &&*pos_sc_name)
|
||||
)
|
||||
.filter(|sc| {
|
||||
sc.p
|
||||
.meta
|
||||
.aliases
|
||||
.as_ref()
|
||||
.expect(INTERNAL_ERROR_MSG)
|
||||
.iter()
|
||||
.any(|&(a, _)| &a == &&*pos_sc_name)
|
||||
})
|
||||
.map(|sc| sc.p.meta.name.clone())
|
||||
.next()
|
||||
.expect(INTERNAL_ERROR_MSG);
|
||||
|
@ -972,18 +978,19 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
let mut reqs_validated = false;
|
||||
if let Some(a) = needs_val_of {
|
||||
debugln!("Parser::validate: needs_val_of={:?}", a);
|
||||
let o = find_by_name!(self, &a, opts, iter).expect(INTERNAL_ERROR_MSG);
|
||||
try!(self.validate_required(matcher));
|
||||
reqs_validated = true;
|
||||
let should_err = if let Some(v) = matcher.0.args.get(&*o.b.name) {
|
||||
v.vals.is_empty() && !(o.v.min_vals.is_some() && o.v.min_vals.unwrap() == 0)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
if should_err {
|
||||
return Err(Error::empty_value(o,
|
||||
&*self.create_current_usage(matcher),
|
||||
self.color()));
|
||||
if let Some(o) = find_by_name!(self, &a, opts, iter) {
|
||||
try!(self.validate_required(matcher));
|
||||
reqs_validated = true;
|
||||
let should_err = if let Some(v) = matcher.0.args.get(&*o.b.name) {
|
||||
v.vals.is_empty() && !(o.v.min_vals.is_some() && o.v.min_vals.unwrap() == 0)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
if should_err {
|
||||
return Err(Error::empty_value(o,
|
||||
&*self.create_current_usage(matcher),
|
||||
self.color()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1523,6 +1530,11 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
{
|
||||
debugln!("Parser::add_single_val_to_arg;");
|
||||
debugln!("Parser::add_single_val_to_arg: adding val...{:?}", v);
|
||||
if let Some(t) = arg.val_terminator() {
|
||||
if t == v {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
matcher.add_val_to(arg.name(), v);
|
||||
|
||||
// Increment or create the group "args"
|
||||
|
@ -2169,8 +2181,8 @@ impl<'a, 'b> Clone for Parser<'a, 'b>
|
|||
overrides: self.overrides.clone(),
|
||||
help_short: self.help_short,
|
||||
version_short: self.version_short,
|
||||
settings: self.settings.clone(),
|
||||
g_settings: self.g_settings.clone(),
|
||||
settings: self.settings,
|
||||
g_settings: self.g_settings,
|
||||
meta: self.meta.clone(),
|
||||
trailing_vals: self.trailing_vals,
|
||||
id: self.id,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Std
|
||||
use std::ascii::AsciiExt;
|
||||
use std::str::FromStr;
|
||||
use std::ops::BitOr;
|
||||
|
||||
bitflags! {
|
||||
flags Flags: u32 {
|
||||
|
@ -40,13 +41,20 @@ bitflags! {
|
|||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Copy)]
|
||||
pub struct AppFlags(Flags);
|
||||
|
||||
impl Clone for AppFlags {
|
||||
fn clone(&self) -> Self { AppFlags(self.0) }
|
||||
}
|
||||
|
||||
impl BitOr for AppFlags {
|
||||
type Output = Self;
|
||||
fn bitor(self, rhs: Self) -> Self {
|
||||
AppFlags(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AppFlags {
|
||||
fn default() -> Self {
|
||||
AppFlags(NEEDS_LONG_VERSION | NEEDS_LONG_HELP | NEEDS_SC_HELP | UTF8_NONE | COLOR_AUTO)
|
||||
|
|
|
@ -38,6 +38,7 @@ pub trait AnyArg<'n, 'e>: std_fmt::Display {
|
|||
fn default_vals_ifs(&self) -> Option<vec_map::Values<(&'n str, Option<&'e str>, &'e str)>>;
|
||||
fn longest_filter(&self) -> bool;
|
||||
fn kind(&self) -> ArgKind;
|
||||
fn val_terminator(&self) -> Option<&'e str>;
|
||||
}
|
||||
|
||||
pub trait DispOrder {
|
||||
|
|
|
@ -84,6 +84,8 @@ pub struct Arg<'a, 'b>
|
|||
pub r_unless: Option<Vec<&'a str>>,
|
||||
#[doc(hidden)]
|
||||
pub r_ifs: Option<Vec<(&'a str, &'b str)>>,
|
||||
#[doc(hidden)]
|
||||
pub val_terminator: Option<&'b str>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Default for Arg<'a, 'b> {
|
||||
|
@ -113,6 +115,7 @@ impl<'a, 'b> Default for Arg<'a, 'b> {
|
|||
disp_ord: 999,
|
||||
r_unless: None,
|
||||
r_ifs: None,
|
||||
val_terminator: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1819,6 +1822,55 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Specifies a value that *stops* parsing multiple values of a give argument. By default when
|
||||
/// one sets [`multiple(true)`] on an argument, clap will continue parsing values for that
|
||||
/// argument until it reaches another valid argument, or one of the other more specific settings
|
||||
/// for multiple values is used (such as [`min_values`], [`max_values`] or
|
||||
/// [`number_of_values`]).
|
||||
///
|
||||
/// **NOTE:** This setting only applies to [options] and [positional arguments]
|
||||
///
|
||||
/// **NOTE:** When the terminator is passed in on the command line, it is **not** stored as one
|
||||
/// of the vaues
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// Arg::with_name("vals")
|
||||
/// .takes_value(true)
|
||||
/// .multiple(true)
|
||||
/// .value_terminator(";")
|
||||
/// # ;
|
||||
/// ```
|
||||
/// The following example uses two arguments, a sequence of commands, and the location in which
|
||||
/// to perform them
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("do")
|
||||
/// .arg(Arg::with_name("cmds")
|
||||
/// .multiple(true)
|
||||
/// .allow_hyphen_values(true)
|
||||
/// .value_terminator(";"))
|
||||
/// .arg(Arg::with_name("location"))
|
||||
/// .get_matches_from(vec!["do", "find", "-type", "f", "-name", "special", ";", "/home/clap"]);
|
||||
/// let cmds: Vec<_> = m.values_of("cmds").unwrap().collect();
|
||||
/// assert_eq!(&cmds, &["find", "-type", "f", "-name", "special"]);
|
||||
/// assert_eq!(m.value_of("location"), Some("/home/clap"));
|
||||
/// ```
|
||||
/// [options]: ./struct.Arg.html#method.takes_value
|
||||
/// [positional arguments]: ./struct.Arg.html#method.index
|
||||
/// [`multiple(true)`]: ./struct.Arg.html#method.multiple
|
||||
/// [`min_values`]: ./struct.Arg.html#method.min_values
|
||||
/// [`number_of_values`]: ./struct.Arg.html#method.number_of_values
|
||||
/// [`max_values`]: ./struct.Arg.html#method.max_values
|
||||
pub fn value_terminator(mut self, term: &'b str) -> Self {
|
||||
self.setb(ArgSettings::TakesValue);
|
||||
self.val_terminator = Some(term);
|
||||
self
|
||||
}
|
||||
|
||||
/// Specifies that an argument can be matched to all child [`SubCommand`]s.
|
||||
///
|
||||
/// **NOTE:** Global arguments *only* propagate down, **not** up (to parent commands)
|
||||
|
@ -3153,6 +3205,7 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for Arg<'a, 'b> {
|
|||
disp_ord: a.disp_ord,
|
||||
r_unless: a.r_unless.clone(),
|
||||
r_ifs: a.r_ifs.clone(),
|
||||
val_terminator: a.val_terminator.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3184,6 +3237,7 @@ impl<'a, 'b> Clone for Arg<'a, 'b> {
|
|||
disp_ord: self.disp_ord,
|
||||
r_unless: self.r_unless.clone(),
|
||||
r_ifs: self.r_ifs.clone(),
|
||||
val_terminator: self.val_terminator.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> {
|
|||
fn long(&self) -> Option<&'e str> { self.s.long }
|
||||
fn val_delim(&self) -> Option<char> { None }
|
||||
fn help(&self) -> Option<&'e str> { self.b.help }
|
||||
fn val_terminator(&self) -> Option<&'e str> {None}
|
||||
fn default_val(&self) -> Option<&'n str> { None }
|
||||
fn default_vals_ifs(&self) -> Option<vec_map::Values<(&'n str, Option<&'e str>, &'e str)>> {None}
|
||||
fn longest_filter(&self) -> bool { self.s.long.is_some() }
|
||||
|
|
|
@ -105,6 +105,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> {
|
|||
fn has_switch(&self) -> bool { true }
|
||||
fn set(&mut self, s: ArgSettings) { self.b.settings.set(s) }
|
||||
fn max_vals(&self) -> Option<u64> { self.v.max_vals }
|
||||
fn val_terminator(&self) -> Option<&'e str> { self.v.terminator }
|
||||
fn num_vals(&self) -> Option<u64> { self.v.num_vals }
|
||||
fn possible_vals(&self) -> Option<&[&'e str]> { self.v.possible_vals.as_ref().map(|o| &o[..]) }
|
||||
fn validator(&self) -> Option<&Rc<Fn(String) -> StdResult<(), String>>> {
|
||||
|
|
|
@ -109,6 +109,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> {
|
|||
fn set(&mut self, s: ArgSettings) { self.b.settings.set(s) }
|
||||
fn has_switch(&self) -> bool { false }
|
||||
fn max_vals(&self) -> Option<u64> { self.v.max_vals }
|
||||
fn val_terminator(&self) -> Option<&'e str> { self.v.terminator }
|
||||
fn num_vals(&self) -> Option<u64> { self.v.num_vals }
|
||||
fn possible_vals(&self) -> Option<&[&'e str]> { self.v.possible_vals.as_ref().map(|o| &o[..]) }
|
||||
fn validator(&self) -> Option<&Rc<Fn(String) -> StdResult<(), String>>> {
|
||||
|
|
|
@ -20,6 +20,7 @@ pub struct Valued<'a, 'b>
|
|||
pub val_delim: Option<char>,
|
||||
pub default_val: Option<&'a str>,
|
||||
pub default_vals_ifs: Option<VecMap<(&'a str, Option<&'b str>, &'b str)>>,
|
||||
pub terminator: Option<&'b str>,
|
||||
}
|
||||
|
||||
impl<'n, 'e> Default for Valued<'n, 'e> {
|
||||
|
@ -35,6 +36,7 @@ impl<'n, 'e> Default for Valued<'n, 'e> {
|
|||
val_delim: Some(','),
|
||||
default_val: None,
|
||||
default_vals_ifs: None,
|
||||
terminator: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +54,7 @@ impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Valued<'n, 'e> {
|
|||
val_delim: a.val_delim,
|
||||
default_val: a.default_val,
|
||||
default_vals_ifs: a.default_vals_ifs.clone(),
|
||||
terminator: a.val_terminator.clone(),
|
||||
};
|
||||
if let Some(ref vec) = a.val_names {
|
||||
if vec.len() > 1 {
|
||||
|
|
|
@ -1105,3 +1105,68 @@ fn low_index_positional_with_flag() {
|
|||
assert_eq!(m.value_of("target").unwrap(), "target");
|
||||
assert!(m.is_present("flg"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_value_terminator_option() {
|
||||
let m = App::new("lip")
|
||||
.arg(Arg::with_name("files")
|
||||
.short("f")
|
||||
.value_terminator(";")
|
||||
.multiple(true))
|
||||
.arg(Arg::with_name("other"))
|
||||
.get_matches_from_safe(vec![
|
||||
"lip",
|
||||
"-f", "val1", "val2", ";",
|
||||
"otherval"
|
||||
]);
|
||||
|
||||
assert!(m.is_ok(), "{:?}", m.unwrap_err().kind);
|
||||
let m = m.unwrap();
|
||||
|
||||
assert!(m.is_present("other"));
|
||||
assert_eq!(m.occurrences_of("other"), 1);
|
||||
assert!(m.is_present("files"));
|
||||
assert_eq!(m.values_of("files").unwrap().collect::<Vec<_>>(), ["val1", "val2"]);
|
||||
assert_eq!(m.value_of("other"), Some("otherval"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_value_terminator_option_other_arg() {
|
||||
let m = App::new("lip")
|
||||
.arg(Arg::with_name("files")
|
||||
.short("f")
|
||||
.value_terminator(";")
|
||||
.multiple(true))
|
||||
.arg(Arg::with_name("other"))
|
||||
.arg(Arg::with_name("flag")
|
||||
.short("-F"))
|
||||
.get_matches_from_safe(vec![
|
||||
"lip",
|
||||
"-f", "val1", "val2",
|
||||
"-F",
|
||||
"otherval"
|
||||
]);
|
||||
|
||||
assert!(m.is_ok(), "{:?}", m.unwrap_err().kind);
|
||||
let m = m.unwrap();
|
||||
|
||||
assert!(m.is_present("other"));
|
||||
assert!(m.is_present("files"));
|
||||
assert_eq!(m.values_of("files").unwrap().collect::<Vec<_>>(), ["val1", "val2"]);
|
||||
assert_eq!(m.value_of("other"), Some("otherval"));
|
||||
assert!(m.is_present("flag"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_vals_with_hyphen() {
|
||||
let m = App::new("do")
|
||||
.arg(Arg::with_name("cmds")
|
||||
.multiple(true)
|
||||
.allow_hyphen_values(true)
|
||||
.value_terminator(";"))
|
||||
.arg(Arg::with_name("location"))
|
||||
.get_matches_from(vec!["do", "find", "-type", "f", "-name", "special", ";", "/home/clap"]);
|
||||
let cmds: Vec<_> = m.values_of("cmds").unwrap().collect();
|
||||
assert_eq!(&cmds, &["find", "-type", "f", "-name", "special"]);
|
||||
assert_eq!(m.value_of("location"), Some("/home/clap"));
|
||||
}
|
Loading…
Reference in a new issue