mirror of
https://github.com/clap-rs/clap
synced 2024-12-22 10:33:10 +00:00
eb1d79dbb1
Instead of using Format::Error directly, we need to use a Colorizer to make sure that the message only gets colorized when it is appropriate to do so. Fixes #775
2061 lines
81 KiB
Rust
2061 lines
81 KiB
Rust
// Std
|
|
use std::collections::{BTreeMap, HashMap, VecDeque};
|
|
use std::ffi::{OsStr, OsString};
|
|
use std::fmt::Display;
|
|
use std::fs::File;
|
|
use std::io::{self, BufWriter, Write};
|
|
#[cfg(feature = "debug")]
|
|
use std::os::unix::ffi::OsStrExt;
|
|
use std::path::PathBuf;
|
|
use std::slice::Iter;
|
|
use std::iter::Peekable;
|
|
|
|
// Third Party
|
|
use vec_map::{self, VecMap};
|
|
|
|
// Internal
|
|
use INTERNAL_ERROR_MSG;
|
|
use INVALID_UTF8;
|
|
use SubCommand;
|
|
use app::App;
|
|
use app::help::Help;
|
|
use app::meta::AppMeta;
|
|
use app::settings::{AppFlags, AppSettings};
|
|
use args::{AnyArg, ArgMatcher, Base, Switched, Arg, ArgGroup, FlagBuilder, OptBuilder, PosBuilder};
|
|
use args::MatchedArg;
|
|
use args::settings::ArgSettings;
|
|
use completions::ComplGen;
|
|
use errors::{Error, ErrorKind};
|
|
use errors::Result as ClapResult;
|
|
use fmt::{Colorizer, ColorWhen};
|
|
use osstringext::OsStrExt2;
|
|
use completions::Shell;
|
|
use suggestions;
|
|
|
|
#[allow(missing_debug_implementations)]
|
|
#[doc(hidden)]
|
|
pub struct Parser<'a, 'b>
|
|
where 'a: 'b
|
|
{
|
|
required: Vec<&'b str>,
|
|
pub short_list: Vec<char>,
|
|
pub long_list: Vec<&'b str>,
|
|
blacklist: Vec<&'b str>,
|
|
// A list of possible flags
|
|
pub flags: Vec<FlagBuilder<'a, 'b>>,
|
|
// A list of possible options
|
|
pub opts: Vec<OptBuilder<'a, 'b>>,
|
|
// A list of positional arguments
|
|
pub 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,
|
|
pub id: usize,
|
|
valid_neg_num: 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,
|
|
id: 0,
|
|
valid_neg_num: 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);
|
|
}
|
|
|
|
pub fn gen_completions_to<W: Write>(&mut self, for_shell: Shell, buf: &mut W) {
|
|
|
|
self.propogate_help_version();
|
|
self.build_bin_names();
|
|
|
|
ComplGen::new(self).generate(for_shell, buf)
|
|
}
|
|
|
|
pub fn gen_completions(&mut self, for_shell: Shell, od: OsString) {
|
|
use std::error::Error;
|
|
|
|
let out_dir = PathBuf::from(od);
|
|
let name = &*self.meta.bin_name.as_ref().unwrap().clone();
|
|
let file_name = match for_shell {
|
|
Shell::Bash => format!("{}.bash-completion", name),
|
|
Shell::Fish => format!("{}.fish", name),
|
|
Shell::Zsh => format!("_{}", name),
|
|
Shell::PowerShell => format!("_{}.ps1", name),
|
|
};
|
|
|
|
let mut file = match File::create(out_dir.join(file_name)) {
|
|
Err(why) => panic!("couldn't create completion file: {}", why.description()),
|
|
Ok(file) => file,
|
|
};
|
|
self.gen_completions_to(for_shell, &mut file)
|
|
}
|
|
|
|
// actually adds the arguments
|
|
pub fn add_arg(&mut self, a: &Arg<'a, 'b>) {
|
|
debug_assert!(!(self.flags.iter().any(|f| &f.b.name == &a.name) ||
|
|
self.opts.iter().any(|o| o.b.name == a.name) ||
|
|
self.positionals.values().any(|p| p.b.name == a.name)),
|
|
format!("Non-unique argument name: {} is already in use", a.name));
|
|
if let Some(ref grps) = a.groups {
|
|
for g in grps {
|
|
let ag = self.groups.entry(g).or_insert_with(|| ArgGroup::with_name(g));
|
|
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);
|
|
let id = self.opts.len();
|
|
ob.b.id = id;
|
|
ob.s.unified_ord = self.flags.len() + self.opts.len();
|
|
self.opts.insert(id, ob);
|
|
} else {
|
|
let mut fb = FlagBuilder::from(a);
|
|
let id = self.flags.len();
|
|
fb.b.id = id;
|
|
fb.s.unified_ord = self.flags.len() + self.opts.len();
|
|
self.flags.insert(id, 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;");
|
|
debugln!("Term width...{:?}", self.meta.term_w);
|
|
subcmd.p.meta.term_w = self.meta.term_w;
|
|
debug!("Is help...");
|
|
if subcmd.p.meta.name == "help" {
|
|
sdebugln!("Yes");
|
|
self.settings.set(AppSettings::NeedsSubcommandHelp);
|
|
} else {
|
|
sdebugln!("No");
|
|
}
|
|
|
|
self.subcommands.push(subcmd);
|
|
}
|
|
|
|
pub fn propogate_settings(&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
|
|
{
|
|
let vsc = self.settings.is_set(AppSettings::VersionlessSubcommands);
|
|
let gv = self.settings.is_set(AppSettings::GlobalVersion);
|
|
|
|
debugln!("VersionlessSubcommands set...{:?}", vsc);
|
|
debugln!("GlobalVersion set...{:?}", gv);
|
|
|
|
if vsc {
|
|
sc.p.settings.set(AppSettings::DisableVersion);
|
|
}
|
|
if gv && sc.p.meta.version.is_none() && self.meta.version.is_some() {
|
|
sc.p.set(AppSettings::GlobalVersion);
|
|
sc.p.meta.version = Some(self.meta.version.unwrap());
|
|
}
|
|
for s in &self.g_settings {
|
|
sc.p.set(*s);
|
|
sc.p.g_settings.push(*s);
|
|
}
|
|
}
|
|
sc.p.propogate_settings();
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(feature = "lints", allow(needless_borrow))]
|
|
pub fn derive_display_order(&mut self) {
|
|
if self.settings.is_set(AppSettings::DeriveDisplayOrder) {
|
|
let unified = self.settings.is_set(AppSettings::UnifiedHelpMessage);
|
|
for (i, o) in self.opts
|
|
.iter_mut()
|
|
.enumerate()
|
|
.filter(|&(_, ref o)| o.s.disp_ord == 999) {
|
|
o.s.disp_ord = if unified { o.s.unified_ord } else { i };
|
|
}
|
|
for (i, f) in self.flags
|
|
.iter_mut()
|
|
.enumerate()
|
|
.filter(|&(_, ref f)| f.s.disp_ord == 999) {
|
|
f.s.disp_ord = if unified { f.s.unified_ord } else { i };
|
|
}
|
|
for (i, sc) in &mut self.subcommands
|
|
.iter_mut()
|
|
.enumerate()
|
|
.filter(|&(_, ref sc)| sc.p.meta.disp_ord == 999) {
|
|
sc.p.meta.disp_ord = i;
|
|
}
|
|
}
|
|
for sc in &mut self.subcommands {
|
|
sc.p.derive_display_order();
|
|
}
|
|
}
|
|
|
|
pub fn required(&self) -> Iter<&str> { self.required.iter() }
|
|
|
|
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.b.name == name) {
|
|
c_flags.push(name);
|
|
} else if self.opts.iter().any(|o| &o.b.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.b.name == a).next() {
|
|
if let Some(ref rl) = a.b.requires {
|
|
for r in rl {
|
|
if !reqs.contains(r) {
|
|
if $_self.$t1.$i1().any(|t| &t.b.name == r) {
|
|
$tmp.push(*r);
|
|
} else if $_self.$t2.$i2().any(|t| &t.b.name == r) {
|
|
$v2.push(r);
|
|
} else if $_self.$t3.$i3().any(|t| &t.b.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 args_in_groups = grps.iter()
|
|
.flat_map(|g| self.arg_names_in_group(g))
|
|
.collect::<Vec<_>>();
|
|
|
|
let pmap = c_pos.into_iter()
|
|
.filter(|&p| matcher.is_none() || !matcher.as_ref().unwrap().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
|
|
debugln!("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);
|
|
}
|
|
}
|
|
macro_rules! write_arg {
|
|
($i:expr, $m:ident, $v:ident, $r:ident, $aig:ident) => {
|
|
for f in $v {
|
|
if $m.is_some() && $m.as_ref().unwrap().contains(f) || $aig.contains(&f) {
|
|
continue;
|
|
}
|
|
$r.push_back($i.filter(|flg| &flg.b.name == &f).next().unwrap().to_string());
|
|
}
|
|
}
|
|
}
|
|
write_arg!(self.flags.iter(), matcher, c_flags, ret_val, args_in_groups);
|
|
write_arg!(self.opts.iter(), matcher, c_opt, ret_val, args_in_groups);
|
|
let mut g_vec = vec![];
|
|
for g in grps {
|
|
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 {
|
|
ret_val.push_back(g);
|
|
}
|
|
|
|
ret_val
|
|
}
|
|
|
|
pub fn get_args_tag(&self) -> Option<String> {
|
|
let mut count = 0;
|
|
'outer: for p in self.positionals.values().filter(|p| !p.is_set(ArgSettings::Required)) {
|
|
if let Some(g_vec) = self.groups_for_arg(p.b.name) {
|
|
for grp_s in &g_vec {
|
|
debugln!("iter;grp_s={};", grp_s);
|
|
if let Some(grp) = self.groups.get(grp_s) {
|
|
debug!("Is group required...");
|
|
if grp.required {
|
|
sdebugln!("Yes (continuing)");
|
|
continue 'outer;
|
|
} else {
|
|
sdebugln!("No (breaking)");
|
|
}
|
|
}
|
|
}
|
|
debugln!("Arg not required...");
|
|
count += 1;
|
|
} else {
|
|
debugln!("Arg not required...");
|
|
count += 1;
|
|
}
|
|
}
|
|
if count > 1 || self.positionals.len() > 1 {
|
|
return None;
|
|
} else if count == 1 {
|
|
let p = self.positionals.values().next().expect(INTERNAL_ERROR_MSG);
|
|
return Some(format!(" [{}]{}", p.name_no_brackets(), p.multiple_str()));
|
|
}
|
|
Some("".into())
|
|
}
|
|
|
|
pub fn needs_flags_tag(&self) -> bool {
|
|
debugln!("fn=needs_flags_tag;");
|
|
'outer: for f in self.flags.iter() {
|
|
debugln!("iter;f={};", f.b.name);
|
|
if let Some(l) = f.s.long {
|
|
if l == "help" || l == "version" {
|
|
continue;
|
|
}
|
|
}
|
|
if let Some(g_vec) = self.groups_for_arg(f.b.name) {
|
|
for grp_s in &g_vec {
|
|
debugln!("iter;grp_s={};", grp_s);
|
|
if let Some(grp) = self.groups.get(grp_s) {
|
|
debug!("Is group required...");
|
|
if grp.required {
|
|
sdebugln!("Yes (continuing)");
|
|
continue 'outer;
|
|
} else {
|
|
sdebugln!("No (breaking)");
|
|
}
|
|
}
|
|
}
|
|
debugln!("Flag not required...(returning true)");
|
|
return true;
|
|
} else {
|
|
debugln!("Flag not required...(returning true)");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
#[inline]
|
|
pub fn has_opts(&self) -> bool { !self.opts.is_empty() }
|
|
|
|
#[inline]
|
|
pub fn has_flags(&self) -> bool { !self.flags.is_empty() }
|
|
|
|
#[inline]
|
|
pub fn has_positionals(&self) -> bool { !self.positionals.is_empty() }
|
|
|
|
#[inline]
|
|
pub fn has_subcommands(&self) -> bool { !self.subcommands.is_empty() }
|
|
|
|
#[inline]
|
|
pub fn is_set(&self, s: AppSettings) -> bool { self.settings.is_set(s) }
|
|
|
|
#[inline]
|
|
pub fn set(&mut self, s: AppSettings) { self.settings.set(s) }
|
|
|
|
#[inline]
|
|
pub fn unset(&mut self, s: AppSettings) { self.settings.unset(s) }
|
|
|
|
#[cfg_attr(feature = "lints", allow(block_in_if_condition_stmt))]
|
|
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, 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.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.b.settings.is_set(ArgSettings::Multiple) &&
|
|
(a.index as usize != self.positionals.len())
|
|
}) {
|
|
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 command");
|
|
|
|
debug_assert!(self.positionals.values()
|
|
.rev()
|
|
.next()
|
|
.unwrap()
|
|
.is_set(ArgSettings::Required),
|
|
"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 argument may be set to .multiple(true)");
|
|
|
|
self.set(AppSettings::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 command");
|
|
|
|
// 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.b.settings.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) {
|
|
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();
|
|
}
|
|
}
|
|
|
|
// Checks if the arg matches a subcommand name, or any of it's aliases (if defined)
|
|
#[inline]
|
|
fn possible_subcommand(&self, arg_os: &OsStr) -> bool {
|
|
debugln!("fn=possible_subcommand");
|
|
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))
|
|
})
|
|
}
|
|
|
|
fn parse_help_subcommand<I, T>(&self, it: &mut I) -> ClapResult<()>
|
|
where I: Iterator<Item = T>,
|
|
T: Into<OsString>
|
|
{
|
|
debugln!("fn=parse_help_subcommand;");
|
|
let cmds: Vec<OsString> = it.map(|c| c.into()).collect();
|
|
let mut help_help = false;
|
|
let mut bin_name = self.meta
|
|
.bin_name
|
|
.as_ref()
|
|
.unwrap_or(&self.meta.name)
|
|
.clone();
|
|
let mut sc = {
|
|
let mut sc: &Parser = self;
|
|
for (i, cmd) in cmds.iter().enumerate() {
|
|
if &*cmd.to_string_lossy() == "help" {
|
|
// cmd help help
|
|
help_help = true;
|
|
}
|
|
if let Some(c) = sc.subcommands
|
|
.iter()
|
|
.find(|s| &*s.p.meta.name == cmd)
|
|
.map(|sc| &sc.p) {
|
|
sc = c;
|
|
if i == cmds.len() - 1 {
|
|
break;
|
|
}
|
|
} else if let Some(c) = sc.subcommands
|
|
.iter()
|
|
.find(|s| if let Some(ref als) = s.p
|
|
.meta
|
|
.aliases {
|
|
als.iter()
|
|
.any(|&(a, _)| &a == &&*cmd.to_string_lossy())
|
|
} else {
|
|
false
|
|
})
|
|
.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()));
|
|
}
|
|
bin_name = format!("{} {}",
|
|
bin_name,
|
|
&*sc.meta.name);
|
|
}
|
|
sc.clone()
|
|
};
|
|
if help_help {
|
|
let mut pb = PosBuilder::new("subcommand", 1);
|
|
pb.b.help = Some("The subcommand whose help message to display");
|
|
pb.set(ArgSettings::Multiple);
|
|
sc.positionals.insert(1, pb);
|
|
for s in self.g_settings.clone() {
|
|
sc.set(s);
|
|
}
|
|
} else {
|
|
sc.create_help_and_version();
|
|
}
|
|
if sc.meta.bin_name != self.meta.bin_name {
|
|
sc.meta.bin_name = Some(format!("{} {}", bin_name, sc.meta.name));
|
|
}
|
|
sc._help()
|
|
}
|
|
|
|
#[inline]
|
|
fn check_is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: Option<&'a str>) -> bool {
|
|
debugln!("fn=check_is_new_arg;nvo={:?}", needs_val_of);
|
|
let app_wide_settings = if self.is_set(AppSettings::AllowLeadingHyphen) {
|
|
true
|
|
} else if self.is_set(AppSettings::AllowNegativeNumbers) {
|
|
let a = arg_os.to_string_lossy();
|
|
if a.parse::<i64>().is_ok() || a.parse::<f64>().is_ok() {
|
|
self.valid_neg_num = true;
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
} else {
|
|
false
|
|
};
|
|
let arg_allows_tac = if let Some(name) = needs_val_of {
|
|
if let Some(o) = find_by_name!(self, &name, opts, iter) {
|
|
!(o.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings)
|
|
} else if let Some(p) = find_by_name!(self, &name, opts, iter) {
|
|
!(p.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings)
|
|
} else {
|
|
true
|
|
}
|
|
} else {
|
|
true
|
|
};
|
|
debugln!("Does arg allow leading hyphen...{:?}", !arg_allows_tac);
|
|
|
|
// Is this a new argument, or values from a previous option?
|
|
debug!("Starts new arg...");
|
|
let mut ret = if arg_os.starts_with(b"--") {
|
|
sdebugln!("--");
|
|
if arg_os.len_() == 2 {
|
|
return true; // We have to return true so override everything else
|
|
}
|
|
true
|
|
} else if arg_os.starts_with(b"-") {
|
|
sdebugln!("-");
|
|
// a singe '-' by itself is a value and typically means "stdin" on unix systems
|
|
!(arg_os.len_() == 1)
|
|
} else {
|
|
sdebugln!("Probable value");
|
|
false
|
|
};
|
|
|
|
ret = ret && arg_allows_tac;
|
|
|
|
debugln!("Starts new arg...{:?}", ret);
|
|
ret
|
|
}
|
|
|
|
// 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 Peekable<I>)
|
|
-> ClapResult<()>
|
|
where I: Iterator<Item = T>,
|
|
T: Into<OsString> + Clone
|
|
{
|
|
debugln!("fn=get_matches_with;");
|
|
// Verify all positional assertions pass
|
|
self.verify_positionals();
|
|
|
|
// Next 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<&'a 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());
|
|
|
|
self.valid_neg_num = false;
|
|
// Is this a new argument, or values from a previous option?
|
|
let starts_new_arg = self.check_is_new_arg(&arg_os, needs_val_of);
|
|
|
|
// Has the user already passed '--'? Meaning only positional args follow
|
|
if !self.trailing_vals {
|
|
// Does the arg match a subcommand name, or any of it's aliases (if defined)
|
|
let pos_sc = self.possible_subcommand(&arg_os);
|
|
|
|
// If the arg doesn't start with a `-` (except numbers, or AllowLeadingHyphen) and
|
|
// isn't a subcommand
|
|
if !starts_new_arg && !pos_sc {
|
|
// Check to see if parsing a value from an option
|
|
if let Some(name) = needs_val_of {
|
|
// get the OptBuilder so we can check the settings
|
|
if let Some(arg) = find_by_name!(self, &name, opts, iter) {
|
|
needs_val_of = try!(self.add_val_to_arg(&*arg, &arg_os, matcher));
|
|
// get the next value from the iterator
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if arg_os.starts_with(b"--") {
|
|
if arg_os.len_() == 2 {
|
|
// The user has passed '--' which means only positional args follow no
|
|
// matter what they start with
|
|
self.trailing_vals = true;
|
|
continue;
|
|
}
|
|
|
|
needs_val_of = try!(self.parse_long_arg(matcher, &arg_os));
|
|
if !(needs_val_of.is_none() && self.is_set(AppSettings::AllowLeadingHyphen)) {
|
|
continue;
|
|
}
|
|
} else if arg_os.starts_with(b"-") && arg_os.len_() != 1 {
|
|
// Try to parse short args like normal, if AllowLeadingHyphen or
|
|
// AllowNegativeNumbers is set, parse_short_arg will *not* throw
|
|
// an error, and instead return Ok(None)
|
|
needs_val_of = try!(self.parse_short_arg(matcher, &arg_os));
|
|
// If it's None, we then check if one of those two AppSettings was set
|
|
if needs_val_of.is_none() {
|
|
if self.is_set(AppSettings::AllowNegativeNumbers) {
|
|
if !(arg_os.to_string_lossy().parse::<i64>().is_ok() ||
|
|
arg_os.to_string_lossy().parse::<f64>().is_ok()) {
|
|
return Err(Error::unknown_argument(&*arg_os.to_string_lossy(),
|
|
"",
|
|
&*self.create_current_usage(matcher),
|
|
self.color()));
|
|
}
|
|
} else if !self.is_set(AppSettings::AllowLeadingHyphen) {
|
|
continue;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if pos_sc {
|
|
if &*arg_os == "help" && self.is_set(AppSettings::NeedsSubcommandHelp) {
|
|
try!(self.parse_help_subcommand(it));
|
|
}
|
|
subcmd_name = Some(arg_os.to_str().expect(INVALID_UTF8).to_owned());
|
|
break;
|
|
} else if 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()));
|
|
}
|
|
}
|
|
|
|
debugln!("Positional counter...{}", pos_counter);
|
|
debug!("Checking for low index multiples...");
|
|
if self.is_set(AppSettings::LowIndexMultiplePositional) &&
|
|
pos_counter == (self.positionals.len() - 1) {
|
|
sdebugln!("Found");
|
|
if let Some(na) = it.peek() {
|
|
let n = (*na).clone().into();
|
|
if self.check_is_new_arg(&n, needs_val_of) || self.possible_subcommand(&n) ||
|
|
suggestions::did_you_mean(&n.to_string_lossy(),
|
|
self.subcommands
|
|
.iter()
|
|
.map(|s| &s.p.meta.name))
|
|
.is_some() {
|
|
debugln!("Bumping the positional counter...");
|
|
pos_counter += 1;
|
|
}
|
|
} else {
|
|
debugln!("Bumping the positional counter...");
|
|
pos_counter += 1;
|
|
}
|
|
} else {
|
|
sdebugln!("None");
|
|
}
|
|
if let Some(p) = self.positionals.get(pos_counter) {
|
|
parse_positional!(self, p, arg_os, pos_counter, matcher);
|
|
} else if self.settings.is_set(AppSettings::AllowExternalSubcommands) {
|
|
// Get external subcommand name
|
|
let sc_name = match arg_os.to_str() {
|
|
Some(s) => s.to_string(),
|
|
None => {
|
|
if !self.settings.is_set(AppSettings::StrictUtf8) {
|
|
return Err(Error::invalid_utf8(&*self.create_current_usage(matcher),
|
|
self.color()));
|
|
}
|
|
arg_os.to_string_lossy().into_owned()
|
|
}
|
|
};
|
|
|
|
// Collect the external subcommand args
|
|
let mut sc_m = ArgMatcher::new();
|
|
while let Some(v) = it.next() {
|
|
let a = v.into();
|
|
if a.to_str().is_none() && !self.settings.is_set(AppSettings::StrictUtf8) {
|
|
return Err(Error::invalid_utf8(&*self.create_current_usage(matcher),
|
|
self.color()));
|
|
}
|
|
sc_m.add_val_to("", &a);
|
|
}
|
|
|
|
matcher.subcommand(SubCommand {
|
|
name: sc_name,
|
|
matches: sc_m.into(),
|
|
});
|
|
} else if !(self.is_set(AppSettings::AllowLeadingHyphen) ||
|
|
self.is_set(AppSettings::AllowNegativeNumbers)) {
|
|
return Err(Error::unknown_argument(&*arg_os.to_string_lossy(),
|
|
"",
|
|
&*self.create_current_usage(matcher),
|
|
self.color()));
|
|
}
|
|
}
|
|
|
|
self.validate(needs_val_of, subcmd_name, matcher, it)
|
|
}
|
|
|
|
fn validate<I, T>(&mut self,
|
|
needs_val_of: Option<&'a str>,
|
|
subcmd_name: Option<String>,
|
|
matcher: &mut ArgMatcher<'a>,
|
|
it: &mut Peekable<I>)
|
|
-> ClapResult<()>
|
|
where I: Iterator<Item = T>,
|
|
T: Into<OsString> + Clone
|
|
{
|
|
let mut reqs_validated = false;
|
|
if let Some(a) = needs_val_of {
|
|
debugln!("needs_val_of={:?}", a);
|
|
let o = find_by_name!(self, &a, opts, iter).expect(INTERNAL_ERROR_MSG);
|
|
try!(self.validate_required(matcher));
|
|
reqs_validated = true;
|
|
let should_err = if let Some(v) = matcher.0.args.get(&*o.b.name) {
|
|
v.vals.is_empty() && !(o.v.min_vals.is_some() && o.v.min_vals.unwrap() == 0)
|
|
} else {
|
|
true
|
|
};
|
|
if should_err {
|
|
return Err(Error::empty_value(o,
|
|
&*self.create_current_usage(matcher),
|
|
self.color()));
|
|
}
|
|
}
|
|
|
|
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 propogate_help_version(&mut self) {
|
|
debugln!("fn=propogate_help_version;");
|
|
self.create_help_and_version();
|
|
for sc in &mut self.subcommands {
|
|
sc.p.propogate_help_version();
|
|
}
|
|
}
|
|
|
|
fn build_bin_names(&mut self) {
|
|
debugln!("fn=build_bin_names;");
|
|
for sc in &mut self.subcommands {
|
|
debug!("bin_name set...");
|
|
if sc.p.meta.bin_name.is_none() {
|
|
sdebugln!("No");
|
|
let bin_name = format!("{}{}{}",
|
|
self.meta
|
|
.bin_name
|
|
.as_ref()
|
|
.unwrap_or(&self.meta.name.clone()),
|
|
if self.meta.bin_name.is_some() {
|
|
" "
|
|
} else {
|
|
""
|
|
},
|
|
&*sc.p.meta.name);
|
|
debugln!("Setting bin_name of {} to {}", self.meta.name, bin_name);
|
|
sc.p.meta.bin_name = Some(bin_name);
|
|
} else {
|
|
sdebugln!("yes ({:?})", sc.p.meta.bin_name);
|
|
}
|
|
debugln!("Calling build_bin_names from...{}", sc.p.meta.name);
|
|
sc.p.build_bin_names();
|
|
}
|
|
}
|
|
|
|
fn parse_subcommand<I, T>(&mut self,
|
|
sc_name: String,
|
|
matcher: &mut ArgMatcher<'a>,
|
|
it: &mut Peekable<I>)
|
|
-> ClapResult<()>
|
|
where I: Iterator<Item = T>,
|
|
T: Into<OsString> + Clone
|
|
{
|
|
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()
|
|
.find(|s| &s.p.meta.name == &sc_name) {
|
|
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 groups_for_arg(&self, name: &str) -> Option<Vec<&'a str>> {
|
|
debugln!("fn=groups_for_arg; name={}", name);
|
|
|
|
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[group].args {
|
|
if let Some(f) = self.flags.iter().find(|f| &f.b.name == n) {
|
|
args.push(f.to_string());
|
|
} else if let Some(f) = self.opts.iter().find(|o| &o.b.name == n) {
|
|
args.push(f.to_string());
|
|
} else if self.groups.contains_key(&**n) {
|
|
g_vec.push(*n);
|
|
} else if let Some(p) = self.positionals
|
|
.values()
|
|
.find(|p| &p.b.name == n) {
|
|
args.push(p.b.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[group].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()
|
|
}
|
|
|
|
pub 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.is_set(AppSettings::NeedsLongHelp) {
|
|
debugln!("Building --help");
|
|
if self.help_short.is_none() && !self.short_list.contains(&'h') {
|
|
self.help_short = Some('h');
|
|
}
|
|
let id = self.flags.len();
|
|
let arg = FlagBuilder {
|
|
b: Base {
|
|
name: "hclap_help",
|
|
help: Some("Prints help information"),
|
|
id: id,
|
|
..Default::default()
|
|
},
|
|
s: Switched {
|
|
short: self.help_short,
|
|
long: Some("help"),
|
|
..Default::default()
|
|
},
|
|
};
|
|
if let Some(h) = self.help_short {
|
|
self.short_list.push(h);
|
|
}
|
|
self.long_list.push("help");
|
|
self.flags.insert(id, arg);
|
|
}
|
|
if !self.settings.is_set(AppSettings::DisableVersion) &&
|
|
self.is_set(AppSettings::NeedsLongVersion) {
|
|
debugln!("Building --version");
|
|
if self.version_short.is_none() && !self.short_list.contains(&'V') {
|
|
self.version_short = Some('V');
|
|
}
|
|
let id = self.flags.len();
|
|
// name is "vclap_version" because flags are sorted by name
|
|
let arg = FlagBuilder {
|
|
b: Base {
|
|
name: "vclap_version",
|
|
help: Some("Prints version information"),
|
|
id: id,
|
|
..Default::default()
|
|
},
|
|
s: Switched {
|
|
short: self.version_short,
|
|
long: Some("version"),
|
|
..Default::default()
|
|
},
|
|
};
|
|
if let Some(v) = self.version_short {
|
|
self.short_list.push(v);
|
|
}
|
|
self.long_list.push("version");
|
|
self.flags.insert(id, arg);
|
|
}
|
|
if !self.subcommands.is_empty() && self.is_set(AppSettings::NeedsSubcommandHelp) {
|
|
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) = find_by_name!(self, *n, opts, iter) {
|
|
!o.b.settings.is_set(ArgSettings::Required)
|
|
} else if let Some(p) = find_by_name!(self, *n, positionals, values) {
|
|
!p.b.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 {
|
|
if arg == h && self.settings.is_set(AppSettings::NeedsLongHelp) {
|
|
sdebugln!("Help");
|
|
try!(self._help());
|
|
}
|
|
}
|
|
if let Some(v) = self.version_short {
|
|
if arg == v && self.settings.is_set(AppSettings::NeedsLongVersion) {
|
|
sdebugln!("Version");
|
|
try!(self._version());
|
|
}
|
|
}
|
|
sdebugln!("Neither");
|
|
Ok(())
|
|
}
|
|
|
|
fn _help(&self) -> ClapResult<()> {
|
|
let mut buf = vec![];
|
|
try!(Help::write_parser_help(&mut buf, self));
|
|
Err(Error {
|
|
message: unsafe { String::from_utf8_unchecked(buf) },
|
|
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<&'a 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) = find_by_long!(self, &arg, opts) {
|
|
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) = find_by_long!(self, &arg, flags) {
|
|
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);
|
|
} else if self.is_set(AppSettings::AllowLeadingHyphen) {
|
|
return Ok(None);
|
|
} else if self.valid_neg_num {
|
|
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();
|
|
|
|
// If AllowLeadingHyphen is set, we want to ensure `-val` gets parsed as `-val` and not `-v` `-a` `-l` assuming
|
|
// `v` `a` and `l` are all, or mostly, valid shorts.
|
|
if self.is_set(AppSettings::AllowLeadingHyphen) {
|
|
let mut good = true;
|
|
for c in arg.chars() {
|
|
good = self.short_list.contains(&c);
|
|
}
|
|
if !good {
|
|
return Ok(None);
|
|
}
|
|
} else if self.valid_neg_num {
|
|
// TODO: Add docs about having AllowNegativeNumbers and `-2` as a valid short
|
|
// May be better to move this to *after* not finding a valid flag/opt?
|
|
debugln!("Valid negative num...");
|
|
return Ok(None);
|
|
}
|
|
|
|
for c in arg.chars() {
|
|
debugln!("iter;short={}", c);
|
|
// 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()
|
|
.find(|&o| o.s.short.is_some() && o.s.short.unwrap() == c) {
|
|
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()
|
|
.find(|&f| f.s.short.is_some() && f.s.short.unwrap() == c) {
|
|
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 {
|
|
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);
|
|
let mut has_eq = false;
|
|
|
|
debug!("Checking for val...");
|
|
if let Some(fv) = val {
|
|
has_eq = fv.starts_with(&[b'=']);
|
|
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_());
|
|
debugln!("{:?} contains '='...{:?}", fv, fv.starts_with(&[b'=']));
|
|
try!(self.add_val_to_arg(opt, v, matcher));
|
|
} else {
|
|
sdebugln!("None");
|
|
}
|
|
|
|
matcher.inc_occurrence_of(opt.b.name);
|
|
// Increment or create the group "args"
|
|
self.groups_for_arg(opt.b.name).and_then(|vec| Some(matcher.inc_occurrences_of(&*vec)));
|
|
|
|
if val.is_none() ||
|
|
!has_eq &&
|
|
(opt.is_set(ArgSettings::Multiple) && !opt.is_set(ArgSettings::RequireDelimiter) &&
|
|
matcher.needs_more_vals(opt)) {
|
|
debugln!("More arg vals required...");
|
|
return Ok(Some(opt.b.name));
|
|
}
|
|
debugln!("More arg vals not required...");
|
|
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));
|
|
}
|
|
// If there was a delimiter used, we're not looking for more values
|
|
if val.contains_byte(delim as u32 as u8) ||
|
|
arg.is_set(ArgSettings::RequireDelimiter) {
|
|
ret = None;
|
|
}
|
|
}
|
|
} 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(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(vtor) = arg.validator() {
|
|
if let Err(e) = vtor(val.to_string_lossy().into_owned()) {
|
|
return Err(Error::value_validation(Some(arg), 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.b.name);
|
|
// Increment or create the group "args"
|
|
self.groups_for_arg(flag.b.name).and_then(|vec| Some(matcher.inc_occurrences_of(&*vec)));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> {
|
|
debugln!("fn=validate_blacklist;blacklist={:?}", self.blacklist);
|
|
macro_rules! build_err {
|
|
($me:ident, $name:expr, $matcher:ident) => ({
|
|
debugln!("macro=build_err;name={}", $name);
|
|
let mut c_with = find_from!($me, $name, blacklist, &$matcher);
|
|
c_with = c_with.or(
|
|
$me.find_any_arg($name).map_or(None, |aa| aa.blacklist())
|
|
.map_or(None, |bl| bl.iter().find(|arg| $matcher.contains(arg)))
|
|
.map_or(None, |an| $me.find_any_arg(an))
|
|
.map_or(None, |aa| Some(format!("{}", aa)))
|
|
);
|
|
debugln!("'{:?}' conflicts with '{}'", c_with, $name);
|
|
$matcher.remove($name);
|
|
let usg = $me.create_current_usage($matcher);
|
|
if let Some(f) = find_by_name!($me, $name, flags, iter) {
|
|
debugln!("It was a flag...");
|
|
Error::argument_conflict(f, c_with, &*usg, self.color())
|
|
} else if let Some(o) = find_by_name!($me, $name, opts, iter) {
|
|
debugln!("It was an option...");
|
|
Error::argument_conflict(o, c_with, &*usg, self.color())
|
|
} else {
|
|
match find_by_name!($me, $name, positionals, values) {
|
|
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() {
|
|
debugln!("iter;name={}", name);
|
|
if let Some(opt) = find_by_name!(self, name, opts, iter) {
|
|
try!(self._validate_num_vals(opt, ma, matcher));
|
|
} else if let Some(pos) = find_by_name!(self, name, positionals, values) {
|
|
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<()> {
|
|
debugln!("fn=validate_required;required={:?};", self.required);
|
|
let c = Colorizer {
|
|
use_stderr: true,
|
|
when: self.color(),
|
|
};
|
|
'outer: for name in &self.required {
|
|
debugln!("iter;name={}", name);
|
|
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) = find_by_name!(self, name, flags, iter) {
|
|
if self.is_missing_required_ok(a, matcher) {
|
|
continue 'outer;
|
|
}
|
|
} else if let Some(a) = find_by_name!(self, name, opts, iter) {
|
|
if self.is_missing_required_ok(a, matcher) {
|
|
continue 'outer;
|
|
}
|
|
} else if let Some(a) = find_by_name!(self, name, positionals, values) {
|
|
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.retain(|n| !matcher.contains(n));
|
|
reqs.dedup();
|
|
Error::missing_required_argument(
|
|
&*self.get_required_from(&*reqs, Some(matcher))
|
|
.iter()
|
|
.fold(String::new(),
|
|
|acc, s| acc + &format!("\n {}", c.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>
|
|
{
|
|
debugln!("fn=is_missing_required_ok;a={}", a.name());
|
|
if let Some(bl) = a.blacklist() {
|
|
debugln!("Conflicts found...{:?}", bl);
|
|
for n in bl.iter() {
|
|
debugln!("iter;conflict={}", n);
|
|
if matcher.contains(n) ||
|
|
self.groups
|
|
.get(n)
|
|
.map_or(false, |g| g.args.iter().any(|an| matcher.contains(an))) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if let Some(ru) = a.required_unless() {
|
|
debugln!("Required unless found...{:?}", ru);
|
|
let mut found_any = false;
|
|
for n in ru.iter() {
|
|
debugln!("iter;ru={}", n);
|
|
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) {
|
|
debugln!("Doesn't require all...returning true");
|
|
return true;
|
|
}
|
|
debugln!("Requires all...next");
|
|
found_any = true;
|
|
} else if a.is_set(ArgSettings::RequiredUnlessAll) {
|
|
debugln!("Not in matcher, or group and requires all...returning false");
|
|
return false;
|
|
}
|
|
}
|
|
return found_any;
|
|
}
|
|
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) = find_by_long!(self, &name, opts) {
|
|
self.groups_for_arg(&*opt.b.name)
|
|
.and_then(|grps| Some(matcher.inc_occurrences_of(&*grps)));
|
|
matcher.insert(&*opt.b.name);
|
|
} else if let Some(flg) = find_by_long!(self, &name, flags) {
|
|
self.groups_for_arg(&*flg.b.name)
|
|
.and_then(|grps| Some(matcher.inc_occurrences_of(&*grps)));
|
|
matcher.insert(&*flg.b.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)[..]);
|
|
|
|
let flags = self.needs_flags_tag();
|
|
if flags && !self.is_set(AppSettings::UnifiedHelpMessage) {
|
|
usage.push_str(" [FLAGS]");
|
|
} else if flags {
|
|
usage.push_str(" [OPTIONS]");
|
|
}
|
|
if !self.is_set(AppSettings::UnifiedHelpMessage) && self.has_opts() &&
|
|
self.opts.iter().any(|o| !o.b.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(|o| o.b.settings.is_set(ArgSettings::Multiple)) &&
|
|
self.positionals.values().any(|p| !p.b.settings.is_set(ArgSettings::Required)) &&
|
|
!self.has_subcommands() {
|
|
usage.push_str(" [--]")
|
|
}
|
|
if self.has_positionals() &&
|
|
self.positionals.values().any(|p| !p.b.settings.is_set(ArgSettings::Required)) {
|
|
if let Some(args_tag) = self.get_args_tag() {
|
|
usage.push_str(&*args_tag);
|
|
} 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.b.name).is_none() {
|
|
try!($_self.add_val_to_arg($a, OsStr::new($a.v.default_val
|
|
.as_ref()
|
|
.unwrap()),
|
|
$m));
|
|
arg_post_processing!($_self, $a, $m);
|
|
}
|
|
};
|
|
}
|
|
for o in self.opts.iter().filter(|o| o.v.default_val.is_some()) {
|
|
add_val!(self, o, matcher);
|
|
}
|
|
for p in self.positionals.values().filter(|p| p.v.default_val.is_some()) {
|
|
add_val!(self, p, matcher);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn flags(&self) -> Iter<FlagBuilder<'a, 'b>> { self.flags.iter() }
|
|
|
|
pub fn opts(&self) -> Iter<OptBuilder<'a, 'b>> { self.opts.iter() }
|
|
|
|
pub fn positionals(&self) -> vec_map::Values<PosBuilder<'a, 'b>> { self.positionals.values() }
|
|
|
|
pub fn subcommands(&self) -> Iter<App> { self.subcommands.iter() }
|
|
|
|
// Should we color the output? None=determined by output location, true=yes, false=no
|
|
#[doc(hidden)]
|
|
pub fn color(&self) -> ColorWhen {
|
|
debugln!("fn=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
|
|
}
|
|
}
|
|
|
|
pub fn find_any_arg(&self, name: &str) -> Option<&AnyArg> {
|
|
if let Some(f) = find_by_name!(self, &name, flags, iter) {
|
|
return Some(f);
|
|
}
|
|
if let Some(o) = find_by_name!(self, &name, opts, iter) {
|
|
return Some(o);
|
|
}
|
|
if let Some(p) = find_by_name!(self, &name, positionals, values) {
|
|
return Some(p);
|
|
}
|
|
None
|
|
}
|
|
|
|
#[cfg_attr(feature = "lints", allow(explicit_iter_loop))]
|
|
pub fn find_subcommand(&'b self, sc: &str) -> Option<&'b App<'a, 'b>> {
|
|
debugln!("fn=find_subcommand;");
|
|
debugln!("Looking for sc...{}", sc);
|
|
debugln!("Currently in Parser...{}", self.meta.bin_name.as_ref().unwrap());
|
|
for s in self.subcommands.iter() {
|
|
if s.p.meta.bin_name.as_ref().unwrap_or(&String::new()) == sc ||
|
|
(s.p.meta.aliases.is_some() &&
|
|
s.p
|
|
.meta
|
|
.aliases
|
|
.as_ref()
|
|
.unwrap()
|
|
.iter()
|
|
.any(|&(s, _)| s == sc.split(' ').rev().next().expect(INTERNAL_ERROR_MSG))) {
|
|
return Some(s);
|
|
}
|
|
if let Some(app) = s.p.find_subcommand(sc) {
|
|
return Some(app);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
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,
|
|
id: self.id,
|
|
valid_neg_num: self.valid_neg_num,
|
|
}
|
|
}
|
|
}
|