mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 06:12:40 +00:00
api(Arg::last): adds the ability to mark a positional argument as 'last' which means it should be used with --
syntax and can be accessed early
Marking a positional argument `.last(true)` will allow accessing this argument earlier if the `--` syntax is used (i.e. skipping other positional args) and also change the usage string to one of the following: * `$ prog [arg1] [-- <last_arg>]` * `$ prog [arg1] -- <last_arg>` (if the arg marked `.last(true)` is also marked `.required(true)`) Closes #888
This commit is contained in:
parent
989862d2cb
commit
f9668297a4
6 changed files with 540 additions and 326 deletions
|
@ -1,5 +1,4 @@
|
|||
// Std
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
|
@ -119,33 +118,44 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn debug_asserts(&self, a: &Arg) {
|
||||
debug_assert!(!arg_names!(self).any(|name| name == a.b.name),
|
||||
format!("Non-unique argument name: {} is already in use", a.b.name));
|
||||
fn debug_asserts(&self, a: &Arg) -> bool {
|
||||
assert!(!arg_names!(self).any(|name| name == a.b.name),
|
||||
format!("Non-unique argument name: {} is already in use", a.b.name));
|
||||
if let Some(l) = a.s.long {
|
||||
debug_assert!(!self.contains_long(l),
|
||||
format!("Argument long must be unique\n\n\t--{} is already in use",
|
||||
l));
|
||||
assert!(!self.contains_long(l),
|
||||
"Argument long must be unique\n\n\t--{} is already in use",
|
||||
l);
|
||||
}
|
||||
if let Some(s) = a.s.short {
|
||||
debug_assert!(!self.contains_short(s),
|
||||
format!("Argument short must be unique\n\n\t-{} is already in use",
|
||||
s));
|
||||
assert!(!self.contains_short(s),
|
||||
"Argument short must be unique\n\n\t-{} is already in use",
|
||||
s);
|
||||
}
|
||||
let i = if a.index.is_none() {
|
||||
(self.positionals.len() + 1)
|
||||
} else {
|
||||
a.index.unwrap() as usize
|
||||
};
|
||||
debug_assert!(!self.positionals.contains_key(i),
|
||||
format!("Argument \"{}\" has the same index as another positional \
|
||||
assert!(!self.positionals.contains_key(i),
|
||||
"Argument \"{}\" has the same index as another positional \
|
||||
argument\n\n\tPerhaps try .multiple(true) to allow one positional argument \
|
||||
to take multiple values",
|
||||
a.b.name));
|
||||
debug_assert!(!(a.is_set(ArgSettings::Required) && a.is_set(ArgSettings::Global)),
|
||||
format!("Global arguments cannot be required.\n\n\t'{}' is marked as \
|
||||
a.b.name);
|
||||
assert!(!(a.is_set(ArgSettings::Required) && a.is_set(ArgSettings::Global)),
|
||||
"Global arguments cannot be required.\n\n\t'{}' is marked as \
|
||||
global and required",
|
||||
a.b.name));
|
||||
a.b.name);
|
||||
if a.b.is_set(ArgSettings::Last) {
|
||||
assert!(!self.positionals.values().any(|p| p.b.is_set(ArgSettings::Last)),
|
||||
"Only one positional argument may have last(true) set. Found two.");
|
||||
assert!(a.s.long.is_none(),
|
||||
"Flags or Options may not have last(true) set. {} has both a long and last(true) set.",
|
||||
a.b.name);
|
||||
assert!(a.s.short.is_none(),
|
||||
"Flags or Options may not have last(true) set. {} has both a short and last(true) set.",
|
||||
a.b.name);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -188,16 +198,27 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn implied_settings(&mut self, a: &Arg<'a, 'b>) {
|
||||
if a.is_set(ArgSettings::Last) {
|
||||
// if an arg has `Last` set, we need to imply DontCollapseArgsInUsage so that args
|
||||
// in the usage string don't get confused or left out.
|
||||
self.set(AS::DontCollapseArgsInUsage);
|
||||
self.set(AS::ContainsLast);
|
||||
}
|
||||
}
|
||||
|
||||
// actually adds the arguments
|
||||
pub fn add_arg(&mut self, a: Arg<'a, 'b>) {
|
||||
// if it's global we have to clone anyways
|
||||
if a.is_set(ArgSettings::Global) {
|
||||
return self.add_arg_ref(&a);
|
||||
}
|
||||
self.debug_asserts(&a);
|
||||
debug_assert!(self.debug_asserts(&a));
|
||||
self.add_conditional_reqs(&a);
|
||||
self.add_arg_groups(&a);
|
||||
self.add_reqs(&a);
|
||||
self.implied_settings(&a);
|
||||
if a.index.is_some() || (a.s.short.is_none() && a.s.long.is_none()) {
|
||||
let i = if a.index.is_none() {
|
||||
(self.positionals.len() + 1)
|
||||
|
@ -217,10 +238,11 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
}
|
||||
// actually adds the arguments but from a borrow (which means we have to do some clonine)
|
||||
pub fn add_arg_ref(&mut self, a: &Arg<'a, 'b>) {
|
||||
self.debug_asserts(a);
|
||||
debug_assert!(self.debug_asserts(&a));
|
||||
self.add_conditional_reqs(a);
|
||||
self.add_arg_groups(a);
|
||||
self.add_reqs(a);
|
||||
self.implied_settings(&a);
|
||||
if a.index.is_some() || (a.s.short.is_none() && a.s.long.is_none()) {
|
||||
let i = if a.index.is_none() {
|
||||
(self.positionals.len() + 1)
|
||||
|
@ -343,220 +365,6 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
pub fn required(&self) -> Iter<&str> { self.required.iter() }
|
||||
|
||||
#[cfg_attr(feature = "lints", allow(needless_borrow))]
|
||||
pub fn get_required_from(&self,
|
||||
reqs: &[&'a str],
|
||||
matcher: Option<&ArgMatcher<'a>>,
|
||||
extra: Option<&str>)
|
||||
-> VecDeque<String> {
|
||||
debugln!("Parser::get_required_from: reqs={:?}, extra={:?}",
|
||||
reqs,
|
||||
extra);
|
||||
let mut desc_reqs: Vec<&str> = vec![];
|
||||
desc_reqs.extend(extra);
|
||||
let mut new_reqs: Vec<&str> = vec![];
|
||||
macro_rules! get_requires {
|
||||
(@group $a: ident, $v:ident, $p:ident) => {{
|
||||
if let Some(rl) = self.groups.iter()
|
||||
.filter(|g| g.requires.is_some())
|
||||
.find(|g| &g.name == $a)
|
||||
.map(|g| g.requires.as_ref().unwrap()) {
|
||||
for r in rl {
|
||||
if !$p.contains(&r) {
|
||||
debugln!("Parser::get_required_from:iter:{}: adding group req={:?}",
|
||||
$a, r);
|
||||
$v.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}};
|
||||
($a:ident, $what:ident, $how:ident, $v:ident, $p:ident) => {{
|
||||
if let Some(rl) = self.$what.$how()
|
||||
.filter(|a| a.b.requires.is_some())
|
||||
.find(|arg| &arg.b.name == $a)
|
||||
.map(|a| a.b.requires.as_ref().unwrap()) {
|
||||
for &(_, r) in rl.iter() {
|
||||
if !$p.contains(&r) {
|
||||
debugln!("Parser::get_required_from:iter:{}: adding arg req={:?}",
|
||||
$a, r);
|
||||
$v.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
// initialize new_reqs
|
||||
for a in reqs {
|
||||
get_requires!(a, flags, iter, new_reqs, reqs);
|
||||
get_requires!(a, opts, iter, new_reqs, reqs);
|
||||
get_requires!(a, positionals, values, new_reqs, reqs);
|
||||
get_requires!(@group a, new_reqs, reqs);
|
||||
}
|
||||
desc_reqs.extend_from_slice(&*new_reqs);
|
||||
debugln!("Parser::get_required_from: after init desc_reqs={:?}",
|
||||
desc_reqs);
|
||||
loop {
|
||||
let mut tmp = vec![];
|
||||
for a in &new_reqs {
|
||||
get_requires!(a, flags, iter, tmp, desc_reqs);
|
||||
get_requires!(a, opts, iter, tmp, desc_reqs);
|
||||
get_requires!(a, positionals, values, tmp, desc_reqs);
|
||||
get_requires!(@group a, tmp, desc_reqs);
|
||||
}
|
||||
if tmp.is_empty() {
|
||||
debugln!("Parser::get_required_from: no more children");
|
||||
break;
|
||||
} else {
|
||||
debugln!("Parser::get_required_from: after iter tmp={:?}", tmp);
|
||||
debugln!("Parser::get_required_from: after iter new_reqs={:?}",
|
||||
new_reqs);
|
||||
desc_reqs.extend_from_slice(&*new_reqs);
|
||||
new_reqs.clear();
|
||||
new_reqs.extend_from_slice(&*tmp);
|
||||
debugln!("Parser::get_required_from: after iter desc_reqs={:?}",
|
||||
desc_reqs);
|
||||
}
|
||||
}
|
||||
desc_reqs.extend_from_slice(reqs);
|
||||
desc_reqs.sort();
|
||||
desc_reqs.dedup();
|
||||
debugln!("Parser::get_required_from: final desc_reqs={:?}", desc_reqs);
|
||||
let mut ret_val = VecDeque::new();
|
||||
let args_in_groups = self.groups
|
||||
.iter()
|
||||
.filter(|gn| desc_reqs.contains(&gn.name))
|
||||
.flat_map(|g| self.arg_names_in_group(&g.name))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let pmap = if let Some(ref m) = matcher {
|
||||
desc_reqs.iter()
|
||||
.filter(|a| self.positionals.values().any(|p| &&p.b.name == a))
|
||||
.filter(|&p| !m.contains(p))
|
||||
.filter_map(|p| self.positionals.values().find(|x| &x.b.name == p))
|
||||
.filter(|p| !args_in_groups.contains(&p.b.name))
|
||||
.map(|p| (p.index, p))
|
||||
.collect::<BTreeMap<u64, &PosBuilder>>() // sort by index
|
||||
} else {
|
||||
desc_reqs.iter()
|
||||
.filter(|a| self.positionals.values().any(|p| &&p.b.name == a))
|
||||
.filter_map(|p| self.positionals.values().find(|x| &x.b.name == p))
|
||||
.filter(|p| !args_in_groups.contains(&p.b.name))
|
||||
.map(|p| (p.index, p))
|
||||
.collect::<BTreeMap<u64, &PosBuilder>>() // sort by index
|
||||
};
|
||||
debugln!("Parser::get_required_from: args_in_groups={:?}",
|
||||
args_in_groups);
|
||||
for &p in pmap.values() {
|
||||
let s = p.to_string();
|
||||
if args_in_groups.is_empty() || !args_in_groups.contains(&&*s) {
|
||||
ret_val.push_back(s);
|
||||
}
|
||||
}
|
||||
for a in desc_reqs.iter()
|
||||
.filter(|name| !self.positionals.values().any(|p| &&p.b.name == name))
|
||||
.filter(|name| !self.groups.iter().any(|g| &&g.name == name))
|
||||
.filter(|name| !args_in_groups.contains(name))
|
||||
.filter(|name| {
|
||||
!(matcher.is_some() && matcher.as_ref().unwrap().contains(name))
|
||||
}) {
|
||||
debugln!("Parser::get_required_from:iter:{}:", a);
|
||||
let arg = find_by_name!(self, a, flags, iter)
|
||||
.map(|f| f.to_string())
|
||||
.unwrap_or_else(|| {
|
||||
find_by_name!(self, a, opts, iter)
|
||||
.map(|o| o.to_string())
|
||||
.expect(INTERNAL_ERROR_MSG)
|
||||
});
|
||||
ret_val.push_back(arg);
|
||||
}
|
||||
let mut g_vec = vec![];
|
||||
for g in desc_reqs.iter().filter(|n| self.groups.iter().any(|g| &&g.name == n)) {
|
||||
let g_string = self.args_in_group(g).join("|");
|
||||
g_vec.push(format!("<{}>", &g_string[..g_string.len()]));
|
||||
}
|
||||
g_vec.sort();
|
||||
g_vec.dedup();
|
||||
for g in g_vec {
|
||||
ret_val.push_back(g);
|
||||
}
|
||||
|
||||
ret_val
|
||||
}
|
||||
|
||||
// Gets the `[ARGS]` tag for the usage string
|
||||
pub fn get_args_tag(&self) -> Option<String> {
|
||||
debugln!("Parser::get_args_tag;");
|
||||
let mut count = 0;
|
||||
'outer: for p in self.positionals.values().filter(|p| {
|
||||
!p.is_set(ArgSettings::Required) &&
|
||||
!p.is_set(ArgSettings::Hidden)
|
||||
}) {
|
||||
debugln!("Parser::get_args_tag:iter:{}:", p.b.name);
|
||||
if let Some(g_vec) = self.groups_for_arg(p.b.name) {
|
||||
for grp_s in &g_vec {
|
||||
debugln!("Parser::get_args_tag:iter:{}:iter:{};", p.b.name, grp_s);
|
||||
// if it's part of a required group we don't want to count it
|
||||
if self.groups.iter().any(|g| g.required && (&g.name == grp_s)) {
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
count += 1;
|
||||
debugln!("Parser::get_args_tag:iter: {} Args not required or hidden",
|
||||
count);
|
||||
}
|
||||
if !self.is_set(AS::DontCollapseArgsInUsage) && count > 1 {
|
||||
return None; // [ARGS]
|
||||
} else if count == 1 {
|
||||
let p = self.positionals
|
||||
.values()
|
||||
.find(|p| !p.is_set(ArgSettings::Required) && !p.is_set(ArgSettings::Hidden))
|
||||
.expect(INTERNAL_ERROR_MSG);
|
||||
return Some(format!(" [{}]{}", p.name_no_brackets(), p.multiple_str()));
|
||||
} else if self.is_set(AS::DontCollapseArgsInUsage) && !self.positionals.is_empty() {
|
||||
return Some(self.positionals
|
||||
.values()
|
||||
.filter(|p| !p.is_set(ArgSettings::Required))
|
||||
.filter(|p| !p.is_set(ArgSettings::Hidden))
|
||||
.map(|p| {
|
||||
format!(" [{}]{}", p.name_no_brackets(), p.multiple_str())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(""));
|
||||
}
|
||||
Some("".into())
|
||||
}
|
||||
|
||||
// Determines if we need the `[FLAGS]` tag in the usage string
|
||||
pub fn needs_flags_tag(&self) -> bool {
|
||||
debugln!("Parser::needs_flags_tag;");
|
||||
'outer: for f in &self.flags {
|
||||
debugln!("Parser::needs_flags_tag:iter: f={};", f.b.name);
|
||||
if let Some(l) = f.s.long {
|
||||
if l == "help" || l == "version" {
|
||||
// Don't print `[FLAGS]` just for help or version
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(g_vec) = self.groups_for_arg(f.b.name) {
|
||||
for grp_s in &g_vec {
|
||||
debugln!("Parser::needs_flags_tag:iter:iter: grp_s={};", grp_s);
|
||||
if self.groups.iter().any(|g| &g.name == grp_s && g.required) {
|
||||
debug!("Parser::needs_flags_tag:iter:iter: Group is required");
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
if f.is_set(ArgSettings::Hidden) {
|
||||
continue;
|
||||
}
|
||||
debugln!("Parser::needs_flags_tag:iter: [FLAGS] required");
|
||||
return true;
|
||||
}
|
||||
|
||||
debugln!("Parser::needs_flags_tag: [FLAGS] not required");
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_args(&self) -> bool {
|
||||
!(self.flags.is_empty() && self.opts.is_empty() && self.positionals.is_empty())
|
||||
|
@ -570,7 +378,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
|
||||
#[inline]
|
||||
pub fn has_positionals(&self) -> bool { !self.positionals.is_empty() }
|
||||
|
||||
|
||||
#[inline]
|
||||
pub fn has_subcommands(&self) -> bool { !self.subcommands.is_empty() }
|
||||
|
||||
|
@ -616,7 +424,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
pub fn unset(&mut self, s: AS) { self.settings.unset(s) }
|
||||
|
||||
#[cfg_attr(feature = "lints", allow(block_in_if_condition_stmt))]
|
||||
pub fn verify_positionals(&mut self) {
|
||||
pub fn verify_positionals(&mut self) -> bool {
|
||||
// Because you must wait until all arguments have been supplied, this is the first chance
|
||||
// to make assertions on positional argument indexes
|
||||
//
|
||||
|
@ -627,71 +435,72 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
.iter()
|
||||
.rev()
|
||||
.next() {
|
||||
debug_assert!(!(idx != self.positionals.len()),
|
||||
format!("Found positional argument \"{}\" who's index is {} but there \
|
||||
assert!(!(idx != self.positionals.len()),
|
||||
"Found positional argument \"{}\" who's index is {} but there \
|
||||
are only {} positional arguments defined",
|
||||
p.b.name,
|
||||
idx,
|
||||
self.positionals.len()));
|
||||
p.b.name,
|
||||
idx,
|
||||
self.positionals.len());
|
||||
}
|
||||
|
||||
// Next we verify that only the highest index has a .multiple(true) (if any)
|
||||
if self.positionals.values().any(|a| {
|
||||
a.is_set(ArgSettings::Multiple) &&
|
||||
a.b.is_set(ArgSettings::Multiple) &&
|
||||
(a.index as usize != self.positionals.len())
|
||||
}) {
|
||||
|
||||
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 \
|
||||
let mut it = self.positionals.values().rev();
|
||||
let last = it.next().unwrap();
|
||||
let second_to_last = it.next().unwrap();
|
||||
// Either the final positional is required
|
||||
// Or the second to last has a terminator or .last(true) set
|
||||
let ok = last.is_set(ArgSettings::Required) ||
|
||||
(second_to_last.v.terminator.is_some() ||
|
||||
second_to_last.b.is_set(ArgSettings::Last));
|
||||
assert!(ok,
|
||||
"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.");
|
||||
|
||||
debug_assert!({
|
||||
let num = self.positionals.len() - 1;
|
||||
self.positionals
|
||||
.get(num)
|
||||
.unwrap()
|
||||
.is_set(ArgSettings::Multiple)
|
||||
},
|
||||
"Only the last positional argument, or second to last positional \
|
||||
with the highest index) *must* have .required(true) or .last(true) set.");
|
||||
let num = self.positionals.len() - 1;
|
||||
let ok = self.positionals
|
||||
.get(num)
|
||||
.unwrap()
|
||||
.is_set(ArgSettings::Multiple);
|
||||
assert!(ok,
|
||||
"Only the last positional argument, or second to last positional \
|
||||
argument may be set to .multiple(true)");
|
||||
|
||||
self.set(AS::LowIndexMultiplePositional);
|
||||
self.settings.set(AS::LowIndexMultiplePositional);
|
||||
}
|
||||
|
||||
debug_assert!(self.positionals
|
||||
.values()
|
||||
.filter(|p| {
|
||||
p.b.settings.is_set(ArgSettings::Multiple) &&
|
||||
p.v.num_vals.is_none()
|
||||
})
|
||||
.map(|_| 1)
|
||||
.sum::<u64>() <= 1,
|
||||
"Only one positional argument with .multiple(true) set is allowed per \
|
||||
let ok = self.positionals
|
||||
.values()
|
||||
.filter(|p| p.b.settings.is_set(ArgSettings::Multiple) && p.v.num_vals.is_none())
|
||||
.map(|_| 1)
|
||||
.sum::<u64>() <= 1;
|
||||
assert!(ok,
|
||||
"Only one positional argument with .multiple(true) set is allowed per \
|
||||
command");
|
||||
|
||||
// If it's required we also need to ensure all previous positionals are
|
||||
// required too
|
||||
if self.is_set(AS::AllowMissingPositional) {
|
||||
// Check that if a required positional argument is found, all positions with a lower
|
||||
// index are also required.
|
||||
let mut found = false;
|
||||
let mut foundx2 = false;
|
||||
for p in self.positionals.values().rev() {
|
||||
if foundx2 && !p.b.settings.is_set(ArgSettings::Required) {
|
||||
// [arg1] <arg2> is Ok
|
||||
// [arg1] <arg2> <arg3> Is not
|
||||
debug_assert!(p.b.settings.is_set(ArgSettings::Required),
|
||||
"Found positional argument which is not required with a lower \
|
||||
assert!(p.b.is_set(ArgSettings::Required),
|
||||
"Found positional argument which is not required with a lower \
|
||||
index than a required positional argument by two or more: {:?} \
|
||||
index {}",
|
||||
p.b.name,
|
||||
p.index);
|
||||
} else if p.b.settings.is_set(ArgSettings::Required) {
|
||||
p.b.name,
|
||||
p.index);
|
||||
} else if p.b.is_set(ArgSettings::Required) && !p.b.is_set(ArgSettings::Last) {
|
||||
// Args that .last(true) don't count since they can be required and have
|
||||
// positionals with a lower index that aren't required
|
||||
// Imagine: prog <req1> [opt1] -- <req2>
|
||||
// Both of these are valid invocations:
|
||||
// $ prog r1 -- r2
|
||||
// $ prog r1 o1 -- r2
|
||||
if found {
|
||||
foundx2 = true;
|
||||
continue;
|
||||
|
@ -703,20 +512,38 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// Check that if a required positional argument is found, all positions with a lower
|
||||
// index are also required
|
||||
let mut found = false;
|
||||
for p in self.positionals.values().rev() {
|
||||
if found {
|
||||
debug_assert!(p.b.settings.is_set(ArgSettings::Required),
|
||||
"Found positional argument which is not required with a lower \
|
||||
assert!(p.b.is_set(ArgSettings::Required),
|
||||
"Found positional argument which is not required with a lower \
|
||||
index than a required positional argument: {:?} index {}",
|
||||
p.b.name,
|
||||
p.index);
|
||||
} else if p.b.settings.is_set(ArgSettings::Required) {
|
||||
p.b.name,
|
||||
p.index);
|
||||
} else if p.b.is_set(ArgSettings::Required) && !p.b.is_set(ArgSettings::Last) {
|
||||
// Args that .last(true) don't count since they can be required and have
|
||||
// positionals with a lower index that aren't required
|
||||
// Imagine: prog <req1> [opt1] -- <req2>
|
||||
// Both of these are valid invocations:
|
||||
// $ prog r1 -- r2
|
||||
// $ prog r1 o1 -- r2
|
||||
found = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.positionals.values().any(|p| {
|
||||
p.b.is_set(ArgSettings::Last) &&
|
||||
p.b.is_set(ArgSettings::Required)
|
||||
}) && self.has_subcommands() &&
|
||||
!self.is_set(AS::SubcommandsNegateReqs) {
|
||||
panic!("Having a required positional argument with .last(true) set *and* child \
|
||||
subcommands without setting SubcommandsNegateReqs isn't compatible.");
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn propogate_globals(&mut self) {
|
||||
|
@ -915,7 +742,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
{
|
||||
debugln!("Parser::get_matches_with;");
|
||||
// Verify all positional assertions pass
|
||||
self.verify_positionals();
|
||||
debug_assert!(self.verify_positionals());
|
||||
let has_args = self.has_args();
|
||||
|
||||
// Next we create the `--help` and `--version` arguments and add them if
|
||||
|
@ -940,7 +767,9 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
// Does the arg match a subcommand name, or any of it's aliases (if defined)
|
||||
{
|
||||
let (is_match, sc_name) = self.possible_subcommand(&arg_os);
|
||||
debugln!("Parser::get_matches_with: possible_sc={:?}, sc={:?}", is_match, sc_name);
|
||||
debugln!("Parser::get_matches_with: possible_sc={:?}, sc={:?}",
|
||||
is_match,
|
||||
sc_name);
|
||||
if is_match {
|
||||
let sc_name = sc_name.expect(INTERNAL_ERROR_MSG);
|
||||
if sc_name == "help" && self.is_set(AS::NeedsSubcommandHelp) {
|
||||
|
@ -1011,7 +840,8 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
.bin_name
|
||||
.as_ref()
|
||||
.unwrap_or(&self.meta.name),
|
||||
&*usage::create_error_usage(self, matcher,
|
||||
&*usage::create_error_usage(self,
|
||||
matcher,
|
||||
None),
|
||||
self.color()));
|
||||
}
|
||||
|
@ -1050,8 +880,21 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
debugln!("Parser::get_matches_with: Bumping the positional counter...");
|
||||
pos_counter += 1;
|
||||
}
|
||||
} else if self.is_set(AS::ContainsLast) && self.is_set(AS::TrailingValues) {
|
||||
// Came to -- and one postional has .last(true) set, so we go immediately
|
||||
// to the last (highest index) positional
|
||||
debugln!("Parser::get_matches_with: .last(true) and --, setting last pos");
|
||||
pos_counter = self.positionals.len();
|
||||
}
|
||||
if let Some(p) = self.positionals.get(pos_counter) {
|
||||
if p.is_set(ArgSettings::Last) && !self.is_set(AS::TrailingValues) {
|
||||
return Err(Error::unknown_argument(&*arg_os.to_string_lossy(),
|
||||
"",
|
||||
&*usage::create_error_usage(self,
|
||||
matcher,
|
||||
None),
|
||||
self.color()));
|
||||
}
|
||||
parse_positional!(self, p, arg_os, pos_counter, matcher);
|
||||
self.settings.set(AS::ValidArgFound);
|
||||
} else if self.is_set(AS::AllowExternalSubcommands) {
|
||||
|
@ -1061,8 +904,8 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
None => {
|
||||
if !self.is_set(AS::StrictUtf8) {
|
||||
return Err(Error::invalid_utf8(&*usage::create_error_usage(self,
|
||||
matcher,
|
||||
None),
|
||||
matcher,
|
||||
None),
|
||||
self.color()));
|
||||
}
|
||||
arg_os.to_string_lossy().into_owned()
|
||||
|
@ -1075,8 +918,8 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
let a = v.into();
|
||||
if a.to_str().is_none() && !self.is_set(AS::StrictUtf8) {
|
||||
return Err(Error::invalid_utf8(&*usage::create_error_usage(self,
|
||||
matcher,
|
||||
None),
|
||||
matcher,
|
||||
None),
|
||||
self.color()));
|
||||
}
|
||||
sc_m.add_val_to("", &a);
|
||||
|
@ -1092,8 +935,8 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
return Err(Error::unknown_argument(&*arg_os.to_string_lossy(),
|
||||
"",
|
||||
&*usage::create_error_usage(self,
|
||||
matcher,
|
||||
None),
|
||||
matcher,
|
||||
None),
|
||||
self.color()));
|
||||
} else if !has_args || self.is_set(AS::InferSubcommands) && self.has_subcommands() {
|
||||
if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(),
|
||||
|
@ -1105,11 +948,12 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
.as_ref()
|
||||
.unwrap_or(&self.meta.name),
|
||||
&*usage::create_error_usage(self,
|
||||
matcher,
|
||||
None),
|
||||
matcher,
|
||||
None),
|
||||
self.color()));
|
||||
} else {
|
||||
return Err(Error::unrecognized_subcommand(arg_os.to_string_lossy().into_owned(),
|
||||
return Err(Error::unrecognized_subcommand(arg_os.to_string_lossy()
|
||||
.into_owned(),
|
||||
self.meta
|
||||
.bin_name
|
||||
.as_ref()
|
||||
|
@ -1135,9 +979,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
.as_ref()
|
||||
.unwrap_or(&self.meta.name);
|
||||
return Err(Error::missing_subcommand(bn,
|
||||
&usage::create_error_usage(self,
|
||||
matcher,
|
||||
None),
|
||||
&usage::create_error_usage(self, matcher, None),
|
||||
self.color()));
|
||||
} else if self.is_set(AS::SubcommandRequiredElseHelp) {
|
||||
debugln!("Parser::get_matches_with: SubcommandRequiredElseHelp=true");
|
||||
|
@ -1214,7 +1056,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
for k in matcher.arg_names() {
|
||||
hs.push(k);
|
||||
}
|
||||
let reqs = self.get_required_from(&hs, Some(matcher), None);
|
||||
let reqs = usage::get_required_usage_from(self, &hs, Some(matcher), None, false);
|
||||
|
||||
for s in &reqs {
|
||||
write!(&mut mid_string, " {}", s).expect(INTERNAL_ERROR_MSG);
|
||||
|
@ -1262,8 +1104,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn groups_for_arg(&self, name: &str) -> Option<Vec<&'a str>> {
|
||||
pub fn groups_for_arg(&self, name: &str) -> Option<Vec<&'a str>> {
|
||||
debugln!("Parser::groups_for_arg: name={}", name);
|
||||
|
||||
if self.groups.is_empty() {
|
||||
|
@ -1287,7 +1128,7 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
Some(res)
|
||||
}
|
||||
|
||||
fn args_in_group(&self, group: &str) -> Vec<String> {
|
||||
pub fn args_in_group(&self, group: &str) -> Vec<String> {
|
||||
let mut g_vec = vec![];
|
||||
let mut args = vec![];
|
||||
|
||||
|
@ -1581,8 +1422,8 @@ impl<'a, 'b> Parser<'a, 'b>
|
|||
return Err(Error::unknown_argument(&*arg,
|
||||
"",
|
||||
&*usage::create_error_usage(self,
|
||||
matcher,
|
||||
None),
|
||||
matcher,
|
||||
None),
|
||||
self.color()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ bitflags! {
|
|||
const PROPOGATED = 1 << 36,
|
||||
const VALID_ARG_FOUND = 1 << 37,
|
||||
const INFER_SUBCOMMANDS = 1 << 38,
|
||||
const CONTAINS_LAST = 1 << 39,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,7 +105,8 @@ impl AppFlags {
|
|||
ValidNegNumFound => VALID_NEG_NUM_FOUND,
|
||||
Propogated => PROPOGATED,
|
||||
ValidArgFound => VALID_ARG_FOUND,
|
||||
InferSubcommands => INFER_SUBCOMMANDS
|
||||
InferSubcommands => INFER_SUBCOMMANDS,
|
||||
ContainsLast => CONTAINS_LAST
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -859,6 +861,9 @@ pub enum AppSettings {
|
|||
|
||||
#[doc(hidden)]
|
||||
ValidArgFound,
|
||||
|
||||
#[doc(hidden)]
|
||||
ContainsLast,
|
||||
}
|
||||
|
||||
impl FromStr for AppSettings {
|
||||
|
|
304
src/app/usage.rs
304
src/app/usage.rs
|
@ -1,3 +1,8 @@
|
|||
// std
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
|
||||
// Internal
|
||||
use INTERNAL_ERROR_MSG;
|
||||
use args::{AnyArg, ArgMatcher, PosBuilder};
|
||||
use args::settings::ArgSettings;
|
||||
use app::settings::AppSettings as AS;
|
||||
|
@ -66,13 +71,14 @@ pub fn create_help_usage(p: &Parser, incl_reqs: bool) -> String {
|
|||
let mut reqs: Vec<&str> = p.required().map(|r| &**r).collect();
|
||||
reqs.sort();
|
||||
reqs.dedup();
|
||||
p.get_required_from(&reqs, None, None).iter().fold(String::new(),
|
||||
|a, s| a + &format!(" {}", s)[..])
|
||||
get_required_usage_from(p, &reqs, None, None, false).iter().fold(String::new(), |a, s| {
|
||||
a + &format!(" {}", s)[..]
|
||||
})
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let flags = p.needs_flags_tag();
|
||||
let flags = needs_flags_tag(p);
|
||||
if flags && !p.is_set(AS::UnifiedHelpMessage) {
|
||||
usage.push_str(" [FLAGS]");
|
||||
} else if flags {
|
||||
|
@ -85,20 +91,41 @@ pub fn create_help_usage(p: &Parser, incl_reqs: bool) -> String {
|
|||
|
||||
usage.push_str(&req_string[..]);
|
||||
|
||||
let has_last = p.positionals.values().any(|p| p.is_set(ArgSettings::Last));
|
||||
// places a '--' in the usage string if there are args and options
|
||||
// supporting multiple values
|
||||
if p.has_positionals() && p.opts.iter().any(|o| o.is_set(ArgSettings::Multiple)) &&
|
||||
if p.opts.iter().any(|o| o.is_set(ArgSettings::Multiple)) &&
|
||||
p.positionals.values().any(|p| !p.is_set(ArgSettings::Required)) &&
|
||||
!p.has_visible_subcommands() {
|
||||
usage.push_str(" [--]")
|
||||
!p.has_visible_subcommands() && !has_last {
|
||||
usage.push_str(" [--]");
|
||||
}
|
||||
let not_req_or_hidden= |p: &PosBuilder| !p.is_set(ArgSettings::Required) && !p.is_set(ArgSettings::Hidden);
|
||||
let not_req_or_hidden =
|
||||
|p: &PosBuilder| !p.is_set(ArgSettings::Required) && !p.is_set(ArgSettings::Hidden);
|
||||
if p.has_positionals() && p.positionals.values().any(not_req_or_hidden) {
|
||||
if let Some(args_tag) = p.get_args_tag() {
|
||||
if let Some(args_tag) = get_args_tag(p, incl_reqs) {
|
||||
usage.push_str(&*args_tag);
|
||||
} else {
|
||||
usage.push_str(" [ARGS]");
|
||||
}
|
||||
if has_last && incl_reqs {
|
||||
let pos = p.positionals
|
||||
.values()
|
||||
.find(|p| p.b.is_set(ArgSettings::Last))
|
||||
.expect(INTERNAL_ERROR_MSG);
|
||||
debugln!("usage::create_help_usage: {} has .last(true)", pos.name());
|
||||
let req = pos.is_set(ArgSettings::Required);
|
||||
if req {
|
||||
usage.push_str(" -- <");
|
||||
} else {
|
||||
usage.push_str(" [-- <");
|
||||
}
|
||||
usage.push_str(pos.name());
|
||||
usage.push_str(">");
|
||||
usage.push_str(pos.multiple_str());
|
||||
if !req {
|
||||
usage.push_str("]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// incl_reqs is only false when this function is called recursively
|
||||
|
@ -120,6 +147,7 @@ pub fn create_help_usage(p: &Parser, incl_reqs: bool) -> String {
|
|||
}
|
||||
}
|
||||
usage.shrink_to_fit();
|
||||
debugln!("usage::create_help_usage: usage={}", usage);
|
||||
usage
|
||||
}
|
||||
|
||||
|
@ -131,9 +159,10 @@ fn create_smart_usage(p: &Parser, used: &[&str]) -> String {
|
|||
let mut hs: Vec<&str> = p.required().map(|s| &**s).collect();
|
||||
hs.extend_from_slice(used);
|
||||
|
||||
let r_string = p.get_required_from(&hs, None, None).iter().fold(String::new(), |acc, s| {
|
||||
acc + &format!(" {}", s)[..]
|
||||
});
|
||||
let r_string =
|
||||
get_required_usage_from(p, &hs, None, None, false).iter().fold(String::new(), |acc, s| {
|
||||
acc + &format!(" {}", s)[..]
|
||||
});
|
||||
|
||||
usage.push_str(&p.meta
|
||||
.usage
|
||||
|
@ -152,3 +181,256 @@ fn create_smart_usage(p: &Parser, used: &[&str]) -> String {
|
|||
usage.shrink_to_fit();
|
||||
usage
|
||||
}
|
||||
|
||||
// Gets the `[ARGS]` tag for the usage string
|
||||
fn get_args_tag(p: &Parser, incl_reqs: bool) -> Option<String> {
|
||||
debugln!("usage::get_args_tag;");
|
||||
let mut count = 0;
|
||||
'outer: for pos in p.positionals
|
||||
.values()
|
||||
.filter(|pos| !pos.is_set(ArgSettings::Required))
|
||||
.filter(|pos| !pos.is_set(ArgSettings::Hidden))
|
||||
.filter(|pos| !pos.is_set(ArgSettings::Last)) {
|
||||
debugln!("usage::get_args_tag:iter:{}:", pos.b.name);
|
||||
if let Some(g_vec) = p.groups_for_arg(pos.b.name) {
|
||||
for grp_s in &g_vec {
|
||||
debugln!("usage::get_args_tag:iter:{}:iter:{};", pos.b.name, grp_s);
|
||||
// if it's part of a required group we don't want to count it
|
||||
if p.groups.iter().any(|g| g.required && (&g.name == grp_s)) {
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
count += 1;
|
||||
debugln!("usage::get_args_tag:iter: {} Args not required or hidden",
|
||||
count);
|
||||
}
|
||||
if !p.is_set(AS::DontCollapseArgsInUsage) && count > 1 {
|
||||
debugln!("usage::get_args_tag:iter: More than one, returning [ARGS]");
|
||||
return None; // [ARGS]
|
||||
} else if count == 1 && incl_reqs {
|
||||
let pos = p.positionals
|
||||
.values()
|
||||
.find(|pos| {
|
||||
!pos.is_set(ArgSettings::Required) && !pos.is_set(ArgSettings::Hidden) &&
|
||||
!pos.is_set(ArgSettings::Last)
|
||||
})
|
||||
.expect(INTERNAL_ERROR_MSG);
|
||||
debugln!("usage::get_args_tag:iter: Exactly one, returning {}",
|
||||
pos.name());
|
||||
return Some(format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()));
|
||||
} else if p.is_set(AS::DontCollapseArgsInUsage) && !p.positionals.is_empty() && incl_reqs {
|
||||
debugln!("usage::get_args_tag:iter: Don't collapse returning all");
|
||||
return Some(p.positionals
|
||||
.values()
|
||||
.filter(|pos| !pos.is_set(ArgSettings::Required))
|
||||
.filter(|pos| !pos.is_set(ArgSettings::Hidden))
|
||||
.filter(|pos| !pos.is_set(ArgSettings::Last))
|
||||
.map(|pos| {
|
||||
format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(""));
|
||||
} else if !incl_reqs {
|
||||
debugln!("usage::get_args_tag:iter: incl_reqs=false, building secondary usage string");
|
||||
let highest_req_pos = p.positionals
|
||||
.iter()
|
||||
.filter_map(|(idx, pos)| if pos.b.is_set(ArgSettings::Required) &&
|
||||
!pos.b.is_set(ArgSettings::Last) {
|
||||
Some(idx)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(p.positionals.len());
|
||||
return Some(p.positionals
|
||||
.iter()
|
||||
.filter_map(|(idx, pos)| if idx <= highest_req_pos {
|
||||
Some(pos)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.filter(|pos| !pos.is_set(ArgSettings::Required))
|
||||
.filter(|pos| !pos.is_set(ArgSettings::Hidden))
|
||||
.filter(|pos| !pos.is_set(ArgSettings::Last))
|
||||
.map(|pos| {
|
||||
format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(""));
|
||||
}
|
||||
Some("".into())
|
||||
}
|
||||
|
||||
// Determines if we need the `[FLAGS]` tag in the usage string
|
||||
fn needs_flags_tag(p: &Parser) -> bool {
|
||||
debugln!("usage::needs_flags_tag;");
|
||||
'outer: for f in &p.flags {
|
||||
debugln!("usage::needs_flags_tag:iter: f={};", f.b.name);
|
||||
if let Some(l) = f.s.long {
|
||||
if l == "help" || l == "version" {
|
||||
// Don't print `[FLAGS]` just for help or version
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(g_vec) = p.groups_for_arg(f.b.name) {
|
||||
for grp_s in &g_vec {
|
||||
debugln!("usage::needs_flags_tag:iter:iter: grp_s={};", grp_s);
|
||||
if p.groups.iter().any(|g| &g.name == grp_s && g.required) {
|
||||
debugln!("usage::needs_flags_tag:iter:iter: Group is required");
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
if f.is_set(ArgSettings::Hidden) {
|
||||
continue;
|
||||
}
|
||||
debugln!("usage::needs_flags_tag:iter: [FLAGS] required");
|
||||
return true;
|
||||
}
|
||||
|
||||
debugln!("usage::needs_flags_tag: [FLAGS] not required");
|
||||
false
|
||||
}
|
||||
|
||||
// Returns the required args in usage string form by fully unrolling all groups
|
||||
pub fn get_required_usage_from<'a, 'b>(p: &Parser<'a, 'b>,
|
||||
reqs: &[&'a str],
|
||||
matcher: Option<&ArgMatcher<'a>>,
|
||||
extra: Option<&str>,
|
||||
incl_last: bool)
|
||||
-> VecDeque<String> {
|
||||
debugln!("usage::get_required_usage_from: reqs={:?}, extra={:?}",
|
||||
reqs,
|
||||
extra);
|
||||
let mut desc_reqs: Vec<&str> = vec![];
|
||||
desc_reqs.extend(extra);
|
||||
let mut new_reqs: Vec<&str> = vec![];
|
||||
macro_rules! get_requires {
|
||||
(@group $a: ident, $v:ident, $p:ident) => {{
|
||||
if let Some(rl) = p.groups.iter()
|
||||
.filter(|g| g.requires.is_some())
|
||||
.find(|g| &g.name == $a)
|
||||
.map(|g| g.requires.as_ref().unwrap()) {
|
||||
for r in rl {
|
||||
if !$p.contains(&r) {
|
||||
debugln!("usage::get_required_usage_from:iter:{}: adding group req={:?}",
|
||||
$a, r);
|
||||
$v.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}};
|
||||
($a:ident, $what:ident, $how:ident, $v:ident, $p:ident) => {{
|
||||
if let Some(rl) = p.$what.$how()
|
||||
.filter(|a| a.b.requires.is_some())
|
||||
.find(|arg| &arg.b.name == $a)
|
||||
.map(|a| a.b.requires.as_ref().unwrap()) {
|
||||
for &(_, r) in rl.iter() {
|
||||
if !$p.contains(&r) {
|
||||
debugln!("usage::get_required_usage_from:iter:{}: adding arg req={:?}",
|
||||
$a, r);
|
||||
$v.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
// initialize new_reqs
|
||||
for a in reqs {
|
||||
get_requires!(a, flags, iter, new_reqs, reqs);
|
||||
get_requires!(a, opts, iter, new_reqs, reqs);
|
||||
get_requires!(a, positionals, values, new_reqs, reqs);
|
||||
get_requires!(@group a, new_reqs, reqs);
|
||||
}
|
||||
desc_reqs.extend_from_slice(&*new_reqs);
|
||||
debugln!("usage::get_required_usage_from: after init desc_reqs={:?}",
|
||||
desc_reqs);
|
||||
loop {
|
||||
let mut tmp = vec![];
|
||||
for a in &new_reqs {
|
||||
get_requires!(a, flags, iter, tmp, desc_reqs);
|
||||
get_requires!(a, opts, iter, tmp, desc_reqs);
|
||||
get_requires!(a, positionals, values, tmp, desc_reqs);
|
||||
get_requires!(@group a, tmp, desc_reqs);
|
||||
}
|
||||
if tmp.is_empty() {
|
||||
debugln!("usage::get_required_usage_from: no more children");
|
||||
break;
|
||||
} else {
|
||||
debugln!("usage::get_required_usage_from: after iter tmp={:?}", tmp);
|
||||
debugln!("usage::get_required_usage_from: after iter new_reqs={:?}",
|
||||
new_reqs);
|
||||
desc_reqs.extend_from_slice(&*new_reqs);
|
||||
new_reqs.clear();
|
||||
new_reqs.extend_from_slice(&*tmp);
|
||||
debugln!("usage::get_required_usage_from: after iter desc_reqs={:?}",
|
||||
desc_reqs);
|
||||
}
|
||||
}
|
||||
desc_reqs.extend_from_slice(reqs);
|
||||
desc_reqs.sort();
|
||||
desc_reqs.dedup();
|
||||
debugln!("usage::get_required_usage_from: final desc_reqs={:?}",
|
||||
desc_reqs);
|
||||
let mut ret_val = VecDeque::new();
|
||||
let args_in_groups = p.groups
|
||||
.iter()
|
||||
.filter(|gn| desc_reqs.contains(&gn.name))
|
||||
.flat_map(|g| p.arg_names_in_group(&g.name))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let pmap = if let Some(ref m) = matcher {
|
||||
desc_reqs.iter()
|
||||
.filter(|a| p.positionals.values().any(|p| &&p.b.name == a))
|
||||
.filter(|&pos| !m.contains(pos))
|
||||
.filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos))
|
||||
.filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last))
|
||||
.filter(|pos| !args_in_groups.contains(&pos.b.name))
|
||||
.map(|pos| (pos.index, pos))
|
||||
.collect::<BTreeMap<u64, &PosBuilder>>() // sort by index
|
||||
} else {
|
||||
desc_reqs.iter()
|
||||
.filter(|a| p.positionals.values().any(|pos| &&pos.b.name == a))
|
||||
.filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos))
|
||||
.filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last))
|
||||
.filter(|pos| !args_in_groups.contains(&pos.b.name))
|
||||
.map(|pos| (pos.index, pos))
|
||||
.collect::<BTreeMap<u64, &PosBuilder>>() // sort by index
|
||||
};
|
||||
debugln!("usage::get_required_usage_from: args_in_groups={:?}",
|
||||
args_in_groups);
|
||||
for &p in pmap.values() {
|
||||
let s = p.to_string();
|
||||
if args_in_groups.is_empty() || !args_in_groups.contains(&&*s) {
|
||||
ret_val.push_back(s);
|
||||
}
|
||||
}
|
||||
for a in desc_reqs.iter()
|
||||
.filter(|name| !p.positionals.values().any(|p| &&p.b.name == name))
|
||||
.filter(|name| !p.groups.iter().any(|g| &&g.name == name))
|
||||
.filter(|name| !args_in_groups.contains(name))
|
||||
.filter(|name| !(matcher.is_some() && matcher.as_ref().unwrap().contains(name))) {
|
||||
debugln!("usage::get_required_usage_from:iter:{}:", a);
|
||||
let arg = find_by_name!(p, a, flags, iter)
|
||||
.map(|f| f.to_string())
|
||||
.unwrap_or_else(|| {
|
||||
find_by_name!(p, a, opts, iter)
|
||||
.map(|o| o.to_string())
|
||||
.expect(INTERNAL_ERROR_MSG)
|
||||
});
|
||||
ret_val.push_back(arg);
|
||||
}
|
||||
let mut g_vec = vec![];
|
||||
for g in desc_reqs.iter().filter(|n| p.groups.iter().any(|g| &&g.name == n)) {
|
||||
let g_string = p.args_in_group(g).join("|");
|
||||
g_vec.push(format!("<{}>", &g_string[..g_string.len()]));
|
||||
}
|
||||
g_vec.sort();
|
||||
g_vec.dedup();
|
||||
for g in g_vec {
|
||||
ret_val.push_back(g);
|
||||
}
|
||||
|
||||
ret_val
|
||||
}
|
||||
|
|
|
@ -415,7 +415,6 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn missing_required_error(&self, matcher: &ArgMatcher, extra: Option<&str>) -> ClapResult<()> {
|
||||
debugln!("Validator::missing_required_error: extra={:?}", extra);
|
||||
let c = Colorizer {
|
||||
|
@ -433,14 +432,13 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
reqs.retain(|n| !matcher.contains(n));
|
||||
reqs.dedup();
|
||||
debugln!("Validator::missing_required_error: reqs={:#?}", reqs);
|
||||
Err(Error::missing_required_argument(&*self.0
|
||||
.get_required_from(&reqs[..],
|
||||
Some(matcher),
|
||||
extra)
|
||||
.iter()
|
||||
.fold(String::new(), |acc, s| {
|
||||
acc + &format!("\n {}", c.error(s))[..]
|
||||
}),
|
||||
let req_args =
|
||||
usage::get_required_usage_from(self.0, &reqs[..], Some(matcher), extra, true)
|
||||
.iter()
|
||||
.fold(String::new(),
|
||||
|acc, s| acc + &format!("\n {}", c.error(s))[..]);
|
||||
debugln!("Validator::missing_required_error: req_args={:#?}", req_args);
|
||||
Err(Error::missing_required_argument(&*req_args,
|
||||
&*usage::create_error_usage(self.0, matcher, extra),
|
||||
self.0.color()))
|
||||
}
|
||||
|
|
|
@ -537,6 +537,86 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Specifies that this arg is the last, or final, positional argument (i.e. has the highest
|
||||
/// index) and is *only* able to be accessed via the `--` syntax (i.e. `$ prog args --
|
||||
/// last_arg`). Even, if no other arguments are left to parse, if the user omits the `--` syntax
|
||||
/// they will receive an [`UnknownArgument`] error. Setting an argument to `.last(true)` also
|
||||
/// allows one to access this arg early using the `--` syntax. Accessing an arg early, even with
|
||||
/// the `--` syntax is otherwise not possible.
|
||||
///
|
||||
/// **NOTE:** This will change the usage string to look like `$ prog [FLAGS] [-- <ARG>]` if
|
||||
/// `ARG` is marked as `.last(true)`.
|
||||
///
|
||||
/// **NOTE:** This setting will imply [`AppSettings::DontCollapseArgsInUsage`] because failing
|
||||
/// to set this can make the usage string very confusing.
|
||||
///
|
||||
/// **NOTE**: This setting only applies to positional arguments, and has no affect on FLAGS /
|
||||
/// OPTIONS
|
||||
///
|
||||
/// **CAUTION:** Setting an argument to `.last(true)` *and* having child subcommands is not
|
||||
/// recommended with the exception of *also* using [`AppSettings::ArgsNegateSubcommands`]
|
||||
/// (or [`AppSettings::SubcommandsNegateReqs`] if the argument marked `.last(true)` is also
|
||||
/// marked [`.required(true)`])
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::Arg;
|
||||
/// Arg::with_name("args")
|
||||
/// .last(true)
|
||||
/// # ;
|
||||
/// ```
|
||||
///
|
||||
/// Setting [`Arg::last(true)`] ensures the arg has the highest [index] of all positional args
|
||||
/// and requires that the `--` syntax be used to access it early.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// let res = App::new("prog")
|
||||
/// .arg(Arg::with_name("first"))
|
||||
/// .arg(Arg::with_name("second"))
|
||||
/// .arg(Arg::with_name("third").last(true))
|
||||
/// .get_matches_from_safe(vec![
|
||||
/// "prog", "one", "--", "three"
|
||||
/// ]);
|
||||
///
|
||||
/// assert!(res.is_ok());
|
||||
/// let m = res.unwrap();
|
||||
/// assert_eq!(m.value_of("third"), Some("three"));
|
||||
/// assert!(m.value_of("second").is_none());
|
||||
/// ```
|
||||
///
|
||||
/// Even if the positional argument marked `.last(true)` is the only argument left to parse,
|
||||
/// failing to use the `--` syntax results in an error.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg, ErrorKind};
|
||||
/// let res = App::new("prog")
|
||||
/// .arg(Arg::with_name("first"))
|
||||
/// .arg(Arg::with_name("second"))
|
||||
/// .arg(Arg::with_name("third").last(true))
|
||||
/// .get_matches_from_safe(vec![
|
||||
/// "prog", "one", "two", "three"
|
||||
/// ]);
|
||||
///
|
||||
/// assert!(res.is_err());
|
||||
/// assert_eq!(res.unwrap_err().kind, ErrorKind::UnknownArgument);
|
||||
/// ```
|
||||
/// [`Arg::last(true)`]: ./struct.Arg.html#method.last
|
||||
/// [index]: ./struct.Arg.html#method.index
|
||||
/// [`AppSettings::DontCollapseArgsInUsage`]: ./enum.AppSettings.html#variant.DontCollapseArgsInUsage
|
||||
/// [`AppSettings::ArgsNegateSubcommands`]: ./enum.AppSettings.html#variant.ArgsNegateSubcommands
|
||||
/// [`AppSettings::SubcommandsNegateReqs`]: ./enum.AppSettings.html#variant.SubcommandsNegateReqs
|
||||
/// [`.required(true)`]: ./struct.Arg.html#method.required
|
||||
/// [`UnknownArgument`]: ./enum.ErrorKind.html#variant.UnknownArgument
|
||||
pub fn last(self, l: bool) -> Self {
|
||||
if l {
|
||||
self.set(ArgSettings::Last)
|
||||
} else {
|
||||
self.unset(ArgSettings::Last)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets whether or not the argument is required by default. Required by default means it is
|
||||
/// required, when no other conflicting rules have been evaluated. Conflicting rules take
|
||||
/// precedence over being required. **Default:** `false`
|
||||
|
|
|
@ -18,6 +18,7 @@ bitflags! {
|
|||
const HIDE_POS_VALS = 1 << 11,
|
||||
const ALLOW_TAC_VALS = 1 << 12,
|
||||
const REQUIRE_EQUALS = 1 << 13,
|
||||
const LAST = 1 << 14,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +43,8 @@ impl ArgFlags {
|
|||
ValueDelimiterNotSet => DELIM_NOT_SET,
|
||||
HidePossibleValues => HIDE_POS_VALS,
|
||||
AllowLeadingHyphen => ALLOW_TAC_VALS,
|
||||
RequireEquals => REQUIRE_EQUALS
|
||||
RequireEquals => REQUIRE_EQUALS,
|
||||
Last => LAST
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,6 +84,9 @@ pub enum ArgSettings {
|
|||
AllowLeadingHyphen,
|
||||
/// Require options use `--option=val` syntax
|
||||
RequireEquals,
|
||||
/// Specifies that the arg is the last positional argument and may be accessed early via `--`
|
||||
/// syntax
|
||||
Last,
|
||||
#[doc(hidden)]
|
||||
RequiredUnlessAll,
|
||||
#[doc(hidden)]
|
||||
|
@ -106,6 +111,7 @@ impl FromStr for ArgSettings {
|
|||
"hidepossiblevalues" => Ok(ArgSettings::HidePossibleValues),
|
||||
"allowleadinghyphen" => Ok(ArgSettings::AllowLeadingHyphen),
|
||||
"requireequals" => Ok(ArgSettings::RequireEquals),
|
||||
"last" => Ok(ArgSettings::Last),
|
||||
_ => Err("unknown ArgSetting, cannot convert from str".to_owned()),
|
||||
}
|
||||
}
|
||||
|
@ -145,6 +151,8 @@ mod test {
|
|||
ArgSettings::ValueDelimiterNotSet);
|
||||
assert_eq!("requireequals".parse::<ArgSettings>().unwrap(),
|
||||
ArgSettings::RequireEquals);
|
||||
assert_eq!("last".parse::<ArgSettings>().unwrap(),
|
||||
ArgSettings::Last);
|
||||
assert!("hahahaha".parse::<ArgSettings>().is_err());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue