mirror of
https://github.com/clap-rs/clap
synced 2024-12-22 10:33:10 +00:00
7b10e7f893
This commit adds support for visible aliases, which function exactly like aliases except that they also appear in the help message, using the help string of the aliased subcommand. i.e. ```rust App::new("myprog") .subcommand(SubCommand::with_name("test") .about("does testy things") .alias("invisible") .visible_alias("visible")); ``` When run with `myprog --help`, the output is: ``` myprog USAGE: myprog [FLAGS] [SUBCOMMAND] FLAGS: -h, --help Prints help information -V, --version Prints version information SUBCOMMANDS: help Prints this message or the help of the given subcommand(s) test|visible does testy things ``` Closes #522
1707 lines
69 KiB
Rust
1707 lines
69 KiB
Rust
use std::collections::{BTreeMap, HashMap, VecDeque};
|
|
use std::slice::Iter;
|
|
use std::io::{self, BufWriter, Write};
|
|
use std::ffi::{OsStr, OsString};
|
|
use std::fmt::Display;
|
|
#[cfg(feature = "debug")]
|
|
use std::os::unix::ffi::OsStrExt;
|
|
|
|
use vec_map::{self, VecMap};
|
|
|
|
use app::help::Help;
|
|
use app::App;
|
|
use args::{Arg, ArgGroup, FlagBuilder, OptBuilder, PosBuilder};
|
|
use app::settings::{AppFlags, AppSettings};
|
|
use args::{AnyArg, ArgMatcher};
|
|
use args::settings::ArgSettings;
|
|
use errors::{Error, ErrorKind};
|
|
use errors::Result as ClapResult;
|
|
use INVALID_UTF8;
|
|
use suggestions;
|
|
use INTERNAL_ERROR_MSG;
|
|
use SubCommand;
|
|
use fmt::{Format, ColorWhen};
|
|
use osstringext::OsStrExt2;
|
|
use app::meta::AppMeta;
|
|
use args::MatchedArg;
|
|
|
|
#[allow(missing_debug_implementations)]
|
|
#[doc(hidden)]
|
|
pub struct Parser<'a, 'b>
|
|
where 'a: 'b
|
|
{
|
|
required: Vec<&'b str>,
|
|
short_list: Vec<char>,
|
|
long_list: Vec<&'b str>,
|
|
blacklist: Vec<&'b str>,
|
|
// A list of possible flags
|
|
flags: Vec<FlagBuilder<'a, 'b>>,
|
|
// A list of possible options
|
|
opts: Vec<OptBuilder<'a, 'b>>,
|
|
// A list of positional arguments
|
|
positionals: VecMap<PosBuilder<'a, 'b>>,
|
|
// A list of subcommands
|
|
#[doc(hidden)]
|
|
pub subcommands: Vec<App<'a, 'b>>,
|
|
groups: HashMap<&'a str, ArgGroup<'a>>,
|
|
global_args: Vec<Arg<'a, 'b>>,
|
|
overrides: Vec<&'b str>,
|
|
help_short: Option<char>,
|
|
version_short: Option<char>,
|
|
settings: AppFlags,
|
|
pub g_settings: Vec<AppSettings>,
|
|
pub meta: AppMeta<'b>,
|
|
trailing_vals: bool,
|
|
}
|
|
|
|
impl<'a, 'b> Default for Parser<'a, 'b> {
|
|
fn default() -> Self {
|
|
Parser {
|
|
flags: vec![],
|
|
opts: vec![],
|
|
positionals: VecMap::new(),
|
|
subcommands: vec![],
|
|
help_short: None,
|
|
version_short: None,
|
|
required: vec![],
|
|
short_list: vec![],
|
|
long_list: vec![],
|
|
blacklist: vec![],
|
|
groups: HashMap::new(),
|
|
global_args: vec![],
|
|
overrides: vec![],
|
|
g_settings: vec![],
|
|
settings: AppFlags::new(),
|
|
meta: AppMeta::new(),
|
|
trailing_vals: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, 'b> Parser<'a, 'b>
|
|
where 'a: 'b
|
|
{
|
|
pub fn with_name(n: String) -> Self {
|
|
Parser { meta: AppMeta::with_name(n), ..Default::default() }
|
|
}
|
|
|
|
pub fn help_short(&mut self, s: &str) {
|
|
self.help_short = s.trim_left_matches(|c| c == '-')
|
|
.chars()
|
|
.nth(0);
|
|
}
|
|
|
|
pub fn version_short(&mut self, s: &str) {
|
|
self.version_short = s.trim_left_matches(|c| c == '-')
|
|
.chars()
|
|
.nth(0);
|
|
}
|
|
|
|
// actually adds the arguments
|
|
pub fn add_arg(&mut self, a: &Arg<'a, 'b>) {
|
|
debug_assert!(!(self.flags.iter().any(|f| &f.name == &a.name) ||
|
|
self.opts.iter().any(|o| o.name == a.name) ||
|
|
self.positionals.values().any(|p| p.name == a.name)),
|
|
format!("Non-unique argument name: {} is already in use", a.name));
|
|
if let Some(grp) = a.group {
|
|
let ag = self.groups.entry(grp).or_insert_with(|| ArgGroup::with_name(grp));
|
|
ag.args.push(a.name);
|
|
}
|
|
if let Some(s) = a.short {
|
|
debug_assert!(!self.short_list.contains(&s),
|
|
format!("Argument short must be unique\n\n\t-{} is already in use",
|
|
s));
|
|
self.short_list.push(s);
|
|
}
|
|
if let Some(l) = a.long {
|
|
debug_assert!(!self.long_list.contains(&l),
|
|
format!("Argument long must be unique\n\n\t--{} is already in use",
|
|
l));
|
|
self.long_list.push(l);
|
|
if l == "help" {
|
|
self.unset(AppSettings::NeedsLongHelp);
|
|
} else if l == "version" {
|
|
self.unset(AppSettings::NeedsLongVersion);
|
|
}
|
|
}
|
|
if a.is_set(ArgSettings::Required) {
|
|
self.required.push(a.name);
|
|
}
|
|
if a.index.is_some() || (a.short.is_none() && a.long.is_none()) {
|
|
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 \
|
|
argument\n\n\tPerhaps try .multiple(true) to allow one positional argument \
|
|
to take multiple values",
|
|
a.name));
|
|
let pb = PosBuilder::from_arg(a, i as u64, &mut self.required);
|
|
self.positionals.insert(i, pb);
|
|
} else if a.is_set(ArgSettings::TakesValue) {
|
|
let mut ob = OptBuilder::from_arg(a, &mut self.required);
|
|
if self.settings.is_set(AppSettings::DeriveDisplayOrder) && a.disp_ord == 999 {
|
|
ob.disp_ord = if self.settings.is_set(AppSettings::UnifiedHelpMessage) {
|
|
self.flags.len() + self.opts.len()
|
|
} else {
|
|
self.opts.len()
|
|
};
|
|
}
|
|
self.opts.push(ob);
|
|
} else {
|
|
let mut fb = FlagBuilder::from(a);
|
|
if self.settings.is_set(AppSettings::DeriveDisplayOrder) && a.disp_ord == 999 {
|
|
fb.disp_ord = if self.settings.is_set(AppSettings::UnifiedHelpMessage) {
|
|
self.flags.len() + self.opts.len()
|
|
} else {
|
|
self.flags.len()
|
|
};
|
|
}
|
|
self.flags.push(fb);
|
|
}
|
|
if a.is_set(ArgSettings::Global) {
|
|
debug_assert!(!a.is_set(ArgSettings::Required),
|
|
format!("Global arguments cannot be required.\n\n\t'{}' is marked as \
|
|
global and required",
|
|
a.name));
|
|
self.global_args.push(a.into());
|
|
}
|
|
}
|
|
|
|
pub fn add_group(&mut self, group: ArgGroup<'a>) {
|
|
if group.required {
|
|
self.required.push(group.name.into());
|
|
if let Some(ref reqs) = group.requires {
|
|
self.required.extend_from_slice(reqs);
|
|
}
|
|
if let Some(ref bl) = group.conflicts {
|
|
self.blacklist.extend_from_slice(bl);
|
|
}
|
|
}
|
|
let mut found = false;
|
|
if let Some(ref mut grp) = self.groups.get_mut(&group.name) {
|
|
grp.args.extend_from_slice(&group.args);
|
|
grp.requires = group.requires.clone();
|
|
grp.conflicts = group.conflicts.clone();
|
|
grp.required = group.required;
|
|
found = true;
|
|
}
|
|
if !found {
|
|
self.groups.insert(group.name.into(), group);
|
|
}
|
|
}
|
|
|
|
pub fn add_subcommand(&mut self, mut subcmd: App<'a, 'b>) {
|
|
debugln!("fn=Parser::add_subcommand;");
|
|
debug!("Is help...");
|
|
if subcmd.p.meta.name == "help" {
|
|
sdebugln!("Yes");
|
|
self.settings.set(AppSettings::NeedsSubcommandHelp);
|
|
} else {
|
|
sdebugln!("No");
|
|
}
|
|
debug!("Using Setting VersionlessSubcommands...");
|
|
if self.settings.is_set(AppSettings::VersionlessSubcommands) {
|
|
sdebugln!("Yes");
|
|
subcmd.p.settings.set(AppSettings::DisableVersion);
|
|
} else {
|
|
sdebugln!("No");
|
|
}
|
|
debug!("Using Setting GlobalVersion...");
|
|
if self.settings.is_set(AppSettings::GlobalVersion) && subcmd.p.meta.version.is_none() &&
|
|
self.meta.version.is_some() {
|
|
sdebugln!("Yes");
|
|
subcmd = subcmd.setting(AppSettings::GlobalVersion)
|
|
.version(self.meta.version.unwrap());
|
|
} else {
|
|
sdebugln!("No");
|
|
}
|
|
if self.settings.is_set(AppSettings::DeriveDisplayOrder) {
|
|
subcmd.p.meta.disp_ord = self.subcommands.len();
|
|
}
|
|
for s in &self.g_settings {
|
|
subcmd.p.set(*s);
|
|
subcmd.p.g_settings.push(*s);
|
|
}
|
|
self.subcommands.push(subcmd);
|
|
}
|
|
|
|
pub fn required(&self) -> Iter<&str> {
|
|
self.required.iter()
|
|
}
|
|
|
|
#[cfg_attr(feature = "lints", allow(for_kv_map))]
|
|
pub fn get_required_from(&self,
|
|
reqs: &[&'a str],
|
|
matcher: Option<&ArgMatcher<'a>>)
|
|
-> VecDeque<String> {
|
|
let mut c_flags: Vec<&str> = vec![];
|
|
let mut c_pos: Vec<&str> = vec![];
|
|
let mut c_opt: Vec<&str> = vec![];
|
|
let mut grps: Vec<&str> = vec![];
|
|
for name in reqs {
|
|
if self.flags.iter().any(|f| &f.name == name) {
|
|
c_flags.push(name);
|
|
} else if self.opts.iter().any(|o| &o.name == name) {
|
|
c_opt.push(name);
|
|
} else if self.groups.contains_key(name) {
|
|
grps.push(name);
|
|
} else {
|
|
c_pos.push(name);
|
|
}
|
|
}
|
|
macro_rules! fill_vecs {
|
|
($_self:ident {
|
|
$t1:ident => $v1:ident => $i1:ident,
|
|
$t2:ident => $v2:ident => $i2:ident,
|
|
$t3:ident => $v3:ident => $i3:ident,
|
|
$gv:ident, $tmp:ident
|
|
}) => {
|
|
for a in &$v1 {
|
|
if let Some(a) = self.$t1.$i1().filter(|arg| &arg.name == a).next() {
|
|
if let Some(ref rl) = a.requires {
|
|
for r in rl {
|
|
if !reqs.contains(r) {
|
|
if $_self.$t1.$i1().any(|t| &t.name == r) {
|
|
$tmp.push(*r);
|
|
} else if $_self.$t2.$i2().any(|t| &t.name == r) {
|
|
$v2.push(r);
|
|
} else if $_self.$t3.$i3().any(|t| &t.name == r) {
|
|
$v3.push(r);
|
|
} else if $_self.groups.contains_key(r) {
|
|
$gv.push(r);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$v1.extend(&$tmp);
|
|
};
|
|
}
|
|
|
|
let mut tmp = vec![];
|
|
fill_vecs!(self {
|
|
flags => c_flags => iter,
|
|
opts => c_opt => iter,
|
|
positionals => c_pos => values,
|
|
grps, tmp
|
|
});
|
|
tmp.clear();
|
|
fill_vecs!(self {
|
|
opts => c_opt => iter,
|
|
flags => c_flags => iter,
|
|
positionals => c_pos => values,
|
|
grps, tmp
|
|
});
|
|
tmp.clear();
|
|
fill_vecs!(self {
|
|
positionals => c_pos => values,
|
|
opts => c_opt => iter,
|
|
flags => c_flags => iter,
|
|
grps, tmp
|
|
});
|
|
let mut ret_val = VecDeque::new();
|
|
c_pos.dedup();
|
|
c_flags.dedup();
|
|
c_opt.dedup();
|
|
grps.dedup();
|
|
let mut args_in_groups = vec![];
|
|
for g in &grps {
|
|
for a in self.arg_names_in_group(g).into_iter() {
|
|
args_in_groups.push(a);
|
|
}
|
|
}
|
|
|
|
let mut pmap = BTreeMap::new();
|
|
for p in c_pos.into_iter() {
|
|
if matcher.is_some() && matcher.as_ref().unwrap().contains(p) {
|
|
continue;
|
|
}
|
|
if let Some(p) = self.positionals.values().filter(|x| &x.name == &p).next() {
|
|
if args_in_groups.contains(&p.name) {
|
|
continue;
|
|
}
|
|
pmap.insert(p.index, p.to_string());
|
|
}
|
|
}
|
|
debugln!("args_in_groups={:?}", args_in_groups);
|
|
for (_, s) in pmap {
|
|
if (!args_in_groups.is_empty()) && (args_in_groups.contains(&&*s)) {
|
|
continue;
|
|
}
|
|
|
|
ret_val.push_back(s);
|
|
}
|
|
macro_rules! write_arg {
|
|
($i:expr, $m:ident, $v:ident, $r:ident) => {
|
|
for f in $v.into_iter() {
|
|
if $m.is_some() && $m.as_ref().unwrap().contains(f) {
|
|
continue;
|
|
}
|
|
$r.push_back($i.filter(|flg| &flg.name == &f).next().unwrap().to_string());
|
|
}
|
|
}
|
|
}
|
|
write_arg!(self.flags.iter(), matcher, c_flags, ret_val);
|
|
write_arg!(self.opts.iter(), matcher, c_opt, ret_val);
|
|
let mut g_vec = vec![];
|
|
for g in grps.into_iter() {
|
|
let g_string = self.args_in_group(g)
|
|
.join("|");
|
|
g_vec.push(format!("<{}>", &g_string[..g_string.len()]));
|
|
}
|
|
g_vec.dedup();
|
|
for g in g_vec.into_iter() {
|
|
ret_val.push_back(g);
|
|
}
|
|
|
|
ret_val
|
|
}
|
|
|
|
pub fn has_flags(&self) -> bool {
|
|
!self.flags.is_empty()
|
|
}
|
|
|
|
pub fn has_opts(&self) -> bool {
|
|
!self.opts.is_empty()
|
|
}
|
|
|
|
pub fn has_positionals(&self) -> bool {
|
|
!self.positionals.is_empty()
|
|
}
|
|
|
|
pub fn has_subcommands(&self) -> bool {
|
|
!self.subcommands.is_empty()
|
|
}
|
|
|
|
pub fn is_set(&self, s: AppSettings) -> bool {
|
|
self.settings.is_set(s)
|
|
}
|
|
|
|
pub fn set(&mut self, s: AppSettings) {
|
|
self.settings.set(s)
|
|
}
|
|
|
|
pub fn unset(&mut self, s: AppSettings) {
|
|
self.settings.unset(s)
|
|
}
|
|
|
|
pub fn verify_positionals(&mut self) {
|
|
// Because you must wait until all arguments have been supplied, this is the first chance
|
|
// to make assertions on positional argument indexes
|
|
//
|
|
// Firt we verify that the index highest supplied index, is equal to the number of
|
|
// positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3
|
|
// but no 2)
|
|
if let Some((idx, ref p)) = self.positionals.iter().rev().next() {
|
|
debug_assert!(!(idx != self.positionals.len()),
|
|
format!("Found positional argument \"{}\" who's index is {} but there are \
|
|
only {} positional arguments defined",
|
|
p.name,
|
|
idx,
|
|
self.positionals.len()));
|
|
}
|
|
|
|
// Next we verify that only the highest index has a .multiple(true) (if any)
|
|
debug_assert!(!self.positionals
|
|
.values()
|
|
.any(|a| a.settings.is_set(ArgSettings::Multiple) &&
|
|
(a.index as usize != self.positionals.len())
|
|
),
|
|
"Only the positional argument with the highest index may accept multiple values");
|
|
|
|
// If it's required we also need to ensure all previous positionals are
|
|
// required too
|
|
let mut found = false;
|
|
for p in self.positionals.values().rev() {
|
|
if found {
|
|
debug_assert!(p.settings.is_set(ArgSettings::Required),
|
|
"Found positional argument which is not required with a lower index \
|
|
than a required positional argument: {:?} index {}",
|
|
p.name,
|
|
p.index);
|
|
} else if p.settings.is_set(ArgSettings::Required) {
|
|
found = true;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn propogate_globals(&mut self) {
|
|
for sc in &mut self.subcommands {
|
|
// We have to create a new scope in order to tell rustc the borrow of `sc` is
|
|
// done and to recursively call this method
|
|
{
|
|
for a in &self.global_args {
|
|
sc.p.add_arg(a);
|
|
}
|
|
}
|
|
sc.p.propogate_globals();
|
|
}
|
|
}
|
|
|
|
// The actual parsing function
|
|
#[cfg_attr(feature = "lints", allow(while_let_on_iterator))]
|
|
pub fn get_matches_with<I, T>(&mut self,
|
|
matcher: &mut ArgMatcher<'a>,
|
|
it: &mut I)
|
|
-> ClapResult<()>
|
|
where I: Iterator<Item = T>,
|
|
T: Into<OsString>
|
|
{
|
|
debugln!("fn=get_matches_with;");
|
|
// First we create the `--help` and `--version` arguments and add them if
|
|
// necessary
|
|
self.create_help_and_version();
|
|
|
|
let mut subcmd_name: Option<String> = None;
|
|
let mut needs_val_of: Option<&str> = None;
|
|
let mut pos_counter = 1;
|
|
while let Some(arg) = it.next() {
|
|
let arg_os = arg.into();
|
|
debugln!("Begin parsing '{:?}' ({:?})", arg_os, &*arg_os.as_bytes());
|
|
|
|
// Is this a new argument, or values from a previous option?
|
|
debug!("Starts new arg...");
|
|
let starts_new_arg = if arg_os.starts_with(b"-") {
|
|
sdebugln!("Yes");
|
|
!(arg_os.len_() == 1)
|
|
} else {
|
|
sdebugln!("No");
|
|
false
|
|
};
|
|
|
|
// Has the user already passed '--'?
|
|
if !self.trailing_vals {
|
|
// Does the arg match a subcommand name, or any of it's aliases (if defined)
|
|
let pos_sc = self.subcommands
|
|
.iter()
|
|
.any(|s| &s.p.meta.name[..] == &*arg_os ||
|
|
(s.p.meta.aliases.is_some() &&
|
|
s.p.meta.aliases
|
|
.as_ref()
|
|
.unwrap()
|
|
.iter()
|
|
.any(|&(a, _)| a == &*arg_os)));
|
|
if (!starts_new_arg || self.is_set(AppSettings::AllowLeadingHyphen)) && !pos_sc {
|
|
// Check to see if parsing a value from an option
|
|
if let Some(nvo) = needs_val_of {
|
|
// get the OptBuilder so we can check the settings
|
|
if let Some(opt) = self.opts.iter().filter(|o| &o.name == &nvo).next() {
|
|
needs_val_of = try!(self.add_val_to_arg(opt, &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));
|
|
continue;
|
|
} else if arg_os.starts_with(b"-") && arg_os.len_() != 1 {
|
|
needs_val_of = try!(self.parse_short_arg(matcher, &arg_os));
|
|
if !(needs_val_of.is_none() && self.is_set(AppSettings::AllowLeadingHyphen)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if pos_sc {
|
|
if &*arg_os == "help" &&
|
|
self.settings.is_set(AppSettings::NeedsSubcommandHelp) {
|
|
let cmds: Vec<OsString> = it.map(|c| c.into()).collect();
|
|
let mut sc = {
|
|
let mut sc: &Parser = self;
|
|
for (i, cmd) in cmds.iter().enumerate() {
|
|
if let Some(c) = sc.subcommands
|
|
.iter()
|
|
.filter(|s| &*s.p.meta.name == cmd)
|
|
.next()
|
|
.map(|sc| &sc.p) {
|
|
sc = c;
|
|
if i == cmds.len() - 1 {
|
|
break;
|
|
}
|
|
} else if let Some(c) = sc.subcommands
|
|
.iter()
|
|
.filter(|s|
|
|
if let Some(ref als) = s.p
|
|
.meta
|
|
.aliases {
|
|
als.iter()
|
|
.any(|&(a, _)| &a == &&*cmd.to_string_lossy())
|
|
} else {
|
|
false
|
|
}
|
|
)
|
|
.next()
|
|
.map(|sc| &sc.p) {
|
|
sc = c;
|
|
if i == cmds.len() - 1 {
|
|
break;
|
|
}
|
|
} else {
|
|
return Err(
|
|
Error::unrecognized_subcommand(
|
|
cmd.to_string_lossy().into_owned(),
|
|
self.meta
|
|
.bin_name
|
|
.as_ref()
|
|
.unwrap_or(&self.meta.name),
|
|
self.color()));
|
|
}
|
|
}
|
|
sc.clone()
|
|
};
|
|
sc.create_help_and_version();
|
|
return sc._help();
|
|
}
|
|
subcmd_name = Some(arg_os.to_str().expect(INVALID_UTF8).to_owned());
|
|
break;
|
|
} else 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()));
|
|
}
|
|
}
|
|
|
|
if let Some(p) = self.positionals.get(pos_counter) {
|
|
parse_positional!(self, p, arg_os, pos_counter, matcher);
|
|
} else {
|
|
if self.settings.is_set(AppSettings::AllowExternalSubcommands) {
|
|
let mut sc_m = ArgMatcher::new();
|
|
while let Some(v) = it.next() {
|
|
let a = v.into();
|
|
if let None = a.to_str() {
|
|
if !self.settings.is_set(AppSettings::StrictUtf8) {
|
|
return Err(
|
|
Error::invalid_utf8(&*self.create_current_usage(matcher), self.color())
|
|
);
|
|
}
|
|
}
|
|
sc_m.add_val_to("EXTERNAL_SUBCOMMAND", &a);
|
|
}
|
|
|
|
matcher.subcommand(SubCommand {
|
|
name: "EXTERNAL_SUBCOMMAND".into(),
|
|
matches: sc_m.into(),
|
|
});
|
|
} else {
|
|
return Err(Error::unknown_argument(&*arg_os.to_string_lossy(),
|
|
"",
|
|
&*self.create_current_usage(matcher),
|
|
self.color()));
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut reqs_validated = false;
|
|
if let Some(a) = needs_val_of {
|
|
if let Some(o) = self.opts.iter().filter(|o| &o.name == &a).next() {
|
|
try!(self.validate_required(matcher));
|
|
reqs_validated = true;
|
|
let should_err = if let Some(ref v) = matcher.0.args.get(&*o.name) {
|
|
v.vals.is_empty() && !(o.min_vals.is_some() && o.min_vals.unwrap() == 0)
|
|
} else {
|
|
true
|
|
};
|
|
if should_err {
|
|
return Err(Error::empty_value(o, &*self.create_current_usage(matcher), self.color()));
|
|
}
|
|
} else {
|
|
return Err(Error::empty_value(self.positionals
|
|
.values()
|
|
.filter(|p| &p.name == &a)
|
|
.next()
|
|
.expect(INTERNAL_ERROR_MSG),
|
|
&*self.create_current_usage(matcher),
|
|
self.color()));
|
|
}
|
|
}
|
|
|
|
try!(self.add_defaults(matcher));
|
|
try!(self.validate_blacklist(matcher));
|
|
try!(self.validate_num_args(matcher));
|
|
matcher.usage(self.create_usage(&[]));
|
|
|
|
if !(self.settings.is_set(AppSettings::SubcommandsNegateReqs) && subcmd_name.is_some()) &&
|
|
!reqs_validated {
|
|
try!(self.validate_required(matcher));
|
|
}
|
|
if let Some(pos_sc_name) = subcmd_name {
|
|
// is this is a real subcommand, or an alias
|
|
let sc_name = if self.subcommands.iter().any(|sc| sc.p.meta.name == pos_sc_name) {
|
|
pos_sc_name
|
|
} else {
|
|
self.subcommands
|
|
.iter()
|
|
.filter(|sc| sc.p.meta.aliases.is_some())
|
|
.filter_map(|sc| if sc.p.meta.aliases
|
|
.as_ref()
|
|
.unwrap()
|
|
.iter()
|
|
.any(|&(a, _)| &a == &&*pos_sc_name) {
|
|
Some(sc.p.meta.name.clone())
|
|
} else {
|
|
None
|
|
})
|
|
.next()
|
|
.expect(INTERNAL_ERROR_MSG)
|
|
};
|
|
try!(self.parse_subcommand(sc_name, matcher, it));
|
|
} else if self.is_set(AppSettings::SubcommandRequired) {
|
|
let bn = self.meta.bin_name.as_ref().unwrap_or(&self.meta.name);
|
|
return Err(Error::missing_subcommand(bn, &self.create_current_usage(matcher), self.color()));
|
|
} else if self.is_set(AppSettings::SubcommandRequiredElseHelp) {
|
|
let mut out = vec![];
|
|
try!(self.write_help_err(&mut out));
|
|
return Err(Error {
|
|
message: String::from_utf8_lossy(&*out).into_owned(),
|
|
kind: ErrorKind::MissingArgumentOrSubcommand,
|
|
info: None,
|
|
});
|
|
}
|
|
if matcher.is_empty() && matcher.subcommand_name().is_none() &&
|
|
self.is_set(AppSettings::ArgRequiredElseHelp) {
|
|
let mut out = vec![];
|
|
try!(self.write_help_err(&mut out));
|
|
return Err(Error {
|
|
message: String::from_utf8_lossy(&*out).into_owned(),
|
|
kind: ErrorKind::MissingArgumentOrSubcommand,
|
|
info: None,
|
|
});
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn parse_subcommand<I, T>(&mut self,
|
|
sc_name: String,
|
|
matcher: &mut ArgMatcher<'a>,
|
|
it: &mut I)
|
|
-> ClapResult<()>
|
|
where I: Iterator<Item = T>,
|
|
T: Into<OsString>
|
|
{
|
|
use std::fmt::Write;
|
|
debugln!("fn=parse_subcommand;");
|
|
let mut mid_string = String::new();
|
|
if !self.settings.is_set(AppSettings::SubcommandsNegateReqs) {
|
|
let mut hs: Vec<&str> = self.required.iter().map(|n| &**n).collect();
|
|
for k in matcher.arg_names() {
|
|
hs.push(k);
|
|
}
|
|
let reqs = self.get_required_from(&hs, Some(matcher));
|
|
|
|
for s in &reqs {
|
|
write!(&mut mid_string, " {}", s).expect(INTERNAL_ERROR_MSG);
|
|
}
|
|
}
|
|
mid_string.push_str(" ");
|
|
if let Some(ref mut sc) = self.subcommands
|
|
.iter_mut()
|
|
.filter(|s| &s.p.meta.name == &sc_name)
|
|
.next() {
|
|
let mut sc_matcher = ArgMatcher::new();
|
|
// bin_name should be parent's bin_name + [<reqs>] + the sc's name separated by
|
|
// a space
|
|
sc.p.meta.usage = Some(format!("{}{}{}",
|
|
self.meta.bin_name.as_ref().unwrap_or(&String::new()),
|
|
if self.meta.bin_name.is_some() {
|
|
&*mid_string
|
|
} else {
|
|
""
|
|
},
|
|
&*sc.p.meta.name));
|
|
sc.p.meta.bin_name = Some(format!("{}{}{}",
|
|
self.meta
|
|
.bin_name
|
|
.as_ref()
|
|
.unwrap_or(&String::new()),
|
|
if self.meta.bin_name.is_some() {
|
|
" "
|
|
} else {
|
|
""
|
|
},
|
|
&*sc.p.meta.name));
|
|
try!(sc.p.get_matches_with(&mut sc_matcher, it));
|
|
matcher.subcommand(SubCommand {
|
|
name: sc.p.meta.name.clone(),
|
|
matches: sc_matcher.into(),
|
|
});
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn blacklisted_from(&self, name: &str, matcher: &ArgMatcher) -> Option<String> {
|
|
for k in matcher.arg_names() {
|
|
if let Some(f) = self.flags.iter().filter(|f| &f.name == &k).next() {
|
|
if let Some(ref bl) = f.blacklist {
|
|
if bl.contains(&name) {
|
|
return Some(f.to_string());
|
|
}
|
|
}
|
|
}
|
|
if let Some(o) = self.opts.iter().filter(|o| &o.name == &k).next() {
|
|
if let Some(ref bl) = o.blacklist {
|
|
if bl.contains(&name) {
|
|
return Some(o.to_string());
|
|
}
|
|
}
|
|
}
|
|
if let Some(pos) = self.positionals.values().filter(|p| &p.name == &k).next() {
|
|
if let Some(ref bl) = pos.blacklist {
|
|
if bl.contains(&name) {
|
|
return Some(pos.name.to_owned());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn overriden_from(&self, name: &str, matcher: &ArgMatcher) -> Option<&'a str> {
|
|
for k in matcher.arg_names() {
|
|
if let Some(f) = self.flags.iter().filter(|f| &f.name == &k).next() {
|
|
if let Some(ref bl) = f.overrides {
|
|
if bl.contains(&name.into()) {
|
|
return Some(f.name);
|
|
}
|
|
}
|
|
}
|
|
if let Some(o) = self.opts.iter().filter(|o| &o.name == &k).next() {
|
|
if let Some(ref bl) = o.overrides {
|
|
if bl.contains(&name.into()) {
|
|
return Some(o.name);
|
|
}
|
|
}
|
|
}
|
|
if let Some(pos) = self.positionals.values().filter(|p| &p.name == &k).next() {
|
|
if let Some(ref bl) = pos.overrides {
|
|
if bl.contains(&name.into()) {
|
|
return Some(pos.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn groups_for_arg(&self, name: &str) -> Option<Vec<&'a str>> {
|
|
debugln!("fn=groups_for_arg;");
|
|
|
|
if self.groups.is_empty() {
|
|
debugln!("No groups defined");
|
|
return None;
|
|
}
|
|
let mut res = vec![];
|
|
debugln!("Searching through groups...");
|
|
for (gn, grp) in &self.groups {
|
|
for a in &grp.args {
|
|
if a == &name {
|
|
sdebugln!("\tFound '{}'", gn);
|
|
res.push(*gn);
|
|
}
|
|
}
|
|
}
|
|
if res.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
Some(res)
|
|
}
|
|
|
|
fn args_in_group(&self, group: &str) -> Vec<String> {
|
|
let mut g_vec = vec![];
|
|
let mut args = vec![];
|
|
|
|
for n in &self.groups.get(group).unwrap().args {
|
|
if let Some(f) = self.flags.iter().filter(|f| &f.name == n).next() {
|
|
args.push(f.to_string());
|
|
} else if let Some(f) = self.opts.iter().filter(|o| &o.name == n).next() {
|
|
args.push(f.to_string());
|
|
} else if self.groups.contains_key(&**n) {
|
|
g_vec.push(*n);
|
|
} else if let Some(p) = self.positionals
|
|
.values()
|
|
.filter(|p| &p.name == n)
|
|
.next() {
|
|
args.push(p.name.to_owned());
|
|
}
|
|
}
|
|
|
|
for av in g_vec.iter().map(|g| self.args_in_group(g)) {
|
|
args.extend(av);
|
|
}
|
|
args.dedup();
|
|
args.iter().map(ToOwned::to_owned).collect()
|
|
}
|
|
|
|
fn arg_names_in_group(&self, group: &str) -> Vec<&'a str> {
|
|
let mut g_vec = vec![];
|
|
let mut args = vec![];
|
|
|
|
for n in &self.groups.get(group).unwrap().args {
|
|
if self.groups.contains_key(&*n) {
|
|
g_vec.push(*n);
|
|
} else {
|
|
args.push(*n);
|
|
}
|
|
}
|
|
|
|
for av in g_vec.iter().map(|g| self.arg_names_in_group(g)) {
|
|
args.extend(av);
|
|
}
|
|
args.dedup();
|
|
args.iter().map(|s| *s).collect()
|
|
}
|
|
|
|
fn create_help_and_version(&mut self) {
|
|
debugln!("fn=create_help_and_version;");
|
|
// name is "hclap_help" because flags are sorted by name
|
|
if !self.flags.iter().any(|a| a.long.is_some() && a.long.unwrap() == "help") {
|
|
debugln!("Building --help");
|
|
if self.help_short.is_none() && !self.short_list.contains(&'h') {
|
|
self.help_short = Some('h');
|
|
}
|
|
let arg = FlagBuilder {
|
|
name: "hclap_help",
|
|
short: self.help_short,
|
|
long: Some("help"),
|
|
help: Some("Prints help information"),
|
|
..Default::default()
|
|
};
|
|
self.long_list.push("help");
|
|
self.flags.push(arg);
|
|
}
|
|
if !self.settings.is_set(AppSettings::DisableVersion) &&
|
|
!self.flags.iter().any(|a| a.long.is_some() && a.long.unwrap() == "version") {
|
|
debugln!("Building --version");
|
|
if self.version_short.is_none() && !self.short_list.contains(&'V') {
|
|
self.version_short = Some('V');
|
|
}
|
|
// name is "vclap_version" because flags are sorted by name
|
|
let arg = FlagBuilder {
|
|
name: "vclap_version",
|
|
short: self.version_short,
|
|
long: Some("version"),
|
|
help: Some("Prints version information"),
|
|
..Default::default()
|
|
};
|
|
self.long_list.push("version");
|
|
self.flags.push(arg);
|
|
}
|
|
if !self.subcommands.is_empty() &&
|
|
!self.subcommands
|
|
.iter()
|
|
.any(|s| &s.p.meta.name[..] == "help") {
|
|
debugln!("Building help");
|
|
self.subcommands
|
|
.push(App::new("help")
|
|
.about("Prints this message or the help of the given subcommand(s)"));
|
|
}
|
|
}
|
|
|
|
// Retrieves the names of all args the user has supplied thus far, except required ones
|
|
// because those will be listed in self.required
|
|
pub fn create_current_usage(&self, matcher: &'b ArgMatcher<'a>) -> String {
|
|
self.create_usage(&*matcher.arg_names()
|
|
.iter()
|
|
.filter(|n| {
|
|
if let Some(o) = self.opts
|
|
.iter()
|
|
.filter(|&o| &&o.name == n)
|
|
.next() {
|
|
!o.settings.is_set(ArgSettings::Required)
|
|
} else if let Some(p) = self.positionals
|
|
.values()
|
|
.filter(|&p| &&p.name == n)
|
|
.next() {
|
|
!p.settings.is_set(ArgSettings::Required)
|
|
} else {
|
|
true // flags can't be required, so they're always true
|
|
}
|
|
})
|
|
.map(|&n| n)
|
|
.collect::<Vec<_>>())
|
|
}
|
|
|
|
fn check_for_help_and_version_str(&self, arg: &OsStr) -> ClapResult<()> {
|
|
debug!("Checking if --{} is help or version...",
|
|
arg.to_str().unwrap());
|
|
if arg == "help" && self.settings.is_set(AppSettings::NeedsLongHelp) {
|
|
sdebugln!("Help");
|
|
try!(self._help());
|
|
}
|
|
if arg == "version" && self.settings.is_set(AppSettings::NeedsLongVersion) {
|
|
sdebugln!("Version");
|
|
try!(self._version());
|
|
}
|
|
sdebugln!("Neither");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn check_for_help_and_version_char(&self, arg: char) -> ClapResult<()> {
|
|
debug!("Checking if -{} is help or version...", arg);
|
|
if let Some(h) = self.help_short {
|
|
sdebugln!("Help");
|
|
if arg == h && self.settings.is_set(AppSettings::NeedsLongHelp) {
|
|
try!(self._help());
|
|
}
|
|
}
|
|
if let Some(v) = self.version_short {
|
|
sdebugln!("Help");
|
|
if arg == v && self.settings.is_set(AppSettings::NeedsLongVersion) {
|
|
try!(self._version());
|
|
}
|
|
}
|
|
sdebugln!("Neither");
|
|
Ok(())
|
|
}
|
|
|
|
fn _help(&self) -> ClapResult<()> {
|
|
try!(self.print_help());
|
|
Err(Error {
|
|
message: String::new(),
|
|
kind: ErrorKind::HelpDisplayed,
|
|
info: None,
|
|
})
|
|
}
|
|
|
|
fn _version(&self) -> ClapResult<()> {
|
|
let out = io::stdout();
|
|
let mut buf_w = BufWriter::new(out.lock());
|
|
try!(self.print_version(&mut buf_w));
|
|
Err(Error {
|
|
message: String::new(),
|
|
kind: ErrorKind::VersionDisplayed,
|
|
info: None,
|
|
})
|
|
}
|
|
|
|
fn parse_long_arg(&mut self,
|
|
matcher: &mut ArgMatcher<'a>,
|
|
full_arg: &OsStr)
|
|
-> ClapResult<Option<&'b str>> {
|
|
// maybe here lifetime should be 'a
|
|
debugln!("fn=parse_long_arg;");
|
|
let mut val = None;
|
|
debug!("Does it contain '='...");
|
|
let arg = if full_arg.contains_byte(b'=') {
|
|
let (p0, p1) = full_arg.trim_left_matches(b'-').split_at_byte(b'=');
|
|
sdebugln!("Yes '{:?}'", p1);
|
|
val = Some(p1);
|
|
p0
|
|
} else {
|
|
sdebugln!("No");
|
|
full_arg.trim_left_matches(b'-')
|
|
};
|
|
|
|
if let Some(opt) = self.opts
|
|
.iter()
|
|
.filter(|v| v.long.is_some() && &*v.long.unwrap() == arg)
|
|
.next() {
|
|
debugln!("Found valid opt '{}'", opt.to_string());
|
|
let ret = try!(self.parse_opt(val, opt, matcher));
|
|
arg_post_processing!(self, opt, matcher);
|
|
|
|
return Ok(ret);
|
|
} else if let Some(flag) = self.flags
|
|
.iter()
|
|
.filter(|v| v.long.is_some() && &*v.long.unwrap() == arg)
|
|
.next() {
|
|
debugln!("Found valid flag '{}'", flag.to_string());
|
|
// Only flags could be help or version, and we need to check the raw long
|
|
// so this is the first point to check
|
|
try!(self.check_for_help_and_version_str(&arg));
|
|
|
|
try!(self.parse_flag(flag, matcher));
|
|
|
|
// Handle conflicts, requirements, etc.
|
|
arg_post_processing!(self, flag, matcher);
|
|
|
|
return Ok(None);
|
|
}
|
|
|
|
debugln!("Didn't match anything");
|
|
self.did_you_mean_error(arg.to_str().expect(INVALID_UTF8), matcher).map(|_| None)
|
|
}
|
|
|
|
fn parse_short_arg(&mut self,
|
|
matcher: &mut ArgMatcher<'a>,
|
|
full_arg: &OsStr)
|
|
-> ClapResult<Option<&'a str>> {
|
|
debugln!("fn=parse_short_arg;");
|
|
// let mut utf8 = true;
|
|
let arg_os = full_arg.trim_left_matches(b'-');
|
|
let arg = arg_os.to_string_lossy();
|
|
|
|
for c in arg.chars() {
|
|
// Check for matching short options, and return the name if there is no trailing
|
|
// concatenated value: -oval
|
|
// Option: -o
|
|
// Value: val
|
|
if let Some(opt) = self.opts
|
|
.iter()
|
|
.filter(|&v| v.short.is_some() && v.short.unwrap() == c)
|
|
.next() {
|
|
debugln!("Found valid short opt -{} in '{}'", c, arg);
|
|
// Check for trailing concatenated value
|
|
let p: Vec<_> = arg.splitn(2, c).collect();
|
|
debugln!("arg: {:?}, arg_os: {:?}, full_arg: {:?}",
|
|
arg,
|
|
arg_os,
|
|
full_arg);
|
|
debugln!("p[0]: {:?}, p[1]: {:?}", p[0].as_bytes(), p[1].as_bytes());
|
|
let i = p[0].as_bytes().len() + 1;
|
|
let val = if p[1].as_bytes().len() > 0 {
|
|
debugln!("setting val: {:?} (bytes), {:?} (ascii)",
|
|
arg_os.split_at(i).1.as_bytes(),
|
|
arg_os.split_at(i).1);
|
|
Some(arg_os.split_at(i).1)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// Default to "we're expecting a value later"
|
|
let ret = try!(self.parse_opt(val, opt, matcher));
|
|
|
|
arg_post_processing!(self, opt, matcher);
|
|
|
|
return Ok(ret);
|
|
} else if let Some(flag) = self.flags
|
|
.iter()
|
|
.filter(|&v| v.short.is_some() && v.short.unwrap() == c)
|
|
.next() {
|
|
debugln!("Found valid short flag -{}", c);
|
|
// Only flags can be help or version
|
|
try!(self.check_for_help_and_version_char(c));
|
|
try!(self.parse_flag(flag, matcher));
|
|
// Handle conflicts, requirements, overrides, etc.
|
|
// Must be called here due to mutablilty
|
|
arg_post_processing!(self, flag, matcher);
|
|
} else if !self.is_set(AppSettings::AllowLeadingHyphen) {
|
|
let mut arg = String::new();
|
|
arg.push('-');
|
|
arg.push(c);
|
|
return Err(Error::unknown_argument(&*arg,
|
|
"",
|
|
&*self.create_current_usage(matcher),
|
|
self.color()));
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
fn parse_opt(&self,
|
|
val: Option<&OsStr>,
|
|
opt: &OptBuilder<'a, 'b>,
|
|
matcher: &mut ArgMatcher<'a>)
|
|
-> ClapResult<Option<&'a str>> {
|
|
debugln!("fn=parse_opt;");
|
|
validate_multiples!(self, opt, matcher);
|
|
|
|
debug!("Checking for val...");
|
|
if let Some(fv) = val {
|
|
let v = fv.trim_left_matches(b'=');
|
|
if !opt.is_set(ArgSettings::EmptyValues) && v.len_() == 0 {
|
|
sdebugln!("Found Empty - Error");
|
|
return Err(Error::empty_value(opt, &*self.create_current_usage(matcher), self.color()));
|
|
}
|
|
sdebugln!("Found - {:?}, len: {}", v, v.len_());
|
|
try!(self.add_val_to_arg(opt, v, matcher));
|
|
} else {
|
|
sdebugln!("None");
|
|
}
|
|
|
|
matcher.inc_occurrence_of(opt.name);
|
|
// Increment or create the group "args"
|
|
self.groups_for_arg(opt.name).and_then(|vec| Some(matcher.inc_occurrences_of(&*vec)));
|
|
|
|
if val.is_none() || (opt.is_set(ArgSettings::Multiple) && matcher.needs_more_vals(opt)) {
|
|
return Ok(Some(opt.name));
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
fn add_val_to_arg<A>(&self,
|
|
arg: &A,
|
|
val: &OsStr,
|
|
matcher: &mut ArgMatcher<'a>)
|
|
-> ClapResult<Option<&'a str>>
|
|
where A: AnyArg<'a, 'b> + Display
|
|
{
|
|
debugln!("fn=add_val_to_arg;");
|
|
let mut ret = None;
|
|
if !(self.trailing_vals && self.is_set(AppSettings::DontDelimitTrailingValues)) {
|
|
if let Some(delim) = arg.val_delim() {
|
|
if val.is_empty_() {
|
|
ret = try!(self.add_single_val_to_arg(arg, val, matcher));
|
|
} else {
|
|
for v in val.split(delim as u32 as u8) {
|
|
ret = try!(self.add_single_val_to_arg(arg, v, matcher));
|
|
}
|
|
}
|
|
} else {
|
|
ret = try!(self.add_single_val_to_arg(arg, val, matcher));
|
|
}
|
|
} else {
|
|
ret = try!(self.add_single_val_to_arg(arg, val, matcher));
|
|
}
|
|
Ok(ret)
|
|
}
|
|
|
|
fn add_single_val_to_arg<A>(&self,
|
|
arg: &A,
|
|
v: &OsStr,
|
|
matcher: &mut ArgMatcher<'a>)
|
|
-> ClapResult<Option<&'a str>>
|
|
where A: AnyArg<'a, 'b> + Display
|
|
{
|
|
debugln!("adding val: {:?}", v);
|
|
matcher.add_val_to(arg.name(), v);
|
|
|
|
// Increment or create the group "args"
|
|
if let Some(grps) = self.groups_for_arg(arg.name()) {
|
|
for grp in grps {
|
|
matcher.add_val_to(&*grp, v);
|
|
}
|
|
}
|
|
|
|
// The validation must come AFTER inserting into 'matcher' or the usage string
|
|
// can't be built
|
|
self.validate_value(arg, v, matcher)
|
|
}
|
|
|
|
fn validate_value<A>(&self,
|
|
arg: &A,
|
|
val: &OsStr,
|
|
matcher: &ArgMatcher<'a>)
|
|
-> ClapResult<Option<&'a str>>
|
|
where A: AnyArg<'a, 'b> + Display
|
|
{
|
|
debugln!("fn=validate_value; val={:?}", val);
|
|
if self.is_set(AppSettings::StrictUtf8) && val.to_str().is_none() {
|
|
return Err(Error::invalid_utf8(&*self.create_current_usage(matcher), self.color()));
|
|
}
|
|
if let Some(ref p_vals) = arg.possible_vals() {
|
|
let val_str = val.to_string_lossy();
|
|
if !p_vals.contains(&&*val_str) {
|
|
return Err(Error::invalid_value(val_str,
|
|
p_vals,
|
|
arg,
|
|
&*self.create_current_usage(matcher),
|
|
self.color()));
|
|
}
|
|
}
|
|
if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty_() &&
|
|
matcher.contains(&*arg.name()) {
|
|
return Err(Error::empty_value(arg, &*self.create_current_usage(matcher), self.color()));
|
|
}
|
|
if let Some(ref vtor) = arg.validator() {
|
|
if let Err(e) = vtor(val.to_string_lossy().into_owned()) {
|
|
return Err(Error::value_validation(e, self.color()));
|
|
}
|
|
}
|
|
if matcher.needs_more_vals(arg) {
|
|
return Ok(Some(arg.name()));
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
fn parse_flag(&self,
|
|
flag: &FlagBuilder<'a, 'b>,
|
|
matcher: &mut ArgMatcher<'a>)
|
|
-> ClapResult<()> {
|
|
debugln!("fn=parse_flag;");
|
|
validate_multiples!(self, flag, matcher);
|
|
|
|
matcher.inc_occurrence_of(flag.name);
|
|
// Increment or create the group "args"
|
|
self.groups_for_arg(flag.name).and_then(|vec| Some(matcher.inc_occurrences_of(&*vec)));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> {
|
|
debugln!("fn=validate_blacklist;");
|
|
macro_rules! build_err {
|
|
($me:ident, $name:expr, $matcher:ident) => ({
|
|
debugln!("macro=build_err;");
|
|
let c_with = $me.blacklisted_from($name, &$matcher);
|
|
debugln!("'{:?}' conflicts with '{}'", c_with, $name);
|
|
$matcher.remove($name);
|
|
let usg = $me.create_current_usage($matcher);
|
|
if let Some(f) = $me.flags.iter().filter(|f| f.name == $name).next() {
|
|
debugln!("It was a flag...");
|
|
Error::argument_conflict(f, c_with, &*usg, self.color())
|
|
} else if let Some(o) = $me.opts.iter()
|
|
.filter(|o| o.name == $name)
|
|
.next() {
|
|
debugln!("It was an option...");
|
|
Error::argument_conflict(o, c_with, &*usg, self.color())
|
|
} else {
|
|
match $me.positionals.values()
|
|
.filter(|p| p.name == $name)
|
|
.next() {
|
|
Some(p) => {
|
|
debugln!("It was a positional...");
|
|
Error::argument_conflict(p, c_with, &*usg, self.color())
|
|
},
|
|
None => panic!(INTERNAL_ERROR_MSG)
|
|
}
|
|
}
|
|
});
|
|
}
|
|
for name in &self.blacklist {
|
|
debugln!("Checking blacklisted name: {}", name);
|
|
if self.groups.contains_key(name) {
|
|
debugln!("groups contains it...");
|
|
for n in self.arg_names_in_group(name) {
|
|
debugln!("Checking arg '{}' in group...", n);
|
|
if matcher.contains(n) {
|
|
debugln!("matcher contains it...");
|
|
return Err(build_err!(self, n, matcher));
|
|
}
|
|
}
|
|
} else if matcher.contains(name) {
|
|
debugln!("matcher contains it...");
|
|
return Err(build_err!(self, *name, matcher));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_num_args(&self, matcher: &mut ArgMatcher) -> ClapResult<()> {
|
|
debugln!("fn=validate_num_args;");
|
|
for (name, ma) in matcher.iter() {
|
|
if self.groups.contains_key(&**name) {
|
|
continue;
|
|
} else {
|
|
if let Some(opt) = self.opts
|
|
.iter()
|
|
.filter(|o| &o.name == name)
|
|
.next() {
|
|
try!(self._validate_num_vals(opt, ma, matcher));
|
|
} else if let Some(pos) = self.positionals
|
|
.values()
|
|
.filter(|p| &p.name == name)
|
|
.next() {
|
|
try!(self._validate_num_vals(pos, ma, matcher));
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn _validate_num_vals<A>(&self, a: &A, ma: &MatchedArg, matcher: &ArgMatcher) -> ClapResult<()>
|
|
where A: AnyArg<'a, 'b> + Display
|
|
{
|
|
debugln!("fn=_validate_num_vals;");
|
|
if let Some(num) = a.num_vals() {
|
|
debugln!("num_vals set: {}", num);
|
|
let should_err = if a.is_set(ArgSettings::Multiple) {
|
|
((ma.vals.len() as u64) % num) != 0
|
|
} else {
|
|
num != (ma.vals.len() as u64)
|
|
};
|
|
if should_err {
|
|
debugln!("Sending error WrongNumberOfValues");
|
|
return Err(Error::wrong_number_of_values(a,
|
|
num,
|
|
if a.is_set(ArgSettings::Multiple) {
|
|
(ma.vals.len() % num as usize)
|
|
} else {
|
|
ma.vals.len()
|
|
},
|
|
if ma.vals.len() == 1 ||
|
|
(a.is_set(ArgSettings::Multiple) &&
|
|
(ma.vals.len() % num as usize) ==
|
|
1) {
|
|
"as"
|
|
} else {
|
|
"ere"
|
|
},
|
|
&*self.create_current_usage(matcher),
|
|
self.color()));
|
|
}
|
|
}
|
|
if let Some(num) = a.max_vals() {
|
|
debugln!("max_vals set: {}", num);
|
|
if (ma.vals.len() as u64) > num {
|
|
debugln!("Sending error TooManyValues");
|
|
return Err(Error::too_many_values(ma.vals
|
|
.get(ma.vals
|
|
.keys()
|
|
.last()
|
|
.expect(INTERNAL_ERROR_MSG))
|
|
.expect(INTERNAL_ERROR_MSG)
|
|
.to_str()
|
|
.expect(INVALID_UTF8),
|
|
a,
|
|
&*self.create_current_usage(matcher),
|
|
self.color()));
|
|
}
|
|
}
|
|
if let Some(num) = a.min_vals() {
|
|
debugln!("min_vals set: {}", num);
|
|
if (ma.vals.len() as u64) < num {
|
|
debugln!("Sending error TooFewValues");
|
|
return Err(Error::too_few_values(a,
|
|
num,
|
|
ma.vals.len(),
|
|
&*self.create_current_usage(matcher),
|
|
self.color()));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_required(&self, matcher: &ArgMatcher) -> ClapResult<()> {
|
|
'outer: for name in &self.required {
|
|
if matcher.contains(name) {
|
|
continue 'outer;
|
|
}
|
|
if let Some(grp) = self.groups.get(name) {
|
|
for arg in &grp.args {
|
|
if matcher.contains(arg) {
|
|
continue 'outer;
|
|
}
|
|
}
|
|
}
|
|
if self.groups.values().any(|g| g.args.contains(name)) {
|
|
continue 'outer;
|
|
}
|
|
if let Some(a) = self.flags.iter().filter(|f| &f.name == name).next() {
|
|
if self.is_missing_required_ok(a, matcher) {
|
|
continue 'outer;
|
|
}
|
|
} else if let Some(a) = self.opts.iter().filter(|o| &o.name == name).next() {
|
|
if self.is_missing_required_ok(a, matcher) {
|
|
continue 'outer;
|
|
}
|
|
} else if let Some(a) = self.positionals.values().filter(|p| &p.name == name).next() {
|
|
if self.is_missing_required_ok(a, matcher) {
|
|
continue 'outer;
|
|
}
|
|
}
|
|
let err = if self.settings.is_set(AppSettings::ArgRequiredElseHelp) &&
|
|
matcher.is_empty() {
|
|
self._help().unwrap_err()
|
|
} else {
|
|
let mut reqs = self.required.iter().map(|&r| &*r).collect::<Vec<_>>();
|
|
reqs.dedup();
|
|
Error::missing_required_argument(
|
|
&*self.get_required_from(&*reqs, Some(matcher))
|
|
.iter()
|
|
.fold(String::new(),
|
|
|acc, s| acc + &format!("\n {}", Format::Error(s))[..]),
|
|
&*self.create_current_usage(matcher),
|
|
self.color())
|
|
};
|
|
return Err(err);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn is_missing_required_ok<A>(&self, a: &A, matcher: &ArgMatcher) -> bool
|
|
where A: AnyArg<'a, 'b>
|
|
{
|
|
if let Some(bl) = a.blacklist() {
|
|
for n in bl.iter() {
|
|
if matcher.contains(n) ||
|
|
self.groups
|
|
.get(n)
|
|
.map_or(false, |g| g.args.iter().any(|an| matcher.contains(an))) {
|
|
return true;
|
|
}
|
|
}
|
|
} else if let Some(ru) = a.required_unless() {
|
|
for n in ru.iter() {
|
|
if matcher.contains(n) ||
|
|
self.groups
|
|
.get(n)
|
|
.map_or(false, |g| g.args.iter().any(|an| matcher.contains(an))) {
|
|
if !a.is_set(ArgSettings::RequiredUnlessAll) {
|
|
return true;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
false
|
|
}
|
|
|
|
fn did_you_mean_error(&self, arg: &str, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> {
|
|
// Didn't match a flag or option...maybe it was a typo and close to one
|
|
let suffix =
|
|
suggestions::did_you_mean_suffix(arg,
|
|
self.long_list.iter(),
|
|
suggestions::DidYouMeanMessageStyle::LongFlag);
|
|
|
|
// Add the arg to the matches to build a proper usage string
|
|
if let Some(name) = suffix.1 {
|
|
if let Some(opt) = self.opts
|
|
.iter()
|
|
.filter(|o| o.long.is_some() && o.long.unwrap() == name)
|
|
.next() {
|
|
self.groups_for_arg(&*opt.name)
|
|
.and_then(|grps| Some(matcher.inc_occurrences_of(&*grps)));
|
|
matcher.insert(&*opt.name);
|
|
} else if let Some(flg) = self.flags
|
|
.iter()
|
|
.filter(|f| f.long.is_some() && f.long.unwrap() == name)
|
|
.next() {
|
|
self.groups_for_arg(&*flg.name)
|
|
.and_then(|grps| Some(matcher.inc_occurrences_of(&*grps)));
|
|
matcher.insert(&*flg.name);
|
|
}
|
|
}
|
|
|
|
let used_arg = format!("--{}", arg);
|
|
Err(Error::unknown_argument(&*used_arg, &*suffix.0, &*self.create_current_usage(matcher), self.color()))
|
|
}
|
|
|
|
// Creates a usage string if one was not provided by the user manually. This happens just
|
|
// after all arguments were parsed, but before any subcommands have been parsed
|
|
// (so as to give subcommands their own usage recursively)
|
|
pub fn create_usage(&self, used: &[&str]) -> String {
|
|
debugln!("fn=create_usage;");
|
|
let mut usage = String::with_capacity(75);
|
|
usage.push_str("USAGE:\n ");
|
|
usage.push_str(&self.create_usage_no_title(used));
|
|
usage
|
|
}
|
|
|
|
// Creates a usage string (*without title*) if one was not provided by the user
|
|
// manually. This happens just
|
|
// after all arguments were parsed, but before any subcommands have been parsed
|
|
// (so as to give subcommands their own usage recursively)
|
|
pub fn create_usage_no_title(&self, used: &[&str]) -> String {
|
|
debugln!("fn=create_usage_no_title;");
|
|
let mut usage = String::with_capacity(75);
|
|
if let Some(u) = self.meta.usage_str {
|
|
usage.push_str(&*u);
|
|
} else if used.is_empty() {
|
|
usage.push_str(&*self.meta
|
|
.usage
|
|
.as_ref()
|
|
.unwrap_or(self.meta
|
|
.bin_name
|
|
.as_ref()
|
|
.unwrap_or(&self.meta.name)));
|
|
let mut reqs: Vec<&str> = self.required().map(|r| &**r).collect();
|
|
reqs.dedup();
|
|
let req_string = self.get_required_from(&reqs, None)
|
|
.iter()
|
|
.fold(String::new(), |a, s| a + &format!(" {}", s)[..]);
|
|
|
|
if self.has_flags() && !self.is_set(AppSettings::UnifiedHelpMessage) {
|
|
usage.push_str(" [FLAGS]");
|
|
} else {
|
|
usage.push_str(" [OPTIONS]");
|
|
}
|
|
if !self.is_set(AppSettings::UnifiedHelpMessage) && self.has_opts() &&
|
|
self.opts.iter().any(|a| !a.settings.is_set(ArgSettings::Required)) {
|
|
usage.push_str(" [OPTIONS]");
|
|
}
|
|
|
|
usage.push_str(&req_string[..]);
|
|
|
|
// places a '--' in the usage string if there are args and options
|
|
// supporting multiple values
|
|
if self.has_positionals() &&
|
|
self.opts.iter().any(|a| a.settings.is_set(ArgSettings::Multiple)) &&
|
|
self.positionals.values().any(|a| !a.settings.is_set(ArgSettings::Required)) &&
|
|
!self.has_subcommands() {
|
|
usage.push_str(" [--]")
|
|
}
|
|
if self.has_positionals() &&
|
|
self.positionals.values().any(|a| !a.settings.is_set(ArgSettings::Required)) {
|
|
if self.positionals.len() == 1 {
|
|
let p = self.positionals.values().next().expect(INTERNAL_ERROR_MSG);
|
|
if !self.groups.values().any(|g| g.args.iter().any(|a| a == &p.name)) {
|
|
usage.push_str(&*format!(" [{}]{}", p.name_no_brackets(),
|
|
p.multiple_str()));
|
|
} else {
|
|
usage.push_str(" [ARGS]");
|
|
}
|
|
} else {
|
|
usage.push_str(" [ARGS]");
|
|
}
|
|
}
|
|
|
|
|
|
if self.has_subcommands() && !self.is_set(AppSettings::SubcommandRequired) {
|
|
usage.push_str(" [SUBCOMMAND]");
|
|
} else if self.is_set(AppSettings::SubcommandRequired) && self.has_subcommands() {
|
|
usage.push_str(" <SUBCOMMAND>");
|
|
}
|
|
} else {
|
|
self.smart_usage(&mut usage, used);
|
|
}
|
|
|
|
usage.shrink_to_fit();
|
|
usage
|
|
}
|
|
|
|
// Creates a context aware usage string, or "smart usage" from currently used
|
|
// args, and requirements
|
|
fn smart_usage(&self, usage: &mut String, used: &[&str]) {
|
|
debugln!("fn=smart_usage;");
|
|
let mut hs: Vec<&str> = self.required().map(|s| &**s).collect();
|
|
hs.extend_from_slice(used);
|
|
|
|
let r_string = self.get_required_from(&hs, None)
|
|
.iter()
|
|
.fold(String::new(), |acc, s| acc + &format!(" {}", s)[..]);
|
|
|
|
usage.push_str(&self.meta
|
|
.usage
|
|
.as_ref()
|
|
.unwrap_or(self.meta
|
|
.bin_name
|
|
.as_ref()
|
|
.unwrap_or(&self.meta
|
|
.name))[..]);
|
|
usage.push_str(&*r_string);
|
|
if self.is_set(AppSettings::SubcommandRequired) {
|
|
usage.push_str(" <SUBCOMMAND>");
|
|
}
|
|
}
|
|
|
|
// Prints the version to the user and exits if quit=true
|
|
fn print_version<W: Write>(&self, w: &mut W) -> ClapResult<()> {
|
|
try!(self.write_version(w));
|
|
w.flush().map_err(Error::from)
|
|
}
|
|
|
|
pub fn write_version<W: Write>(&self, w: &mut W) -> io::Result<()> {
|
|
if let Some(bn) = self.meta.bin_name.as_ref() {
|
|
if bn.contains(' ') {
|
|
// Incase we're dealing with subcommands i.e. git mv is translated to git-mv
|
|
write!(w,
|
|
"{} {}",
|
|
bn.replace(" ", "-"),
|
|
self.meta.version.unwrap_or("".into()))
|
|
} else {
|
|
write!(w,
|
|
"{} {}",
|
|
&self.meta.name[..],
|
|
self.meta.version.unwrap_or("".into()))
|
|
}
|
|
} else {
|
|
write!(w,
|
|
"{} {}",
|
|
&self.meta.name[..],
|
|
self.meta.version.unwrap_or("".into()))
|
|
}
|
|
}
|
|
|
|
pub fn print_help(&self) -> ClapResult<()> {
|
|
let out = io::stdout();
|
|
let mut buf_w = BufWriter::new(out.lock());
|
|
self.write_help(&mut buf_w)
|
|
}
|
|
|
|
pub fn write_help<W: Write>(&self, w: &mut W) -> ClapResult<()> {
|
|
Help::write_parser_help(w, self)
|
|
}
|
|
|
|
pub fn write_help_err<W: Write>(&self, w: &mut W) -> ClapResult<()> {
|
|
Help::write_parser_help_to_stderr(w, self)
|
|
}
|
|
|
|
fn add_defaults(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> {
|
|
macro_rules! add_val {
|
|
($_self:ident, $a:ident, $m:ident) => {
|
|
if $m.get($a.name).is_none() {
|
|
try!($_self.add_val_to_arg($a, OsStr::new($a.default_val
|
|
.as_ref()
|
|
.unwrap()), $m));
|
|
arg_post_processing!($_self, $a, $m);
|
|
}
|
|
};
|
|
}
|
|
for o in self.opts.iter().filter(|o| o.default_val.is_some()) {
|
|
add_val!(self, o, matcher);
|
|
}
|
|
for p in self.positionals.values().filter(|p| p.default_val.is_some()) {
|
|
add_val!(self, p, matcher);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn iter_flags(&self) -> Iter<FlagBuilder> {
|
|
self.flags.iter()
|
|
}
|
|
|
|
pub fn iter_opts(&self) -> Iter<OptBuilder> {
|
|
self.opts.iter()
|
|
}
|
|
|
|
pub fn iter_positionals(&self) -> vec_map::Values<PosBuilder> {
|
|
self.positionals.values()
|
|
}
|
|
|
|
// Should we color the output? None=determined by output location, true=yes, false=no
|
|
#[doc(hidden)]
|
|
pub fn color(&self) -> ColorWhen {
|
|
debugln!("exec=color;");
|
|
debug!("Color setting...");
|
|
if self.is_set(AppSettings::ColorNever) {
|
|
sdebugln!("Never");
|
|
ColorWhen::Never
|
|
} else if self.is_set(AppSettings::ColorAlways) {
|
|
sdebugln!("Always");
|
|
ColorWhen::Always
|
|
} else {
|
|
sdebugln!("Auto");
|
|
ColorWhen::Auto
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, 'b> Clone for Parser<'a, 'b>
|
|
where 'a: 'b
|
|
{
|
|
fn clone(&self) -> Self {
|
|
Parser {
|
|
required: self.required.clone(),
|
|
short_list: self.short_list.clone(),
|
|
long_list: self.long_list.clone(),
|
|
blacklist: self.blacklist.clone(),
|
|
flags: self.flags.clone(),
|
|
opts: self.opts.clone(),
|
|
positionals: self.positionals.clone(),
|
|
subcommands: self.subcommands.clone(),
|
|
groups: self.groups.clone(),
|
|
global_args: self.global_args.clone(),
|
|
overrides: self.overrides.clone(),
|
|
help_short: self.help_short,
|
|
version_short: self.version_short,
|
|
settings: self.settings.clone(),
|
|
g_settings: self.g_settings.clone(),
|
|
meta: self.meta.clone(),
|
|
trailing_vals: self.trailing_vals,
|
|
}
|
|
}
|
|
}
|