更新libclamav库1.0.0版本

This commit is contained in:
2023-01-14 18:28:39 +08:00
parent b879ee0b2e
commit 45fe15f472
8531 changed files with 1222046 additions and 177272 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
#[doc(hidden)]
#[allow(missing_debug_implementations)]
#[derive(Default, Clone)]
pub struct AppMeta<'b> {
pub name: String,
pub bin_name: Option<String>,
pub author: Option<&'b str>,
pub version: Option<&'b str>,
pub long_version: Option<&'b str>,
pub about: Option<&'b str>,
pub long_about: Option<&'b str>,
pub more_help: Option<&'b str>,
pub pre_help: Option<&'b str>,
pub aliases: Option<Vec<(&'b str, bool)>>, // (name, visible)
pub usage_str: Option<&'b str>,
pub usage: Option<String>,
pub help_str: Option<&'b str>,
pub disp_ord: usize,
pub term_w: Option<usize>,
pub max_w: Option<usize>,
pub template: Option<&'b str>,
}
impl<'b> AppMeta<'b> {
pub fn new() -> Self {
Default::default()
}
pub fn with_name(s: String) -> Self {
AppMeta {
name: s,
disp_ord: 999,
..Default::default()
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,493 @@
// std
use std::collections::{BTreeMap, VecDeque};
// Internal
use crate::{
app::{parser::Parser, settings::AppSettings as AS},
args::{settings::ArgSettings, AnyArg, ArgMatcher, PosBuilder},
INTERNAL_ERROR_MSG,
};
// Creates a usage string for display. 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_with_title(p: &Parser, used: &[&str]) -> String {
debugln!("usage::create_usage_with_title;");
let mut usage = String::with_capacity(75);
usage.push_str("USAGE:\n ");
usage.push_str(&*create_usage_no_title(p, used));
usage
}
// Creates a usage string to be used in error message (i.e. one with currently used args)
pub fn create_error_usage<'a, 'b>(
p: &Parser<'a, 'b>,
matcher: &'b ArgMatcher<'a>,
extra: Option<&str>,
) -> String {
let mut args: Vec<_> = matcher
.arg_names()
.iter()
.filter(|n| {
if let Some(o) = find_by_name!(p, **n, opts, iter) {
!o.b.is_set(ArgSettings::Required) && !o.b.is_set(ArgSettings::Hidden)
} else if let Some(p) = find_by_name!(p, **n, positionals, values) {
!p.b.is_set(ArgSettings::Required) && p.b.is_set(ArgSettings::Hidden)
} else {
true // flags can't be required, so they're always true
}
})
.copied()
.collect();
if let Some(r) = extra {
args.push(r);
}
create_usage_with_title(p, &*args)
}
// Creates a usage string (*without title*) if one was not provided by the user manually.
pub fn create_usage_no_title(p: &Parser, used: &[&str]) -> String {
debugln!("usage::create_usage_no_title;");
if let Some(u) = p.meta.usage_str {
String::from(&*u)
} else if used.is_empty() {
create_help_usage(p, true)
} else {
create_smart_usage(p, used)
}
}
// Creates a usage string for display in help messages (i.e. not for errors)
pub fn create_help_usage(p: &Parser, incl_reqs: bool) -> String {
let mut usage = String::with_capacity(75);
let name = p
.meta
.usage
.as_ref()
.unwrap_or_else(|| p.meta.bin_name.as_ref().unwrap_or(&p.meta.name));
usage.push_str(&*name);
let req_string = if incl_reqs {
let mut reqs: Vec<&str> = p.required().map(|r| &**r).collect();
reqs.sort_unstable();
reqs.dedup();
get_required_usage_from(p, &reqs, None, None, false)
.iter()
.fold(String::new(), |a, s| a + &format!(" {}", s)[..])
} else {
String::new()
};
let flags = needs_flags_tag(p);
if flags && !p.is_set(AS::UnifiedHelpMessage) {
usage.push_str(" [FLAGS]");
} else if flags {
usage.push_str(" [OPTIONS]");
}
if !p.is_set(AS::UnifiedHelpMessage)
&& p.opts
.iter()
.any(|o| !o.is_set(ArgSettings::Required) && !o.is_set(ArgSettings::Hidden))
{
usage.push_str(" [OPTIONS]");
}
usage.push_str(&req_string[..]);
let has_last = p.positionals.values().any(|p| p.is_set(ArgSettings::Last));
// places a '--' in the usage string if there are args and options
// supporting multiple values
if p.opts.iter().any(|o| o.is_set(ArgSettings::Multiple))
&& p.positionals
.values()
.any(|p| !p.is_set(ArgSettings::Required))
&& !(p.has_visible_subcommands() || p.is_set(AS::AllowExternalSubcommands))
&& !has_last
{
usage.push_str(" [--]");
}
let not_req_or_hidden = |p: &PosBuilder| {
(!p.is_set(ArgSettings::Required) || p.is_set(ArgSettings::Last))
&& !p.is_set(ArgSettings::Hidden)
};
if p.has_positionals() && p.positionals.values().any(not_req_or_hidden) {
if let Some(args_tag) = get_args_tag(p, incl_reqs) {
usage.push_str(&*args_tag);
} else {
usage.push_str(" [ARGS]");
}
if has_last && incl_reqs {
let pos = p
.positionals
.values()
.find(|p| p.b.is_set(ArgSettings::Last))
.expect(INTERNAL_ERROR_MSG);
debugln!("usage::create_help_usage: '{}' has .last(true)", pos.name());
let req = pos.is_set(ArgSettings::Required);
if req
&& p.positionals
.values()
.any(|p| !p.is_set(ArgSettings::Required))
{
usage.push_str(" -- <");
} else if req {
usage.push_str(" [--] <");
} else {
usage.push_str(" [-- <");
}
usage.push_str(&*pos.name_no_brackets());
usage.push('>');
usage.push_str(pos.multiple_str());
if !req {
usage.push(']');
}
}
}
// incl_reqs is only false when this function is called recursively
if p.has_visible_subcommands() && incl_reqs || p.is_set(AS::AllowExternalSubcommands) {
if p.is_set(AS::SubcommandsNegateReqs) || p.is_set(AS::ArgsNegateSubcommands) {
usage.push_str("\n ");
if !p.is_set(AS::ArgsNegateSubcommands) {
usage.push_str(&*create_help_usage(p, false));
} else {
usage.push_str(&*name);
}
usage.push_str(" <SUBCOMMAND>");
} else if p.is_set(AS::SubcommandRequired) || p.is_set(AS::SubcommandRequiredElseHelp) {
usage.push_str(" <SUBCOMMAND>");
} else {
usage.push_str(" [SUBCOMMAND]");
}
}
usage.shrink_to_fit();
debugln!("usage::create_help_usage: usage={}", usage);
usage
}
// Creates a context aware usage string, or "smart usage" from currently used
// args, and requirements
fn create_smart_usage(p: &Parser, used: &[&str]) -> String {
debugln!("usage::smart_usage;");
let mut usage = String::with_capacity(75);
let mut hs: Vec<&str> = p.required().map(|s| &**s).collect();
hs.extend_from_slice(used);
let r_string = get_required_usage_from(p, &hs, None, None, false)
.iter()
.fold(String::new(), |acc, s| acc + &format!(" {}", s)[..]);
usage.push_str(
&p.meta
.usage
.as_ref()
.unwrap_or_else(|| p.meta.bin_name.as_ref().unwrap_or(&p.meta.name))[..],
);
usage.push_str(&*r_string);
if p.is_set(AS::SubcommandRequired) {
usage.push_str(" <SUBCOMMAND>");
}
usage.shrink_to_fit();
usage
}
// Gets the `[ARGS]` tag for the usage string
fn get_args_tag(p: &Parser, incl_reqs: bool) -> Option<String> {
debugln!("usage::get_args_tag;");
let mut count = 0;
'outer: for pos in p
.positionals
.values()
.filter(|pos| !pos.is_set(ArgSettings::Required))
.filter(|pos| !pos.is_set(ArgSettings::Hidden))
.filter(|pos| !pos.is_set(ArgSettings::Last))
{
debugln!("usage::get_args_tag:iter:{}:", pos.b.name);
if let Some(g_vec) = p.groups_for_arg(pos.b.name) {
for grp_s in &g_vec {
debugln!("usage::get_args_tag:iter:{}:iter:{};", pos.b.name, grp_s);
// if it's part of a required group we don't want to count it
if p.groups.iter().any(|g| g.required && (&g.name == grp_s)) {
continue 'outer;
}
}
}
count += 1;
debugln!(
"usage::get_args_tag:iter: {} Args not required or hidden",
count
);
}
if !p.is_set(AS::DontCollapseArgsInUsage) && count > 1 {
debugln!("usage::get_args_tag:iter: More than one, returning [ARGS]");
return None; // [ARGS]
} else if count == 1 && incl_reqs {
let pos = p
.positionals
.values()
.find(|pos| {
!pos.is_set(ArgSettings::Required)
&& !pos.is_set(ArgSettings::Hidden)
&& !pos.is_set(ArgSettings::Last)
})
.expect(INTERNAL_ERROR_MSG);
debugln!(
"usage::get_args_tag:iter: Exactly one, returning '{}'",
pos.name()
);
return Some(format!(
" [{}]{}",
pos.name_no_brackets(),
pos.multiple_str()
));
} else if p.is_set(AS::DontCollapseArgsInUsage) && !p.positionals.is_empty() && incl_reqs {
debugln!("usage::get_args_tag:iter: Don't collapse returning all");
return Some(
p.positionals
.values()
.filter(|pos| !pos.is_set(ArgSettings::Required))
.filter(|pos| !pos.is_set(ArgSettings::Hidden))
.filter(|pos| !pos.is_set(ArgSettings::Last))
.map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()))
.collect::<Vec<_>>()
.join(""),
);
} else if !incl_reqs {
debugln!("usage::get_args_tag:iter: incl_reqs=false, building secondary usage string");
let highest_req_pos = p
.positionals
.iter()
.filter_map(|(idx, pos)| {
if pos.b.is_set(ArgSettings::Required) && !pos.b.is_set(ArgSettings::Last) {
Some(idx)
} else {
None
}
})
.max()
.unwrap_or_else(|| p.positionals.len());
return Some(
p.positionals
.iter()
.filter_map(|(idx, pos)| {
if idx <= highest_req_pos {
Some(pos)
} else {
None
}
})
.filter(|pos| !pos.is_set(ArgSettings::Required))
.filter(|pos| !pos.is_set(ArgSettings::Hidden))
.filter(|pos| !pos.is_set(ArgSettings::Last))
.map(|pos| format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()))
.collect::<Vec<_>>()
.join(""),
);
}
Some("".into())
}
// Determines if we need the `[FLAGS]` tag in the usage string
fn needs_flags_tag(p: &Parser) -> bool {
debugln!("usage::needs_flags_tag;");
'outer: for f in &p.flags {
debugln!("usage::needs_flags_tag:iter: f={};", f.b.name);
if let Some(l) = f.s.long {
if l == "help" || l == "version" {
// Don't print `[FLAGS]` just for help or version
continue;
}
}
if let Some(g_vec) = p.groups_for_arg(f.b.name) {
for grp_s in &g_vec {
debugln!("usage::needs_flags_tag:iter:iter: grp_s={};", grp_s);
if p.groups.iter().any(|g| &g.name == grp_s && g.required) {
debugln!("usage::needs_flags_tag:iter:iter: Group is required");
continue 'outer;
}
}
}
if f.is_set(ArgSettings::Hidden) {
continue;
}
debugln!("usage::needs_flags_tag:iter: [FLAGS] required");
return true;
}
debugln!("usage::needs_flags_tag: [FLAGS] not required");
false
}
// Returns the required args in usage string form by fully unrolling all groups
pub fn get_required_usage_from<'a, 'b>(
p: &Parser<'a, 'b>,
reqs: &[&'a str],
matcher: Option<&ArgMatcher<'a>>,
extra: Option<&str>,
incl_last: bool,
) -> VecDeque<String> {
debugln!(
"usage::get_required_usage_from: reqs={:?}, extra={:?}",
reqs,
extra
);
let mut desc_reqs: Vec<&str> = vec![];
desc_reqs.extend(extra);
let mut new_reqs: Vec<&str> = vec![];
macro_rules! get_requires {
(@group $a: ident, $v:ident, $p:ident) => {{
if let Some(rl) = p
.groups
.iter()
.filter(|g| g.requires.is_some())
.find(|g| &g.name == $a)
.map(|g| g.requires.as_ref().unwrap())
{
for r in rl {
if !$p.contains(&r) {
debugln!(
"usage::get_required_usage_from:iter:{}: adding group req={:?}",
$a,
r
);
$v.push(r);
}
}
}
}};
($a:ident, $what:ident, $how:ident, $v:ident, $p:ident) => {{
if let Some(rl) = p
.$what
.$how()
.filter(|a| a.b.requires.is_some())
.find(|arg| &arg.b.name == $a)
.map(|a| a.b.requires.as_ref().unwrap())
{
for &(_, r) in rl.iter() {
if !$p.contains(&r) {
debugln!(
"usage::get_required_usage_from:iter:{}: adding arg req={:?}",
$a,
r
);
$v.push(r);
}
}
}
}};
}
// initialize new_reqs
for a in reqs {
get_requires!(a, flags, iter, new_reqs, reqs);
get_requires!(a, opts, iter, new_reqs, reqs);
get_requires!(a, positionals, values, new_reqs, reqs);
get_requires!(@group a, new_reqs, reqs);
}
desc_reqs.extend_from_slice(&*new_reqs);
debugln!(
"usage::get_required_usage_from: after init desc_reqs={:?}",
desc_reqs
);
loop {
let mut tmp = vec![];
for a in &new_reqs {
get_requires!(a, flags, iter, tmp, desc_reqs);
get_requires!(a, opts, iter, tmp, desc_reqs);
get_requires!(a, positionals, values, tmp, desc_reqs);
get_requires!(@group a, tmp, desc_reqs);
}
if tmp.is_empty() {
debugln!("usage::get_required_usage_from: no more children");
break;
} else {
debugln!("usage::get_required_usage_from: after iter tmp={:?}", tmp);
debugln!(
"usage::get_required_usage_from: after iter new_reqs={:?}",
new_reqs
);
desc_reqs.extend_from_slice(&*new_reqs);
new_reqs.clear();
new_reqs.extend_from_slice(&*tmp);
debugln!(
"usage::get_required_usage_from: after iter desc_reqs={:?}",
desc_reqs
);
}
}
desc_reqs.extend_from_slice(reqs);
desc_reqs.sort_unstable();
desc_reqs.dedup();
debugln!(
"usage::get_required_usage_from: final desc_reqs={:?}",
desc_reqs
);
let mut ret_val = VecDeque::new();
let args_in_groups = p
.groups
.iter()
.filter(|gn| desc_reqs.contains(&gn.name))
.flat_map(|g| p.arg_names_in_group(g.name))
.collect::<Vec<_>>();
let pmap = if let Some(m) = matcher {
desc_reqs
.iter()
.filter(|a| p.positionals.values().any(|p| &&p.b.name == a))
.filter(|&pos| !m.contains(pos))
.filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos))
.filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last))
.filter(|pos| !args_in_groups.contains(&pos.b.name))
.map(|pos| (pos.index, pos))
.collect::<BTreeMap<u64, &PosBuilder>>() // sort by index
} else {
desc_reqs
.iter()
.filter(|a| p.positionals.values().any(|pos| &&pos.b.name == a))
.filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos))
.filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last))
.filter(|pos| !args_in_groups.contains(&pos.b.name))
.map(|pos| (pos.index, pos))
.collect::<BTreeMap<u64, &PosBuilder>>() // sort by index
};
debugln!(
"usage::get_required_usage_from: args_in_groups={:?}",
args_in_groups
);
for &p in pmap.values() {
let s = p.to_string();
if args_in_groups.is_empty() || !args_in_groups.contains(&&*s) {
ret_val.push_back(s);
}
}
for a in desc_reqs
.iter()
.filter(|name| !p.positionals.values().any(|p| &&p.b.name == name))
.filter(|name| !p.groups.iter().any(|g| &&g.name == name))
.filter(|name| !args_in_groups.contains(name))
.filter(|name| !(matcher.is_some() && matcher.as_ref().unwrap().contains(name)))
{
debugln!("usage::get_required_usage_from:iter:{}:", a);
let arg = find_by_name!(p, *a, flags, iter)
.map(|f| f.to_string())
.unwrap_or_else(|| {
find_by_name!(p, *a, opts, iter)
.map(|o| o.to_string())
.expect(INTERNAL_ERROR_MSG)
});
ret_val.push_back(arg);
}
let mut g_vec: Vec<String> = vec![];
for g in desc_reqs
.iter()
.filter(|n| p.groups.iter().any(|g| &&g.name == n))
{
let g_string = p.args_in_group(g).join("|");
let elem = format!("<{}>", &g_string[..g_string.len()]);
if !g_vec.contains(&elem) {
g_vec.push(elem);
}
}
for g in g_vec {
ret_val.push_back(g);
}
ret_val
}

View File

@@ -0,0 +1,584 @@
// std
#[allow(deprecated, unused_imports)]
use std::{ascii::AsciiExt, fmt::Display};
// Internal
use crate::{
app::{
parser::{ParseResult, Parser},
settings::AppSettings as AS,
usage,
},
args::{settings::ArgSettings, AnyArg, ArgMatcher, MatchedArg},
errors::{Error, ErrorKind, Result as ClapResult},
fmt::{Colorizer, ColorizerOption},
INTERNAL_ERROR_MSG, INVALID_UTF8,
};
pub struct Validator<'a, 'b, 'z>(&'z mut Parser<'a, 'b>)
where
'a: 'b,
'b: 'z;
impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
pub fn new(p: &'z mut Parser<'a, 'b>) -> Self {
Validator(p)
}
pub fn validate(
&mut self,
needs_val_of: ParseResult<'a>,
subcmd_name: Option<String>,
matcher: &mut ArgMatcher<'a>,
) -> ClapResult<()> {
debugln!("Validator::validate;");
let mut reqs_validated = false;
self.0.add_env(matcher)?;
self.0.add_defaults(matcher)?;
if let ParseResult::Opt(a) = needs_val_of {
debugln!("Validator::validate: needs_val_of={:?}", a);
let o = {
self.0
.opts
.iter()
.find(|o| o.b.name == a)
.expect(INTERNAL_ERROR_MSG)
.clone()
};
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,
&*usage::create_error_usage(self.0, matcher, None),
self.0.color(),
));
}
}
if matcher.is_empty()
&& matcher.subcommand_name().is_none()
&& self.0.is_set(AS::ArgRequiredElseHelp)
{
let mut out = vec![];
self.0.write_help_err(&mut out)?;
return Err(Error {
message: String::from_utf8_lossy(&*out).into_owned(),
kind: ErrorKind::MissingArgumentOrSubcommand,
info: None,
});
}
self.validate_blacklist(matcher)?;
if !(reqs_validated || self.0.is_set(AS::SubcommandsNegateReqs) && subcmd_name.is_some()) {
self.validate_required(matcher)?;
}
self.validate_matched_args(matcher)?;
matcher.usage(usage::create_usage_with_title(self.0, &[]));
Ok(())
}
fn validate_arg_values<A>(
&self,
arg: &A,
ma: &MatchedArg,
matcher: &ArgMatcher<'a>,
) -> ClapResult<()>
where
A: AnyArg<'a, 'b> + Display,
{
debugln!("Validator::validate_arg_values: arg={:?}", arg.name());
for val in &ma.vals {
if self.0.is_set(AS::StrictUtf8) && val.to_str().is_none() {
debugln!(
"Validator::validate_arg_values: invalid UTF-8 found in val {:?}",
val
);
return Err(Error::invalid_utf8(
&*usage::create_error_usage(self.0, matcher, None),
self.0.color(),
));
}
if let Some(p_vals) = arg.possible_vals() {
debugln!("Validator::validate_arg_values: possible_vals={:?}", p_vals);
let val_str = val.to_string_lossy();
let ok = if arg.is_set(ArgSettings::CaseInsensitive) {
p_vals.iter().any(|pv| pv.eq_ignore_ascii_case(&*val_str))
} else {
p_vals.contains(&&*val_str)
};
if !ok {
return Err(Error::invalid_value(
val_str,
p_vals,
arg,
&*usage::create_error_usage(self.0, matcher, None),
self.0.color(),
));
}
}
if !arg.is_set(ArgSettings::EmptyValues)
&& val.is_empty()
&& matcher.contains(&*arg.name())
{
debugln!("Validator::validate_arg_values: illegal empty val found");
return Err(Error::empty_value(
arg,
&*usage::create_error_usage(self.0, matcher, None),
self.0.color(),
));
}
if let Some(vtor) = arg.validator() {
debug!("Validator::validate_arg_values: checking validator...");
if let Err(e) = vtor(val.to_string_lossy().into_owned()) {
sdebugln!("error");
return Err(Error::value_validation(Some(arg), e, self.0.color()));
} else {
sdebugln!("good");
}
}
if let Some(vtor) = arg.validator_os() {
debug!("Validator::validate_arg_values: checking validator_os...");
if let Err(e) = vtor(val) {
sdebugln!("error");
return Err(Error::value_validation(
Some(arg),
(*e).to_string_lossy().to_string(),
self.0.color(),
));
} else {
sdebugln!("good");
}
}
}
Ok(())
}
fn build_err(&self, name: &str, matcher: &ArgMatcher) -> ClapResult<()> {
debugln!("build_err!: name={}", name);
let mut c_with = find_from!(self.0, &name, blacklist, matcher);
c_with = c_with.or_else(|| {
self.0
.find_any_arg(name)
.and_then(|aa| aa.blacklist())
.and_then(|bl| bl.iter().find(|arg| matcher.contains(arg)))
.and_then(|an| self.0.find_any_arg(an))
.map(|aa| format!("{}", aa))
});
debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, &name);
// matcher.remove(&name);
let usg = usage::create_error_usage(self.0, matcher, None);
if let Some(f) = find_by_name!(self.0, name, flags, iter) {
debugln!("build_err!: It was a flag...");
Err(Error::argument_conflict(f, c_with, &*usg, self.0.color()))
} else if let Some(o) = find_by_name!(self.0, name, opts, iter) {
debugln!("build_err!: It was an option...");
Err(Error::argument_conflict(o, c_with, &*usg, self.0.color()))
} else {
match find_by_name!(self.0, name, positionals, values) {
Some(p) => {
debugln!("build_err!: It was a positional...");
Err(Error::argument_conflict(p, c_with, &*usg, self.0.color()))
}
None => panic!("{}", INTERNAL_ERROR_MSG),
}
}
}
fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> {
debugln!("Validator::validate_blacklist;");
let mut conflicts: Vec<&str> = vec![];
for (&name, _) in matcher.iter() {
debugln!("Validator::validate_blacklist:iter:{};", name);
if let Some(grps) = self.0.groups_for_arg(name) {
for grp in &grps {
if let Some(g) = self.0.groups.iter().find(|g| &g.name == grp) {
if !g.multiple {
for arg in &g.args {
if arg == &name {
continue;
}
conflicts.push(arg);
}
}
if let Some(ref gc) = g.conflicts {
conflicts.extend(&*gc);
}
}
}
}
if let Some(arg) = find_any_by_name!(self.0, name) {
if let Some(bl) = arg.blacklist() {
for conf in bl {
if matcher.get(conf).is_some() {
conflicts.push(conf);
}
}
}
} else {
debugln!("Validator::validate_blacklist:iter:{}:group;", name);
let args = self.0.arg_names_in_group(name);
for arg in &args {
debugln!(
"Validator::validate_blacklist:iter:{}:group:iter:{};",
name,
arg
);
if let Some(bl) = find_any_by_name!(self.0, *arg).unwrap().blacklist() {
for conf in bl {
if matcher.get(conf).is_some() {
conflicts.push(conf);
}
}
}
}
}
}
for name in &conflicts {
debugln!(
"Validator::validate_blacklist:iter:{}: Checking blacklisted arg",
name
);
let mut should_err = false;
if self.0.groups.iter().any(|g| &g.name == name) {
debugln!(
"Validator::validate_blacklist:iter:{}: groups contains it...",
name
);
for n in self.0.arg_names_in_group(name) {
debugln!(
"Validator::validate_blacklist:iter:{}:iter:{}: looking in group...",
name,
n
);
if matcher.contains(n) {
debugln!(
"Validator::validate_blacklist:iter:{}:iter:{}: matcher contains it...",
name,
n
);
return self.build_err(n, matcher);
}
}
} else if let Some(ma) = matcher.get(name) {
debugln!(
"Validator::validate_blacklist:iter:{}: matcher contains it...",
name
);
should_err = ma.occurs > 0;
}
if should_err {
return self.build_err(*name, matcher);
}
}
Ok(())
}
fn validate_matched_args(&self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> {
debugln!("Validator::validate_matched_args;");
for (name, ma) in matcher.iter() {
debugln!(
"Validator::validate_matched_args:iter:{}: vals={:#?}",
name,
ma.vals
);
if let Some(opt) = find_by_name!(self.0, *name, opts, iter) {
self.validate_arg_num_vals(opt, ma, matcher)?;
self.validate_arg_values(opt, ma, matcher)?;
self.validate_arg_requires(opt, ma, matcher)?;
self.validate_arg_num_occurs(opt, ma, matcher)?;
} else if let Some(flag) = find_by_name!(self.0, *name, flags, iter) {
self.validate_arg_requires(flag, ma, matcher)?;
self.validate_arg_num_occurs(flag, ma, matcher)?;
} else if let Some(pos) = find_by_name!(self.0, *name, positionals, values) {
self.validate_arg_num_vals(pos, ma, matcher)?;
self.validate_arg_num_occurs(pos, ma, matcher)?;
self.validate_arg_values(pos, ma, matcher)?;
self.validate_arg_requires(pos, ma, matcher)?;
} else {
let grp = self
.0
.groups
.iter()
.find(|g| &g.name == name)
.expect(INTERNAL_ERROR_MSG);
if let Some(ref g_reqs) = grp.requires {
if g_reqs.iter().any(|&n| !matcher.contains(n)) {
return self.missing_required_error(matcher, None);
}
}
}
}
Ok(())
}
fn validate_arg_num_occurs<A>(
&self,
a: &A,
ma: &MatchedArg,
matcher: &ArgMatcher,
) -> ClapResult<()>
where
A: AnyArg<'a, 'b> + Display,
{
debugln!("Validator::validate_arg_num_occurs: a={};", a.name());
if ma.occurs > 1 && !a.is_set(ArgSettings::Multiple) {
// Not the first time, and we don't allow multiples
return Err(Error::unexpected_multiple_usage(
a,
&*usage::create_error_usage(self.0, matcher, None),
self.0.color(),
));
}
Ok(())
}
fn validate_arg_num_vals<A>(
&self,
a: &A,
ma: &MatchedArg,
matcher: &ArgMatcher,
) -> ClapResult<()>
where
A: AnyArg<'a, 'b> + Display,
{
debugln!("Validator::validate_arg_num_vals:{}", a.name());
if let Some(num) = a.num_vals() {
debugln!("Validator::validate_arg_num_vals: 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!("Validator::validate_arg_num_vals: 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"
},
&*usage::create_error_usage(self.0, matcher, None),
self.0.color(),
));
}
}
if let Some(num) = a.max_vals() {
debugln!("Validator::validate_arg_num_vals: max_vals set...{}", num);
if (ma.vals.len() as u64) > num {
debugln!("Validator::validate_arg_num_vals: Sending error TooManyValues");
return Err(Error::too_many_values(
ma.vals
.iter()
.last()
.expect(INTERNAL_ERROR_MSG)
.to_str()
.expect(INVALID_UTF8),
a,
&*usage::create_error_usage(self.0, matcher, None),
self.0.color(),
));
}
}
let min_vals_zero = if let Some(num) = a.min_vals() {
debugln!("Validator::validate_arg_num_vals: min_vals set: {}", num);
if (ma.vals.len() as u64) < num && num != 0 {
debugln!("Validator::validate_arg_num_vals: Sending error TooFewValues");
return Err(Error::too_few_values(
a,
num,
ma.vals.len(),
&*usage::create_error_usage(self.0, matcher, None),
self.0.color(),
));
}
num == 0
} else {
false
};
// Issue 665 (https://github.com/clap-rs/clap/issues/665)
// Issue 1105 (https://github.com/clap-rs/clap/issues/1105)
if a.takes_value() && !min_vals_zero && ma.vals.is_empty() {
return Err(Error::empty_value(
a,
&*usage::create_error_usage(self.0, matcher, None),
self.0.color(),
));
}
Ok(())
}
fn validate_arg_requires<A>(
&self,
a: &A,
ma: &MatchedArg,
matcher: &ArgMatcher,
) -> ClapResult<()>
where
A: AnyArg<'a, 'b> + Display,
{
debugln!("Validator::validate_arg_requires:{};", a.name());
if let Some(a_reqs) = a.requires() {
for &(val, name) in a_reqs.iter().filter(|&&(val, _)| val.is_some()) {
let missing_req =
|v| v == val.expect(INTERNAL_ERROR_MSG) && !matcher.contains(name);
if ma.vals.iter().any(missing_req) {
return self.missing_required_error(matcher, None);
}
}
for &(_, name) in a_reqs.iter().filter(|&&(val, _)| val.is_none()) {
if !matcher.contains(name) {
return self.missing_required_error(matcher, Some(name));
}
}
}
Ok(())
}
fn validate_required(&mut self, matcher: &ArgMatcher) -> ClapResult<()> {
debugln!(
"Validator::validate_required: required={:?};",
self.0.required
);
let mut should_err = false;
let mut to_rem = Vec::new();
for name in &self.0.required {
debugln!("Validator::validate_required:iter:{}:", name);
if matcher.contains(name) {
continue;
}
if to_rem.contains(name) {
continue;
} else if let Some(a) = find_any_by_name!(self.0, *name) {
if self.is_missing_required_ok(a, matcher) {
to_rem.push(a.name());
if let Some(reqs) = a.requires() {
for r in reqs
.iter()
.filter(|&&(val, _)| val.is_none())
.map(|&(_, name)| name)
{
to_rem.push(r);
}
}
continue;
}
}
should_err = true;
break;
}
if should_err {
for r in &to_rem {
'inner: for i in (0..self.0.required.len()).rev() {
if &self.0.required[i] == r {
self.0.required.swap_remove(i);
break 'inner;
}
}
}
return self.missing_required_error(matcher, None);
}
// Validate the conditionally required args
for &(a, v, r) in &self.0.r_ifs {
if let Some(ma) = matcher.get(a) {
if matcher.get(r).is_none() && ma.vals.iter().any(|val| val == v) {
return self.missing_required_error(matcher, Some(r));
}
}
}
Ok(())
}
fn validate_arg_conflicts(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option<bool> {
debugln!("Validator::validate_arg_conflicts: a={:?};", a.name());
a.blacklist().map(|bl| {
bl.iter().any(|conf| {
matcher.contains(conf)
|| self
.0
.groups
.iter()
.find(|g| &g.name == conf)
.map_or(false, |g| g.args.iter().any(|arg| matcher.contains(arg)))
})
})
}
fn validate_required_unless(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option<bool> {
debugln!("Validator::validate_required_unless: a={:?};", a.name());
macro_rules! check {
($how:ident, $_self:expr, $a:ident, $m:ident) => {{
$a.required_unless().map(|ru| {
ru.iter().$how(|n| {
$m.contains(n) || {
if let Some(grp) = $_self.groups.iter().find(|g| &g.name == n) {
grp.args.iter().any(|arg| $m.contains(arg))
} else {
false
}
}
})
})
}};
}
if a.is_set(ArgSettings::RequiredUnlessAll) {
check!(all, self.0, a, matcher)
} else {
check!(any, self.0, a, matcher)
}
}
fn missing_required_error(&self, matcher: &ArgMatcher, extra: Option<&str>) -> ClapResult<()> {
debugln!("Validator::missing_required_error: extra={:?}", extra);
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: self.0.color(),
});
let mut reqs = self.0.required.iter().map(|&r| &*r).collect::<Vec<_>>();
if let Some(r) = extra {
reqs.push(r);
}
reqs.retain(|n| !matcher.contains(n));
reqs.dedup();
debugln!("Validator::missing_required_error: reqs={:#?}", reqs);
let req_args =
usage::get_required_usage_from(self.0, &reqs[..], Some(matcher), extra, true)
.iter()
.fold(String::new(), |acc, s| {
acc + &format!("\n {}", c.error(s))[..]
});
debugln!(
"Validator::missing_required_error: req_args={:#?}",
req_args
);
Err(Error::missing_required_argument(
&*req_args,
&*usage::create_error_usage(self.0, matcher, extra),
self.0.color(),
))
}
#[inline]
fn is_missing_required_ok(&self, a: &AnyArg, matcher: &ArgMatcher) -> bool {
debugln!("Validator::is_missing_required_ok: a={}", a.name());
self.validate_arg_conflicts(a, matcher).unwrap_or(false)
|| self.validate_required_unless(a, matcher).unwrap_or(false)
}
}

View File

@@ -0,0 +1,139 @@
// Std
use std::{
ffi::{OsStr, OsString},
fmt as std_fmt,
rc::Rc,
};
// Internal
use crate::{
args::settings::ArgSettings,
map::{self, VecMap},
INTERNAL_ERROR_MSG,
};
#[doc(hidden)]
pub trait AnyArg<'n, 'e>: std_fmt::Display {
fn name(&self) -> &'n str;
fn overrides(&self) -> Option<&[&'e str]>;
fn aliases(&self) -> Option<Vec<&'e str>>;
fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]>;
fn blacklist(&self) -> Option<&[&'e str]>;
fn required_unless(&self) -> Option<&[&'e str]>;
fn is_set(&self, setting: ArgSettings) -> bool;
fn set(&mut self, setting: ArgSettings);
fn has_switch(&self) -> bool;
fn max_vals(&self) -> Option<u64>;
fn min_vals(&self) -> Option<u64>;
fn num_vals(&self) -> Option<u64>;
fn possible_vals(&self) -> Option<&[&'e str]>;
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
fn validator(&self) -> Option<&Rc<Fn(String) -> Result<(), String>>>;
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
fn validator_os(&self) -> Option<&Rc<Fn(&OsStr) -> Result<(), OsString>>>;
fn short(&self) -> Option<char>;
fn long(&self) -> Option<&'e str>;
fn val_delim(&self) -> Option<char>;
fn takes_value(&self) -> bool;
fn val_names(&self) -> Option<&VecMap<&'e str>>;
fn help(&self) -> Option<&'e str>;
fn long_help(&self) -> Option<&'e str>;
fn default_val(&self) -> Option<&'e OsStr>;
fn default_vals_ifs(&self) -> Option<map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>>;
fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)>;
fn longest_filter(&self) -> bool;
fn val_terminator(&self) -> Option<&'e str>;
}
pub trait DispOrder {
fn disp_ord(&self) -> usize;
}
impl<'n, 'e, 'z, T: ?Sized> AnyArg<'n, 'e> for &'z T
where
T: AnyArg<'n, 'e> + 'z,
{
fn name(&self) -> &'n str {
(*self).name()
}
fn overrides(&self) -> Option<&[&'e str]> {
(*self).overrides()
}
fn aliases(&self) -> Option<Vec<&'e str>> {
(*self).aliases()
}
fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> {
(*self).requires()
}
fn blacklist(&self) -> Option<&[&'e str]> {
(*self).blacklist()
}
fn required_unless(&self) -> Option<&[&'e str]> {
(*self).required_unless()
}
fn is_set(&self, a: ArgSettings) -> bool {
(*self).is_set(a)
}
fn set(&mut self, _: ArgSettings) {
panic!("{}", INTERNAL_ERROR_MSG)
}
fn has_switch(&self) -> bool {
(*self).has_switch()
}
fn max_vals(&self) -> Option<u64> {
(*self).max_vals()
}
fn min_vals(&self) -> Option<u64> {
(*self).min_vals()
}
fn num_vals(&self) -> Option<u64> {
(*self).num_vals()
}
fn possible_vals(&self) -> Option<&[&'e str]> {
(*self).possible_vals()
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
fn validator(&self) -> Option<&Rc<Fn(String) -> Result<(), String>>> {
(*self).validator()
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
fn validator_os(&self) -> Option<&Rc<Fn(&OsStr) -> Result<(), OsString>>> {
(*self).validator_os()
}
fn short(&self) -> Option<char> {
(*self).short()
}
fn long(&self) -> Option<&'e str> {
(*self).long()
}
fn val_delim(&self) -> Option<char> {
(*self).val_delim()
}
fn takes_value(&self) -> bool {
(*self).takes_value()
}
fn val_names(&self) -> Option<&VecMap<&'e str>> {
(*self).val_names()
}
fn help(&self) -> Option<&'e str> {
(*self).help()
}
fn long_help(&self) -> Option<&'e str> {
(*self).long_help()
}
fn default_val(&self) -> Option<&'e OsStr> {
(*self).default_val()
}
fn default_vals_ifs(&self) -> Option<map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>> {
(*self).default_vals_ifs()
}
fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> {
(*self).env()
}
fn longest_filter(&self) -> bool {
(*self).longest_filter()
}
fn val_terminator(&self) -> Option<&'e str> {
(*self).val_terminator()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
use crate::args::{Arg, ArgFlags, ArgSettings};
#[derive(Debug, Clone, Default)]
pub struct Base<'a, 'b>
where
'a: 'b,
{
pub name: &'a str,
pub help: Option<&'b str>,
pub long_help: Option<&'b str>,
pub blacklist: Option<Vec<&'a str>>,
pub settings: ArgFlags,
pub r_unless: Option<Vec<&'a str>>,
pub overrides: Option<Vec<&'a str>>,
pub groups: Option<Vec<&'a str>>,
pub requires: Option<Vec<(Option<&'b str>, &'a str)>>,
}
impl<'n, 'e> Base<'n, 'e> {
pub fn new(name: &'n str) -> Self {
Base {
name,
..Default::default()
}
}
pub fn set(&mut self, s: ArgSettings) {
self.settings.set(s);
}
pub fn unset(&mut self, s: ArgSettings) {
self.settings.unset(s);
}
pub fn is_set(&self, s: ArgSettings) -> bool {
self.settings.is_set(s)
}
}
impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Base<'n, 'e> {
fn from(a: &'z Arg<'n, 'e>) -> Self {
a.b.clone()
}
}
impl<'n, 'e> PartialEq for Base<'n, 'e> {
fn eq(&self, other: &Base<'n, 'e>) -> bool {
self.name == other.name
}
}

View File

@@ -0,0 +1,216 @@
// Std
use std::{
convert::From,
ffi::{OsStr, OsString},
fmt::{Display, Formatter, Result},
mem,
rc::Rc,
result::Result as StdResult,
};
// Internal
use crate::{
args::{AnyArg, Arg, ArgSettings, Base, DispOrder, Switched},
map::{self, VecMap},
};
#[derive(Default, Clone, Debug)]
#[doc(hidden)]
pub struct FlagBuilder<'n, 'e>
where
'n: 'e,
{
pub b: Base<'n, 'e>,
pub s: Switched<'e>,
}
impl<'n, 'e> FlagBuilder<'n, 'e> {
pub fn new(name: &'n str) -> Self {
FlagBuilder {
b: Base::new(name),
..Default::default()
}
}
}
impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for FlagBuilder<'a, 'b> {
fn from(a: &'z Arg<'a, 'b>) -> Self {
FlagBuilder {
b: Base::from(a),
s: Switched::from(a),
}
}
}
impl<'a, 'b> From<Arg<'a, 'b>> for FlagBuilder<'a, 'b> {
fn from(mut a: Arg<'a, 'b>) -> Self {
FlagBuilder {
b: mem::take(&mut a.b),
s: mem::take(&mut a.s),
}
}
}
impl<'n, 'e> Display for FlagBuilder<'n, 'e> {
fn fmt(&self, f: &mut Formatter) -> Result {
if let Some(l) = self.s.long {
write!(f, "--{}", l)?;
} else {
write!(f, "-{}", self.s.short.unwrap())?;
}
Ok(())
}
}
impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> {
fn name(&self) -> &'n str {
self.b.name
}
fn overrides(&self) -> Option<&[&'e str]> {
self.b.overrides.as_ref().map(|o| &o[..])
}
fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> {
self.b.requires.as_ref().map(|o| &o[..])
}
fn blacklist(&self) -> Option<&[&'e str]> {
self.b.blacklist.as_ref().map(|o| &o[..])
}
fn required_unless(&self) -> Option<&[&'e str]> {
self.b.r_unless.as_ref().map(|o| &o[..])
}
fn is_set(&self, s: ArgSettings) -> bool {
self.b.settings.is_set(s)
}
fn has_switch(&self) -> bool {
true
}
fn takes_value(&self) -> bool {
false
}
fn set(&mut self, s: ArgSettings) {
self.b.settings.set(s)
}
fn max_vals(&self) -> Option<u64> {
None
}
fn val_names(&self) -> Option<&VecMap<&'e str>> {
None
}
fn num_vals(&self) -> Option<u64> {
None
}
fn possible_vals(&self) -> Option<&[&'e str]> {
None
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
fn validator(&self) -> Option<&Rc<Fn(String) -> StdResult<(), String>>> {
None
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
fn validator_os(&self) -> Option<&Rc<Fn(&OsStr) -> StdResult<(), OsString>>> {
None
}
fn min_vals(&self) -> Option<u64> {
None
}
fn short(&self) -> Option<char> {
self.s.short
}
fn long(&self) -> Option<&'e str> {
self.s.long
}
fn val_delim(&self) -> Option<char> {
None
}
fn help(&self) -> Option<&'e str> {
self.b.help
}
fn long_help(&self) -> Option<&'e str> {
self.b.long_help
}
fn val_terminator(&self) -> Option<&'e str> {
None
}
fn default_val(&self) -> Option<&'e OsStr> {
None
}
fn default_vals_ifs(&self) -> Option<map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>> {
None
}
fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> {
None
}
fn longest_filter(&self) -> bool {
self.s.long.is_some()
}
fn aliases(&self) -> Option<Vec<&'e str>> {
if let Some(ref aliases) = self.s.aliases {
let vis_aliases: Vec<_> = aliases
.iter()
.filter_map(|&(n, v)| if v { Some(n) } else { None })
.collect();
if vis_aliases.is_empty() {
None
} else {
Some(vis_aliases)
}
} else {
None
}
}
}
impl<'n, 'e> DispOrder for FlagBuilder<'n, 'e> {
fn disp_ord(&self) -> usize {
self.s.disp_ord
}
}
impl<'n, 'e> PartialEq for FlagBuilder<'n, 'e> {
fn eq(&self, other: &FlagBuilder<'n, 'e>) -> bool {
self.b == other.b
}
}
#[cfg(test)]
mod test {
use super::FlagBuilder;
use crate::args::settings::ArgSettings;
#[test]
fn flagbuilder_display() {
let mut f = FlagBuilder::new("flg");
f.b.settings.set(ArgSettings::Multiple);
f.s.long = Some("flag");
assert_eq!(&*format!("{}", f), "--flag");
let mut f2 = FlagBuilder::new("flg");
f2.s.short = Some('f');
assert_eq!(&*format!("{}", f2), "-f");
}
#[test]
fn flagbuilder_display_single_alias() {
let mut f = FlagBuilder::new("flg");
f.s.long = Some("flag");
f.s.aliases = Some(vec![("als", true)]);
assert_eq!(&*format!("{}", f), "--flag");
}
#[test]
fn flagbuilder_display_multiple_aliases() {
let mut f = FlagBuilder::new("flg");
f.s.short = Some('f');
f.s.aliases = Some(vec![
("alias_not_visible", false),
("f2", true),
("f3", true),
("f4", true),
]);
assert_eq!(&*format!("{}", f), "-f");
}
}

View File

@@ -0,0 +1,13 @@
pub use self::base::Base;
pub use self::flag::FlagBuilder;
pub use self::option::OptBuilder;
pub use self::positional::PosBuilder;
pub use self::switched::Switched;
pub use self::valued::Valued;
mod base;
mod flag;
mod option;
mod positional;
mod switched;
mod valued;

View File

@@ -0,0 +1,295 @@
// Std
use std::{
ffi::{OsStr, OsString},
fmt::{Display, Formatter, Result},
mem,
rc::Rc,
result::Result as StdResult,
};
// Internal
use crate::{
args::{AnyArg, Arg, ArgSettings, Base, DispOrder, Switched, Valued},
map::{self, VecMap},
INTERNAL_ERROR_MSG,
};
#[allow(missing_debug_implementations)]
#[doc(hidden)]
#[derive(Default, Clone)]
pub struct OptBuilder<'n, 'e>
where
'n: 'e,
{
pub b: Base<'n, 'e>,
pub s: Switched<'e>,
pub v: Valued<'n, 'e>,
}
impl<'n, 'e> OptBuilder<'n, 'e> {
pub fn new(name: &'n str) -> Self {
OptBuilder {
b: Base::new(name),
..Default::default()
}
}
}
impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for OptBuilder<'n, 'e> {
fn from(a: &'z Arg<'n, 'e>) -> Self {
OptBuilder {
b: Base::from(a),
s: Switched::from(a),
v: Valued::from(a),
}
}
}
impl<'n, 'e> From<Arg<'n, 'e>> for OptBuilder<'n, 'e> {
fn from(mut a: Arg<'n, 'e>) -> Self {
a.v.fill_in();
OptBuilder {
b: mem::take(&mut a.b),
s: mem::take(&mut a.s),
v: mem::take(&mut a.v),
}
}
}
impl<'n, 'e> Display for OptBuilder<'n, 'e> {
fn fmt(&self, f: &mut Formatter) -> Result {
debugln!("OptBuilder::fmt:{}", self.b.name);
let sep = if self.b.is_set(ArgSettings::RequireEquals) {
"="
} else {
" "
};
// Write the name such --long or -l
if let Some(l) = self.s.long {
write!(f, "--{}{}", l, sep)?;
} else {
write!(f, "-{}{}", self.s.short.unwrap(), sep)?;
}
let delim = if self.is_set(ArgSettings::RequireDelimiter) {
self.v.val_delim.expect(INTERNAL_ERROR_MSG)
} else {
' '
};
// Write the values such as <name1> <name2>
if let Some(ref vec) = self.v.val_names {
let mut it = vec.iter().peekable();
while let Some((_, val)) = it.next() {
write!(f, "<{}>", val)?;
if it.peek().is_some() {
write!(f, "{}", delim)?;
}
}
let num = vec.len();
if self.is_set(ArgSettings::Multiple) && num == 1 {
write!(f, "...")?;
}
} else if let Some(num) = self.v.num_vals {
let mut it = (0..num).peekable();
while let Some(_) = it.next() {
write!(f, "<{}>", self.b.name)?;
if it.peek().is_some() {
write!(f, "{}", delim)?;
}
}
if self.is_set(ArgSettings::Multiple) && num == 1 {
write!(f, "...")?;
}
} else {
write!(
f,
"<{}>{}",
self.b.name,
if self.is_set(ArgSettings::Multiple) {
"..."
} else {
""
}
)?;
}
Ok(())
}
}
impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> {
fn name(&self) -> &'n str {
self.b.name
}
fn overrides(&self) -> Option<&[&'e str]> {
self.b.overrides.as_ref().map(|o| &o[..])
}
fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> {
self.b.requires.as_ref().map(|o| &o[..])
}
fn blacklist(&self) -> Option<&[&'e str]> {
self.b.blacklist.as_ref().map(|o| &o[..])
}
fn required_unless(&self) -> Option<&[&'e str]> {
self.b.r_unless.as_ref().map(|o| &o[..])
}
fn val_names(&self) -> Option<&VecMap<&'e str>> {
self.v.val_names.as_ref()
}
fn is_set(&self, s: ArgSettings) -> bool {
self.b.settings.is_set(s)
}
fn has_switch(&self) -> bool {
true
}
fn set(&mut self, s: ArgSettings) {
self.b.settings.set(s)
}
fn max_vals(&self) -> Option<u64> {
self.v.max_vals
}
fn val_terminator(&self) -> Option<&'e str> {
self.v.terminator
}
fn num_vals(&self) -> Option<u64> {
self.v.num_vals
}
fn possible_vals(&self) -> Option<&[&'e str]> {
self.v.possible_vals.as_ref().map(|o| &o[..])
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
fn validator(&self) -> Option<&Rc<Fn(String) -> StdResult<(), String>>> {
self.v.validator.as_ref()
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
fn validator_os(&self) -> Option<&Rc<Fn(&OsStr) -> StdResult<(), OsString>>> {
self.v.validator_os.as_ref()
}
fn min_vals(&self) -> Option<u64> {
self.v.min_vals
}
fn short(&self) -> Option<char> {
self.s.short
}
fn long(&self) -> Option<&'e str> {
self.s.long
}
fn val_delim(&self) -> Option<char> {
self.v.val_delim
}
fn takes_value(&self) -> bool {
true
}
fn help(&self) -> Option<&'e str> {
self.b.help
}
fn long_help(&self) -> Option<&'e str> {
self.b.long_help
}
fn default_val(&self) -> Option<&'e OsStr> {
self.v.default_val
}
fn default_vals_ifs(&self) -> Option<map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>> {
self.v.default_vals_ifs.as_ref().map(|vm| vm.values())
}
fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> {
self.v
.env
.as_ref()
.map(|&(key, ref value)| (key, value.as_ref()))
}
fn longest_filter(&self) -> bool {
true
}
fn aliases(&self) -> Option<Vec<&'e str>> {
if let Some(ref aliases) = self.s.aliases {
let vis_aliases: Vec<_> = aliases
.iter()
.filter_map(|&(n, v)| if v { Some(n) } else { None })
.collect();
if vis_aliases.is_empty() {
None
} else {
Some(vis_aliases)
}
} else {
None
}
}
}
impl<'n, 'e> DispOrder for OptBuilder<'n, 'e> {
fn disp_ord(&self) -> usize {
self.s.disp_ord
}
}
impl<'n, 'e> PartialEq for OptBuilder<'n, 'e> {
fn eq(&self, other: &OptBuilder<'n, 'e>) -> bool {
self.b == other.b
}
}
#[cfg(test)]
mod test {
use super::OptBuilder;
use crate::{args::settings::ArgSettings, map::VecMap};
#[test]
fn optbuilder_display1() {
let mut o = OptBuilder::new("opt");
o.s.long = Some("option");
o.b.settings.set(ArgSettings::Multiple);
assert_eq!(&*format!("{}", o), "--option <opt>...");
}
#[test]
fn optbuilder_display2() {
let mut v_names = VecMap::new();
v_names.insert(0, "file");
v_names.insert(1, "name");
let mut o2 = OptBuilder::new("opt");
o2.s.short = Some('o');
o2.v.val_names = Some(v_names);
assert_eq!(&*format!("{}", o2), "-o <file> <name>");
}
#[test]
fn optbuilder_display3() {
let mut v_names = VecMap::new();
v_names.insert(0, "file");
v_names.insert(1, "name");
let mut o2 = OptBuilder::new("opt");
o2.s.short = Some('o');
o2.v.val_names = Some(v_names);
o2.b.settings.set(ArgSettings::Multiple);
assert_eq!(&*format!("{}", o2), "-o <file> <name>");
}
#[test]
fn optbuilder_display_single_alias() {
let mut o = OptBuilder::new("opt");
o.s.long = Some("option");
o.s.aliases = Some(vec![("als", true)]);
assert_eq!(&*format!("{}", o), "--option <opt>");
}
#[test]
fn optbuilder_display_multiple_aliases() {
let mut o = OptBuilder::new("opt");
o.s.long = Some("option");
o.s.aliases = Some(vec![
("als_not_visible", false),
("als2", true),
("als3", true),
("als4", true),
]);
assert_eq!(&*format!("{}", o), "--option <opt>");
}
}

View File

@@ -0,0 +1,284 @@
// Std
use std::{
borrow::Cow,
ffi::{OsStr, OsString},
fmt::{Display, Formatter, Result},
mem,
rc::Rc,
result::Result as StdResult,
};
// Internal
use crate::{
args::{AnyArg, Arg, ArgSettings, Base, DispOrder, Valued},
map::{self, VecMap},
INTERNAL_ERROR_MSG,
};
#[allow(missing_debug_implementations)]
#[doc(hidden)]
#[derive(Clone, Default)]
pub struct PosBuilder<'n, 'e>
where
'n: 'e,
{
pub b: Base<'n, 'e>,
pub v: Valued<'n, 'e>,
pub index: u64,
}
impl<'n, 'e> PosBuilder<'n, 'e> {
pub fn new(name: &'n str, idx: u64) -> Self {
PosBuilder {
b: Base::new(name),
index: idx,
..Default::default()
}
}
pub fn from_arg_ref(a: &Arg<'n, 'e>, idx: u64) -> Self {
let mut pb = PosBuilder {
b: Base::from(a),
v: Valued::from(a),
index: idx,
};
if a.v.max_vals.is_some()
|| a.v.min_vals.is_some()
|| (a.v.num_vals.is_some() && a.v.num_vals.unwrap() > 1)
{
pb.b.settings.set(ArgSettings::Multiple);
}
pb
}
pub fn from_arg(mut a: Arg<'n, 'e>, idx: u64) -> Self {
if a.v.max_vals.is_some()
|| a.v.min_vals.is_some()
|| (a.v.num_vals.is_some() && a.v.num_vals.unwrap() > 1)
{
a.b.settings.set(ArgSettings::Multiple);
}
PosBuilder {
b: mem::take(&mut a.b),
v: mem::take(&mut a.v),
index: idx,
}
}
pub fn multiple_str(&self) -> &str {
let mult_vals = self
.v
.val_names
.as_ref()
.map_or(true, |names| names.len() < 2);
if self.is_set(ArgSettings::Multiple) && mult_vals {
"..."
} else {
""
}
}
pub fn name_no_brackets(&self) -> Cow<str> {
debugln!("PosBuilder::name_no_brackets;");
let mut delim = String::new();
delim.push(if self.is_set(ArgSettings::RequireDelimiter) {
self.v.val_delim.expect(INTERNAL_ERROR_MSG)
} else {
' '
});
if let Some(ref names) = self.v.val_names {
debugln!("PosBuilder:name_no_brackets: val_names={:#?}", names);
if names.len() > 1 {
Cow::Owned(
names
.values()
.map(|n| format!("<{}>", n))
.collect::<Vec<_>>()
.join(&*delim),
)
} else {
Cow::Borrowed(names.values().next().expect(INTERNAL_ERROR_MSG))
}
} else {
debugln!("PosBuilder:name_no_brackets: just name");
Cow::Borrowed(self.b.name)
}
}
}
impl<'n, 'e> Display for PosBuilder<'n, 'e> {
fn fmt(&self, f: &mut Formatter) -> Result {
let mut delim = String::new();
delim.push(if self.is_set(ArgSettings::RequireDelimiter) {
self.v.val_delim.expect(INTERNAL_ERROR_MSG)
} else {
' '
});
if let Some(ref names) = self.v.val_names {
write!(
f,
"{}",
names
.values()
.map(|n| format!("<{}>", n))
.collect::<Vec<_>>()
.join(&*delim)
)?;
} else {
write!(f, "<{}>", self.b.name)?;
}
if self.b.settings.is_set(ArgSettings::Multiple)
&& (self.v.val_names.is_none() || self.v.val_names.as_ref().unwrap().len() == 1)
{
write!(f, "...")?;
}
Ok(())
}
}
impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> {
fn name(&self) -> &'n str {
self.b.name
}
fn overrides(&self) -> Option<&[&'e str]> {
self.b.overrides.as_ref().map(|o| &o[..])
}
fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> {
self.b.requires.as_ref().map(|o| &o[..])
}
fn blacklist(&self) -> Option<&[&'e str]> {
self.b.blacklist.as_ref().map(|o| &o[..])
}
fn required_unless(&self) -> Option<&[&'e str]> {
self.b.r_unless.as_ref().map(|o| &o[..])
}
fn val_names(&self) -> Option<&VecMap<&'e str>> {
self.v.val_names.as_ref()
}
fn is_set(&self, s: ArgSettings) -> bool {
self.b.settings.is_set(s)
}
fn set(&mut self, s: ArgSettings) {
self.b.settings.set(s)
}
fn has_switch(&self) -> bool {
false
}
fn max_vals(&self) -> Option<u64> {
self.v.max_vals
}
fn val_terminator(&self) -> Option<&'e str> {
self.v.terminator
}
fn num_vals(&self) -> Option<u64> {
self.v.num_vals
}
fn possible_vals(&self) -> Option<&[&'e str]> {
self.v.possible_vals.as_ref().map(|o| &o[..])
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
fn validator(&self) -> Option<&Rc<Fn(String) -> StdResult<(), String>>> {
self.v.validator.as_ref()
}
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
fn validator_os(&self) -> Option<&Rc<Fn(&OsStr) -> StdResult<(), OsString>>> {
self.v.validator_os.as_ref()
}
fn min_vals(&self) -> Option<u64> {
self.v.min_vals
}
fn short(&self) -> Option<char> {
None
}
fn long(&self) -> Option<&'e str> {
None
}
fn val_delim(&self) -> Option<char> {
self.v.val_delim
}
fn takes_value(&self) -> bool {
true
}
fn help(&self) -> Option<&'e str> {
self.b.help
}
fn long_help(&self) -> Option<&'e str> {
self.b.long_help
}
fn default_vals_ifs(&self) -> Option<map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>> {
self.v.default_vals_ifs.as_ref().map(|vm| vm.values())
}
fn default_val(&self) -> Option<&'e OsStr> {
self.v.default_val
}
fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> {
self.v
.env
.as_ref()
.map(|&(key, ref value)| (key, value.as_ref()))
}
fn longest_filter(&self) -> bool {
true
}
fn aliases(&self) -> Option<Vec<&'e str>> {
None
}
}
impl<'n, 'e> DispOrder for PosBuilder<'n, 'e> {
fn disp_ord(&self) -> usize {
self.index as usize
}
}
impl<'n, 'e> PartialEq for PosBuilder<'n, 'e> {
fn eq(&self, other: &PosBuilder<'n, 'e>) -> bool {
self.b == other.b
}
}
#[cfg(test)]
mod test {
use super::PosBuilder;
use crate::{args::settings::ArgSettings, map::VecMap};
#[test]
fn display_mult() {
let mut p = PosBuilder::new("pos", 1);
p.b.settings.set(ArgSettings::Multiple);
assert_eq!(&*format!("{}", p), "<pos>...");
}
#[test]
fn display_required() {
let mut p2 = PosBuilder::new("pos", 1);
p2.b.settings.set(ArgSettings::Required);
assert_eq!(&*format!("{}", p2), "<pos>");
}
#[test]
fn display_val_names() {
let mut p2 = PosBuilder::new("pos", 1);
let mut vm = VecMap::new();
vm.insert(0, "file1");
vm.insert(1, "file2");
p2.v.val_names = Some(vm);
assert_eq!(&*format!("{}", p2), "<file1> <file2>");
}
#[test]
fn display_val_names_req() {
let mut p2 = PosBuilder::new("pos", 1);
p2.b.settings.set(ArgSettings::Required);
let mut vm = VecMap::new();
vm.insert(0, "file1");
vm.insert(1, "file2");
p2.v.val_names = Some(vm);
assert_eq!(&*format!("{}", p2), "<file1> <file2>");
}
}

View File

@@ -0,0 +1,40 @@
use crate::Arg;
#[derive(Debug)]
pub struct Switched<'b> {
pub short: Option<char>,
pub long: Option<&'b str>,
pub aliases: Option<Vec<(&'b str, bool)>>, // (name, visible)
pub disp_ord: usize,
pub unified_ord: usize,
}
impl<'e> Default for Switched<'e> {
fn default() -> Self {
Switched {
short: None,
long: None,
aliases: None,
disp_ord: 999,
unified_ord: 999,
}
}
}
impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Switched<'e> {
fn from(a: &'z Arg<'n, 'e>) -> Self {
a.s.clone()
}
}
impl<'e> Clone for Switched<'e> {
fn clone(&self) -> Self {
Switched {
short: self.short,
long: self.long,
aliases: self.aliases.clone(),
disp_ord: self.disp_ord,
unified_ord: self.unified_ord,
}
}
}

View File

@@ -0,0 +1,70 @@
use std::{
ffi::{OsStr, OsString},
rc::Rc,
};
use crate::{map::VecMap, Arg};
#[allow(missing_debug_implementations)]
#[derive(Clone)]
pub struct Valued<'a, 'b>
where
'a: 'b,
{
pub possible_vals: Option<Vec<&'b str>>,
pub val_names: Option<VecMap<&'b str>>,
pub num_vals: Option<u64>,
pub max_vals: Option<u64>,
pub min_vals: Option<u64>,
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
pub validator: Option<Rc<Fn(String) -> Result<(), String>>>,
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
pub validator_os: Option<Rc<Fn(&OsStr) -> Result<(), OsString>>>,
pub val_delim: Option<char>,
pub default_val: Option<&'b OsStr>,
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
pub default_vals_ifs: Option<VecMap<(&'a str, Option<&'b OsStr>, &'b OsStr)>>,
pub env: Option<(&'a OsStr, Option<OsString>)>,
pub terminator: Option<&'b str>,
}
impl<'n, 'e> Default for Valued<'n, 'e> {
fn default() -> Self {
Valued {
possible_vals: None,
num_vals: None,
min_vals: None,
max_vals: None,
val_names: None,
validator: None,
validator_os: None,
val_delim: None,
default_val: None,
default_vals_ifs: None,
env: None,
terminator: None,
}
}
}
impl<'n, 'e> Valued<'n, 'e> {
pub fn fill_in(&mut self) {
if let Some(ref vec) = self.val_names {
if vec.len() > 1 {
self.num_vals = Some(vec.len() as u64);
}
}
}
}
impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Valued<'n, 'e> {
fn from(a: &'z Arg<'n, 'e>) -> Self {
let mut v = a.v.clone();
if let Some(ref vec) = a.v.val_names {
if vec.len() > 1 {
v.num_vals = Some(vec.len() as u64);
}
}
v
}
}

View File

@@ -0,0 +1,274 @@
// Std
use std::{
collections::{
hash_map::{Entry, Iter},
HashMap,
},
ffi::OsStr,
mem,
ops::Deref,
};
// Internal
use crate::args::{settings::ArgSettings, AnyArg, ArgMatches, MatchedArg, SubCommand};
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ArgMatcher<'a>(pub ArgMatches<'a>);
impl<'a> Default for ArgMatcher<'a> {
fn default() -> Self {
ArgMatcher(ArgMatches::default())
}
}
impl<'a> ArgMatcher<'a> {
pub fn new() -> Self {
ArgMatcher::default()
}
pub fn process_arg_overrides<'b>(
&mut self,
a: Option<&AnyArg<'a, 'b>>,
overrides: &mut Vec<(&'b str, &'a str)>,
required: &mut Vec<&'a str>,
check_all: bool,
) {
debugln!(
"ArgMatcher::process_arg_overrides:{:?};",
a.map_or(None, |a| Some(a.name()))
);
if let Some(aa) = a {
let mut self_done = false;
if let Some(a_overrides) = aa.overrides() {
for overr in a_overrides {
debugln!("ArgMatcher::process_arg_overrides:iter:{};", overr);
if overr == &aa.name() {
self_done = true;
self.handle_self_overrides(a);
} else if self.is_present(overr) {
debugln!(
"ArgMatcher::process_arg_overrides:iter:{}: removing from matches;",
overr
);
self.remove(overr);
for i in (0..required.len()).rev() {
if &required[i] == overr {
debugln!(
"ArgMatcher::process_arg_overrides:iter:{}: removing required;",
overr
);
required.swap_remove(i);
break;
}
}
overrides.push((overr, aa.name()));
} else {
overrides.push((overr, aa.name()));
}
}
}
if check_all && !self_done {
self.handle_self_overrides(a);
}
}
}
pub fn handle_self_overrides<'b>(&mut self, a: Option<&AnyArg<'a, 'b>>) {
debugln!(
"ArgMatcher::handle_self_overrides:{:?};",
a.map_or(None, |a| Some(a.name()))
);
if let Some(aa) = a {
if !aa.has_switch() || aa.is_set(ArgSettings::Multiple) {
// positional args can't override self or else we would never advance to the next
// Also flags with --multiple set are ignored otherwise we could never have more
// than one
return;
}
if let Some(ma) = self.get_mut(aa.name()) {
if ma.vals.len() > 1 {
// swap_remove(0) would be O(1) but does not preserve order, which
// we need
ma.vals.remove(0);
ma.occurs = 1;
} else if !aa.takes_value() && ma.occurs > 1 {
ma.occurs = 1;
}
}
}
}
pub fn is_present(&self, name: &str) -> bool {
self.0.is_present(name)
}
pub fn propagate_globals(&mut self, global_arg_vec: &[&'a str]) {
debugln!(
"ArgMatcher::get_global_values: global_arg_vec={:?}",
global_arg_vec
);
let mut vals_map = HashMap::new();
self.fill_in_global_values(global_arg_vec, &mut vals_map);
}
fn fill_in_global_values(
&mut self,
global_arg_vec: &[&'a str],
vals_map: &mut HashMap<&'a str, MatchedArg>,
) {
for global_arg in global_arg_vec {
if let Some(ma) = self.get(global_arg) {
// We have to check if the parent's global arg wasn't used but still exists
// such as from a default value.
//
// For example, `myprog subcommand --global-arg=value` where --global-arg defines
// a default value of `other` myprog would have an existing MatchedArg for
// --global-arg where the value is `other`, however the occurs will be 0.
let to_update = if let Some(parent_ma) = vals_map.get(global_arg) {
if parent_ma.occurs > 0 && ma.occurs == 0 {
parent_ma.clone()
} else {
ma.clone()
}
} else {
ma.clone()
};
vals_map.insert(global_arg, to_update);
}
}
if let Some(ref mut sc) = self.0.subcommand {
let mut am = ArgMatcher(mem::replace(&mut sc.matches, ArgMatches::new()));
am.fill_in_global_values(global_arg_vec, vals_map);
mem::swap(&mut am.0, &mut sc.matches);
}
for (name, matched_arg) in vals_map.iter_mut() {
self.0.args.insert(name, matched_arg.clone());
}
}
pub fn get_mut(&mut self, arg: &str) -> Option<&mut MatchedArg> {
self.0.args.get_mut(arg)
}
pub fn get(&self, arg: &str) -> Option<&MatchedArg> {
self.0.args.get(arg)
}
pub fn remove(&mut self, arg: &str) {
self.0.args.remove(arg);
}
pub fn remove_all(&mut self, args: &[&str]) {
for &arg in args {
self.0.args.remove(arg);
}
}
pub fn insert(&mut self, name: &'a str) {
self.0.args.insert(name, MatchedArg::new());
}
pub fn contains(&self, arg: &str) -> bool {
self.0.args.contains_key(arg)
}
pub fn is_empty(&self) -> bool {
self.0.args.is_empty()
}
pub fn usage(&mut self, usage: String) {
self.0.usage = Some(usage);
}
pub fn arg_names(&'a self) -> Vec<&'a str> {
self.0.args.keys().map(Deref::deref).collect()
}
pub fn entry(&mut self, arg: &'a str) -> Entry<&'a str, MatchedArg> {
self.0.args.entry(arg)
}
pub fn subcommand(&mut self, sc: SubCommand<'a>) {
self.0.subcommand = Some(Box::new(sc));
}
pub fn subcommand_name(&self) -> Option<&str> {
self.0.subcommand_name()
}
pub fn iter(&self) -> Iter<&str, MatchedArg> {
self.0.args.iter()
}
pub fn inc_occurrence_of(&mut self, arg: &'a str) {
debugln!("ArgMatcher::inc_occurrence_of: arg={}", arg);
if let Some(a) = self.get_mut(arg) {
a.occurs += 1;
return;
}
debugln!("ArgMatcher::inc_occurrence_of: first instance");
self.insert(arg);
}
pub fn inc_occurrences_of(&mut self, args: &[&'a str]) {
debugln!("ArgMatcher::inc_occurrences_of: args={:?}", args);
for arg in args {
self.inc_occurrence_of(arg);
}
}
pub fn add_val_to(&mut self, arg: &'a str, val: &OsStr) {
let ma = self.entry(arg).or_insert(MatchedArg {
occurs: 0,
indices: Vec::with_capacity(1),
vals: Vec::with_capacity(1),
});
ma.vals.push(val.to_owned());
}
pub fn add_index_to(&mut self, arg: &'a str, idx: usize) {
let ma = self.entry(arg).or_insert(MatchedArg {
occurs: 0,
indices: Vec::with_capacity(1),
vals: Vec::new(),
});
ma.indices.push(idx);
}
pub fn needs_more_vals<'b, A>(&self, o: &A) -> bool
where
A: AnyArg<'a, 'b>,
{
debugln!("ArgMatcher::needs_more_vals: o={}", o.name());
if let Some(ma) = self.get(o.name()) {
if let Some(num) = o.num_vals() {
debugln!("ArgMatcher::needs_more_vals: num_vals...{}", num);
return if o.is_set(ArgSettings::Multiple) {
((ma.vals.len() as u64) % num) != 0
} else {
num != (ma.vals.len() as u64)
};
} else if let Some(num) = o.max_vals() {
debugln!("ArgMatcher::needs_more_vals: max_vals...{}", num);
return (ma.vals.len() as u64) <= num;
} else if o.min_vals().is_some() {
debugln!("ArgMatcher::needs_more_vals: min_vals...true");
return true;
}
return o.is_set(ArgSettings::Multiple);
}
true
}
}
// Not changing to From just to not deal with possible breaking changes on v2 since v3 is coming
// in the future anyways
#[cfg_attr(feature = "cargo-clippy", allow(clippy::from_over_into))]
impl<'a> Into<ArgMatches<'a>> for ArgMatcher<'a> {
fn into(self) -> ArgMatches<'a> {
self.0
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,637 @@
#[cfg(feature = "yaml")]
use std::collections::BTreeMap;
use std::fmt::{Debug, Formatter, Result};
#[cfg(feature = "yaml")]
use yaml_rust::Yaml;
/// `ArgGroup`s are a family of related [arguments] and way for you to express, "Any of these
/// arguments". By placing arguments in a logical group, you can create easier requirement and
/// exclusion rules instead of having to list each argument individually, or when you want a rule
/// to apply "any but not all" arguments.
///
/// For instance, you can make an entire `ArgGroup` required. If [`ArgGroup::multiple(true)`] is
/// set, this means that at least one argument from that group must be present. If
/// [`ArgGroup::multiple(false)`] is set (the default), one and *only* one must be present.
///
/// You can also do things such as name an entire `ArgGroup` as a [conflict] or [requirement] for
/// another argument, meaning any of the arguments that belong to that group will cause a failure
/// if present, or must present respectively.
///
/// Perhaps the most common use of `ArgGroup`s is to require one and *only* one argument to be
/// present out of a given set. Imagine that you had multiple arguments, and you want one of them
/// to be required, but making all of them required isn't feasible because perhaps they conflict
/// with each other. For example, lets say that you were building an application where one could
/// set a given version number by supplying a string with an option argument, i.e.
/// `--set-ver v1.2.3`, you also wanted to support automatically using a previous version number
/// and simply incrementing one of the three numbers. So you create three flags `--major`,
/// `--minor`, and `--patch`. All of these arguments shouldn't be used at one time but you want to
/// specify that *at least one* of them is used. For this, you can create a group.
///
/// Finally, you may use `ArgGroup`s to pull a value from a group of arguments when you don't care
/// exactly which argument was actually used at runtime.
///
/// # Examples
///
/// The following example demonstrates using an `ArgGroup` to ensure that one, and only one, of
/// the arguments from the specified group is present at runtime.
///
/// ```rust
/// # use clap::{App, ArgGroup, ErrorKind};
/// let result = App::new("app")
/// .args_from_usage(
/// "--set-ver [ver] 'set the version manually'
/// --major 'auto increase major'
/// --minor 'auto increase minor'
/// --patch 'auto increase patch'")
/// .group(ArgGroup::with_name("vers")
/// .args(&["set-ver", "major", "minor", "patch"])
/// .required(true))
/// .get_matches_from_safe(vec!["app", "--major", "--patch"]);
/// // Because we used two args in the group it's an error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind, ErrorKind::ArgumentConflict);
/// ```
/// This next example shows a passing parse of the same scenario
///
/// ```rust
/// # use clap::{App, ArgGroup};
/// let result = App::new("app")
/// .args_from_usage(
/// "--set-ver [ver] 'set the version manually'
/// --major 'auto increase major'
/// --minor 'auto increase minor'
/// --patch 'auto increase patch'")
/// .group(ArgGroup::with_name("vers")
/// .args(&["set-ver", "major", "minor","patch"])
/// .required(true))
/// .get_matches_from_safe(vec!["app", "--major"]);
/// assert!(result.is_ok());
/// let matches = result.unwrap();
/// // We may not know which of the args was used, so we can test for the group...
/// assert!(matches.is_present("vers"));
/// // we could also alternatively check each arg individually (not shown here)
/// ```
/// [`ArgGroup::multiple(true)`]: ./struct.ArgGroup.html#method.multiple
/// [arguments]: ./struct.Arg.html
/// [conflict]: ./struct.Arg.html#method.conflicts_with
/// [requirement]: ./struct.Arg.html#method.requires
#[derive(Default)]
pub struct ArgGroup<'a> {
#[doc(hidden)]
pub name: &'a str,
#[doc(hidden)]
pub args: Vec<&'a str>,
#[doc(hidden)]
pub required: bool,
#[doc(hidden)]
pub requires: Option<Vec<&'a str>>,
#[doc(hidden)]
pub conflicts: Option<Vec<&'a str>>,
#[doc(hidden)]
pub multiple: bool,
}
impl<'a> ArgGroup<'a> {
/// Creates a new instance of `ArgGroup` using a unique string name. The name will be used to
/// get values from the group or refer to the group inside of conflict and requirement rules.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, ArgGroup};
/// ArgGroup::with_name("config")
/// # ;
/// ```
pub fn with_name(n: &'a str) -> Self {
ArgGroup {
name: n,
required: false,
args: vec![],
requires: None,
conflicts: None,
multiple: false,
}
}
/// Creates a new instance of `ArgGroup` from a .yml (YAML) file.
///
/// # Examples
///
/// ```ignore
/// # #[macro_use]
/// # extern crate clap;
/// # use clap::ArgGroup;
/// # fn main() {
/// let yml = load_yaml!("group.yml");
/// let ag = ArgGroup::from_yaml(yml);
/// # }
/// ```
#[cfg(feature = "yaml")]
pub fn from_yaml(y: &'a Yaml) -> ArgGroup<'a> {
ArgGroup::from(y.as_hash().unwrap())
}
/// Adds an [argument] to this group by name
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ArgGroup};
/// let m = App::new("myprog")
/// .arg(Arg::with_name("flag")
/// .short("f"))
/// .arg(Arg::with_name("color")
/// .short("c"))
/// .group(ArgGroup::with_name("req_flags")
/// .arg("flag")
/// .arg("color"))
/// .get_matches_from(vec!["myprog", "-f"]);
/// // maybe we don't know which of the two flags was used...
/// assert!(m.is_present("req_flags"));
/// // but we can also check individually if needed
/// assert!(m.is_present("flag"));
/// ```
/// [argument]: ./struct.Arg.html
pub fn arg(mut self, n: &'a str) -> Self {
assert!(
self.name != n,
"ArgGroup '{}' can not have same name as arg inside it",
&*self.name
);
self.args.push(n);
self
}
/// Adds multiple [arguments] to this group by name
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ArgGroup};
/// let m = App::new("myprog")
/// .arg(Arg::with_name("flag")
/// .short("f"))
/// .arg(Arg::with_name("color")
/// .short("c"))
/// .group(ArgGroup::with_name("req_flags")
/// .args(&["flag", "color"]))
/// .get_matches_from(vec!["myprog", "-f"]);
/// // maybe we don't know which of the two flags was used...
/// assert!(m.is_present("req_flags"));
/// // but we can also check individually if needed
/// assert!(m.is_present("flag"));
/// ```
/// [arguments]: ./struct.Arg.html
pub fn args(mut self, ns: &[&'a str]) -> Self {
for n in ns {
self = self.arg(n);
}
self
}
/// Allows more than one of the ['Arg']s in this group to be used. (Default: `false`)
///
/// # Examples
///
/// Notice in this example we use *both* the `-f` and `-c` flags which are both part of the
/// group
///
/// ```rust
/// # use clap::{App, Arg, ArgGroup};
/// let m = App::new("myprog")
/// .arg(Arg::with_name("flag")
/// .short("f"))
/// .arg(Arg::with_name("color")
/// .short("c"))
/// .group(ArgGroup::with_name("req_flags")
/// .args(&["flag", "color"])
/// .multiple(true))
/// .get_matches_from(vec!["myprog", "-f", "-c"]);
/// // maybe we don't know which of the two flags was used...
/// assert!(m.is_present("req_flags"));
/// ```
/// In this next example, we show the default behavior (i.e. `multiple(false)) which will throw
/// an error if more than one of the args in the group was used.
///
/// ```rust
/// # use clap::{App, Arg, ArgGroup, ErrorKind};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("flag")
/// .short("f"))
/// .arg(Arg::with_name("color")
/// .short("c"))
/// .group(ArgGroup::with_name("req_flags")
/// .args(&["flag", "color"]))
/// .get_matches_from_safe(vec!["myprog", "-f", "-c"]);
/// // Because we used both args in the group it's an error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind, ErrorKind::ArgumentConflict);
/// ```
/// ['Arg']: ./struct.Arg.html
pub fn multiple(mut self, m: bool) -> Self {
self.multiple = m;
self
}
/// Sets the group as required or not. A required group will be displayed in the usage string
/// of the application in the format `<arg|arg2|arg3>`. A required `ArgGroup` simply states
/// that one argument from this group *must* be present at runtime (unless
/// conflicting with another argument).
///
/// **NOTE:** This setting only applies to the current [`App`] / [`SubCommand`], and not
/// globally.
///
/// **NOTE:** By default, [`ArgGroup::multiple`] is set to `false` which when combined with
/// `ArgGroup::required(true)` states, "One and *only one* arg must be used from this group.
/// Use of more than one arg is an error." Vice setting `ArgGroup::multiple(true)` which
/// states, '*At least* one arg from this group must be used. Using multiple is OK."
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ArgGroup, ErrorKind};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("flag")
/// .short("f"))
/// .arg(Arg::with_name("color")
/// .short("c"))
/// .group(ArgGroup::with_name("req_flags")
/// .args(&["flag", "color"])
/// .required(true))
/// .get_matches_from_safe(vec!["myprog"]);
/// // Because we didn't use any of the args in the group, it's an error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind, ErrorKind::MissingRequiredArgument);
/// ```
/// [`App`]: ./struct.App.html
/// [`SubCommand`]: ./struct.SubCommand.html
/// [`ArgGroup::multiple`]: ./struct.ArgGroup.html#method.multiple
pub fn required(mut self, r: bool) -> Self {
self.required = r;
self
}
/// Sets the requirement rules of this group. This is not to be confused with a
/// [required group]. Requirement rules function just like [argument requirement rules], you
/// can name other arguments or groups that must be present when any one of the arguments from
/// this group is used.
///
/// **NOTE:** The name provided may be an argument, or group name
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ArgGroup, ErrorKind};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("flag")
/// .short("f"))
/// .arg(Arg::with_name("color")
/// .short("c"))
/// .arg(Arg::with_name("debug")
/// .short("d"))
/// .group(ArgGroup::with_name("req_flags")
/// .args(&["flag", "color"])
/// .requires("debug"))
/// .get_matches_from_safe(vec!["myprog", "-c"]);
/// // because we used an arg from the group, and the group requires "-d" to be used, it's an
/// // error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind, ErrorKind::MissingRequiredArgument);
/// ```
/// [required group]: ./struct.ArgGroup.html#method.required
/// [argument requirement rules]: ./struct.Arg.html#method.requires
pub fn requires(mut self, n: &'a str) -> Self {
if let Some(ref mut reqs) = self.requires {
reqs.push(n);
} else {
self.requires = Some(vec![n]);
}
self
}
/// Sets the requirement rules of this group. This is not to be confused with a
/// [required group]. Requirement rules function just like [argument requirement rules], you
/// can name other arguments or groups that must be present when one of the arguments from this
/// group is used.
///
/// **NOTE:** The names provided may be an argument, or group name
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ArgGroup, ErrorKind};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("flag")
/// .short("f"))
/// .arg(Arg::with_name("color")
/// .short("c"))
/// .arg(Arg::with_name("debug")
/// .short("d"))
/// .arg(Arg::with_name("verb")
/// .short("v"))
/// .group(ArgGroup::with_name("req_flags")
/// .args(&["flag", "color"])
/// .requires_all(&["debug", "verb"]))
/// .get_matches_from_safe(vec!["myprog", "-c", "-d"]);
/// // because we used an arg from the group, and the group requires "-d" and "-v" to be used,
/// // yet we only used "-d" it's an error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind, ErrorKind::MissingRequiredArgument);
/// ```
/// [required group]: ./struct.ArgGroup.html#method.required
/// [argument requirement rules]: ./struct.Arg.html#method.requires_all
pub fn requires_all(mut self, ns: &[&'a str]) -> Self {
for n in ns {
self = self.requires(n);
}
self
}
/// Sets the exclusion rules of this group. Exclusion (aka conflict) rules function just like
/// [argument exclusion rules], you can name other arguments or groups that must *not* be
/// present when one of the arguments from this group are used.
///
/// **NOTE:** The name provided may be an argument, or group name
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ArgGroup, ErrorKind};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("flag")
/// .short("f"))
/// .arg(Arg::with_name("color")
/// .short("c"))
/// .arg(Arg::with_name("debug")
/// .short("d"))
/// .group(ArgGroup::with_name("req_flags")
/// .args(&["flag", "color"])
/// .conflicts_with("debug"))
/// .get_matches_from_safe(vec!["myprog", "-c", "-d"]);
/// // because we used an arg from the group, and the group conflicts with "-d", it's an error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind, ErrorKind::ArgumentConflict);
/// ```
/// [argument exclusion rules]: ./struct.Arg.html#method.conflicts_with
pub fn conflicts_with(mut self, n: &'a str) -> Self {
if let Some(ref mut confs) = self.conflicts {
confs.push(n);
} else {
self.conflicts = Some(vec![n]);
}
self
}
/// Sets the exclusion rules of this group. Exclusion rules function just like
/// [argument exclusion rules], you can name other arguments or groups that must *not* be
/// present when one of the arguments from this group are used.
///
/// **NOTE:** The names provided may be an argument, or group name
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ArgGroup, ErrorKind};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("flag")
/// .short("f"))
/// .arg(Arg::with_name("color")
/// .short("c"))
/// .arg(Arg::with_name("debug")
/// .short("d"))
/// .arg(Arg::with_name("verb")
/// .short("v"))
/// .group(ArgGroup::with_name("req_flags")
/// .args(&["flag", "color"])
/// .conflicts_with_all(&["debug", "verb"]))
/// .get_matches_from_safe(vec!["myprog", "-c", "-v"]);
/// // because we used an arg from the group, and the group conflicts with either "-v" or "-d"
/// // it's an error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind, ErrorKind::ArgumentConflict);
/// ```
/// [argument exclusion rules]: ./struct.Arg.html#method.conflicts_with_all
pub fn conflicts_with_all(mut self, ns: &[&'a str]) -> Self {
for n in ns {
self = self.conflicts_with(n);
}
self
}
}
impl<'a> Debug for ArgGroup<'a> {
fn fmt(&self, f: &mut Formatter) -> Result {
write!(
f,
"{{\n\
\tname: {:?},\n\
\targs: {:?},\n\
\trequired: {:?},\n\
\trequires: {:?},\n\
\tconflicts: {:?},\n\
}}",
self.name, self.args, self.required, self.requires, self.conflicts
)
}
}
impl<'a, 'z> From<&'z ArgGroup<'a>> for ArgGroup<'a> {
fn from(g: &'z ArgGroup<'a>) -> Self {
ArgGroup {
name: g.name,
required: g.required,
args: g.args.clone(),
requires: g.requires.clone(),
conflicts: g.conflicts.clone(),
multiple: g.multiple,
}
}
}
#[cfg(feature = "yaml")]
impl<'a> From<&'a BTreeMap<Yaml, Yaml>> for ArgGroup<'a> {
fn from(b: &'a BTreeMap<Yaml, Yaml>) -> Self {
// We WANT this to panic on error...so expect() is good.
let mut a = ArgGroup::default();
let group_settings = if b.len() == 1 {
let name_yml = b.keys().nth(0).expect("failed to get name");
let name_str = name_yml
.as_str()
.expect("failed to convert arg YAML name to str");
a.name = name_str;
b.get(name_yml)
.expect("failed to get name_str")
.as_hash()
.expect("failed to convert to a hash")
} else {
b
};
for (k, v) in group_settings {
a = match k.as_str().unwrap() {
"required" => a.required(v.as_bool().unwrap()),
"multiple" => a.multiple(v.as_bool().unwrap()),
"args" => yaml_vec_or_str!(v, a, arg),
"arg" => {
if let Some(ys) = v.as_str() {
a = a.arg(ys);
}
a
}
"requires" => yaml_vec_or_str!(v, a, requires),
"conflicts_with" => yaml_vec_or_str!(v, a, conflicts_with),
"name" => {
if let Some(ys) = v.as_str() {
a.name = ys;
}
a
}
s => panic!(
"Unknown ArgGroup setting '{}' in YAML file for \
ArgGroup '{}'",
s, a.name
),
}
}
a
}
}
#[cfg(test)]
mod test {
use super::ArgGroup;
#[cfg(feature = "yaml")]
use yaml_rust::YamlLoader;
#[test]
fn groups() {
let g = ArgGroup::with_name("test")
.arg("a1")
.arg("a4")
.args(&["a2", "a3"])
.required(true)
.conflicts_with("c1")
.conflicts_with_all(&["c2", "c3"])
.conflicts_with("c4")
.requires("r1")
.requires_all(&["r2", "r3"])
.requires("r4");
let args = vec!["a1", "a4", "a2", "a3"];
let reqs = vec!["r1", "r2", "r3", "r4"];
let confs = vec!["c1", "c2", "c3", "c4"];
assert_eq!(g.args, args);
assert_eq!(g.requires, Some(reqs));
assert_eq!(g.conflicts, Some(confs));
}
#[test]
fn test_debug() {
let g = ArgGroup::with_name("test")
.arg("a1")
.arg("a4")
.args(&["a2", "a3"])
.required(true)
.conflicts_with("c1")
.conflicts_with_all(&["c2", "c3"])
.conflicts_with("c4")
.requires("r1")
.requires_all(&["r2", "r3"])
.requires("r4");
let args = vec!["a1", "a4", "a2", "a3"];
let reqs = vec!["r1", "r2", "r3", "r4"];
let confs = vec!["c1", "c2", "c3", "c4"];
let debug_str = format!(
"{{\n\
\tname: \"test\",\n\
\targs: {:?},\n\
\trequired: {:?},\n\
\trequires: {:?},\n\
\tconflicts: {:?},\n\
}}",
args,
true,
Some(reqs),
Some(confs)
);
assert_eq!(&*format!("{:?}", g), &*debug_str);
}
#[test]
fn test_from() {
let g = ArgGroup::with_name("test")
.arg("a1")
.arg("a4")
.args(&["a2", "a3"])
.required(true)
.conflicts_with("c1")
.conflicts_with_all(&["c2", "c3"])
.conflicts_with("c4")
.requires("r1")
.requires_all(&["r2", "r3"])
.requires("r4");
let args = vec!["a1", "a4", "a2", "a3"];
let reqs = vec!["r1", "r2", "r3", "r4"];
let confs = vec!["c1", "c2", "c3", "c4"];
let g2 = ArgGroup::from(&g);
assert_eq!(g2.args, args);
assert_eq!(g2.requires, Some(reqs));
assert_eq!(g2.conflicts, Some(confs));
}
#[cfg(feature = "yaml")]
#[cfg_attr(feature = "yaml", test)]
fn test_yaml() {
let g_yaml = "name: test
args:
- a1
- a4
- a2
- a3
conflicts_with:
- c1
- c2
- c3
- c4
requires:
- r1
- r2
- r3
- r4";
let yml = &YamlLoader::load_from_str(g_yaml).expect("failed to load YAML file")[0];
let g = ArgGroup::from_yaml(yml);
let args = vec!["a1", "a4", "a2", "a3"];
let reqs = vec!["r1", "r2", "r3", "r4"];
let confs = vec!["c1", "c2", "c3", "c4"];
assert_eq!(g.args, args);
assert_eq!(g.requires, Some(reqs));
assert_eq!(g.conflicts, Some(confs));
}
}
impl<'a> Clone for ArgGroup<'a> {
fn clone(&self) -> Self {
ArgGroup {
name: self.name,
required: self.required,
args: self.args.clone(),
requires: self.requires.clone(),
conflicts: self.conflicts.clone(),
multiple: self.multiple,
}
}
}

View File

@@ -0,0 +1,121 @@
#[cfg(feature = "yaml")]
macro_rules! yaml_tuple2 {
($a:ident, $v:ident, $c:ident) => {{
if let Some(vec) = $v.as_vec() {
for ys in vec {
if let Some(tup) = ys.as_vec() {
debug_assert_eq!(2, tup.len());
$a = $a.$c(yaml_str!(tup[0]), yaml_str!(tup[1]));
} else {
panic!("Failed to convert YAML value to vec");
}
}
} else {
panic!("Failed to convert YAML value to vec");
}
$a
}};
}
#[cfg(feature = "yaml")]
macro_rules! yaml_tuple3 {
($a:ident, $v:ident, $c:ident) => {{
if let Some(vec) = $v.as_vec() {
for ys in vec {
if let Some(tup) = ys.as_vec() {
debug_assert_eq!(3, tup.len());
$a = $a.$c(yaml_str!(tup[0]), yaml_opt_str!(tup[1]), yaml_str!(tup[2]));
} else {
panic!("Failed to convert YAML value to vec");
}
}
} else {
panic!("Failed to convert YAML value to vec");
}
$a
}};
}
#[cfg(feature = "yaml")]
macro_rules! yaml_vec_or_str {
($v:ident, $a:ident, $c:ident) => {{
let maybe_vec = $v.as_vec();
if let Some(vec) = maybe_vec {
for ys in vec {
if let Some(s) = ys.as_str() {
$a = $a.$c(s);
} else {
panic!("Failed to convert YAML value {:?} to a string", ys);
}
}
} else {
if let Some(s) = $v.as_str() {
$a = $a.$c(s);
} else {
panic!(
"Failed to convert YAML value {:?} to either a vec or string",
$v
);
}
}
$a
}};
}
#[cfg(feature = "yaml")]
macro_rules! yaml_opt_str {
($v:expr) => {{
if $v.is_null() {
Some(
$v.as_str()
.unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)),
)
} else {
None
}
}};
}
#[cfg(feature = "yaml")]
macro_rules! yaml_str {
($v:expr) => {{
$v.as_str()
.unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v))
}};
}
#[cfg(feature = "yaml")]
macro_rules! yaml_to_str {
($a:ident, $v:ident, $c:ident) => {{
$a.$c(yaml_str!($v))
}};
}
#[cfg(feature = "yaml")]
macro_rules! yaml_to_bool {
($a:ident, $v:ident, $c:ident) => {{
$a.$c($v
.as_bool()
.unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)))
}};
}
#[cfg(feature = "yaml")]
macro_rules! yaml_to_u64 {
($a:ident, $v:ident, $c:ident) => {{
$a.$c($v
.as_i64()
.unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v))
as u64)
}};
}
#[cfg(feature = "yaml")]
macro_rules! yaml_to_usize {
($a:ident, $v:ident, $c:ident) => {{
$a.$c($v
.as_i64()
.unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v))
as usize)
}};
}

View File

@@ -0,0 +1,29 @@
// Std
use std::ffi::OsString;
#[doc(hidden)]
#[derive(Debug, Clone)]
pub struct MatchedArg {
#[doc(hidden)]
pub occurs: u64,
#[doc(hidden)]
pub indices: Vec<usize>,
#[doc(hidden)]
pub vals: Vec<OsString>,
}
impl Default for MatchedArg {
fn default() -> Self {
MatchedArg {
occurs: 1,
indices: Vec::new(),
vals: Vec::new(),
}
}
}
impl MatchedArg {
pub fn new() -> Self {
MatchedArg::default()
}
}

View File

@@ -0,0 +1,21 @@
pub use self::any_arg::{AnyArg, DispOrder};
pub use self::arg::Arg;
pub use self::arg_builder::{Base, FlagBuilder, OptBuilder, PosBuilder, Switched, Valued};
pub use self::arg_matcher::ArgMatcher;
pub use self::arg_matches::{ArgMatches, OsValues, Values};
pub use self::group::ArgGroup;
pub use self::matched_arg::MatchedArg;
pub use self::settings::{ArgFlags, ArgSettings};
pub use self::subcommand::SubCommand;
#[macro_use]
mod macros;
pub mod any_arg;
mod arg;
mod arg_builder;
mod arg_matcher;
mod arg_matches;
mod group;
mod matched_arg;
pub mod settings;
mod subcommand;

View File

@@ -0,0 +1,237 @@
// Std
#[allow(deprecated, unused_imports)]
use std::ascii::AsciiExt;
use std::str::FromStr;
bitflags! {
struct Flags: u32 {
const REQUIRED = 1;
const MULTIPLE = 1 << 1;
const EMPTY_VALS = 1 << 2;
const GLOBAL = 1 << 3;
const HIDDEN = 1 << 4;
const TAKES_VAL = 1 << 5;
const USE_DELIM = 1 << 6;
const NEXT_LINE_HELP = 1 << 7;
const R_UNLESS_ALL = 1 << 8;
const REQ_DELIM = 1 << 9;
const DELIM_NOT_SET = 1 << 10;
const HIDE_POS_VALS = 1 << 11;
const ALLOW_TAC_VALS = 1 << 12;
const REQUIRE_EQUALS = 1 << 13;
const LAST = 1 << 14;
const HIDE_DEFAULT_VAL = 1 << 15;
const CASE_INSENSITIVE = 1 << 16;
const HIDE_ENV_VALS = 1 << 17;
const HIDDEN_SHORT_H = 1 << 18;
const HIDDEN_LONG_H = 1 << 19;
}
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy)]
pub struct ArgFlags(Flags);
impl ArgFlags {
pub fn new() -> Self {
ArgFlags::default()
}
impl_settings! {ArgSettings,
Required => Flags::REQUIRED,
Multiple => Flags::MULTIPLE,
EmptyValues => Flags::EMPTY_VALS,
Global => Flags::GLOBAL,
Hidden => Flags::HIDDEN,
TakesValue => Flags::TAKES_VAL,
UseValueDelimiter => Flags::USE_DELIM,
NextLineHelp => Flags::NEXT_LINE_HELP,
RequiredUnlessAll => Flags::R_UNLESS_ALL,
RequireDelimiter => Flags::REQ_DELIM,
ValueDelimiterNotSet => Flags::DELIM_NOT_SET,
HidePossibleValues => Flags::HIDE_POS_VALS,
AllowLeadingHyphen => Flags::ALLOW_TAC_VALS,
RequireEquals => Flags::REQUIRE_EQUALS,
Last => Flags::LAST,
CaseInsensitive => Flags::CASE_INSENSITIVE,
HideEnvValues => Flags::HIDE_ENV_VALS,
HideDefaultValue => Flags::HIDE_DEFAULT_VAL,
HiddenShortHelp => Flags::HIDDEN_SHORT_H,
HiddenLongHelp => Flags::HIDDEN_LONG_H
}
}
impl Default for ArgFlags {
fn default() -> Self {
ArgFlags(Flags::EMPTY_VALS | Flags::DELIM_NOT_SET)
}
}
/// Various settings that apply to arguments and may be set, unset, and checked via getter/setter
/// methods [`Arg::set`], [`Arg::unset`], and [`Arg::is_set`]
///
/// [`Arg::set`]: ./struct.Arg.html#method.set
/// [`Arg::unset`]: ./struct.Arg.html#method.unset
/// [`Arg::is_set`]: ./struct.Arg.html#method.is_set
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum ArgSettings {
/// The argument must be used
Required,
/// The argument may be used multiple times such as `--flag --flag`
Multiple,
/// The argument allows empty values such as `--option ""`
EmptyValues,
/// The argument should be propagated down through all child [`SubCommand`]s
///
/// [`SubCommand`]: ./struct.SubCommand.html
Global,
/// The argument should **not** be shown in help text
Hidden,
/// The argument accepts a value, such as `--option <value>`
TakesValue,
/// Determines if the argument allows values to be grouped via a delimiter
UseValueDelimiter,
/// Prints the help text on the line after the argument
NextLineHelp,
/// Requires the use of a value delimiter for all multiple values
RequireDelimiter,
/// Hides the possible values from the help string
HidePossibleValues,
/// Allows vals that start with a '-'
AllowLeadingHyphen,
/// Require options use `--option=val` syntax
RequireEquals,
/// Specifies that the arg is the last positional argument and may be accessed early via `--`
/// syntax
Last,
/// Hides the default value from the help string
HideDefaultValue,
/// Makes `Arg::possible_values` case insensitive
CaseInsensitive,
/// Hides ENV values in the help message
HideEnvValues,
/// The argument should **not** be shown in short help text
HiddenShortHelp,
/// The argument should **not** be shown in long help text
HiddenLongHelp,
#[doc(hidden)]
RequiredUnlessAll,
#[doc(hidden)]
ValueDelimiterNotSet,
}
impl FromStr for ArgSettings {
type Err = String;
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
match &*s.to_ascii_lowercase() {
"required" => Ok(ArgSettings::Required),
"multiple" => Ok(ArgSettings::Multiple),
"global" => Ok(ArgSettings::Global),
"emptyvalues" => Ok(ArgSettings::EmptyValues),
"hidden" => Ok(ArgSettings::Hidden),
"takesvalue" => Ok(ArgSettings::TakesValue),
"usevaluedelimiter" => Ok(ArgSettings::UseValueDelimiter),
"nextlinehelp" => Ok(ArgSettings::NextLineHelp),
"requiredunlessall" => Ok(ArgSettings::RequiredUnlessAll),
"requiredelimiter" => Ok(ArgSettings::RequireDelimiter),
"valuedelimiternotset" => Ok(ArgSettings::ValueDelimiterNotSet),
"hidepossiblevalues" => Ok(ArgSettings::HidePossibleValues),
"allowleadinghyphen" => Ok(ArgSettings::AllowLeadingHyphen),
"requireequals" => Ok(ArgSettings::RequireEquals),
"last" => Ok(ArgSettings::Last),
"hidedefaultvalue" => Ok(ArgSettings::HideDefaultValue),
"caseinsensitive" => Ok(ArgSettings::CaseInsensitive),
"hideenvvalues" => Ok(ArgSettings::HideEnvValues),
"hiddenshorthelp" => Ok(ArgSettings::HiddenShortHelp),
"hiddenlonghelp" => Ok(ArgSettings::HiddenLongHelp),
_ => Err("unknown ArgSetting, cannot convert from str".to_owned()),
}
}
}
#[cfg(test)]
mod test {
use super::ArgSettings;
#[test]
fn arg_settings_fromstr() {
assert_eq!(
"allowleadinghyphen".parse::<ArgSettings>().unwrap(),
ArgSettings::AllowLeadingHyphen
);
assert_eq!(
"emptyvalues".parse::<ArgSettings>().unwrap(),
ArgSettings::EmptyValues
);
assert_eq!(
"global".parse::<ArgSettings>().unwrap(),
ArgSettings::Global
);
assert_eq!(
"hidepossiblevalues".parse::<ArgSettings>().unwrap(),
ArgSettings::HidePossibleValues
);
assert_eq!(
"hidden".parse::<ArgSettings>().unwrap(),
ArgSettings::Hidden
);
assert_eq!(
"multiple".parse::<ArgSettings>().unwrap(),
ArgSettings::Multiple
);
assert_eq!(
"nextlinehelp".parse::<ArgSettings>().unwrap(),
ArgSettings::NextLineHelp
);
assert_eq!(
"requiredunlessall".parse::<ArgSettings>().unwrap(),
ArgSettings::RequiredUnlessAll
);
assert_eq!(
"requiredelimiter".parse::<ArgSettings>().unwrap(),
ArgSettings::RequireDelimiter
);
assert_eq!(
"required".parse::<ArgSettings>().unwrap(),
ArgSettings::Required
);
assert_eq!(
"takesvalue".parse::<ArgSettings>().unwrap(),
ArgSettings::TakesValue
);
assert_eq!(
"usevaluedelimiter".parse::<ArgSettings>().unwrap(),
ArgSettings::UseValueDelimiter
);
assert_eq!(
"valuedelimiternotset".parse::<ArgSettings>().unwrap(),
ArgSettings::ValueDelimiterNotSet
);
assert_eq!(
"requireequals".parse::<ArgSettings>().unwrap(),
ArgSettings::RequireEquals
);
assert_eq!("last".parse::<ArgSettings>().unwrap(), ArgSettings::Last);
assert_eq!(
"hidedefaultvalue".parse::<ArgSettings>().unwrap(),
ArgSettings::HideDefaultValue
);
assert_eq!(
"caseinsensitive".parse::<ArgSettings>().unwrap(),
ArgSettings::CaseInsensitive
);
assert_eq!(
"hideenvvalues".parse::<ArgSettings>().unwrap(),
ArgSettings::HideEnvValues
);
assert_eq!(
"hiddenshorthelp".parse::<ArgSettings>().unwrap(),
ArgSettings::HiddenShortHelp
);
assert_eq!(
"hiddenlonghelp".parse::<ArgSettings>().unwrap(),
ArgSettings::HiddenLongHelp
);
assert!("hahahaha".parse::<ArgSettings>().is_err());
}
}

View File

@@ -0,0 +1,71 @@
// Third Party
#[cfg(feature = "yaml")]
use yaml_rust::Yaml;
// Internal
use crate::{App, ArgMatches};
/// The abstract representation of a command line subcommand.
///
/// This struct describes all the valid options of the subcommand for the program. Subcommands are
/// essentially "sub-[`App`]s" and contain all the same possibilities (such as their own
/// [arguments], subcommands, and settings).
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, SubCommand};
/// App::new("myprog")
/// .subcommand(
/// SubCommand::with_name("config")
/// .about("Used for configuration")
/// .arg(Arg::with_name("config_file")
/// .help("The configuration file to use")
/// .index(1)))
/// # ;
/// ```
/// [`App`]: ./struct.App.html
/// [arguments]: ./struct.Arg.html
#[derive(Debug, Clone)]
pub struct SubCommand<'a> {
#[doc(hidden)]
pub name: String,
#[doc(hidden)]
pub matches: ArgMatches<'a>,
}
impl<'a> SubCommand<'a> {
/// Creates a new instance of a subcommand requiring a name. The name will be displayed
/// to the user when they print version or help and usage information.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, SubCommand};
/// App::new("myprog")
/// .subcommand(
/// SubCommand::with_name("config"))
/// # ;
/// ```
pub fn with_name<'b>(name: &str) -> App<'a, 'b> {
App::new(name)
}
/// Creates a new instance of a subcommand from a YAML (.yml) document
///
/// # Examples
///
/// ```ignore
/// # #[macro_use]
/// # extern crate clap;
/// # use clap::Subcommand;
/// # fn main() {
/// let sc_yaml = load_yaml!("test_subcommand.yml");
/// let sc = SubCommand::from_yaml(sc_yaml);
/// # }
/// ```
#[cfg(feature = "yaml")]
pub fn from_yaml(yaml: &Yaml) -> App {
App::from_yaml(yaml)
}
}

View File

@@ -0,0 +1,223 @@
// Std
use std::io::Write;
// Internal
use crate::{
app::parser::Parser,
args::{AnyArg, OptBuilder},
completions,
};
pub struct BashGen<'a, 'b>
where
'a: 'b,
{
p: &'b Parser<'a, 'b>,
}
impl<'a, 'b> BashGen<'a, 'b> {
pub fn new(p: &'b Parser<'a, 'b>) -> Self {
BashGen { p }
}
pub fn generate_to<W: Write>(&self, buf: &mut W) {
w!(
buf,
format!(
r#"_{name}() {{
local i cur prev opts cmds
COMPREPLY=()
cur="${{COMP_WORDS[COMP_CWORD]}}"
prev="${{COMP_WORDS[COMP_CWORD-1]}}"
cmd=""
opts=""
for i in ${{COMP_WORDS[@]}}
do
case "${{i}}" in
{name})
cmd="{name}"
;;
{subcmds}
*)
;;
esac
done
case "${{cmd}}" in
{name})
opts="{name_opts}"
if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
return 0
fi
case "${{prev}}" in
{name_opts_details}
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
return 0
;;
{subcmd_details}
esac
}}
complete -F _{name} -o bashdefault -o default {name}
"#,
name = self.p.meta.bin_name.as_ref().unwrap(),
name_opts = self.all_options_for_path(self.p.meta.bin_name.as_ref().unwrap()),
name_opts_details =
self.option_details_for_path(self.p.meta.bin_name.as_ref().unwrap()),
subcmds = self.all_subcommands(),
subcmd_details = self.subcommand_details()
)
.as_bytes()
);
}
fn all_subcommands(&self) -> String {
debugln!("BashGen::all_subcommands;");
let mut subcmds = String::new();
let scs = completions::all_subcommand_names(self.p);
for sc in &scs {
subcmds = format!(
r#"{}
{name})
cmd+="__{fn_name}"
;;"#,
subcmds,
name = sc,
fn_name = sc.replace("-", "__")
);
}
subcmds
}
fn subcommand_details(&self) -> String {
debugln!("BashGen::subcommand_details;");
let mut subcmd_dets = String::new();
let mut scs = completions::get_all_subcommand_paths(self.p, true);
scs.sort();
scs.dedup();
for sc in &scs {
subcmd_dets = format!(
r#"{}
{subcmd})
opts="{sc_opts}"
if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq {level} ]] ; then
COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
return 0
fi
case "${{prev}}" in
{opts_details}
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
return 0
;;"#,
subcmd_dets,
subcmd = sc.replace("-", "__"),
sc_opts = self.all_options_for_path(&*sc),
level = sc.split("__").count(),
opts_details = self.option_details_for_path(&*sc)
);
}
subcmd_dets
}
fn option_details_for_path(&self, path: &str) -> String {
debugln!("BashGen::option_details_for_path: path={}", path);
let mut p = self.p;
for sc in path.split("__").skip(1) {
debugln!("BashGen::option_details_for_path:iter: sc={}", sc);
p = &find_subcmd!(p, sc).unwrap().p;
}
let mut opts = String::new();
for o in p.opts() {
if let Some(l) = o.s.long {
opts = format!(
"{}
--{})
COMPREPLY=({})
return 0
;;",
opts,
l,
self.vals_for(o)
);
}
if let Some(s) = o.s.short {
opts = format!(
"{}
-{})
COMPREPLY=({})
return 0
;;",
opts,
s,
self.vals_for(o)
);
}
}
opts
}
fn vals_for(&self, o: &OptBuilder) -> String {
debugln!("BashGen::vals_for: o={}", o.b.name);
if let Some(vals) = o.possible_vals() {
format!(r#"$(compgen -W "{}" -- "${{cur}}")"#, vals.join(" "))
} else {
String::from(r#"$(compgen -f "${cur}")"#)
}
}
fn all_options_for_path(&self, path: &str) -> String {
debugln!("BashGen::all_options_for_path: path={}", path);
let mut p = self.p;
for sc in path.split("__").skip(1) {
debugln!("BashGen::all_options_for_path:iter: sc={}", sc);
p = &find_subcmd!(p, sc).unwrap().p;
}
let mut opts = shorts!(p).fold(String::new(), |acc, s| format!("{} -{}", acc, s));
opts = format!(
"{} {}",
opts,
longs!(p).fold(String::new(), |acc, l| format!("{} --{}", acc, l))
);
opts = format!(
"{} {}",
opts,
p.positionals
.values()
.fold(String::new(), |acc, p| format!("{} {}", acc, p))
);
opts = format!(
"{} {}",
opts,
p.subcommands
.iter()
.fold(String::new(), |acc, s| format!("{} {}", acc, s.p.meta.name))
);
for sc in &p.subcommands {
if let Some(ref aliases) = sc.p.meta.aliases {
opts = format!(
"{} {}",
opts,
aliases
.iter()
.map(|&(n, _)| n)
.fold(String::new(), |acc, a| format!("{} {}", acc, a))
);
}
}
opts
}
}

View File

@@ -0,0 +1,127 @@
// Std
use std::io::Write;
// Internal
use crate::{app::parser::Parser, INTERNAL_ERROR_MSG};
pub struct ElvishGen<'a, 'b>
where
'a: 'b,
{
p: &'b Parser<'a, 'b>,
}
impl<'a, 'b> ElvishGen<'a, 'b> {
pub fn new(p: &'b Parser<'a, 'b>) -> Self {
ElvishGen { p }
}
pub fn generate_to<W: Write>(&self, buf: &mut W) {
let bin_name = self.p.meta.bin_name.as_ref().unwrap();
let mut names = vec![];
let subcommands_cases = generate_inner(self.p, "", &mut names);
let result = format!(
r#"
edit:completion:arg-completer[{bin_name}] = [@words]{{
fn spaces [n]{{
repeat $n ' ' | joins ''
}}
fn cand [text desc]{{
edit:complex-candidate $text &display-suffix=' '(spaces (- 14 (wcswidth $text)))$desc
}}
command = '{bin_name}'
for word $words[1:-1] {{
if (has-prefix $word '-') {{
break
}}
command = $command';'$word
}}
completions = [{subcommands_cases}
]
$completions[$command]
}}
"#,
bin_name = bin_name,
subcommands_cases = subcommands_cases
);
w!(buf, result.as_bytes());
}
}
// Escape string inside single quotes
fn escape_string(string: &str) -> String {
string.replace("'", "''")
}
fn get_tooltip<T: ToString>(help: Option<&str>, data: T) -> String {
match help {
Some(help) => escape_string(help),
_ => data.to_string(),
}
}
fn generate_inner<'a, 'b, 'p>(
p: &'p Parser<'a, 'b>,
previous_command_name: &str,
names: &mut Vec<&'p str>,
) -> String {
debugln!("ElvishGen::generate_inner;");
let command_name = if previous_command_name.is_empty() {
p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG).clone()
} else {
format!("{};{}", previous_command_name, &p.meta.name)
};
let mut completions = String::new();
let preamble = String::from("\n cand ");
for option in p.opts() {
if let Some(data) = option.s.short {
let tooltip = get_tooltip(option.b.help, data);
completions.push_str(&preamble);
completions.push_str(format!("-{} '{}'", data, tooltip).as_str());
}
if let Some(data) = option.s.long {
let tooltip = get_tooltip(option.b.help, data);
completions.push_str(&preamble);
completions.push_str(format!("--{} '{}'", data, tooltip).as_str());
}
}
for flag in p.flags() {
if let Some(data) = flag.s.short {
let tooltip = get_tooltip(flag.b.help, data);
completions.push_str(&preamble);
completions.push_str(format!("-{} '{}'", data, tooltip).as_str());
}
if let Some(data) = flag.s.long {
let tooltip = get_tooltip(flag.b.help, data);
completions.push_str(&preamble);
completions.push_str(format!("--{} '{}'", data, tooltip).as_str());
}
}
for subcommand in &p.subcommands {
let data = &subcommand.p.meta.name;
let tooltip = get_tooltip(subcommand.p.meta.about, data);
completions.push_str(&preamble);
completions.push_str(format!("{} '{}'", data, tooltip).as_str());
}
let mut subcommands_cases = format!(
r"
&'{}'= {{{}
}}",
&command_name, completions
);
for subcommand in &p.subcommands {
let subcommand_subcommands_cases = generate_inner(&subcommand.p, &command_name, names);
subcommands_cases.push_str(&subcommand_subcommands_cases);
}
subcommands_cases
}

View File

@@ -0,0 +1,103 @@
// Std
use std::io::Write;
// Internal
use crate::app::parser::Parser;
pub struct FishGen<'a, 'b>
where
'a: 'b,
{
p: &'b Parser<'a, 'b>,
}
impl<'a, 'b> FishGen<'a, 'b> {
pub fn new(p: &'b Parser<'a, 'b>) -> Self {
FishGen { p }
}
pub fn generate_to<W: Write>(&self, buf: &mut W) {
let command = self.p.meta.bin_name.as_ref().unwrap();
let mut buffer = String::new();
gen_fish_inner(command, self, command, &mut buffer);
w!(buf, buffer.as_bytes());
}
}
// Escape string inside single quotes
fn escape_string(string: &str) -> String {
string.replace("\\", "\\\\").replace("'", "\\'")
}
fn gen_fish_inner(root_command: &str, comp_gen: &FishGen, subcommand: &str, buffer: &mut String) {
debugln!("FishGen::gen_fish_inner;");
// example :
//
// complete
// -c {command}
// -d "{description}"
// -s {short}
// -l {long}
// -a "{possible_arguments}"
// -r # if require parameter
// -f # don't use file completion
// -n "__fish_use_subcommand" # complete for command "myprog"
// -n "__fish_seen_subcommand_from subcmd1" # complete for command "myprog subcmd1"
let mut basic_template = format!("complete -c {} -n ", root_command);
if root_command == subcommand {
basic_template.push_str("\"__fish_use_subcommand\"");
} else {
basic_template.push_str(format!("\"__fish_seen_subcommand_from {}\"", subcommand).as_str());
}
for option in comp_gen.p.opts() {
let mut template = basic_template.clone();
if let Some(data) = option.s.short {
template.push_str(format!(" -s {}", data).as_str());
}
if let Some(data) = option.s.long {
template.push_str(format!(" -l {}", data).as_str());
}
if let Some(data) = option.b.help {
template.push_str(format!(" -d '{}'", escape_string(data)).as_str());
}
if let Some(ref data) = option.v.possible_vals {
template.push_str(format!(" -r -f -a \"{}\"", data.join(" ")).as_str());
}
buffer.push_str(template.as_str());
buffer.push('\n');
}
for flag in comp_gen.p.flags() {
let mut template = basic_template.clone();
if let Some(data) = flag.s.short {
template.push_str(format!(" -s {}", data).as_str());
}
if let Some(data) = flag.s.long {
template.push_str(format!(" -l {}", data).as_str());
}
if let Some(data) = flag.b.help {
template.push_str(format!(" -d '{}'", escape_string(data)).as_str());
}
buffer.push_str(template.as_str());
buffer.push('\n');
}
for subcommand in &comp_gen.p.subcommands {
let mut template = basic_template.clone();
template.push_str(" -f");
template.push_str(format!(" -a \"{}\"", &subcommand.p.meta.name).as_str());
if let Some(data) = subcommand.p.meta.about {
template.push_str(format!(" -d '{}'", escape_string(data)).as_str())
}
buffer.push_str(template.as_str());
buffer.push('\n');
}
// generate options of subcommands
for subcommand in &comp_gen.p.subcommands {
let sub_comp_gen = FishGen::new(&subcommand.p);
gen_fish_inner(root_command, &sub_comp_gen, &subcommand.to_string(), buffer);
}
}

View File

@@ -0,0 +1,28 @@
macro_rules! w {
($buf:expr, $to_w:expr) => {
match $buf.write_all($to_w) {
Ok(..) => (),
Err(..) => panic!("Failed to write to completions file"),
}
};
}
macro_rules! get_zsh_arg_conflicts {
($p:ident, $arg:ident, $msg:ident) => {
if let Some(conf_vec) = $arg.blacklist() {
let mut v = vec![];
for arg_name in conf_vec {
let arg = $p.find_any_arg(arg_name).expect($msg);
if let Some(s) = arg.short() {
v.push(format!("-{}", s));
}
if let Some(l) = arg.long() {
v.push(format!("--{}", l));
}
}
v.join(" ")
} else {
String::new()
}
};
}

View File

@@ -0,0 +1,182 @@
#[macro_use]
mod macros;
mod bash;
mod elvish;
mod fish;
mod powershell;
mod shell;
mod zsh;
// Std
use std::io::Write;
// Internal
pub use crate::completions::shell::Shell;
use crate::{
app::parser::Parser,
completions::{
bash::BashGen, elvish::ElvishGen, fish::FishGen, powershell::PowerShellGen, zsh::ZshGen,
},
};
pub struct ComplGen<'a, 'b>
where
'a: 'b,
{
p: &'b Parser<'a, 'b>,
}
impl<'a, 'b> ComplGen<'a, 'b> {
pub fn new(p: &'b Parser<'a, 'b>) -> Self {
ComplGen { p }
}
pub fn generate<W: Write>(&self, for_shell: Shell, buf: &mut W) {
match for_shell {
Shell::Bash => BashGen::new(self.p).generate_to(buf),
Shell::Fish => FishGen::new(self.p).generate_to(buf),
Shell::Zsh => ZshGen::new(self.p).generate_to(buf),
Shell::PowerShell => PowerShellGen::new(self.p).generate_to(buf),
Shell::Elvish => ElvishGen::new(self.p).generate_to(buf),
}
}
}
// Gets all subcommands including child subcommands in the form of 'name' where the name
// is a single word (i.e. "install") of the path to said subcommand (i.e.
// "rustup toolchain install")
//
// Also note, aliases are treated as their own subcommands but duplicates of whatever they're
// aliasing.
pub fn all_subcommand_names(p: &Parser) -> Vec<String> {
debugln!("all_subcommand_names;");
let mut subcmds: Vec<_> = subcommands_of(p)
.iter()
.map(|&(ref n, _)| n.clone())
.collect();
for sc_v in p.subcommands.iter().map(|s| all_subcommand_names(&s.p)) {
subcmds.extend(sc_v);
}
subcmds.sort();
subcmds.dedup();
subcmds
}
// Gets all subcommands including child subcommands in the form of ('name', 'bin_name') where the name
// is a single word (i.e. "install") of the path and full bin_name of said subcommand (i.e.
// "rustup toolchain install")
//
// Also note, aliases are treated as their own subcommands but duplicates of whatever they're
// aliasing.
pub fn all_subcommands(p: &Parser) -> Vec<(String, String)> {
debugln!("all_subcommands;");
let mut subcmds: Vec<_> = subcommands_of(p);
for sc_v in p.subcommands.iter().map(|s| all_subcommands(&s.p)) {
subcmds.extend(sc_v);
}
subcmds
}
// Gets all subcommands excluding child subcommands in the form of (name, bin_name) where the name
// is a single word (i.e. "install") and the bin_name is a space delineated list of the path to said
// subcommand (i.e. "rustup toolchain install")
//
// Also note, aliases are treated as their own subcommands but duplicates of whatever they're
// aliasing.
pub fn subcommands_of(p: &Parser) -> Vec<(String, String)> {
debugln!(
"subcommands_of: name={}, bin_name={}",
p.meta.name,
p.meta.bin_name.as_ref().unwrap()
);
let mut subcmds = vec![];
debugln!(
"subcommands_of: Has subcommands...{:?}",
p.has_subcommands()
);
if !p.has_subcommands() {
let mut ret = vec![];
debugln!("subcommands_of: Looking for aliases...");
if let Some(ref aliases) = p.meta.aliases {
for &(n, _) in aliases {
debugln!("subcommands_of:iter:iter: Found alias...{}", n);
let mut als_bin_name: Vec<_> =
p.meta.bin_name.as_ref().unwrap().split(' ').collect();
als_bin_name.push(n);
let old = als_bin_name.len() - 2;
als_bin_name.swap_remove(old);
ret.push((n.to_owned(), als_bin_name.join(" ")));
}
}
return ret;
}
for sc in &p.subcommands {
debugln!(
"subcommands_of:iter: name={}, bin_name={}",
sc.p.meta.name,
sc.p.meta.bin_name.as_ref().unwrap()
);
debugln!("subcommands_of:iter: Looking for aliases...");
if let Some(ref aliases) = sc.p.meta.aliases {
for &(n, _) in aliases {
debugln!("subcommands_of:iter:iter: Found alias...{}", n);
let mut als_bin_name: Vec<_> =
p.meta.bin_name.as_ref().unwrap().split(' ').collect();
als_bin_name.push(n);
let old = als_bin_name.len() - 2;
als_bin_name.swap_remove(old);
subcmds.push((n.to_owned(), als_bin_name.join(" ")));
}
}
subcmds.push((
sc.p.meta.name.clone(),
sc.p.meta.bin_name.as_ref().unwrap().clone(),
));
}
subcmds
}
pub fn get_all_subcommand_paths(p: &Parser, first: bool) -> Vec<String> {
debugln!("get_all_subcommand_paths;");
let mut subcmds = vec![];
if !p.has_subcommands() {
if !first {
let name = &*p.meta.name;
let path = p.meta.bin_name.as_ref().unwrap().clone().replace(" ", "__");
let mut ret = vec![path.clone()];
if let Some(ref aliases) = p.meta.aliases {
for &(n, _) in aliases {
ret.push(path.replace(name, n));
}
}
return ret;
}
return vec![];
}
for sc in &p.subcommands {
let name = &*sc.p.meta.name;
let path =
sc.p.meta
.bin_name
.as_ref()
.unwrap()
.clone()
.replace(" ", "__");
subcmds.push(path.clone());
if let Some(ref aliases) = sc.p.meta.aliases {
for &(n, _) in aliases {
subcmds.push(path.replace(name, n));
}
}
}
for sc_v in p
.subcommands
.iter()
.map(|s| get_all_subcommand_paths(&s.p, false))
{
subcmds.extend(sc_v);
}
subcmds
}

View File

@@ -0,0 +1,165 @@
// Std
use std::io::Write;
// Internal
use crate::{app::parser::Parser, INTERNAL_ERROR_MSG};
pub struct PowerShellGen<'a, 'b>
where
'a: 'b,
{
p: &'b Parser<'a, 'b>,
}
impl<'a, 'b> PowerShellGen<'a, 'b> {
pub fn new(p: &'b Parser<'a, 'b>) -> Self {
PowerShellGen { p }
}
pub fn generate_to<W: Write>(&self, buf: &mut W) {
let bin_name = self.p.meta.bin_name.as_ref().unwrap();
let mut names = vec![];
let subcommands_cases = generate_inner(self.p, "", &mut names);
let result = format!(
r#"
using namespace System.Management.Automation
using namespace System.Management.Automation.Language
Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{
param($wordToComplete, $commandAst, $cursorPosition)
$commandElements = $commandAst.CommandElements
$command = @(
'{bin_name}'
for ($i = 1; $i -lt $commandElements.Count; $i++) {{
$element = $commandElements[$i]
if ($element -isnot [StringConstantExpressionAst] -or
$element.StringConstantType -ne [StringConstantType]::BareWord -or
$element.Value.StartsWith('-')) {{
break
}}
$element.Value
}}) -join ';'
$completions = @(switch ($command) {{{subcommands_cases}
}})
$completions.Where{{ $_.CompletionText -like "$wordToComplete*" }} |
Sort-Object -Property ListItemText
}}
"#,
bin_name = bin_name,
subcommands_cases = subcommands_cases
);
w!(buf, result.as_bytes());
}
}
// Escape string inside single quotes
fn escape_string(string: &str) -> String {
string.replace("'", "''")
}
fn get_tooltip<T: ToString>(help: Option<&str>, data: T) -> String {
match help {
Some(help) => escape_string(help),
_ => data.to_string(),
}
}
fn generate_inner<'a, 'b, 'p>(
p: &'p Parser<'a, 'b>,
previous_command_name: &str,
names: &mut Vec<&'p str>,
) -> String {
debugln!("PowerShellGen::generate_inner;");
let command_name = if previous_command_name.is_empty() {
p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG).clone()
} else {
format!("{};{}", previous_command_name, &p.meta.name)
};
let mut completions = String::new();
let preamble = String::from("\n [CompletionResult]::new(");
for option in p.opts() {
if let Some(data) = option.s.short {
let tooltip = get_tooltip(option.b.help, data);
completions.push_str(&preamble);
completions.push_str(
format!(
"'-{}', '{}', {}, '{}')",
data, data, "[CompletionResultType]::ParameterName", tooltip
)
.as_str(),
);
}
if let Some(data) = option.s.long {
let tooltip = get_tooltip(option.b.help, data);
completions.push_str(&preamble);
completions.push_str(
format!(
"'--{}', '{}', {}, '{}')",
data, data, "[CompletionResultType]::ParameterName", tooltip
)
.as_str(),
);
}
}
for flag in p.flags() {
if let Some(data) = flag.s.short {
let tooltip = get_tooltip(flag.b.help, data);
completions.push_str(&preamble);
completions.push_str(
format!(
"'-{}', '{}', {}, '{}')",
data, data, "[CompletionResultType]::ParameterName", tooltip
)
.as_str(),
);
}
if let Some(data) = flag.s.long {
let tooltip = get_tooltip(flag.b.help, data);
completions.push_str(&preamble);
completions.push_str(
format!(
"'--{}', '{}', {}, '{}')",
data, data, "[CompletionResultType]::ParameterName", tooltip
)
.as_str(),
);
}
}
for subcommand in &p.subcommands {
let data = &subcommand.p.meta.name;
let tooltip = get_tooltip(subcommand.p.meta.about, data);
completions.push_str(&preamble);
completions.push_str(
format!(
"'{}', '{}', {}, '{}')",
data, data, "[CompletionResultType]::ParameterValue", tooltip
)
.as_str(),
);
}
let mut subcommands_cases = format!(
r"
'{}' {{{}
break
}}",
&command_name, completions
);
for subcommand in &p.subcommands {
let subcommand_subcommands_cases = generate_inner(&subcommand.p, &command_name, names);
subcommands_cases.push_str(&subcommand_subcommands_cases);
}
subcommands_cases
}

View File

@@ -0,0 +1,56 @@
#[allow(deprecated, unused_imports)]
use std::ascii::AsciiExt;
use std::fmt;
use std::str::FromStr;
/// Describes which shell to produce a completions file for
#[derive(Debug, Copy, Clone)]
pub enum Shell {
/// Generates a .bash completion file for the Bourne Again SHell (BASH)
Bash,
/// Generates a .fish completion file for the Friendly Interactive SHell (fish)
Fish,
/// Generates a completion file for the Z SHell (ZSH)
Zsh,
/// Generates a completion file for PowerShell
PowerShell,
/// Generates a completion file for Elvish
Elvish,
}
impl Shell {
/// A list of possible variants in `&'static str` form
pub fn variants() -> [&'static str; 5] {
["zsh", "bash", "fish", "powershell", "elvish"]
}
}
impl FromStr for Shell {
type Err = String;
#[cfg_attr(feature = "cargo-clippy", allow(clippy::wildcard_in_or_patterns))]
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ZSH" | _ if s.eq_ignore_ascii_case("zsh") => Ok(Shell::Zsh),
"FISH" | _ if s.eq_ignore_ascii_case("fish") => Ok(Shell::Fish),
"BASH" | _ if s.eq_ignore_ascii_case("bash") => Ok(Shell::Bash),
"POWERSHELL" | _ if s.eq_ignore_ascii_case("powershell") => Ok(Shell::PowerShell),
"ELVISH" | _ if s.eq_ignore_ascii_case("elvish") => Ok(Shell::Elvish),
_ => Err(String::from(
"[valid values: bash, fish, zsh, powershell, elvish]",
)),
}
}
}
impl fmt::Display for Shell {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Shell::Bash => write!(f, "BASH"),
Shell::Fish => write!(f, "FISH"),
Shell::Zsh => write!(f, "ZSH"),
Shell::PowerShell => write!(f, "POWERSHELL"),
Shell::Elvish => write!(f, "ELVISH"),
}
}
}

View File

@@ -0,0 +1,484 @@
// Std
#[allow(deprecated, unused_imports)]
use std::{ascii::AsciiExt, io::Write};
// Internal
use crate::{
app::{parser::Parser, App},
args::{AnyArg, ArgSettings},
completions, INTERNAL_ERROR_MSG,
};
pub struct ZshGen<'a, 'b>
where
'a: 'b,
{
p: &'b Parser<'a, 'b>,
}
impl<'a, 'b> ZshGen<'a, 'b> {
pub fn new(p: &'b Parser<'a, 'b>) -> Self {
debugln!("ZshGen::new;");
ZshGen { p }
}
pub fn generate_to<W: Write>(&self, buf: &mut W) {
debugln!("ZshGen::generate_to;");
w!(
buf,
format!(
"\
#compdef {name}
autoload -U is-at-least
_{name}() {{
typeset -A opt_args
typeset -a _arguments_options
local ret=1
if is-at-least 5.2; then
_arguments_options=(-s -S -C)
else
_arguments_options=(-s -C)
fi
local context curcontext=\"$curcontext\" state line
{initial_args}
{subcommands}
}}
{subcommand_details}
_{name} \"$@\"",
name = self.p.meta.bin_name.as_ref().unwrap(),
initial_args = get_args_of(self.p),
subcommands = get_subcommands_of(self.p),
subcommand_details = subcommand_details(self.p)
)
.as_bytes()
);
}
}
// Displays the commands of a subcommand
// (( $+functions[_[bin_name_underscore]_commands] )) ||
// _[bin_name_underscore]_commands() {
// local commands; commands=(
// '[arg_name]:[arg_help]'
// )
// _describe -t commands '[bin_name] commands' commands "$@"
//
// Where the following variables are present:
// [bin_name_underscore]: The full space delineated bin_name, where spaces have been replaced by
// underscore characters
// [arg_name]: The name of the subcommand
// [arg_help]: The help message of the subcommand
// [bin_name]: The full space delineated bin_name
//
// Here's a snippet from rustup:
//
// (( $+functions[_rustup_commands] )) ||
// _rustup_commands() {
// local commands; commands=(
// 'show:Show the active and installed toolchains'
// 'update:Update Rust toolchains'
// # ... snip for brevity
// 'help:Prints this message or the help of the given subcommand(s)'
// )
// _describe -t commands 'rustup commands' commands "$@"
//
fn subcommand_details(p: &Parser) -> String {
debugln!("ZshGen::subcommand_details;");
// First we do ourself
let mut ret = vec![format!(
"\
(( $+functions[_{bin_name_underscore}_commands] )) ||
_{bin_name_underscore}_commands() {{
local commands; commands=(
{subcommands_and_args}
)
_describe -t commands '{bin_name} commands' commands \"$@\"
}}",
bin_name_underscore = p.meta.bin_name.as_ref().unwrap().replace(" ", "__"),
bin_name = p.meta.bin_name.as_ref().unwrap(),
subcommands_and_args = subcommands_of(p)
)];
// Next we start looping through all the children, grandchildren, etc.
let mut all_subcommands = completions::all_subcommands(p);
all_subcommands.sort();
all_subcommands.dedup();
for &(_, ref bin_name) in &all_subcommands {
debugln!("ZshGen::subcommand_details:iter: bin_name={}", bin_name);
ret.push(format!(
"\
(( $+functions[_{bin_name_underscore}_commands] )) ||
_{bin_name_underscore}_commands() {{
local commands; commands=(
{subcommands_and_args}
)
_describe -t commands '{bin_name} commands' commands \"$@\"
}}",
bin_name_underscore = bin_name.replace(" ", "__"),
bin_name = bin_name,
subcommands_and_args = subcommands_of(parser_of(p, bin_name))
));
}
ret.join("\n")
}
// Generates subcommand completions in form of
//
// '[arg_name]:[arg_help]'
//
// Where:
// [arg_name]: the subcommand's name
// [arg_help]: the help message of the subcommand
//
// A snippet from rustup:
// 'show:Show the active and installed toolchains'
// 'update:Update Rust toolchains'
fn subcommands_of(p: &Parser) -> String {
debugln!("ZshGen::subcommands_of;");
let mut ret = vec![];
fn add_sc(sc: &App, n: &str, ret: &mut Vec<String>) {
debugln!("ZshGen::add_sc;");
let s = format!(
"\"{name}:{help}\" \\",
name = n,
help =
sc.p.meta
.about
.unwrap_or("")
.replace("[", "\\[")
.replace("]", "\\]")
);
if !s.is_empty() {
ret.push(s);
}
}
// The subcommands
for sc in p.subcommands() {
debugln!("ZshGen::subcommands_of:iter: subcommand={}", sc.p.meta.name);
add_sc(sc, &sc.p.meta.name, &mut ret);
if let Some(ref v) = sc.p.meta.aliases {
for alias in v.iter().filter(|&&(_, vis)| vis).map(|&(n, _)| n) {
add_sc(sc, alias, &mut ret);
}
}
}
ret.join("\n")
}
// Get's the subcommand section of a completion file
// This looks roughly like:
//
// case $state in
// ([bin_name]_args)
// curcontext=\"${curcontext%:*:*}:[name_hyphen]-command-$words[1]:\"
// case $line[1] in
//
// ([name])
// _arguments -C -s -S \
// [subcommand_args]
// && ret=0
//
// [RECURSIVE_CALLS]
//
// ;;",
//
// [repeat]
//
// esac
// ;;
// esac",
//
// Where the following variables are present:
// [name] = The subcommand name in the form of "install" for "rustup toolchain install"
// [bin_name] = The full space delineated bin_name such as "rustup toolchain install"
// [name_hyphen] = The full space delineated bin_name, but replace spaces with hyphens
// [repeat] = From the same recursive calls, but for all subcommands
// [subcommand_args] = The same as zsh::get_args_of
fn get_subcommands_of(p: &Parser) -> String {
debugln!("get_subcommands_of;");
debugln!(
"get_subcommands_of: Has subcommands...{:?}",
p.has_subcommands()
);
if !p.has_subcommands() {
return String::new();
}
let sc_names = completions::subcommands_of(p);
let mut subcmds = vec![];
for &(ref name, ref bin_name) in &sc_names {
let mut v = vec![format!("({})", name)];
let subcommand_args = get_args_of(parser_of(p, &*bin_name));
if !subcommand_args.is_empty() {
v.push(subcommand_args);
}
let subcommands = get_subcommands_of(parser_of(p, &*bin_name));
if !subcommands.is_empty() {
v.push(subcommands);
}
v.push(String::from(";;"));
subcmds.push(v.join("\n"));
}
format!(
"case $state in
({name})
words=($line[{pos}] \"${{words[@]}}\")
(( CURRENT += 1 ))
curcontext=\"${{curcontext%:*:*}}:{name_hyphen}-command-$line[{pos}]:\"
case $line[{pos}] in
{subcommands}
esac
;;
esac",
name = p.meta.name,
name_hyphen = p.meta.bin_name.as_ref().unwrap().replace(" ", "-"),
subcommands = subcmds.join("\n"),
pos = p.positionals().len() + 1
)
}
fn parser_of<'a, 'b>(p: &'b Parser<'a, 'b>, sc: &str) -> &'b Parser<'a, 'b> {
debugln!("parser_of: sc={}", sc);
if sc == p.meta.bin_name.as_ref().unwrap_or(&String::new()) {
return p;
}
&p.find_subcommand(sc).expect(INTERNAL_ERROR_MSG).p
}
// Writes out the args section, which ends up being the flags, opts and postionals, and a jump to
// another ZSH function if there are subcommands.
// The structer works like this:
// ([conflicting_args]) [multiple] arg [takes_value] [[help]] [: :(possible_values)]
// ^-- list '-v -h' ^--'*' ^--'+' ^-- list 'one two three'
//
// An example from the rustup command:
//
// _arguments -C -s -S \
// '(-h --help --verbose)-v[Enable verbose output]' \
// '(-V -v --version --verbose --help)-h[Prints help information]' \
// # ... snip for brevity
// ':: :_rustup_commands' \ # <-- displays subcommands
// '*::: :->rustup' \ # <-- displays subcommand args and child subcommands
// && ret=0
//
// The args used for _arguments are as follows:
// -C: modify the $context internal variable
// -s: Allow stacking of short args (i.e. -a -b -c => -abc)
// -S: Do not complete anything after '--' and treat those as argument values
fn get_args_of(p: &Parser) -> String {
debugln!("get_args_of;");
let mut ret = vec![String::from("_arguments \"${_arguments_options[@]}\" \\")];
let opts = write_opts_of(p);
let flags = write_flags_of(p);
let positionals = write_positionals_of(p);
let sc_or_a = if p.has_subcommands() {
format!(
"\":: :_{name}_commands\" \\",
name = p.meta.bin_name.as_ref().unwrap().replace(" ", "__")
)
} else {
String::new()
};
let sc = if p.has_subcommands() {
format!("\"*::: :->{name}\" \\", name = p.meta.name)
} else {
String::new()
};
if !opts.is_empty() {
ret.push(opts);
}
if !flags.is_empty() {
ret.push(flags);
}
if !positionals.is_empty() {
ret.push(positionals);
}
if !sc_or_a.is_empty() {
ret.push(sc_or_a);
}
if !sc.is_empty() {
ret.push(sc);
}
ret.push(String::from("&& ret=0"));
ret.join("\n")
}
// Escape help string inside single quotes and brackets
fn escape_help(string: &str) -> String {
string
.replace("\\", "\\\\")
.replace("'", "'\\''")
.replace("[", "\\[")
.replace("]", "\\]")
}
// Escape value string inside single quotes and parentheses
fn escape_value(string: &str) -> String {
string
.replace("\\", "\\\\")
.replace("'", "'\\''")
.replace("(", "\\(")
.replace(")", "\\)")
.replace(" ", "\\ ")
}
fn write_opts_of(p: &Parser) -> String {
debugln!("write_opts_of;");
let mut ret = vec![];
for o in p.opts() {
debugln!("write_opts_of:iter: o={}", o.name());
let help = o.help().map_or(String::new(), escape_help);
let mut conflicts = get_zsh_arg_conflicts!(p, o, INTERNAL_ERROR_MSG);
conflicts = if conflicts.is_empty() {
String::new()
} else {
format!("({})", conflicts)
};
let multiple = if o.is_set(ArgSettings::Multiple) {
"*"
} else {
""
};
let pv = if let Some(pv_vec) = o.possible_vals() {
format!(
": :({})",
pv_vec
.iter()
.map(|v| escape_value(*v))
.collect::<Vec<String>>()
.join(" ")
)
} else {
String::new()
};
if let Some(short) = o.short() {
let s = format!(
"'{conflicts}{multiple}-{arg}+[{help}]{possible_values}' \\",
conflicts = conflicts,
multiple = multiple,
arg = short,
possible_values = pv,
help = help
);
debugln!("write_opts_of:iter: Wrote...{}", &*s);
ret.push(s);
}
if let Some(long) = o.long() {
let l = format!(
"'{conflicts}{multiple}--{arg}=[{help}]{possible_values}' \\",
conflicts = conflicts,
multiple = multiple,
arg = long,
possible_values = pv,
help = help
);
debugln!("write_opts_of:iter: Wrote...{}", &*l);
ret.push(l);
}
}
ret.join("\n")
}
fn write_flags_of(p: &Parser) -> String {
debugln!("write_flags_of;");
let mut ret = vec![];
for f in p.flags() {
debugln!("write_flags_of:iter: f={}", f.name());
let help = f.help().map_or(String::new(), escape_help);
let mut conflicts = get_zsh_arg_conflicts!(p, f, INTERNAL_ERROR_MSG);
conflicts = if conflicts.is_empty() {
String::new()
} else {
format!("({})", conflicts)
};
let multiple = if f.is_set(ArgSettings::Multiple) {
"*"
} else {
""
};
if let Some(short) = f.short() {
let s = format!(
"'{conflicts}{multiple}-{arg}[{help}]' \\",
multiple = multiple,
conflicts = conflicts,
arg = short,
help = help
);
debugln!("write_flags_of:iter: Wrote...{}", &*s);
ret.push(s);
}
if let Some(long) = f.long() {
let l = format!(
"'{conflicts}{multiple}--{arg}[{help}]' \\",
conflicts = conflicts,
multiple = multiple,
arg = long,
help = help
);
debugln!("write_flags_of:iter: Wrote...{}", &*l);
ret.push(l);
}
}
ret.join("\n")
}
fn write_positionals_of(p: &Parser) -> String {
debugln!("write_positionals_of;");
let mut ret = vec![];
for arg in p.positionals() {
debugln!("write_positionals_of:iter: arg={}", arg.b.name);
let a = format!(
"'{optional}:{name}{help}:{action}' \\",
optional = if !arg.b.is_set(ArgSettings::Required) {
":"
} else {
""
},
name = arg.b.name,
help = arg
.b
.help
.map_or("".to_owned(), |v| " -- ".to_owned() + v)
.replace("[", "\\[")
.replace("]", "\\]"),
action = arg.possible_vals().map_or("_files".to_owned(), |values| {
format!(
"({})",
values
.iter()
.map(|v| escape_value(*v))
.collect::<Vec<String>>()
.join(" ")
)
})
);
debugln!("write_positionals_of:iter: Wrote...{}", a);
ret.push(a);
}
ret.join("\n")
}

View File

@@ -0,0 +1,933 @@
// Std
use std::{
convert::From,
error::Error as StdError,
fmt as std_fmt,
fmt::Display,
io::{self, Write},
process,
result::Result as StdResult,
};
// Internal
use crate::{
args::AnyArg,
fmt::{ColorWhen, Colorizer, ColorizerOption},
suggestions,
};
/// Short hand for [`Result`] type
///
/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
pub type Result<T> = StdResult<T, Error>;
/// Command line argument parser kind of error
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ErrorKind {
/// Occurs when an [`Arg`] has a set of possible values,
/// and the user provides a value which isn't in that set.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ErrorKind};
/// let result = App::new("prog")
/// .arg(Arg::with_name("speed")
/// .possible_value("fast")
/// .possible_value("slow"))
/// .get_matches_from_safe(vec!["prog", "other"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::InvalidValue);
/// ```
/// [`Arg`]: ./struct.Arg.html
InvalidValue,
/// Occurs when a user provides a flag, option, argument or subcommand which isn't defined.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ErrorKind};
/// let result = App::new("prog")
/// .arg(Arg::from_usage("--flag 'some flag'"))
/// .get_matches_from_safe(vec!["prog", "--other"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::UnknownArgument);
/// ```
UnknownArgument,
/// Occurs when the user provides an unrecognized [`SubCommand`] which meets the threshold for
/// being similar enough to an existing subcommand.
/// If it doesn't meet the threshold, or the 'suggestions' feature is disabled,
/// the more general [`UnknownArgument`] error is returned.
///
/// # Examples
///
#[cfg_attr(not(feature = "suggestions"), doc = " ```no_run")]
#[cfg_attr(feature = "suggestions", doc = " ```")]
/// # use clap::{App, Arg, ErrorKind, SubCommand};
/// let result = App::new("prog")
/// .subcommand(SubCommand::with_name("config")
/// .about("Used for configuration")
/// .arg(Arg::with_name("config_file")
/// .help("The configuration file to use")
/// .index(1)))
/// .get_matches_from_safe(vec!["prog", "confi"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::InvalidSubcommand);
/// ```
/// [`SubCommand`]: ./struct.SubCommand.html
/// [`UnknownArgument`]: ./enum.ErrorKind.html#variant.UnknownArgument
InvalidSubcommand,
/// Occurs when the user provides an unrecognized [`SubCommand`] which either
/// doesn't meet the threshold for being similar enough to an existing subcommand,
/// or the 'suggestions' feature is disabled.
/// Otherwise the more detailed [`InvalidSubcommand`] error is returned.
///
/// This error typically happens when passing additional subcommand names to the `help`
/// subcommand. Otherwise, the more general [`UnknownArgument`] error is used.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ErrorKind, SubCommand};
/// let result = App::new("prog")
/// .subcommand(SubCommand::with_name("config")
/// .about("Used for configuration")
/// .arg(Arg::with_name("config_file")
/// .help("The configuration file to use")
/// .index(1)))
/// .get_matches_from_safe(vec!["prog", "help", "nothing"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::UnrecognizedSubcommand);
/// ```
/// [`SubCommand`]: ./struct.SubCommand.html
/// [`InvalidSubcommand`]: ./enum.ErrorKind.html#variant.InvalidSubcommand
/// [`UnknownArgument`]: ./enum.ErrorKind.html#variant.UnknownArgument
UnrecognizedSubcommand,
/// Occurs when the user provides an empty value for an option that does not allow empty
/// values.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ErrorKind};
/// let res = App::new("prog")
/// .arg(Arg::with_name("color")
/// .long("color")
/// .empty_values(false))
/// .get_matches_from_safe(vec!["prog", "--color="]);
/// assert!(res.is_err());
/// assert_eq!(res.unwrap_err().kind, ErrorKind::EmptyValue);
/// ```
EmptyValue,
/// Occurs when the user provides a value for an argument with a custom validation and the
/// value fails that validation.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ErrorKind};
/// fn is_numeric(val: String) -> Result<(), String> {
/// match val.parse::<i64>() {
/// Ok(..) => Ok(()),
/// Err(..) => Err(String::from("Value wasn't a number!")),
/// }
/// }
///
/// let result = App::new("prog")
/// .arg(Arg::with_name("num")
/// .validator(is_numeric))
/// .get_matches_from_safe(vec!["prog", "NotANumber"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::ValueValidation);
/// ```
ValueValidation,
/// Occurs when a user provides more values for an argument than were defined by setting
/// [`Arg::max_values`].
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ErrorKind};
/// let result = App::new("prog")
/// .arg(Arg::with_name("arg")
/// .multiple(true)
/// .max_values(2))
/// .get_matches_from_safe(vec!["prog", "too", "many", "values"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::TooManyValues);
/// ```
/// [`Arg::max_values`]: ./struct.Arg.html#method.max_values
TooManyValues,
/// Occurs when the user provides fewer values for an argument than were defined by setting
/// [`Arg::min_values`].
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ErrorKind};
/// let result = App::new("prog")
/// .arg(Arg::with_name("some_opt")
/// .long("opt")
/// .min_values(3))
/// .get_matches_from_safe(vec!["prog", "--opt", "too", "few"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::TooFewValues);
/// ```
/// [`Arg::min_values`]: ./struct.Arg.html#method.min_values
TooFewValues,
/// Occurs when the user provides a different number of values for an argument than what's
/// been defined by setting [`Arg::number_of_values`] or than was implicitly set by
/// [`Arg::value_names`].
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ErrorKind};
/// let result = App::new("prog")
/// .arg(Arg::with_name("some_opt")
/// .long("opt")
/// .takes_value(true)
/// .number_of_values(2))
/// .get_matches_from_safe(vec!["prog", "--opt", "wrong"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::WrongNumberOfValues);
/// ```
///
/// [`Arg::number_of_values`]: ./struct.Arg.html#method.number_of_values
/// [`Arg::value_names`]: ./struct.Arg.html#method.value_names
WrongNumberOfValues,
/// Occurs when the user provides two values which conflict with each other and can't be used
/// together.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ErrorKind};
/// let result = App::new("prog")
/// .arg(Arg::with_name("debug")
/// .long("debug")
/// .conflicts_with("color"))
/// .arg(Arg::with_name("color")
/// .long("color"))
/// .get_matches_from_safe(vec!["prog", "--debug", "--color"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::ArgumentConflict);
/// ```
ArgumentConflict,
/// Occurs when the user does not provide one or more required arguments.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ErrorKind};
/// let result = App::new("prog")
/// .arg(Arg::with_name("debug")
/// .required(true))
/// .get_matches_from_safe(vec!["prog"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::MissingRequiredArgument);
/// ```
MissingRequiredArgument,
/// Occurs when a subcommand is required (as defined by [`AppSettings::SubcommandRequired`]),
/// but the user does not provide one.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, AppSettings, SubCommand, ErrorKind};
/// let err = App::new("prog")
/// .setting(AppSettings::SubcommandRequired)
/// .subcommand(SubCommand::with_name("test"))
/// .get_matches_from_safe(vec![
/// "myprog",
/// ]);
/// assert!(err.is_err());
/// assert_eq!(err.unwrap_err().kind, ErrorKind::MissingSubcommand);
/// # ;
/// ```
/// [`AppSettings::SubcommandRequired`]: ./enum.AppSettings.html#variant.SubcommandRequired
MissingSubcommand,
/// Occurs when either an argument or [`SubCommand`] is required, as defined by
/// [`AppSettings::ArgRequiredElseHelp`], but the user did not provide one.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, AppSettings, ErrorKind, SubCommand};
/// let result = App::new("prog")
/// .setting(AppSettings::ArgRequiredElseHelp)
/// .subcommand(SubCommand::with_name("config")
/// .about("Used for configuration")
/// .arg(Arg::with_name("config_file")
/// .help("The configuration file to use")))
/// .get_matches_from_safe(vec!["prog"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::MissingArgumentOrSubcommand);
/// ```
/// [`SubCommand`]: ./struct.SubCommand.html
/// [`AppSettings::ArgRequiredElseHelp`]: ./enum.AppSettings.html#variant.ArgRequiredElseHelp
MissingArgumentOrSubcommand,
/// Occurs when the user provides multiple values to an argument which doesn't allow that.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ErrorKind};
/// let result = App::new("prog")
/// .arg(Arg::with_name("debug")
/// .long("debug")
/// .multiple(false))
/// .get_matches_from_safe(vec!["prog", "--debug", "--debug"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::UnexpectedMultipleUsage);
/// ```
UnexpectedMultipleUsage,
/// Occurs when the user provides a value containing invalid UTF-8 for an argument and
/// [`AppSettings::StrictUtf8`] is set.
///
/// # Platform Specific
///
/// Non-Windows platforms only (such as Linux, Unix, macOS, etc.)
///
/// # Examples
///
#[cfg_attr(not(unix), doc = " ```ignore")]
#[cfg_attr(unix, doc = " ```")]
/// # use clap::{App, Arg, ErrorKind, AppSettings};
/// # use std::os::unix::ffi::OsStringExt;
/// # use std::ffi::OsString;
/// let result = App::new("prog")
/// .setting(AppSettings::StrictUtf8)
/// .arg(Arg::with_name("utf8")
/// .short("u")
/// .takes_value(true))
/// .get_matches_from_safe(vec![OsString::from("myprog"),
/// OsString::from("-u"),
/// OsString::from_vec(vec![0xE9])]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::InvalidUtf8);
/// ```
/// [`AppSettings::StrictUtf8`]: ./enum.AppSettings.html#variant.StrictUtf8
InvalidUtf8,
/// Not a true "error" as it means `--help` or similar was used.
/// The help message will be sent to `stdout`.
///
/// **Note**: If the help is displayed due to an error (such as missing subcommands) it will
/// be sent to `stderr` instead of `stdout`.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ErrorKind};
/// let result = App::new("prog")
/// .get_matches_from_safe(vec!["prog", "--help"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::HelpDisplayed);
/// ```
HelpDisplayed,
/// Not a true "error" as it means `--version` or similar was used.
/// The message will be sent to `stdout`.
///
/// # Examples
///
/// ```rust
/// # use clap::{App, Arg, ErrorKind};
/// let result = App::new("prog")
/// .get_matches_from_safe(vec!["prog", "--version"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind, ErrorKind::VersionDisplayed);
/// ```
VersionDisplayed,
/// Occurs when using the [`value_t!`] and [`values_t!`] macros to convert an argument value
/// into type `T`, but the argument you requested wasn't used. I.e. you asked for an argument
/// with name `config` to be converted, but `config` wasn't used by the user.
/// [`value_t!`]: ./macro.value_t!.html
/// [`values_t!`]: ./macro.values_t!.html
ArgumentNotFound,
/// Represents an [I/O error].
/// Can occur when writing to `stderr` or `stdout` or reading a configuration file.
/// [I/O error]: https://doc.rust-lang.org/std/io/struct.Error.html
Io,
/// Represents a [Format error] (which is a part of [`Display`]).
/// Typically caused by writing to `stderr` or `stdout`.
///
/// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
/// [Format error]: https://doc.rust-lang.org/std/fmt/struct.Error.html
Format,
}
/// Command Line Argument Parser Error
#[derive(Debug)]
pub struct Error {
/// Formatted error message
pub message: String,
/// The type of error
pub kind: ErrorKind,
/// Any additional information passed along, such as the argument name that caused the error
pub info: Option<Vec<String>>,
}
impl Error {
/// Should the message be written to `stdout` or not
pub fn use_stderr(&self) -> bool {
!matches!(
self.kind,
ErrorKind::HelpDisplayed | ErrorKind::VersionDisplayed
)
}
/// Prints the error message and exits. If `Error::use_stderr` evaluates to `true`, the message
/// will be written to `stderr` and exits with a status of `1`. Otherwise, `stdout` is used
/// with a status of `0`.
pub fn exit(&self) -> ! {
if self.use_stderr() {
wlnerr!(@nopanic "{}", self.message);
process::exit(1);
}
// We are deliberately dropping errors here. We could match on the error kind, and only
// drop things such as `std::io::ErrorKind::BrokenPipe`, however nothing is being bubbled
// up or reported back to the caller and we will be exit'ing the process anyways.
// Additionally, changing this API to bubble up the result would be a breaking change.
//
// Another approach could be to try and write to stdout, if that fails due to a broken pipe
// then use stderr. However, that would change the semantics in what could be argued is a
// breaking change. Simply dropping the error, can always be changed to this "use stderr if
// stdout is closed" approach later if desired.
//
// A good explanation of the types of errors are SIGPIPE where the read side of the pipe
// closes before the write side. See the README in `calm_io` for a good explanation:
//
// https://github.com/myrrlyn/calm_io/blob/a42845575a04cd8b65e92c19d104627f5fcad3d7/README.md
let _ = writeln!(&mut io::stdout().lock(), "{}", self.message);
process::exit(0);
}
#[doc(hidden)]
pub fn write_to<W: Write>(&self, w: &mut W) -> io::Result<()> {
write!(w, "{}", self.message)
}
#[doc(hidden)]
pub fn argument_conflict<O, U>(
arg: &AnyArg,
other: Option<O>,
usage: U,
color: ColorWhen,
) -> Self
where
O: Into<String>,
U: Display,
{
let mut v = vec![arg.name().to_owned()];
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!(
"{} The argument '{}' cannot be used with {}\n\n\
{}\n\n\
For more information try {}",
c.error("error:"),
c.warning(&*arg.to_string()),
match other {
Some(name) => {
let n = name.into();
v.push(n.clone());
c.warning(format!("'{}'", n))
}
None => c.none("one or more of the other specified arguments".to_owned()),
},
usage,
c.good("--help")
),
kind: ErrorKind::ArgumentConflict,
info: Some(v),
}
}
#[doc(hidden)]
pub fn empty_value<U>(arg: &AnyArg, usage: U, color: ColorWhen) -> Self
where
U: Display,
{
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!(
"{} The argument '{}' requires a value but none was supplied\
\n\n\
{}\n\n\
For more information try {}",
c.error("error:"),
c.warning(arg.to_string()),
usage,
c.good("--help")
),
kind: ErrorKind::EmptyValue,
info: Some(vec![arg.name().to_owned()]),
}
}
#[doc(hidden)]
pub fn invalid_value<B, G, U>(
bad_val: B,
good_vals: &[G],
arg: &AnyArg,
usage: U,
color: ColorWhen,
) -> Self
where
B: AsRef<str>,
G: AsRef<str> + Display,
U: Display,
{
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
let suffix = suggestions::did_you_mean_value_suffix(bad_val.as_ref(), good_vals.iter());
let mut sorted = vec![];
for v in good_vals {
let val = format!("{}", c.good(v));
sorted.push(val);
}
sorted.sort();
let valid_values = sorted.join(", ");
Error {
message: format!(
"{} '{}' isn't a valid value for '{}'\n\t\
[possible values: {}]\n\
{}\n\n\
{}\n\n\
For more information try {}",
c.error("error:"),
c.warning(bad_val.as_ref()),
c.warning(arg.to_string()),
valid_values,
suffix.0,
usage,
c.good("--help")
),
kind: ErrorKind::InvalidValue,
info: Some(vec![arg.name().to_owned(), bad_val.as_ref().to_owned()]),
}
}
#[doc(hidden)]
pub fn invalid_subcommand<S, D, N, U>(
subcmd: S,
did_you_mean: D,
name: N,
usage: U,
color: ColorWhen,
) -> Self
where
S: Into<String>,
D: AsRef<str> + Display,
N: Display,
U: Display,
{
let s = subcmd.into();
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!(
"{} The subcommand '{}' wasn't recognized\n\t\
Did you mean '{}'?\n\n\
If you believe you received this message in error, try \
re-running with '{} {} {}'\n\n\
{}\n\n\
For more information try {}",
c.error("error:"),
c.warning(&*s),
c.good(did_you_mean.as_ref()),
name,
c.good("--"),
&*s,
usage,
c.good("--help")
),
kind: ErrorKind::InvalidSubcommand,
info: Some(vec![s]),
}
}
#[doc(hidden)]
pub fn unrecognized_subcommand<S, N>(subcmd: S, name: N, color: ColorWhen) -> Self
where
S: Into<String>,
N: Display,
{
let s = subcmd.into();
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!(
"{} The subcommand '{}' wasn't recognized\n\n\
{}\n\t\
{} help <subcommands>...\n\n\
For more information try {}",
c.error("error:"),
c.warning(&*s),
c.warning("USAGE:"),
name,
c.good("--help")
),
kind: ErrorKind::UnrecognizedSubcommand,
info: Some(vec![s]),
}
}
#[doc(hidden)]
pub fn missing_required_argument<R, U>(required: R, usage: U, color: ColorWhen) -> Self
where
R: Display,
U: Display,
{
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!(
"{} The following required arguments were not provided:{}\n\n\
{}\n\n\
For more information try {}",
c.error("error:"),
required,
usage,
c.good("--help")
),
kind: ErrorKind::MissingRequiredArgument,
info: None,
}
}
#[doc(hidden)]
pub fn missing_subcommand<N, U>(name: N, usage: U, color: ColorWhen) -> Self
where
N: AsRef<str> + Display,
U: Display,
{
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!(
"{} '{}' requires a subcommand, but one was not provided\n\n\
{}\n\n\
For more information try {}",
c.error("error:"),
c.warning(name),
usage,
c.good("--help")
),
kind: ErrorKind::MissingSubcommand,
info: None,
}
}
#[doc(hidden)]
pub fn invalid_utf8<U>(usage: U, color: ColorWhen) -> Self
where
U: Display,
{
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!(
"{} Invalid UTF-8 was detected in one or more arguments\n\n\
{}\n\n\
For more information try {}",
c.error("error:"),
usage,
c.good("--help")
),
kind: ErrorKind::InvalidUtf8,
info: None,
}
}
#[doc(hidden)]
pub fn too_many_values<V, U>(val: V, arg: &AnyArg, usage: U, color: ColorWhen) -> Self
where
V: AsRef<str> + Display + ToOwned,
U: Display,
{
let v = val.as_ref();
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!(
"{} The value '{}' was provided to '{}', but it wasn't expecting \
any more values\n\n\
{}\n\n\
For more information try {}",
c.error("error:"),
c.warning(v),
c.warning(arg.to_string()),
usage,
c.good("--help")
),
kind: ErrorKind::TooManyValues,
info: Some(vec![arg.name().to_owned(), v.to_owned()]),
}
}
#[doc(hidden)]
pub fn too_few_values<U>(
arg: &AnyArg,
min_vals: u64,
curr_vals: usize,
usage: U,
color: ColorWhen,
) -> Self
where
U: Display,
{
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!(
"{} The argument '{}' requires at least {} values, but only {} w{} \
provided\n\n\
{}\n\n\
For more information try {}",
c.error("error:"),
c.warning(arg.to_string()),
c.warning(min_vals.to_string()),
c.warning(curr_vals.to_string()),
if curr_vals > 1 { "ere" } else { "as" },
usage,
c.good("--help")
),
kind: ErrorKind::TooFewValues,
info: Some(vec![arg.name().to_owned()]),
}
}
#[doc(hidden)]
pub fn value_validation(arg: Option<&AnyArg>, err: String, color: ColorWhen) -> Self {
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!(
"{} Invalid value{}: {}",
c.error("error:"),
if let Some(a) = arg {
format!(" for '{}'", c.warning(a.to_string()))
} else {
"".to_string()
},
err
),
kind: ErrorKind::ValueValidation,
info: None,
}
}
#[doc(hidden)]
pub fn value_validation_auto(err: String) -> Self {
let n: Option<&AnyArg> = None;
Error::value_validation(n, err, ColorWhen::Auto)
}
#[doc(hidden)]
pub fn wrong_number_of_values<S, U>(
arg: &AnyArg,
num_vals: u64,
curr_vals: usize,
suffix: S,
usage: U,
color: ColorWhen,
) -> Self
where
S: Display,
U: Display,
{
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!(
"{} The argument '{}' requires {} values, but {} w{} \
provided\n\n\
{}\n\n\
For more information try {}",
c.error("error:"),
c.warning(arg.to_string()),
c.warning(num_vals.to_string()),
c.warning(curr_vals.to_string()),
suffix,
usage,
c.good("--help")
),
kind: ErrorKind::WrongNumberOfValues,
info: Some(vec![arg.name().to_owned()]),
}
}
#[doc(hidden)]
pub fn unexpected_multiple_usage<U>(arg: &AnyArg, usage: U, color: ColorWhen) -> Self
where
U: Display,
{
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!(
"{} The argument '{}' was provided more than once, but cannot \
be used multiple times\n\n\
{}\n\n\
For more information try {}",
c.error("error:"),
c.warning(arg.to_string()),
usage,
c.good("--help")
),
kind: ErrorKind::UnexpectedMultipleUsage,
info: Some(vec![arg.name().to_owned()]),
}
}
#[doc(hidden)]
pub fn unknown_argument<A, U>(arg: A, did_you_mean: &str, usage: U, color: ColorWhen) -> Self
where
A: Into<String>,
U: Display,
{
let a = arg.into();
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!(
"{} Found argument '{}' which wasn't expected, or isn't valid in \
this context{}\n\
{}\n\n\
For more information try {}",
c.error("error:"),
c.warning(&*a),
if did_you_mean.is_empty() {
"\n".to_owned()
} else {
format!("{}\n", did_you_mean)
},
usage,
c.good("--help")
),
kind: ErrorKind::UnknownArgument,
info: Some(vec![a]),
}
}
#[doc(hidden)]
pub fn io_error(e: &Error, color: ColorWhen) -> Self {
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: color,
});
Error {
message: format!("{} {}", c.error("error:"), e.description()),
kind: ErrorKind::Io,
info: None,
}
}
#[doc(hidden)]
pub fn argument_not_found_auto<A>(arg: A) -> Self
where
A: Into<String>,
{
let a = arg.into();
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: ColorWhen::Auto,
});
Error {
message: format!("{} The argument '{}' wasn't found", c.error("error:"), a),
kind: ErrorKind::ArgumentNotFound,
info: Some(vec![a]),
}
}
/// Create an error with a custom description.
///
/// This can be used in combination with `Error::exit` to exit your program
/// with a custom error message.
pub fn with_description(description: &str, kind: ErrorKind) -> Self {
let c = Colorizer::new(ColorizerOption {
use_stderr: true,
when: ColorWhen::Auto,
});
Error {
message: format!("{} {}", c.error("error:"), description),
kind,
info: None,
}
}
}
impl StdError for Error {
fn description(&self) -> &str {
&*self.message
}
}
impl Display for Error {
fn fmt(&self, f: &mut std_fmt::Formatter) -> std_fmt::Result {
writeln!(f, "{}", self.message)
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::with_description(e.description(), ErrorKind::Io)
}
}
impl From<std_fmt::Error> for Error {
fn from(e: std_fmt::Error) -> Self {
Error::with_description(e.description(), ErrorKind::Format)
}
}

View File

@@ -0,0 +1,192 @@
#[cfg(all(feature = "color", not(target_os = "windows")))]
use ansi_term::ANSIString;
#[cfg(all(feature = "color", not(target_os = "windows")))]
use ansi_term::Colour::{Green, Red, Yellow};
use std::env;
use std::fmt;
#[doc(hidden)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ColorWhen {
Auto,
Always,
Never,
}
#[cfg(feature = "color")]
pub fn is_a_tty(stderr: bool) -> bool {
debugln!("is_a_tty: stderr={:?}", stderr);
let stream = if stderr {
atty::Stream::Stderr
} else {
atty::Stream::Stdout
};
atty::is(stream)
}
#[cfg(not(feature = "color"))]
pub fn is_a_tty(_: bool) -> bool {
debugln!("is_a_tty;");
false
}
pub fn is_term_dumb() -> bool {
env::var("TERM").ok() == Some(String::from("dumb"))
}
#[doc(hidden)]
pub struct ColorizerOption {
pub use_stderr: bool,
pub when: ColorWhen,
}
#[doc(hidden)]
pub struct Colorizer {
when: ColorWhen,
}
macro_rules! color {
($_self:ident, $c:ident, $m:expr) => {
match $_self.when {
ColorWhen::Auto => Format::$c($m),
ColorWhen::Always => Format::$c($m),
ColorWhen::Never => Format::None($m),
}
};
}
impl Colorizer {
pub fn new(option: ColorizerOption) -> Colorizer {
let is_a_tty = is_a_tty(option.use_stderr);
let is_term_dumb = is_term_dumb();
Colorizer {
when: match option.when {
ColorWhen::Auto if is_a_tty && !is_term_dumb => ColorWhen::Auto,
ColorWhen::Auto => ColorWhen::Never,
when => when,
},
}
}
pub fn good<T>(&self, msg: T) -> Format<T>
where
T: fmt::Display + AsRef<str>,
{
debugln!("Colorizer::good;");
color!(self, Good, msg)
}
pub fn warning<T>(&self, msg: T) -> Format<T>
where
T: fmt::Display + AsRef<str>,
{
debugln!("Colorizer::warning;");
color!(self, Warning, msg)
}
pub fn error<T>(&self, msg: T) -> Format<T>
where
T: fmt::Display + AsRef<str>,
{
debugln!("Colorizer::error;");
color!(self, Error, msg)
}
pub fn none<T>(&self, msg: T) -> Format<T>
where
T: fmt::Display + AsRef<str>,
{
debugln!("Colorizer::none;");
Format::None(msg)
}
}
impl Default for Colorizer {
fn default() -> Self {
Colorizer::new(ColorizerOption {
use_stderr: true,
when: ColorWhen::Auto,
})
}
}
/// Defines styles for different types of error messages. Defaults to Error=Red, Warning=Yellow,
/// and Good=Green
#[derive(Debug)]
#[doc(hidden)]
pub enum Format<T> {
/// Defines the style used for errors, defaults to Red
Error(T),
/// Defines the style used for warnings, defaults to Yellow
Warning(T),
/// Defines the style used for good values, defaults to Green
Good(T),
/// Defines no formatting style
None(T),
}
#[cfg(all(feature = "color", not(target_os = "windows")))]
impl<T: AsRef<str>> Format<T> {
fn format(&self) -> ANSIString {
match *self {
Format::Error(ref e) => Red.bold().paint(e.as_ref()),
Format::Warning(ref e) => Yellow.paint(e.as_ref()),
Format::Good(ref e) => Green.paint(e.as_ref()),
Format::None(ref e) => ANSIString::from(e.as_ref()),
}
}
}
#[cfg(any(not(feature = "color"), target_os = "windows"))]
#[cfg_attr(feature = "cargo-clippy", allow(clippy::match_same_arms))]
impl<T: fmt::Display> Format<T> {
fn format(&self) -> &T {
match *self {
Format::Error(ref e) => e,
Format::Warning(ref e) => e,
Format::Good(ref e) => e,
Format::None(ref e) => e,
}
}
}
#[cfg(all(feature = "color", not(target_os = "windows")))]
impl<T: AsRef<str>> fmt::Display for Format<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", &self.format())
}
}
#[cfg(any(not(feature = "color"), target_os = "windows"))]
impl<T: fmt::Display> fmt::Display for Format<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", &self.format())
}
}
#[cfg(all(test, feature = "color", not(target_os = "windows")))]
mod test {
use super::Format;
use ansi_term::ANSIString;
use ansi_term::Colour::{Green, Red, Yellow};
#[test]
fn colored_output() {
let err = Format::Error("error");
assert_eq!(
&*format!("{}", err),
&*format!("{}", Red.bold().paint("error"))
);
let good = Format::Good("good");
assert_eq!(&*format!("{}", good), &*format!("{}", Green.paint("good")));
let warn = Format::Warning("warn");
assert_eq!(&*format!("{}", warn), &*format!("{}", Yellow.paint("warn")));
let none = Format::None("none");
assert_eq!(
&*format!("{}", none),
&*format!("{}", ANSIString::from("none"))
);
}
}

View File

@@ -0,0 +1,638 @@
// Copyright ⓒ 2015-2016 Kevin B. Knapp and [`clap-rs` contributors](https://github.com/clap-rs/clap/blob/v2.33.1/CONTRIBUTORS.md).
// Licensed under the MIT license
// (see LICENSE or <http://opensource.org/licenses/MIT>) All files in the project carrying such
// notice may not be copied, modified, or distributed except according to those terms.
//! `clap` is a simple-to-use, efficient, and full-featured library for parsing command line
//! arguments and subcommands when writing console/terminal applications.
//!
//! ## About
//!
//! `clap` is used to parse *and validate* the string of command line arguments provided by the user
//! at runtime. You provide the list of valid possibilities, and `clap` handles the rest. This means
//! you focus on your *applications* functionality, and less on the parsing and validating of
//! arguments.
//!
//! `clap` also provides the traditional version and help switches (or flags) 'for free' meaning
//! automatically with no configuration. It does this by checking the list of valid possibilities you
//! supplied and adding only the ones you haven't already defined. If you are using subcommands,
//! `clap` will also auto-generate a `help` subcommand for you in addition to the traditional flags.
//!
//! Once `clap` parses the user provided string of arguments, it returns the matches along with any
//! applicable values. If the user made an error or typo, `clap` informs them of the mistake and
//! exits gracefully (or returns a `Result` type and allows you to perform any clean up prior to
//! exit). Because of this, you can make reasonable assumptions in your code about the validity of
//! the arguments.
//!
//!
//! ## Quick Example
//!
//! The following examples show a quick example of some of the very basic functionality of `clap`.
//! For more advanced usage, such as requirements, conflicts, groups, multiple values and
//! occurrences see the [documentation](https://docs.rs/clap/), [examples/] directory of
//! this repository or the [video tutorials].
//!
//! **NOTE:** All of these examples are functionally the same, but show different styles in which to
//! use `clap`
//!
//! The first example shows a method that allows more advanced configuration options (not shown in
//! this small example), or even dynamically generating arguments when desired. The downside is it's
//! more verbose.
//!
//! ```no_run
//! // (Full example with detailed comments in examples/01b_quick_example.rs)
//! //
//! // This example demonstrates clap's full 'builder pattern' style of creating arguments which is
//! // more verbose, but allows easier editing, and at times more advanced options, or the possibility
//! // to generate arguments dynamically.
//! extern crate clap;
//! use clap::{Arg, App, SubCommand};
//!
//! fn main() {
//! let matches = App::new("My Super Program")
//! .version("1.0")
//! .author("Kevin K. <kbknapp@gmail.com>")
//! .about("Does awesome things")
//! .arg(Arg::with_name("config")
//! .short("c")
//! .long("config")
//! .value_name("FILE")
//! .help("Sets a custom config file")
//! .takes_value(true))
//! .arg(Arg::with_name("INPUT")
//! .help("Sets the input file to use")
//! .required(true)
//! .index(1))
//! .arg(Arg::with_name("v")
//! .short("v")
//! .multiple(true)
//! .help("Sets the level of verbosity"))
//! .subcommand(SubCommand::with_name("test")
//! .about("controls testing features")
//! .version("1.3")
//! .author("Someone E. <someone_else@other.com>")
//! .arg(Arg::with_name("debug")
//! .short("d")
//! .help("print debug information verbosely")))
//! .get_matches();
//!
//! // Gets a value for config if supplied by user, or defaults to "default.conf"
//! let config = matches.value_of("config").unwrap_or("default.conf");
//! println!("Value for config: {}", config);
//!
//! // Calling .unwrap() is safe here because "INPUT" is required (if "INPUT" wasn't
//! // required we could have used an 'if let' to conditionally get the value)
//! println!("Using input file: {}", matches.value_of("INPUT").unwrap());
//!
//! // Vary the output based on how many times the user used the "verbose" flag
//! // (i.e. 'myprog -v -v -v' or 'myprog -vvv' vs 'myprog -v'
//! match matches.occurrences_of("v") {
//! 0 => println!("No verbose info"),
//! 1 => println!("Some verbose info"),
//! 2 => println!("Tons of verbose info"),
//! 3 | _ => println!("Don't be crazy"),
//! }
//!
//! // You can handle information about subcommands by requesting their matches by name
//! // (as below), requesting just the name used, or both at the same time
//! if let Some(matches) = matches.subcommand_matches("test") {
//! if matches.is_present("debug") {
//! println!("Printing debug info...");
//! } else {
//! println!("Printing normally...");
//! }
//! }
//!
//! // more program logic goes here...
//! }
//! ```
//!
//! The next example shows a far less verbose method, but sacrifices some of the advanced
//! configuration options (not shown in this small example). This method also takes a *very* minor
//! runtime penalty.
//!
//! ```no_run
//! // (Full example with detailed comments in examples/01a_quick_example.rs)
//! //
//! // This example demonstrates clap's "usage strings" method of creating arguments
//! // which is less verbose
//! extern crate clap;
//! use clap::{Arg, App, SubCommand};
//!
//! fn main() {
//! let matches = App::new("myapp")
//! .version("1.0")
//! .author("Kevin K. <kbknapp@gmail.com>")
//! .about("Does awesome things")
//! .args_from_usage(
//! "-c, --config=[FILE] 'Sets a custom config file'
//! <INPUT> 'Sets the input file to use'
//! -v... 'Sets the level of verbosity'")
//! .subcommand(SubCommand::with_name("test")
//! .about("controls testing features")
//! .version("1.3")
//! .author("Someone E. <someone_else@other.com>")
//! .arg_from_usage("-d, --debug 'Print debug information'"))
//! .get_matches();
//!
//! // Same as previous example...
//! }
//! ```
//!
//! This third method shows how you can use a YAML file to build your CLI and keep your Rust source
//! tidy or support multiple localized translations by having different YAML files for each
//! localization.
//!
//! First, create the `cli.yml` file to hold your CLI options, but it could be called anything we
//! like:
//!
//! ```yaml
//! name: myapp
//! version: "1.0"
//! author: Kevin K. <kbknapp@gmail.com>
//! about: Does awesome things
//! args:
//! - config:
//! short: c
//! long: config
//! value_name: FILE
//! help: Sets a custom config file
//! takes_value: true
//! - INPUT:
//! help: Sets the input file to use
//! required: true
//! index: 1
//! - verbose:
//! short: v
//! multiple: true
//! help: Sets the level of verbosity
//! subcommands:
//! - test:
//! about: controls testing features
//! version: "1.3"
//! author: Someone E. <someone_else@other.com>
//! args:
//! - debug:
//! short: d
//! help: print debug information
//! ```
//!
//! Since this feature requires additional dependencies that not everyone may want, it is *not*
//! compiled in by default and we need to enable a feature flag in Cargo.toml:
//!
//! Simply change your `clap = "~2.27.0"` to `clap = {version = "~2.27.0", features = ["yaml"]}`.
//!
//! At last we create our `main.rs` file just like we would have with the previous two examples:
//!
//! ```ignore
//! // (Full example with detailed comments in examples/17_yaml.rs)
//! //
//! // This example demonstrates clap's building from YAML style of creating arguments which is far
//! // more clean, but takes a very small performance hit compared to the other two methods.
//! #[macro_use]
//! extern crate clap;
//! use clap::App;
//!
//! fn main() {
//! // The YAML file is found relative to the current file, similar to how modules are found
//! let yaml = load_yaml!("cli.yml");
//! let matches = App::from_yaml(yaml).get_matches();
//!
//! // Same as previous examples...
//! }
//! ```
//!
//! Finally there is a macro version, which is like a hybrid approach offering the speed of the
//! builder pattern (the first example), but without all the verbosity.
//!
//! ```no_run
//! #[macro_use]
//! extern crate clap;
//!
//! fn main() {
//! let matches = clap_app!(myapp =>
//! (version: "1.0")
//! (author: "Kevin K. <kbknapp@gmail.com>")
//! (about: "Does awesome things")
//! (@arg CONFIG: -c --config +takes_value "Sets a custom config file")
//! (@arg INPUT: +required "Sets the input file to use")
//! (@arg debug: -d ... "Sets the level of debugging information")
//! (@subcommand test =>
//! (about: "controls testing features")
//! (version: "1.3")
//! (author: "Someone E. <someone_else@other.com>")
//! (@arg verbose: -v --verbose "Print test information verbosely")
//! )
//! ).get_matches();
//!
//! // Same as before...
//! }
//! ```
//!
//! If you were to compile any of the above programs and run them with the flag `--help` or `-h` (or
//! `help` subcommand, since we defined `test` as a subcommand) the following would be output
//!
//! ```text
//! $ myprog --help
//! My Super Program 1.0
//! Kevin K. <kbknapp@gmail.com>
//! Does awesome things
//!
//! USAGE:
//! MyApp [FLAGS] [OPTIONS] <INPUT> [SUBCOMMAND]
//!
//! FLAGS:
//! -h, --help Prints this message
//! -v Sets the level of verbosity
//! -V, --version Prints version information
//!
//! OPTIONS:
//! -c, --config <FILE> Sets a custom config file
//!
//! ARGS:
//! INPUT The input file to use
//!
//! SUBCOMMANDS:
//! help Prints this message
//! test Controls testing features
//! ```
//!
//! **NOTE:** You could also run `myapp test --help` to see similar output and options for the
//! `test` subcommand.
//!
//! ## Try it!
//!
//! ### Pre-Built Test
//!
//! To try out the pre-built example, use the following steps:
//!
//! * Clone the repository `$ git clone https://github.com/clap-rs/clap && cd clap-rs/tests`
//! * Compile the example `$ cargo build --release`
//! * Run the help info `$ ./target/release/claptests --help`
//! * Play with the arguments!
//!
//! ### BYOB (Build Your Own Binary)
//!
//! To test out `clap`'s default auto-generated help/version follow these steps:
//!
//! * Create a new cargo project `$ cargo new fake --bin && cd fake`
//! * Add `clap` to your `Cargo.toml`
//!
//! ```toml
//! [dependencies]
//! clap = "2"
//! ```
//!
//! * Add the following to your `src/main.rs`
//!
//! ```no_run
//! extern crate clap;
//! use clap::App;
//!
//! fn main() {
//! App::new("fake").version("v1.0-beta").get_matches();
//! }
//! ```
//!
//! * Build your program `$ cargo build --release`
//! * Run with help or version `$ ./target/release/fake --help` or `$ ./target/release/fake
//! --version`
//!
//! ## Usage
//!
//! For full usage, add `clap` as a dependency in your `Cargo.toml` (it is **highly** recommended to
//! use the `~major.minor.patch` style versions in your `Cargo.toml`, for more information see
//! [Compatibility Policy](#compatibility-policy)) to use from crates.io:
//!
//! ```toml
//! [dependencies]
//! clap = "~2.27.0"
//! ```
//!
//! Or get the latest changes from the master branch at github:
//!
//! ```toml
//! [dependencies.clap]
//! git = "https://github.com/clap-rs/clap.git"
//! ```
//!
//! Add `extern crate clap;` to your crate root.
//!
//! Define a list of valid arguments for your program (see the
//! [documentation](https://docs.rs/clap/) or [examples/] directory of this repo)
//!
//! Then run `cargo build` or `cargo update && cargo build` for your project.
//!
//! ### Optional Dependencies / Features
//!
//! #### Features enabled by default
//!
//! * `suggestions`: Turns on the `Did you mean '--myoption'?` feature for when users make typos. (builds dependency `strsim`)
//! * `color`: Turns on colored error messages. This feature only works on non-Windows OSs. (builds dependency `ansi-term` and `atty`)
//! * `wrap_help`: Wraps the help at the actual terminal width when
//! available, instead of 120 characters. (builds dependency `textwrap`
//! with feature `term_size`)
//!
//! To disable these, add this to your `Cargo.toml`:
//!
//! ```toml
//! [dependencies.clap]
//! version = "~2.27.0"
//! default-features = false
//! ```
//!
//! You can also selectively enable only the features you'd like to include, by adding:
//!
//! ```toml
//! [dependencies.clap]
//! version = "~2.27.0"
//! default-features = false
//!
//! # Cherry-pick the features you'd like to use
//! features = [ "suggestions", "color" ]
//! ```
//!
//! #### Opt-in features
//!
//! * **"yaml"**: Enables building CLIs from YAML documents. (builds dependency `yaml-rust`)
//! * **"unstable"**: Enables unstable `clap` features that may change from release to release
//!
//! ### Dependencies Tree
//!
//! The following graphic depicts `clap`s dependency graph (generated using
//! [cargo-graph](https://github.com/kbknapp/cargo-graph)).
//!
//! * **Dashed** Line: Optional dependency
//! * **Red** Color: **NOT** included by default (must use cargo `features` to enable)
//! * **Blue** Color: Dev dependency, only used while developing.
//!
//! ![clap dependencies](https://github.com/clap-rs/clap/blob/v2.34.0/clap_dep_graph.png)
//!
//! ### More Information
//!
//! You can find complete documentation on the [docs.rs](https://docs.rs/clap/) for this project.
//!
//! You can also find usage examples in the [examples/] directory of this repo.
//!
//! #### Video Tutorials
//!
//! There's also the video tutorial series [Argument Parsing with Rust v2][video tutorials].
//!
//! These videos slowly trickle out as I finish them and currently a work in progress.
//!
//! ## How to Contribute
//!
//! Contributions are always welcome! And there is a multitude of ways in which you can help
//! depending on what you like to do, or are good at. Anything from documentation, code cleanup,
//! issue completion, new features, you name it, even filing issues is contributing and greatly
//! appreciated!
//!
//! Another really great way to help is if you find an interesting, or helpful way in which to use
//! `clap`. You can either add it to the [examples/] directory, or file an issue and tell
//! me. I'm all about giving credit where credit is due :)
//!
//! Please read [CONTRIBUTING.md](https://github.com/clap-rs/clap/blob/v2.34.0/.github/CONTRIBUTING.md) before you start contributing.
//!
//!
//! ### Testing Code
//!
//! To test with all features both enabled and disabled, you can run theese commands:
//!
//! ```text
//! $ cargo test --no-default-features
//! $ cargo test --features "yaml unstable"
//! ```
//!
//! Alternatively, if you have [`just`](https://github.com/casey/just) installed you can run the
//! prebuilt recipes. *Not* using `just` is perfectly fine as well, it simply bundles commands
//! automatically.
//!
//! For example, to test the code, as above simply run:
//!
//! ```text
//! $ just run-tests
//! ```
//!
//! From here on, I will list the appropriate `cargo` command as well as the `just` command.
//!
//! Sometimes it's helpful to only run a subset of the tests, which can be done via:
//!
//! ```text
//! $ cargo test --test <test_name>
//!
//! # Or
//!
//! $ just run-test <test_name>
//! ```
//!
//! ### Linting Code
//!
//! During the CI process `clap` runs against many different lints using
//! [`clippy`](https://github.com/Manishearth/rust-clippy). In order to check if these lints pass on
//! your own computer prior to submitting a PR you'll need a nightly compiler.
//!
//! In order to check the code for lints run either:
//!
//! ```text
//! $ rustup override add nightly
//! $ cargo build --features lints
//! $ rustup override remove
//!
//! # Or
//!
//! $ just lint
//! ```
//!
//! ### Debugging Code
//!
//! Another helpful technique is to see the `clap` debug output while developing features. In order
//! to see the debug output while running the full test suite or individual tests, run:
//!
//! ```text
//! $ cargo test --features debug
//!
//! # Or for individual tests
//! $ cargo test --test <test_name> --features debug
//!
//! # The corresponding just command for individual debugging tests is:
//! $ just debug <test_name>
//! ```
//!
//! ### Goals
//!
//! There are a few goals of `clap` that I'd like to maintain throughout contributions. If your
//! proposed changes break, or go against any of these goals we'll discuss the changes further
//! before merging (but will *not* be ignored, all contributes are welcome!). These are by no means
//! hard-and-fast rules, as I'm no expert and break them myself from time to time (even if by
//! mistake or ignorance).
//!
//! * Remain backwards compatible when possible
//! - If backwards compatibility *must* be broken, use deprecation warnings if at all possible before
//! removing legacy code - This does not apply for security concerns
//! * Parse arguments quickly
//! - Parsing of arguments shouldn't slow down usage of the main program - This is also true of
//! generating help and usage information (although *slightly* less stringent, as the program is about
//! to exit)
//! * Try to be cognizant of memory usage
//! - Once parsing is complete, the memory footprint of `clap` should be low since the main program
//! is the star of the show
//! * `panic!` on *developer* error, exit gracefully on *end-user* error
//!
//! ### Compatibility Policy
//!
//! Because `clap` takes `SemVer` and compatibility seriously, this is the official policy regarding
//! breaking changes and previous versions of Rust.
//!
//! `clap` will pin the minimum required version of Rust to the CI builds. Bumping the minimum
//! version of Rust is considered a minor breaking change, meaning *at a minimum* the minor version
//! of `clap` will be bumped.
//!
//! In order to keep from being surprised by breaking changes, it is **highly** recommended to use
//! the `~major.minor.patch` style in your `Cargo.toml`:
//!
//! ```toml
//! [dependencies] clap = "~2.27.0"
//! ```
//!
//! This will cause *only* the patch version to be updated upon a `cargo update` call, and therefore
//! cannot break due to new features, or bumped minimum versions of Rust.
//!
//! #### Minimum Version of Rust
//!
//! `clap` will officially support current stable Rust, minus two releases, but may work with prior
//! releases as well. For example, current stable Rust at the time of this writing is 1.21.0,
//! meaning `clap` is guaranteed to compile with 1.19.0 and beyond. At the 1.22.0 release, `clap`
//! will be guaranteed to compile with 1.20.0 and beyond, etc.
//!
//! Upon bumping the minimum version of Rust (assuming it's within the stable-2 range), it *must* be
//! clearly annotated in the `CHANGELOG.md`
//!
//! ## License
//!
//! `clap` is licensed under the MIT license. Please read the [LICENSE-MIT][license] file in
//! this repository for more information.
//!
//! [examples/]: https://github.com/clap-rs/clap/tree/v2.34.0/examples
//! [video tutorials]: https://www.youtube.com/playlist?list=PLza5oFLQGTl2Z5T8g1pRkIynR3E0_pc7U
//! [license]: https://github.com/clap-rs/clap/blob/v2.34.0/LICENSE-MIT
#![crate_type = "lib"]
#![doc(html_root_url = "https://docs.rs/clap/2.34.0")]
#![deny(
missing_docs,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
unused_import_braces,
unused_allocation
)]
// Lints we'd like to deny but are currently failing for upstream crates
// unused_qualifications (bitflags, clippy)
// trivial_numeric_casts (bitflags)
#![cfg_attr(
not(any(feature = "cargo-clippy", feature = "nightly")),
forbid(unstable_features)
)]
//#![cfg_attr(feature = "lints", feature(plugin))]
//#![cfg_attr(feature = "lints", plugin(clippy))]
// Need to disable deny(warnings) while deprecations are active
//#![cfg_attr(feature = "cargo-clippy", deny(warnings))]
// Due to our "MSRV for 2.x will remain unchanged" policy, we can't fix these warnings
#![allow(bare_trait_objects, deprecated)]
#[cfg(all(feature = "color", not(target_os = "windows")))]
extern crate ansi_term;
#[cfg(feature = "color")]
extern crate atty;
#[macro_use]
extern crate bitflags;
#[cfg(feature = "suggestions")]
extern crate strsim;
#[cfg(feature = "wrap_help")]
extern crate term_size;
extern crate textwrap;
extern crate unicode_width;
#[cfg(feature = "vec_map")]
extern crate vec_map;
#[cfg(feature = "yaml")]
extern crate yaml_rust;
pub use app::{App, AppSettings};
pub use args::{Arg, ArgGroup, ArgMatches, ArgSettings, OsValues, SubCommand, Values};
pub use completions::Shell;
pub use errors::{Error, ErrorKind, Result};
pub use fmt::Format;
#[cfg(feature = "yaml")]
pub use yaml_rust::YamlLoader;
#[macro_use]
mod macros;
mod app;
mod args;
mod completions;
mod errors;
mod fmt;
mod map;
mod osstringext;
mod strext;
mod suggestions;
mod usage_parser;
const INTERNAL_ERROR_MSG: &str = "Fatal internal error. Please consider filing a bug \
report at https://github.com/clap-rs/clap/issues";
const INVALID_UTF8: &str = "unexpected invalid UTF-8 code point";
#[cfg(unstable)]
pub use derive::{ArgEnum, ClapApp, FromArgMatches, IntoApp};
#[cfg(unstable)]
mod derive {
/// @TODO @release @docs
pub trait ClapApp: IntoApp + FromArgMatches + Sized {
/// @TODO @release @docs
fn parse() -> Self {
Self::from_argmatches(Self::into_app().get_matches())
}
/// @TODO @release @docs
fn parse_from<I, T>(argv: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
Self::from_argmatches(Self::into_app().get_matches_from(argv))
}
/// @TODO @release @docs
fn try_parse() -> Result<Self, clap::Error> {
Self::try_from_argmatches(Self::into_app().get_matches_safe()?)
}
/// @TODO @release @docs
fn try_parse_from<I, T>(argv: I) -> Result<Self, clap::Error>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
Self::try_from_argmatches(Self::into_app().get_matches_from_safe(argv)?)
}
}
/// @TODO @release @docs
pub trait IntoApp {
/// @TODO @release @docs
fn into_app<'a, 'b>() -> clap::App<'a, 'b>;
}
/// @TODO @release @docs
pub trait FromArgMatches: Sized {
/// @TODO @release @docs
fn from_argmatches<'a>(matches: clap::ArgMatches<'a>) -> Self;
/// @TODO @release @docs
fn try_from_argmatches<'a>(matches: clap::ArgMatches<'a>) -> Result<Self, clap::Error>;
}
/// @TODO @release @docs
pub trait ArgEnum {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,88 @@
#[cfg(feature = "vec_map")]
pub use vec_map::{Values, VecMap};
#[cfg(not(feature = "vec_map"))]
pub use self::vec_map::{Values, VecMap};
#[cfg(not(feature = "vec_map"))]
mod vec_map {
use std::collections::btree_map;
use std::collections::BTreeMap;
use std::fmt::{self, Debug, Formatter};
#[derive(Clone, Default, Debug)]
pub struct VecMap<V> {
inner: BTreeMap<usize, V>,
}
impl<V> VecMap<V> {
pub fn new() -> Self {
VecMap {
inner: Default::default(),
}
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn insert(&mut self, key: usize, value: V) -> Option<V> {
self.inner.insert(key, value)
}
pub fn values(&self) -> Values<V> {
self.inner.values()
}
pub fn iter(&self) -> Iter<V> {
Iter {
inner: self.inner.iter(),
}
}
pub fn contains_key(&self, key: usize) -> bool {
self.inner.contains_key(&key)
}
pub fn entry(&mut self, key: usize) -> Entry<V> {
self.inner.entry(key)
}
pub fn get(&self, key: usize) -> Option<&V> {
self.inner.get(&key)
}
}
pub type Values<'a, V> = btree_map::Values<'a, usize, V>;
pub type Entry<'a, V> = btree_map::Entry<'a, usize, V>;
#[derive(Clone)]
pub struct Iter<'a, V: 'a> {
inner: btree_map::Iter<'a, usize, V>,
}
impl<'a, V: 'a + Debug> Debug for Iter<'a, V> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_list().entries(self.inner.clone()).finish()
}
}
impl<'a, V: 'a> Iterator for Iter<'a, V> {
type Item = (usize, &'a V);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(k, v)| (*k, v))
}
}
impl<'a, V: 'a> DoubleEndedIterator for Iter<'a, V> {
fn next_back(&mut self) -> Option<Self::Item> {
self.inner.next_back().map(|(k, v)| (*k, v))
}
}
}

View File

@@ -0,0 +1,203 @@
use std::ffi::OsStr;
#[cfg(not(any(target_os = "windows", target_arch = "wasm32")))]
use std::os::unix::ffi::OsStrExt;
#[cfg(any(target_os = "windows", target_arch = "wasm32"))]
use crate::INVALID_UTF8;
#[cfg(any(target_os = "windows", target_arch = "wasm32"))]
pub trait OsStrExt3 {
fn from_bytes(b: &[u8]) -> &Self;
fn as_bytes(&self) -> &[u8];
}
#[doc(hidden)]
pub trait OsStrExt2 {
fn starts_with(&self, s: &[u8]) -> bool;
fn split_at_byte(&self, b: u8) -> (&OsStr, &OsStr);
fn split_at(&self, i: usize) -> (&OsStr, &OsStr);
fn trim_left_matches(&self, b: u8) -> &OsStr;
fn contains_byte(&self, b: u8) -> bool;
fn split(&self, b: u8) -> OsSplit;
}
// A starts-with implementation that does not panic when the OsStr contains
// invalid Unicode.
//
// A Windows OsStr is usually UTF-16. If `prefix` is valid UTF-8, we can
// re-encode it as UTF-16, and ask whether `osstr` starts with the same series
// of u16 code units. If `prefix` is not valid UTF-8, then this comparison
// isn't meaningful, and we just return false.
#[cfg(target_os = "windows")]
fn windows_osstr_starts_with(osstr: &OsStr, prefix: &[u8]) -> bool {
use std::os::windows::ffi::OsStrExt;
let prefix_str = if let Ok(s) = std::str::from_utf8(prefix) {
s
} else {
return false;
};
let mut osstr_units = osstr.encode_wide();
let mut prefix_units = prefix_str.encode_utf16();
loop {
match (osstr_units.next(), prefix_units.next()) {
// These code units match. Keep looping.
(Some(o), Some(p)) if o == p => continue,
// We've reached the end of the prefix. It's a match.
(_, None) => return true,
// Otherwise, it's not a match.
_ => return false,
}
}
}
#[test]
#[cfg(target_os = "windows")]
fn test_windows_osstr_starts_with() {
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
fn from_ascii(ascii: &[u8]) -> OsString {
let u16_vec: Vec<u16> = ascii.iter().map(|&c| c as u16).collect();
OsString::from_wide(&u16_vec)
}
// Test all the basic cases.
assert!(windows_osstr_starts_with(&from_ascii(b"abcdef"), b"abc"));
assert!(windows_osstr_starts_with(&from_ascii(b"abcdef"), b"abcdef"));
assert!(!windows_osstr_starts_with(&from_ascii(b"abcdef"), b"def"));
assert!(!windows_osstr_starts_with(&from_ascii(b"abc"), b"abcd"));
// Test the case where the candidate prefix is not valid UTF-8. Note that a
// standalone \xff byte is valid ASCII but not valid UTF-8. Thus although
// these strings look identical, they do not match.
assert!(!windows_osstr_starts_with(&from_ascii(b"\xff"), b"\xff"));
// Test the case where the OsString is not valid UTF-16. It should still be
// possible to match the valid characters at the front.
//
// UTF-16 surrogate characters are only valid in pairs. Including one on
// the end by itself makes this invalid UTF-16.
let surrogate_char: u16 = 0xDC00;
let invalid_unicode =
OsString::from_wide(&['a' as u16, 'b' as u16, 'c' as u16, surrogate_char]);
assert!(
invalid_unicode.to_str().is_none(),
"This string is invalid Unicode, and conversion to &str should fail.",
);
assert!(windows_osstr_starts_with(&invalid_unicode, b"abc"));
assert!(!windows_osstr_starts_with(&invalid_unicode, b"abcd"));
}
#[cfg(any(target_os = "windows", target_arch = "wasm32"))]
impl OsStrExt3 for OsStr {
fn from_bytes(b: &[u8]) -> &Self {
use std::mem;
unsafe { mem::transmute(b) }
}
fn as_bytes(&self) -> &[u8] {
self.to_str().map(|s| s.as_bytes()).expect(INVALID_UTF8)
}
}
impl OsStrExt2 for OsStr {
fn starts_with(&self, s: &[u8]) -> bool {
#[cfg(target_os = "windows")]
{
// On Windows, the as_bytes() method will panic if the OsStr
// contains invalid Unicode. To avoid this, we use a
// Windows-specific starts-with function that doesn't rely on
// as_bytes(). This is necessary for Windows command line
// applications to handle non-Unicode arguments successfully. This
// allows common cases like `clap.exe [invalid]` to succeed, though
// cases that require string splitting will still fail, like
// `clap.exe --arg=[invalid]`. Note that this entire module is
// replaced in Clap 3.x, so this workaround is specific to the 2.x
// branch.
windows_osstr_starts_with(self, s)
}
#[cfg(not(target_os = "windows"))]
{
self.as_bytes().starts_with(s)
}
}
fn contains_byte(&self, byte: u8) -> bool {
for b in self.as_bytes() {
if b == &byte {
return true;
}
}
false
}
fn split_at_byte(&self, byte: u8) -> (&OsStr, &OsStr) {
for (i, b) in self.as_bytes().iter().enumerate() {
if b == &byte {
return (
OsStr::from_bytes(&self.as_bytes()[..i]),
OsStr::from_bytes(&self.as_bytes()[i + 1..]),
);
}
}
(
&*self,
OsStr::from_bytes(&self.as_bytes()[self.len()..self.len()]),
)
}
fn trim_left_matches(&self, byte: u8) -> &OsStr {
let mut found = false;
for (i, b) in self.as_bytes().iter().enumerate() {
if b != &byte {
return OsStr::from_bytes(&self.as_bytes()[i..]);
} else {
found = true;
}
}
if found {
return OsStr::from_bytes(&self.as_bytes()[self.len()..]);
}
&*self
}
fn split_at(&self, i: usize) -> (&OsStr, &OsStr) {
(
OsStr::from_bytes(&self.as_bytes()[..i]),
OsStr::from_bytes(&self.as_bytes()[i..]),
)
}
fn split(&self, b: u8) -> OsSplit {
OsSplit {
sep: b,
val: self.as_bytes(),
pos: 0,
}
}
}
#[doc(hidden)]
#[derive(Clone, Debug)]
pub struct OsSplit<'a> {
sep: u8,
val: &'a [u8],
pos: usize,
}
impl<'a> Iterator for OsSplit<'a> {
type Item = &'a OsStr;
fn next(&mut self) -> Option<&'a OsStr> {
debugln!("OsSplit::next: self={:?}", self);
if self.pos == self.val.len() {
return None;
}
let start = self.pos;
for b in &self.val[start..] {
self.pos += 1;
if *b == self.sep {
return Some(OsStr::from_bytes(&self.val[start..self.pos - 1]));
}
}
Some(OsStr::from_bytes(&self.val[start..]))
}
}

View File

@@ -0,0 +1,16 @@
pub trait _StrExt {
fn _is_char_boundary(&self, index: usize) -> bool;
}
impl _StrExt for str {
#[inline]
fn _is_char_boundary(&self, index: usize) -> bool {
if index == self.len() {
return true;
}
match self.as_bytes().get(index) {
None => false,
Some(&b) => !(128..192).contains(&b),
}
}
}

View File

@@ -0,0 +1,141 @@
// Internal
use crate::{app::App, fmt::Format};
/// Produces a string from a given list of possible values which is similar to
/// the passed in value `v` with a certain confidence.
/// Thus in a list of possible values like ["foo", "bar"], the value "fop" will yield
/// `Some("foo")`, whereas "blark" would yield `None`.
#[cfg(feature = "suggestions")]
#[cfg_attr(feature = "cargo-clippy", allow(clippy::needless_lifetimes))]
pub fn did_you_mean<'a, T: ?Sized, I>(v: &str, possible_values: I) -> Option<&'a str>
where
T: AsRef<str> + 'a,
I: IntoIterator<Item = &'a T>,
{
let mut candidate: Option<(f64, &str)> = None;
for pv in possible_values {
let confidence = strsim::jaro_winkler(v, pv.as_ref());
if confidence > 0.8 && (candidate.is_none() || (candidate.as_ref().unwrap().0 < confidence))
{
candidate = Some((confidence, pv.as_ref()));
}
}
match candidate {
None => None,
Some((_, candidate)) => Some(candidate),
}
}
#[cfg(not(feature = "suggestions"))]
pub fn did_you_mean<'a, T: ?Sized, I>(_: &str, _: I) -> Option<&'a str>
where
T: AsRef<str> + 'a,
I: IntoIterator<Item = &'a T>,
{
None
}
/// Returns a suffix that can be empty, or is the standard 'did you mean' phrase
pub fn did_you_mean_flag_suffix<'z, T, I>(
arg: &str,
args_rest: &'z [&str],
longs: I,
subcommands: &'z [App],
) -> (String, Option<&'z str>)
where
T: AsRef<str> + 'z,
I: IntoIterator<Item = &'z T>,
{
if let Some(candidate) = did_you_mean(arg, longs) {
let suffix = format!(
"\n\tDid you mean {}{}?",
Format::Good("--"),
Format::Good(candidate)
);
return (suffix, Some(candidate));
}
subcommands
.iter()
.filter_map(|subcommand| {
let opts = subcommand
.p
.flags
.iter()
.filter_map(|f| f.s.long)
.chain(subcommand.p.opts.iter().filter_map(|o| o.s.long));
let candidate = match did_you_mean(arg, opts) {
Some(candidate) => candidate,
None => return None,
};
let score = match args_rest.iter().position(|x| *x == subcommand.get_name()) {
Some(score) => score,
None => return None,
};
let suffix = format!(
"\n\tDid you mean to put '{}{}' after the subcommand '{}'?",
Format::Good("--"),
Format::Good(candidate),
Format::Good(subcommand.get_name())
);
Some((score, (suffix, Some(candidate))))
})
.min_by_key(|&(score, _)| score)
.map(|(_, suggestion)| suggestion)
.unwrap_or_else(|| (String::new(), None))
}
/// Returns a suffix that can be empty, or is the standard 'did you mean' phrase
pub fn did_you_mean_value_suffix<'z, T, I>(arg: &str, values: I) -> (String, Option<&'z str>)
where
T: AsRef<str> + 'z,
I: IntoIterator<Item = &'z T>,
{
match did_you_mean(arg, values) {
Some(candidate) => {
let suffix = format!("\n\tDid you mean '{}'?", Format::Good(candidate));
(suffix, Some(candidate))
}
None => (String::new(), None),
}
}
#[cfg(all(test, features = "suggestions"))]
mod test {
use super::*;
#[test]
fn possible_values_match() {
let p_vals = ["test", "possible", "values"];
assert_eq!(did_you_mean("tst", p_vals.iter()), Some("test"));
}
#[test]
fn possible_values_nomatch() {
let p_vals = ["test", "possible", "values"];
assert!(did_you_mean("hahaahahah", p_vals.iter()).is_none());
}
#[test]
fn suffix_long() {
let p_vals = ["test", "possible", "values"];
let suffix = "\n\tDid you mean \'--test\'?";
assert_eq!(
did_you_mean_flag_suffix("tst", p_vals.iter(), []),
(suffix, Some("test"))
);
}
#[test]
fn suffix_enum() {
let p_vals = ["test", "possible", "values"];
let suffix = "\n\tDid you mean \'test\'?";
assert_eq!(
did_you_mean_value_suffix("tst", p_vals.iter()),
(suffix, Some("test"))
);
}
}

File diff suppressed because it is too large Load Diff