Auto merge of #339 - kbknapp:cleanup, r=Vinatorul

Huge Cleanup

Various cleanups and refactorings, the biggest of which (so far) is error handling, although I'm not 100% sure if I'm happy with the implementation yet or not. It's a big improvement from previous versions, but I think it can get better.

~~**DO NOT MERGE**~~
This commit is contained in:
Homu 2015-11-13 06:29:41 +09:00
commit e4182ad950
33 changed files with 4221 additions and 4304 deletions

View file

@ -14,6 +14,7 @@ script:
cargo test --features yaml &&
make -C clap-tests test &&
travis-cargo --only stable doc -- --features yaml
travis-cargo --only nightly bench
addons:
apt:
packages:

View file

@ -3,7 +3,7 @@
name = "clap"
version = "1.4.7"
authors = ["Kevin K. <kbknapp@gmail.com>"]
exclude = ["examples/*", "clap-tests/*", "tests/*", "benches/*", "clap.png"]
exclude = ["examples/*", "clap-tests/*", "tests/*", "benches/*", "*.png", "clap-perf/*"]
description = "A simple to use, efficient, and full featured Command Line Argument Parser"
repository = "https://github.com/kbknapp/clap-rs.git"
documentation = "http://kbknapp.github.io/clap-rs"
@ -12,8 +12,9 @@ license = "MIT"
keywords = ["argument", "command", "arg", "parser", "parse"]
[dependencies]
bitflags = "0.3.2"
ansi_term = { version = "~0.6.3", optional = true }
bitflags = "0.3.2"
vec_map = "0.3"
ansi_term = { version = "~0.7", optional = true }
strsim = { version = "~0.4.0", optional = true }
yaml-rust = { version = "~0.2.2", optional = true }
clippy = { version = "~0.0.22", optional = true }

View file

@ -70,33 +70,23 @@ First, let me say that these comparisons are highly subjective, and not meant in
#### How does `clap` compare to [getopts](https://github.com/rust-lang-nursery/getopts)?
`getopts` is a very basic, fairly minimalist argument parsing library. This isn't a bad thing, sometimes you don't need tons of features, you just want to parse some simple arguments, and have some help text generated for you based on valid arguments you specify. When using `getopts` you must manually implement most of the common features (such as checking to display help messages, usage strings, etc.). If you want a highly custom argument parser, and don't mind writing most the argument parser yourself, `getopts` is an excellent base.
`getopts` is a very basic, fairly minimalist argument parsing library. This isn't a bad thing, sometimes you don't need tons of features, you just want to parse some simple arguments, and have some help text generated for you based on valid arguments you specify. The downside to this approach is that you must manually implement most of the common features (such as checking to display help messages, usage strings, etc.). If you want a highly custom argument parser, and don't mind writing the majority of the functionality yourself, `getopts` is an excellent base.
Due to it's lack of features, `getopts` also doesn't allocate much, or at all. This gives it somewhat of a performance boost. Although, as you start implementing those features you need manually, that boost quickly disappears.
`getopts` also doesn't allocate much, or at all. This gives it somewhat of a performance boost. Although, as you start implementing additional features, that boost quickly disappears.
Personally, I find many, many people that use `getopts` are manually implementing features that `clap` has by default. Using `clap` simplifies your codebase allowing you to focus on your application, and not argument parsing.
Reasons to use `getopts` instead of `clap`
* You need a few allocations as possible, don't plan on implementing any additional features
* You want a highly custom argument parser, but want to use an established parser as a base
#### How does `clap` compare to [docopt.rs](https://github.com/docopt/docopt.rs)?
I first want to say I'm a big a fan of BurntSushi's work, the creator of `Docopt.rs`. I aspire to produce the quality of libraries that this man does! When it comes to comparing these two libraries they are very different. `docopt` tasks you with writing a help message, and then it parsers that message for you to determine all valid arguments and their use. Some people LOVE this, others not so much. If you're willing to write a detailed help message, it's nice that you can stick that in your program and have `docopt` do the rest. On the downside, it's somewhat less flexible than other options out there, and requires the help message change if you need to make changes.
I first want to say I'm a big a fan of BurntSushi's work, the creator of `Docopt.rs`. I aspire to produce the quality of libraries that this man does! When it comes to comparing these two libraries they are very different. `docopt` tasks you with writing a help message, and then it parsers that message for you to determine all valid arguments and their use. Some people LOVE this approach, others not so much. If you're willing to write a detailed help message, it's nice that you can stick that in your program and have `docopt` do the rest. On the downside, it's somewhat less flexible, and requires you to change the help message if you need to make changes.
`docopt` is also excellent at translating arguments into Rust types automatically. There is even a syntax extension which will do all this for you, if you're willing to use a nightly compiler (use of a stable compiler requires you to manually translate from arguments to Rust types). To use BurntSushi's words, `docopt` is also somewhat of a black box. You get what you get, and it's hard to tweak implementation or customise your experience for your use case.
`docopt` is also excellent at translating arguments into Rust types automatically. There is even a syntax extension which will do all this for you, if you're willing to use a nightly compiler (use of a stable compiler requires you to somewhat manually translate from arguments to Rust types). To use BurntSushi's words, `docopt` is also a sort of black box. You get what you get, and it's hard to tweak implementation or customise the experience for your use case.
Because `docopt` is doing a ton of work to parse your help messages and determine what you were trying to communicate as valid arguments, it's also one of the more heavy weight parsers performance-wise. For most applications this isn't a concern, but it's something to keep in mind.
Reasons to use `docopt` instead of `clap`
* You want automatic translation from arguments to Rust types, and are using a nightly compiler
* Performance isn't a concern
* You don't have any complex relationships between arguments
Because `docopt` is doing a ton of work to parse your help messages and determine what you were trying to communicate as valid arguments, it's also one of the more heavy weight parsers performance-wise. For most applications this isn't a concern and this isn't to say `docopt` is slow, in fact from it. This is just something to keep in mind while comparing.
#### All else being equal, what are some reasons to use `clap`?
`clap` is fast, and as lightweight as possible while still giving all the features you'd expect from a modern argument parser. If you use `clap` when just need some simple arguments parsed, you'll find it a walk in the park. But `clap` also makes it possible to represent extremely complex, and advanced requirements, without too much thought. `clap` aims to be intuitive, easy to use, and fully capable for wide variety use cases and needs.
`clap` is as fast, and as lightweight as possible while still giving all the features you'd expect from a modern argument parser. In fact, for the amount and type of features `clap` offers the fact it remains about as fast as `getopts` is great. If you use `clap` when just need some simple arguments parsed, you'll find it a walk in the park. But `clap` also makes it possible to represent extremely complex, and advanced requirements, without too much thought. `clap` aims to be intuitive, easy to use, and fully capable for wide variety use cases and needs.
## Features

View file

@ -212,7 +212,7 @@ fn parse_sc_positional(b: &mut Bencher) {
#[bench]
fn parse_complex1(b: &mut Bencher) {
b.iter(|| create_app!().get_matches_from(vec!["", "-ff", "-o", "option1", "arg1", "-O", "fast", "arg2", "--multvals", "one", "two", "three"]));
b.iter(|| create_app!().get_matches_from(vec!["", "-ff", "-o", "option1", "arg1", "-O", "fast", "arg2", "--multvals", "one", "two", "emacs"]));
}
#[bench]

7
clap-perf/clap_perf.dat Normal file
View file

@ -0,0 +1,7 @@
#Version Date Builder Err Usage Err Parse1 Err Parse2 Err
1.0 2015-07-07 12408 840 16830 229 21235 725 27387 910
1.1 2015-07-16 11885 191 16670 595 20919 252 26868 457
1.2 2015-08-14 12563 587 17190 311 22421 233 28232 624
1.3 2015-09-01 10534 131 14648 874 18213 1070 24101 361
1.4 2015-09-09 10223 852 13203 749 18924 1216 23492 944
1.5 2015-11-12 5422 416 7974 680 9723 792 13389 1151

BIN
clap-perf/clap_perf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

26
clap-perf/plot_perf.gp Normal file
View file

@ -0,0 +1,26 @@
#!/usr/bin/gnuplot
reset
set terminal png
set output "clap_perf.png"
set xlabel "Version"
set xrange [0.9:1.6]
set ylabel "Time (ns)"
set yrange [0:35000]
set title "clap-rs Performance by Version"
set key inside left bottom
set grid
set style line 1 lc rgb '#0060ad' lt 1 lw 1 pt 7 ps .5 # --- blue
set style line 2 lc rgb '#dd181f' lt 1 lw 1 pt 5 ps .5 # --- red
set style line 3 lc rgb '#18dd00' lt 1 lw 1 pt 7 ps .5 # --- green
set style line 4 lc rgb '#000000' lt 1 lw 1 pt 5 ps .5 # --- black
plot "clap_perf.dat" u 1:3:4 notitle w yerrorbars ls 1, \
"" u 1:3 t "Create Parser Using Builder" w lines ls 1, \
"" u 1:5:6 notitle w yerrorbars ls 2, \
"" u 1:5 t "Create Parser Usage String" w lines ls 2, \
"" u 1:7:8 notitle "Parse Complex Args" w yerrorbars ls 3, \
"" u 1:7 t "Parse Complex Args" w lines ls 3, \
"" u 1:9:10 notitle w yerrorbars ls 4, \
"" u 1:9 t "Parse Very Complex Args" w lines ls 4

View file

@ -2,6 +2,7 @@
import sys
import subprocess
import re
import difflib
failed = False
@ -216,14 +217,14 @@ option NOT present
positional present with value: too
subcmd NOT present'''
_mult_vals_more = '''error: The argument '--multvals' was supplied more than once, but does not support multiple values
_mult_vals_more = '''error: The argument '--multvals <one> <two>' was supplied more than once, but does not support multiple occurrences
USAGE:
\tclaptests --multvals <one> <two>
For more information try --help'''
_mult_vals_few = '''error: The argument '--multvals <one> <two>' requires a value but none was supplied
_mult_vals_few = '''error: The argument '--multvals <one> <two>' requires 2 values, but 1 was provided
USAGE:
\tclaptests --multvals <one> <two>
@ -293,6 +294,9 @@ def pass_fail(name, cmd, check, good):
return 0
print('Fail')
print('\n\n{}\n# Should be:\n$ {}\n{}\n\n{}\n# But is:\n$ {}\n{}\n\n'.format('#'*25, cmd, good, '#'*25, cmd, check))
for line in difflib.context_diff(good.splitlines(), check.splitlines(), fromfile="Should Be", tofile="Currently Is", lineterm=""):
print(line)
print()
return 1

View file

@ -1,2 +1,4 @@
format_strings = false
reorder_imports = true
chain_overflow_last = false
same_line_if_else = true

File diff suppressed because it is too large Load diff

View file

@ -1,296 +0,0 @@
use std::process;
use std::error::Error;
use std::fmt;
/// Command line argument parser error types
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum ClapErrorType {
/// Error occurs when some possible values were set, but clap found unexpected value
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug").index(1)
/// .possible_value("fast")
/// .possible_value("slow"))
/// .get_matches_from_safe(vec!["", "other"]);
/// ```
InvalidValue,
/// Error occurs when clap found unexpected flag or option
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::from_usage("-f, --flag 'some flag'"))
/// .get_matches_from_safe(vec!["", "--other"]);
/// ```
InvalidArgument,
/// Error occurs when clap found unexpected subcommand
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg, SubCommand};
/// let result = 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)))
/// .get_matches_from_safe(vec!["", "other"]);
/// ```
InvalidSubcommand,
/// Error occurs when option does not allow empty values but some was found
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug")
/// .empty_values(false))
/// .arg(Arg::with_name("color"))
/// .get_matches_from_safe(vec!["", "--debug", "--color"]);
/// ```
EmptyValue,
/// Option fails validation of a custom validator
ValueValidationError,
/// Parser inner error
ArgumentError,
/// Error occurs when an application got more arguments then were expected
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug").index(1)
/// .max_values(2))
/// .get_matches_from_safe(vec!["", "too", "much", "values"]);
/// ```
TooManyArgs,
/// Error occurs when argument got more values then were expected
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug").index(1)
/// .max_values(2))
/// .get_matches_from_safe(vec!["", "too", "much", "values"]);
/// ```
TooManyValues,
/// Error occurs when argument got less values then were expected
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug").index(1)
/// .min_values(3))
/// .get_matches_from_safe(vec!["", "too", "few"]);
/// ```
TooFewValues,
/// Error occurs when argument got a different number of values then were expected
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug").index(1)
/// .max_values(2))
/// .get_matches_from_safe(vec!["", "too", "much", "values"]);
/// ```
WrongNumValues,
/// Error occurs when clap find two ore more conflicting arguments
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug")
/// .conflicts_with("color"))
/// .get_matches_from_safe(vec!["", "--debug", "--color"]);
/// ```
ArgumentConflict,
/// Error occurs when one or more required arguments missing
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug")
/// .required(true))
/// .get_matches_from_safe(vec![""]);
/// ```
MissingRequiredArgument,
/// Error occurs when required subcommand missing
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg, AppSettings, SubCommand};
/// let result = App::new("myprog")
/// .setting(AppSettings::SubcommandRequired)
/// .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![""]);
/// ```
MissingSubcommand,
/// Occurs when no argument or subcommand has been supplied and
/// `AppSettings::ArgRequiredElseHelp` was used
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg, AppSettings, SubCommand};
/// let result = App::new("myprog")
/// .setting(AppSettings::ArgRequiredElseHelp)
/// .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![""]);
/// ```
MissingArgumentOrSubcommand,
/// Error occurs when clap find argument while is was not expecting any
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App};
/// let result = App::new("myprog")
/// .get_matches_from_safe(vec!["", "--arg"]);
/// ```
UnexpectedArgument,
/// Error occurs when argument was used multiple times and was not set as multiple.
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug")
/// .multiple(false))
/// .get_matches_from_safe(vec!["", "--debug", "--debug"]);
/// ```
UnexpectedMultipleUsage,
/// Error occurs when argument contains invalid unicode characters
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// # use std::os::unix::ffi::OsStringExt;
/// # use std::ffi::OsString;
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug")
/// .short("u")
/// .takes_value(true))
/// .get_matches_from_safe(vec![OsString::from_vec(vec![0x20]),
/// OsString::from_vec(vec![0xE9])]);
/// assert!(result.is_err());
/// ```
InvalidUnicode,
/// Not a true 'error' as it means `--help` or similar was used. The help message will be sent
/// to `stdout` unless the help is displayed due to an error (such as missing subcommands) at
/// which point it will be sent to `stderr`
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// # use clap::ClapErrorType;
/// let result = App::new("myprog")
/// .get_matches_from_safe(vec!["", "--help"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().error_type, ClapErrorType::HelpDisplayed);
/// ```
HelpDisplayed,
/// Not a true 'error' as it means `--version` or similar was used. The message will be sent
/// to `stdout`
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// # use clap::ClapErrorType;
/// let result = App::new("myprog")
/// .get_matches_from_safe(vec!["", "--version"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().error_type, ClapErrorType::VersionDisplayed);
/// ```
VersionDisplayed,
/// Represents an internal error, please consider filing a bug report if this happens!
InternalError,
}
/// Command line argument parser error
#[derive(Debug)]
pub struct ClapError {
/// Formated error message
pub error: String,
/// Command line argument parser error type
pub error_type: ClapErrorType,
}
impl ClapError {
/// Prints the error to `stderr` and exits with a status of `1`
pub fn exit(&self) -> ! {
if self.use_stderr() {
wlnerr!("{}", self.error);
process::exit(1);
}
println!("{}", self.error);
process::exit(0);
}
fn use_stderr(&self) -> bool {
match self.error_type {
ClapErrorType::HelpDisplayed | ClapErrorType::VersionDisplayed => false,
_ => true
}
}
}
impl Error for ClapError {
fn description(&self) -> &str {
&*self.error
}
}
impl fmt::Display for ClapError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.error)
}
}

130
src/app/macros.rs Normal file
View file

@ -0,0 +1,130 @@
macro_rules! remove_overriden {
($me:ident, $name:expr) => ({
if let Some(ref o) = $me.opts.iter().filter(|o| &o.name == $name).next() {
if let Some(ref ora) = o.requires {
for a in ora {
vec_remove!($me.required, a);
}
}
if let Some(ref ora) = o.blacklist {
for a in ora {
vec_remove!($me.blacklist, a);
}
}
if let Some(ref ora) = o.overrides {
for a in ora {
vec_remove!($me.overrides, a);
}
}
} else if let Some(ref o) = $me.flags.iter().filter(|f| &f.name == $name).next() {
if let Some(ref ora) = o.requires {
for a in ora {
vec_remove!($me.required, a);
}
}
if let Some(ref ora) = o.blacklist {
for a in ora {
vec_remove!($me.blacklist, a);
}
}
if let Some(ref ora) = o.overrides {
for a in ora {
vec_remove!($me.overrides, a);
}
}
} else if let Some(p) = $me.positionals.values().filter(|p| &&p.name == &$name).next() {
if let Some(ref ora) = p.requires {
for a in ora {
vec_remove!($me.required, a);
}
}
if let Some(ref ora) = p.blacklist {
for a in ora {
vec_remove!($me.blacklist, a);
}
}
if let Some(ref ora) = p.overrides {
for a in ora {
vec_remove!($me.overrides, a);
}
}
}
})
}
macro_rules! arg_post_processing(
($me:ident, $arg:ident, $matcher:ident) => ({
use args::AnyArg;
// Handle POSIX overrides
if $me.overrides.contains(&$arg.name()) {
if let Some(ref name) = $me.overriden_from($arg.name(), $matcher) {
$matcher.remove(name);
remove_overriden!($me, name);
}
}
if let Some(or) = $arg.overrides() {
for pa in or {
$matcher.remove(pa);
remove_overriden!($me, pa);
$me.overrides.push(pa);
vec_remove!($me.required, pa);
}
}
// Handle conflicts
if let Some(bl) = $arg.blacklist() {
for name in bl {
$me.blacklist.push(name);
vec_remove!($me.overrides, name);
vec_remove!($me.required, name);
}
}
// Add all required args which aren't already found in matcher to the master
// list
if let Some(reqs) = $arg.requires() {
for n in reqs {
if $matcher.contains(n) {
continue;
}
$me.required.push(n);
}
}
_handle_group_reqs!($me, $arg);
})
);
macro_rules! _handle_group_reqs{
($me:ident, $arg:ident) => ({
use args::AnyArg;
for grp in $me.groups.values() {
let mut found = false;
for name in grp.args.iter() {
if name == &$arg.name() {
vec_remove!($me.required, name);
if let Some(ref reqs) = grp.requires {
for r in reqs {
$me.required.push(r);
}
}
if let Some(ref bl) = grp.conflicts {
for b in bl {
$me.blacklist.push(b);
}
}
found = true;
break;
}
}
if found {
for name in grp.args.iter() {
if name == &$arg.name() { continue }
vec_remove!($me.required, name);
$me.blacklist.push(name);
}
}
}
})
}

File diff suppressed because it is too large Load diff

19
src/args/any_arg.rs Normal file
View file

@ -0,0 +1,19 @@
use std::rc::Rc;
use args::settings::ArgSettings;
#[doc(hidden)]
pub trait AnyArg<'n> {
fn name(&self) -> &'n str;
fn overrides(&self) -> Option<&[&'n str]>;
fn requires(&self) -> Option<&[&'n str]>;
fn blacklist(&self) -> Option<&[&'n str]>;
fn is_set(&self, ArgSettings) -> bool;
fn set(&mut self, ArgSettings);
fn has_switch(&self) -> bool;
fn max_vals(&self) -> Option<u8>;
fn min_vals(&self) -> Option<u8>;
fn num_vals(&self) -> Option<u8>;
fn possible_vals(&self) -> Option<&[&'n str]>;
fn validator(&self) -> Option<&Rc<Fn(String) -> Result<(), String>>>;
}

View file

@ -98,6 +98,34 @@ pub struct Arg<'n, 'l, 'h, 'g, 'p, 'r> {
pub hidden: bool,
}
impl<'n, 'l, 'h, 'g, 'p, 'r> Default for Arg<'n, 'l, 'h, 'g, 'p, 'r> {
fn default() -> Self {
Arg {
name: "",
short: None,
long: None,
help: None,
required: false,
takes_value: false,
multiple: false,
index: None,
possible_vals: None,
blacklist: None,
requires: None,
num_vals: None,
min_vals: None,
max_vals: None,
val_names: None,
group: None,
global: false,
empty_vals: true,
validator: None,
overrides: None,
hidden: false,
}
}
}
impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> {
/// Creates a new instance of `Arg` using a unique string name.
/// The name will be used by the library consumer to get information about
@ -120,26 +148,7 @@ impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> {
pub fn with_name(n: &'n str) -> Self {
Arg {
name: n,
short: None,
long: None,
help: None,
required: false,
takes_value: false,
multiple: false,
index: None,
possible_vals: None,
blacklist: None,
requires: None,
num_vals: None,
min_vals: None,
max_vals: None,
val_names: None,
group: None,
global: false,
empty_vals: true,
validator: None,
overrides: None,
hidden: false,
..Default::default()
}
}
@ -370,10 +379,6 @@ impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> {
required: required,
takes_value: takes_value,
multiple: multiple,
index: None,
possible_vals: None,
blacklist: None,
requires: None,
num_vals: if num_names > 1 {
Some(num_names)
} else {
@ -384,14 +389,7 @@ impl<'n, 'l, 'h, 'g, 'p, 'r> Arg<'n, 'l, 'h, 'g, 'p, 'r> {
} else {
None
},
max_vals: None,
min_vals: None,
group: None,
global: false,
empty_vals: true,
validator: None,
overrides: None,
hidden: false,
..Default::default()
}
}

View file

@ -2,8 +2,11 @@
use std::fmt::{Display, Formatter, Result};
use std::convert::From;
use std::io;
use std::rc::Rc;
use std::result::Result as StdResult;
use Arg;
use args::AnyArg;
use args::settings::{ArgFlags, ArgSettings};
#[derive(Debug)]
@ -167,6 +170,33 @@ impl<'n> Display for FlagBuilder<'n> {
}
}
}
impl<'n> AnyArg<'n> for FlagBuilder<'n> {
fn name(&self) -> &'n str { self.name }
fn overrides(&self) -> Option<&[&'n str]> { self.overrides.as_ref().map(|o| &o[..]) }
fn requires(&self) -> Option<&[&'n str]> { self.requires.as_ref().map(|o| &o[..]) }
fn blacklist(&self) -> Option<&[&'n str]> { self.blacklist.as_ref().map(|o| &o[..]) }
fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(&s) }
fn has_switch(&self) -> bool { true }
fn set(&mut self, s: ArgSettings) { self.settings.set(&s) }
fn max_vals(&self) -> Option<u8> { None }
fn num_vals(&self) -> Option<u8> { None }
fn possible_vals(&self) -> Option<&[&'n str]> { None }
fn validator(&self) -> Option<&Rc<Fn(String) -> StdResult<(), String>>> { None }
fn min_vals(&self) -> Option<u8> { None }
}
#[cfg(test)]
mod test {
use super::FlagBuilder;

View file

@ -4,8 +4,10 @@ use std::fmt::{Display, Formatter, Result};
use std::result::Result as StdResult;
use std::io;
use Arg;
use args::{AnyArg, ArgMatcher, Arg};
use args::settings::{ArgFlags, ArgSettings};
use errors::{ClapResult, error_builder};
use app::App;
#[allow(missing_debug_implementations)]
pub struct OptBuilder<'n> {
@ -34,10 +36,10 @@ pub struct OptBuilder<'n> {
pub settings: ArgFlags,
}
impl<'n> OptBuilder<'n> {
pub fn new(name: &'n str) -> Self {
impl<'n> Default for OptBuilder<'n> {
fn default() -> Self {
OptBuilder {
name: name,
name: "",
short: None,
long: None,
help: None,
@ -53,6 +55,14 @@ impl<'n> OptBuilder<'n> {
settings: ArgFlags::new(),
}
}
}
impl<'n> OptBuilder<'n> {
pub fn new(name: &'n str) -> Self {
OptBuilder {
name: name,
..Default::default()
}
}
pub fn from_arg(a: &Arg<'n, 'n, 'n, 'n, 'n, 'n>, reqs: &mut Vec<&'n str>) -> Self {
if a.short.is_none() && a.long.is_none() {
@ -65,17 +75,12 @@ impl<'n> OptBuilder<'n> {
name: a.name,
short: a.short,
long: a.long,
blacklist: None,
help: a.help,
possible_vals: None,
num_vals: a.num_vals,
min_vals: a.min_vals,
max_vals: a.max_vals,
val_names: a.val_names.clone(),
requires: None,
validator: None,
overrides: None,
settings: ArgFlags::new(),
..Default::default()
};
if a.multiple {
ob.settings.set(&ArgSettings::Multiple);
@ -187,6 +192,50 @@ impl<'n> OptBuilder<'n> {
print_opt_help!(self, longest + 12, w);
write!(w, "\n")
}
pub fn validate_value(&self,
val: &str,
matcher: &ArgMatcher,
app: &App)
-> ClapResult<()> {
// Check the possible values
if let Some(ref p_vals) = self.possible_vals {
if !p_vals.contains(&val) {
let usage = try!(app.create_current_usage(matcher));
return Err(error_builder::InvalidValue(val, p_vals, &self.to_string(), &usage));
}
}
// Check the required number of values
if let Some(num) = self.num_vals {
if let Some(ref ma) = matcher.get(self.name) {
if let Some(ref vals) = ma.values {
if (vals.len() as u8) > num && !self.settings.is_set(&ArgSettings::Multiple) {
return Err(error_builder::TooManyValues(
val,
&self.to_string(),
&*try!(app.create_current_usage(matcher))));
}
}
}
}
// if it's an empty value, and we don't allow that, report the error
if !self.settings.is_set(&ArgSettings::EmptyValues) &&
matcher.contains(self.name) &&
val.is_empty() {
return Err(error_builder::EmptyValue(&*self.to_string(),
&*try!(app.create_current_usage(matcher))));
}
if let Some(ref vtor) = self.validator {
if let Err(e) = vtor(val.to_owned()) {
return Err(error_builder::ValueValidationError(&*e));
}
}
Ok(())
}
}
impl<'n> Display for OptBuilder<'n> {
@ -217,6 +266,54 @@ impl<'n> Display for OptBuilder<'n> {
}
}
impl<'n> AnyArg<'n> for OptBuilder<'n> {
fn name(&self) -> &'n str {
self.name
}
fn overrides(&self) -> Option<&[&'n str]> {
self.overrides.as_ref().map(|o| &o[..])
}
fn requires(&self) -> Option<&[&'n str]> {
self.requires.as_ref().map(|o| &o[..])
}
fn blacklist(&self) -> Option<&[&'n str]> {
self.blacklist.as_ref().map(|o| &o[..])
}
fn is_set(&self, s: ArgSettings) -> bool {
self.settings.is_set(&s)
}
fn has_switch(&self) -> bool {
true
}
fn set(&mut self, s: ArgSettings) {
self.settings.set(&s)
}
fn max_vals(&self) -> Option<u8> {
self.max_vals
}
fn num_vals(&self) -> Option<u8> {
self.num_vals
}
fn possible_vals(&self) -> Option<&[&'n str]> {
self.possible_vals.as_ref().map(|o| &o[..])
}
fn validator(&self) -> Option<&Rc<Fn(String) -> StdResult<(), String>>> {
self.validator.as_ref()
}
fn min_vals(&self) -> Option<u8> {
self.min_vals
}
}
#[cfg(test)]
mod test {
use super::OptBuilder;

View file

@ -4,6 +4,7 @@ use std::rc::Rc;
use std::io;
use Arg;
use args::AnyArg;
use args::settings::{ArgFlags, ArgSettings};
#[allow(missing_debug_implementations)]
@ -181,6 +182,53 @@ impl<'n> Display for PosBuilder<'n> {
Ok(())
}
}
impl<'n> AnyArg<'n> for PosBuilder<'n> {
fn name(&self) -> &'n str {
self.name
}
fn overrides(&self) -> Option<&[&'n str]> {
self.overrides.as_ref().map(|o| &o[..])
}
fn requires(&self) -> Option<&[&'n str]> {
self.requires.as_ref().map(|o| &o[..])
}
fn blacklist(&self) -> Option<&[&'n str]> {
self.blacklist.as_ref().map(|o| &o[..])
}
fn is_set(&self, s: ArgSettings) -> bool {
self.settings.is_set(&s)
}
fn set(&mut self, s: ArgSettings) {
self.settings.set(&s)
}
fn has_switch(&self) -> bool {
false
}
fn max_vals(&self) -> Option<u8> {
self.max_vals
}
fn num_vals(&self) -> Option<u8> {
self.num_vals
}
fn possible_vals(&self) -> Option<&[&'n str]> {
self.possible_vals.as_ref().map(|o| &o[..])
}
fn validator(&self) -> Option<&Rc<Fn(String) -> StdResult<(), String>>> {
self.validator.as_ref()
}
fn min_vals(&self) -> Option<u8> {
self.min_vals
}
}
#[cfg(test)]
mod test {
use super::PosBuilder;

97
src/args/arg_matcher.rs Normal file
View file

@ -0,0 +1,97 @@
use vec_map::VecMap;
use args::{ArgMatches, MatchedArg, SubCommand};
use std::collections::hash_map::{Entry, Keys, Iter};
pub struct ArgMatcher<'ar>(ArgMatches<'ar, 'ar>);
impl<'ar> ArgMatcher<'ar> {
pub fn new() -> Self {
ArgMatcher(ArgMatches::new())
}
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 insert(&mut self, name: &'ar 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 values_of(&self, arg: &str) -> Option<Vec<&str>> {
self.0.values_of(arg)
}
pub fn usage(&mut self, usage: String) {
self.0.usage = Some(usage);
}
pub fn arg_names(&self) -> Keys<&'ar str, MatchedArg> {
self.0.args.keys()
}
pub fn entry(&mut self, arg: &'ar str) -> Entry<&'ar str, MatchedArg> {
self.0.args.entry(arg)
}
pub fn subcommand(&mut self, sc: SubCommand<'ar, 'ar>) {
self.0.subcommand = Some(Box::new(sc));
}
pub fn subcommand_name(&self) -> Option<&str> {
self.0.subcommand_name()
}
pub fn iter(&self) -> Iter<&'ar str, MatchedArg> {
self.0.args.iter()
}
pub fn inc_occurrence_of(&mut self, arg: &'ar str) {
if let Some(a) = self.get_mut(arg) {
a.occurrences += 1;
return;
}
self.insert(arg);
}
pub fn inc_occurrences_of(&mut self, args: &[&'ar str]) {
for arg in args {
self.inc_occurrence_of(arg);
}
}
pub fn add_val_to(&mut self, arg: &'ar str, val: String) {
let ma = self.entry(arg).or_insert(MatchedArg {
// occurrences will be incremented on getting a value
occurrences: 0,
values: Some(VecMap::new()),
});
if let Some(ref mut vals) = ma.values {
let len = vals.len() + 1;
vals.insert(len, val);
ma.occurrences += 1;
}
}
}
impl<'ar> Into<ArgMatches<'ar, 'ar>> for ArgMatcher<'ar> {
fn into(self) -> ArgMatches<'ar, 'ar> {
self.0
}
}

19
src/args/matched_arg.rs Normal file
View file

@ -0,0 +1,19 @@
use vec_map::VecMap;
#[doc(hidden)]
#[derive(Debug)]
pub struct MatchedArg {
#[doc(hidden)]
pub occurrences: u8,
#[doc(hidden)]
pub values: Option<VecMap<String>>,
}
impl MatchedArg {
pub fn new() -> Self {
MatchedArg {
occurrences: 1,
values: None
}
}
}

View file

@ -1,12 +0,0 @@
use std::collections::BTreeMap;
#[doc(hidden)]
#[derive(Debug)]
pub struct MatchedArg {
// #[doc(hidden)]
// pub name: String,
#[doc(hidden)]
pub occurrences: u8,
#[doc(hidden)]
pub values: Option<BTreeMap<u8, String>>,
}

View file

@ -1,15 +1,19 @@
pub use self::arg::Arg;
pub use self::argmatches::ArgMatches;
pub use self::arg_matches::ArgMatches;
pub use self::arg_matcher::ArgMatcher;
pub use self::subcommand::SubCommand;
pub use self::argbuilder::{FlagBuilder, OptBuilder, PosBuilder};
pub use self::matchedarg::MatchedArg;
pub use self::arg_builder::{FlagBuilder, OptBuilder, PosBuilder};
pub use self::matched_arg::MatchedArg;
pub use self::group::ArgGroup;
pub use self::any_arg::AnyArg;
mod arg;
mod argmatches;
pub mod any_arg;
mod arg_matches;
mod arg_matcher;
mod subcommand;
mod argbuilder;
mod matchedarg;
mod arg_builder;
mod matched_arg;
mod group;
#[allow(dead_code)]
pub mod settings;

616
src/errors.rs Normal file
View file

@ -0,0 +1,616 @@
use std::process;
use std::error::Error;
use std::fmt as std_fmt;
use std::io::{self, Write};
use std::convert::From;
use fmt::Format;
pub type ClapResult<T> = Result<T, ClapError>;
#[doc(hidden)]
#[allow(non_snake_case)]
pub mod error_builder {
use suggestions;
use super::ClapError;
use super::ClapErrorType as cet;
use fmt::Format;
/// Error occurs when clap find two ore more conflicting arguments
pub fn ArgumentConflict<S>(arg: S, other: Option<S>, usage: S) -> ClapError
where S: AsRef<str>
{
ClapError {
error: format!("{} The argument '{}' cannot be used with {}\n\n\
{}\n\n\
For more information try {}",
Format::Error("error:"),
Format::Warning(arg.as_ref()),
match other {
Some(name) => format!("'{}'", Format::Warning(name)),
None => "one or more of the other specified arguments".to_owned(),
},
usage.as_ref(),
Format::Good("--help")),
error_type: cet::ArgumentConflict,
}
}
/// Error occurs when option does not allow empty values but some was found
pub fn EmptyValue<S>(arg: S, usage: S) -> ClapError
where S: AsRef<str>
{
ClapError {
error: format!("{} The argument '{}' requires a value but none was supplied\
\n\n\
{}\n\n\
For more information try {}",
Format::Error("error:"),
Format::Warning(arg.as_ref()),
usage.as_ref(),
Format::Good("--help")),
error_type: cet::EmptyValue,
}
}
/// Error occurs when some possible values were set, but clap found unexpected value
pub fn InvalidValue<S>(bad_val: S,
good_vals: &[S],
arg: S,
usage: S)
-> ClapError
where S: AsRef<str>
{
let suffix = suggestions::did_you_mean_suffix(arg.as_ref(),
good_vals.iter(),
suggestions::DidYouMeanMessageStyle::EnumValue);
let mut sorted = vec![];
for v in good_vals {
sorted.push(v.as_ref());
}
sorted.sort();
let valid_values = sorted.iter()
.fold(String::new(), |a, name| a + &format!( " {}", name)[..]);
ClapError {
error: format!("{} '{}' isn't a valid value for '{}'\n\t\
[valid values:{}]\n\
{}\n\n\
{}\n\n\
For more information try {}",
Format::Error("error:"),
Format::Warning(bad_val.as_ref()),
Format::Warning(arg.as_ref()),
valid_values,
suffix.0,
usage.as_ref(),
Format::Good("--help")),
error_type: cet::InvalidValue,
}
}
/// Error occurs when clap found unexpected flag or option
pub fn InvalidArgument<S>(arg: S, did_you_mean: Option<S>, usage: S) -> ClapError
where S: AsRef<str>
{
ClapError {
error: format!("{} The argument '{}' isn't valid{}\n\
{}\n\n\
For more information try {}",
Format::Error("error:"),
Format::Warning(arg),
if did_you_mean.is_some() {
format!("{}\n", did_you_mean.unwrap().as_ref())
} else {
"".to_owned()
},
usage.as_ref(),
Format::Good("--help")),
error_type: cet::InvalidArgument,
}
}
/// Error occurs when clap found unexpected subcommand
pub fn InvalidSubcommand<S>(subcmd: S, did_you_mean: S, name: S, usage: S) -> ClapError
where S: AsRef<str>
{
ClapError {
error: format!("{} The subcommand '{}' isn't valid\n\t\
Did you mean '{}' ?\n\n\
If you received this message in error, try \
re-running with '{} {} {}'\n\n\
{}\n\n\
For more information try {}",
Format::Error("error:"),
Format::Warning(subcmd.as_ref()),
Format::Good(did_you_mean.as_ref()),
name.as_ref(),
Format::Good("--"),
subcmd.as_ref(),
usage.as_ref(),
Format::Good("--help")),
error_type: cet::InvalidSubcommand,
}
}
/// Error occurs when one or more required arguments missing
pub fn MissingRequiredArgument<S>(required: S, usage: S) -> ClapError
where S: AsRef<str>
{
ClapError {
error: format!("{} The following required arguments were not supplied:{}\n\n\
{}\n\n\
For more information try {}",
Format::Error("error:"),
required.as_ref(),
usage.as_ref(),
Format::Good("--help")),
error_type: cet::MissingRequiredArgument,
}
}
/// Error occurs when required subcommand missing
pub fn MissingSubcommand<S>(name: S, usage: S) -> ClapError
where S: AsRef<str>
{
ClapError {
error: format!("{} '{}' requires a subcommand but none was provided\n\n\
{}\n\n\
For more information try {}",
Format::Error("error:"),
Format::Warning(name),
usage.as_ref(),
Format::Good("--help")),
error_type: cet::MissingSubcommand,
}
}
/// Error occurs when argument contains invalid unicode characters
pub fn InvalidUnicode<S>(usage: S) -> ClapError
where S: AsRef<str>
{
ClapError {
error: format!("{} Invalid unicode character in one or more arguments\n\n\
{}\n\n\
For more information try {}",
Format::Error("error:"),
usage.as_ref(),
Format::Good("--help")),
error_type: cet::InvalidUnicode,
}
}
/// Error occurs when argument got more values then were expected
pub fn TooManyValues<S>(val: S, arg: S, usage: S) -> ClapError
where S: AsRef<str>
{
ClapError {
error: format!("{} The argument '{}' was found, but '{}' wasn't expecting \
any more values\n\n\
{}\n\n\
For more information try {}",
Format::Error("error:"),
Format::Warning(val),
Format::Warning(arg),
usage.as_ref(),
Format::Good("--help")),
error_type: cet::TooManyValues,
}
}
/// Error occurs when argument got less values then were expected
pub fn TooFewValues<S>(arg: S, min_vals: u8, curr_vals: usize, usage: S) -> ClapError
where S: AsRef<str>
{
ClapError {
error: format!("{} The argument '{}' requires at least {} values, but {} w{} \
provided\n\n\
{}\n\n\
For more information try {}",
Format::Error("error:"),
Format::Warning(arg.as_ref()),
Format::Warning(min_vals.to_string()),
Format::Warning(curr_vals.to_string()),
if curr_vals > 1 {
"ere"
} else {
"as"
},
usage.as_ref(),
Format::Good("--help")),
error_type: cet::TooFewValues,
}
}
/// Option fails validation of a custom validator
pub fn ValueValidationError<S>(err: S) -> ClapError
where S: AsRef<str>
{
ClapError {
error: format!("{} {}", Format::Error("error:"), err.as_ref()),
error_type: cet::ValueValidationError,
}
}
/// Error occurs when argument got a different number of values then were expected
pub fn WrongNumValues<S>(arg: S,
num_vals: u8,
curr_vals: usize,
suffix: S,
usage: S)
-> ClapError
where S: AsRef<str>
{
ClapError {
error: format!("{} The argument '{}' requires {} values, but {} w{} \
provided\n\n\
{}\n\n\
For more information try {}",
Format::Error("error:"),
Format::Warning(arg.as_ref()),
Format::Warning(num_vals.to_string()),
Format::Warning(curr_vals.to_string()),
suffix.as_ref(),
usage.as_ref(),
Format::Good("--help")),
error_type: cet::InvalidSubcommand,
}
}
/// Error occurs when clap find argument while is was not expecting any
pub fn UnexpectedArgument<S>(arg: S, name: S, usage: S) -> ClapError
where S: AsRef<str>
{
ClapError {
error: format!("{} Found argument '{}', but {} wasn't expecting any\n\n\
{}\n\n\
For more information try {}",
Format::Error("error:"),
Format::Warning(arg),
Format::Warning(name),
usage.as_ref(),
Format::Good("--help")),
error_type: cet::UnexpectedArgument,
}
}
/// Error occurs when argument was used multiple times and was not set as multiple.
pub fn UnexpectedMultipleUsage<S>(arg: S, usage: S) -> ClapError
where S: AsRef<str>
{
ClapError {
error: format!("{} The argument '{}' was supplied more than once, but does \
not support multiple occurrences\n\n\
{}\n\n\
For more information try {}",
Format::Error("error:"),
Format::Warning(arg),
usage.as_ref(),
Format::Good("--help")),
error_type: cet::UnexpectedMultipleUsage,
}
}
}
/// Command line argument parser error types
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ClapErrorType {
/// Error occurs when some possible values were set, but clap found unexpected value
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug").index(1)
/// .possible_value("fast")
/// .possible_value("slow"))
/// .get_matches_from_safe(vec!["", "other"]);
/// ```
InvalidValue,
/// Error occurs when clap found unexpected flag or option
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::from_usage("-f, --flag 'some flag'"))
/// .get_matches_from_safe(vec!["", "--other"]);
/// ```
InvalidArgument,
/// Error occurs when clap found unexpected subcommand
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg, SubCommand};
/// let result = 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)))
/// .get_matches_from_safe(vec!["", "other"]);
/// ```
InvalidSubcommand,
/// Error occurs when option does not allow empty values but some was found
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug")
/// .empty_values(false))
/// .arg(Arg::with_name("color"))
/// .get_matches_from_safe(vec!["", "--debug", "--color"]);
/// ```
EmptyValue,
/// Option fails validation of a custom validator
ValueValidationError,
/// Parser inner error
ArgumentError,
/// Error occurs when an application got more arguments then were expected
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug").index(1)
/// .max_values(2))
/// .get_matches_from_safe(vec!["", "too", "much", "values"]);
/// ```
TooManyArgs,
/// Error occurs when argument got more values then were expected
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug").index(1)
/// .max_values(2))
/// .get_matches_from_safe(vec!["", "too", "much", "values"]);
/// ```
TooManyValues,
/// Error occurs when argument got less values then were expected
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug").index(1)
/// .min_values(3))
/// .get_matches_from_safe(vec!["", "too", "few"]);
/// ```
TooFewValues,
/// Error occurs when argument got a different number of values then were expected
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug").index(1)
/// .max_values(2))
/// .get_matches_from_safe(vec!["", "too", "much", "values"]);
/// ```
WrongNumValues,
/// Error occurs when clap find two ore more conflicting arguments
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug")
/// .conflicts_with("color"))
/// .get_matches_from_safe(vec!["", "--debug", "--color"]);
/// ```
ArgumentConflict,
/// Error occurs when one or more required arguments missing
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug")
/// .required(true))
/// .get_matches_from_safe(vec![""]);
/// ```
MissingRequiredArgument,
/// Error occurs when required subcommand missing
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg, AppSettings, SubCommand};
/// let result = App::new("myprog")
/// .setting(AppSettings::SubcommandRequired)
/// .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![""]);
/// ```
MissingSubcommand,
/// Occurs when no argument or subcommand has been supplied and
/// `AppSettings::ArgRequiredElseHelp` was used
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg, AppSettings, SubCommand};
/// let result = App::new("myprog")
/// .setting(AppSettings::ArgRequiredElseHelp)
/// .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![""]);
/// ```
MissingArgumentOrSubcommand,
/// Error occurs when clap find argument while is was not expecting any
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App};
/// let result = App::new("myprog")
/// .get_matches_from_safe(vec!["", "--arg"]);
/// ```
UnexpectedArgument,
/// Error occurs when argument was used multiple times and was not set as multiple.
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug")
/// .multiple(false))
/// .get_matches_from_safe(vec!["", "--debug", "--debug"]);
/// ```
UnexpectedMultipleUsage,
/// Error occurs when argument contains invalid unicode characters
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// # use std::os::unix::ffi::OsStringExt;
/// # use std::ffi::OsString;
/// let result = App::new("myprog")
/// .arg(Arg::with_name("debug")
/// .short("u")
/// .takes_value(true))
/// .get_matches_from_safe(vec![OsString::from_vec(vec![0x20]),
/// OsString::from_vec(vec![0xE9])]);
/// assert!(result.is_err());
/// ```
InvalidUnicode,
/// Not a true 'error' as it means `--help` or similar was used. The help message will be sent
/// to `stdout` unless the help is displayed due to an error (such as missing subcommands) at
/// which point it will be sent to `stderr`
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// # use clap::ClapErrorType;
/// let result = App::new("myprog")
/// .get_matches_from_safe(vec!["", "--help"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().error_type, ClapErrorType::HelpDisplayed);
/// ```
HelpDisplayed,
/// Not a true 'error' as it means `--version` or similar was used. The message will be sent
/// to `stdout`
///
///
/// # Examples
///
/// ```no_run
/// # use clap::{App, Arg};
/// # use clap::ClapErrorType;
/// let result = App::new("myprog")
/// .get_matches_from_safe(vec!["", "--version"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().error_type, ClapErrorType::VersionDisplayed);
/// ```
VersionDisplayed,
/// Represents an internal error, please consider filing a bug report if this happens!
InternalError,
/// Represents an I/O error, typically white writing to stderr or stdout
Io,
/// Represents an Rust Display Format error, typically white writing to stderr or stdout
Format,
}
/// Command line argument parser error
#[derive(Debug)]
pub struct ClapError {
/// Formated error message
pub error: String,
/// The type of error
pub error_type: ClapErrorType,
}
impl ClapError {
/// Should the message be written to `stdout` or not
pub fn use_stderr(&self) -> bool {
match self.error_type {
ClapErrorType::HelpDisplayed | ClapErrorType::VersionDisplayed => false,
_ => true,
}
}
/// Prints the error to `stderr` and exits with a status of `1`
pub fn exit(&self) -> ! {
if self.use_stderr() {
werr!("{}", self.error);
process::exit(1);
}
let out = io::stdout();
writeln!(&mut out.lock(), "{}", self.error).expect("Error writing ClapError to stdout");
process::exit(0);
}
}
impl Error for ClapError {
fn description(&self) -> &str {
&*self.error
}
fn cause(&self) -> Option<&Error> {
match self.error_type {
_ => None,
}
}
}
impl std_fmt::Display for ClapError {
fn fmt(&self, f: &mut std_fmt::Formatter) -> std_fmt::Result {
write!(f, "{}", self.error)
}
}
impl From<io::Error> for ClapError {
fn from(e: io::Error) -> Self {
ClapError {
error: format!("{} {}", Format::Error("error:"), e.description()),
error_type: ClapErrorType::Io,
}
}
}
impl From<std_fmt::Error> for ClapError {
fn from(e: std_fmt::Error) -> Self {
ClapError {
error: format!("{} {}", Format::Error("error:"), e),
error_type: ClapErrorType::Format,
}
}
}

View file

@ -38,31 +38,43 @@
//!
//! ### Comparisons
//!
//! First, let me say that these comparisons are highly subjective, and not meant
//! First, let me say that these comparisons are highly subjective, and not
//! meant
//! in a critical or harsh manner. All the argument parsing libraries out there
//! (to include `clap`) have their own strengths and weaknesses. Sometimes it just
//! comes down to personal taste when all other factors are equal. When in doubt,
//! try them all and pick one that you enjoy :) There's plenty of room in the Rust
//! (to include `clap`) have their own strengths and weaknesses. Sometimes it
//! just
//! comes down to personal taste when all other factors are equal. When in
//! doubt,
//! try them all and pick one that you enjoy :) There's plenty of room in the
//! Rust
//! community for multiple implementations!
//!
//! #### How does `clap` compare to `getopts`?
//!
//! [getopts](https://github.com/rust-lang-nursery/getopts) is a very basic, fairly
//! [getopts](https://github.com/rust-lang-nursery/getopts) is a very basic,
//! fairly
//! minimalist argument parsing library. This isn't a bad thing, sometimes you
//! don't need tons of features, you just want to parse some simple arguments, and
//! don't need tons of features, you just want to parse some simple arguments,
//! and
//! have some help text generated for you based on valid arguments you specify.
//! When using `getopts` you must manually implement most of the common features
//! (such as checking to display help messages, usage strings, etc.). If you want a
//! highly custom argument parser, and don't mind writing most the argument parser
//! (such as checking to display help messages, usage strings, etc.). If you
//! want a
//! highly custom argument parser, and don't mind writing most the argument
//! parser
//! yourself, `getopts` is an excellent base.
//!
//! Due to it's lack of features, `getopts` also doesn't allocate much, or at all.
//! Due to it's lack of features, `getopts` also doesn't allocate much, or at
//! all.
//! This gives it somewhat of a performance boost. Although, as you start
//! implementing those features you need manually, that boost quickly disappears.
//! implementing those features you need manually, that boost quickly
//! disappears.
//!
//! Personally, I find many, many people that use `getopts` are manually
//! implementing features that `clap` has by default. Using `clap` simplifies your
//! codebase allowing you to focus on your application, and not argument parsing.
//! implementing features that `clap` has by default. Using `clap` simplifies
//! your
//! codebase allowing you to focus on your application, and not argument
//! parsing.
//!
//! Reasons to use `getopts` instead of `clap`
//!
@ -75,28 +87,39 @@
//!
//! I first want to say I'm a big a fan of BurntSushi's work, the creator of
//! [Docopt.rs](https://github.com/docopt/docopt.rs). I aspire to produce the
//! quality of libraries that this man does! When it comes to comparing these two
//! quality of libraries that this man does! When it comes to comparing these
//! two
//! libraries they are very different. `docopt` tasks you with writing a help
//! message, and then it parsers that message for you to determine all valid
//! arguments and their use. Some people LOVE this, others not so much. If you're
//! willing to write a detailed help message, it's nice that you can stick that in
//! your program and have `docopt` do the rest. On the downside, it's somewhat less
//! flexible than other options out there, and requires the help message change if
//! arguments and their use. Some people LOVE this, others not so much. If
//! you're
//! willing to write a detailed help message, it's nice that you can stick that
//! in
//! your program and have `docopt` do the rest. On the downside, it's somewhat
//! less
//! flexible than other options out there, and requires the help message change
//! if
//! you need to make changes.
//!
//! `docopt` is also excellent at translating arguments into Rust types
//! automatically. There is even a syntax extension which will do all this for you,
//! ifou to manually translate from arguments to Rust types). To use BurntSushi's
//! words, `docopt` is also somewhat of a black box. You get what you get, and it's
//! automatically. There is even a syntax extension which will do all this for
//! you,
//! ifou to manually translate from arguments to Rust types). To use
//! BurntSushi's
//! words, `docopt` is also somewhat of a black box. You get what you get, and
//! it's
//! hard to tweak implementation or customise your experience for your use case.
//!
//! Because `docopt` is doing a ton of work to parse your help messages and
//! determine what you were trying to communicate as valid arguments, it's also one
//! of the more heavy weight parsers performance-wise. For most applications this
//! determine what you were trying to communicate as valid arguments, it's also
//! one
//! of the more heavy weight parsers performance-wise. For most applications
//! this
//! isn't a concern, but it's something to keep in mind.
//!
//! Reasons to use `docopt` instead of `clap`
//! * You want automatic translation from arguments to Rust types, and are using a
//! * You want automatic translation from arguments to Rust types, and are
//! using a
//! nightly compiler
//! * Performance isn't a concern
//! * You don't have any complex relationships between arguments
@ -105,7 +128,8 @@
//!
//! `clap` is fast, and as lightweight as possible while still giving all the
//! features you'd expect from a modern argument parser. If you use `clap` when
//! just need some simple arguments parsed, you'll find it a walk in the park. But
//! just need some simple arguments parsed, you'll find it a walk in the park.
//! But
//! `clap` also makes it possible to represent extremely complex, and advanced
//! requirements, without too much thought. `clap` aims to be intuitive, easy to
//! use, and fully capable for wide variety use cases and needs.
@ -624,12 +648,13 @@
#![cfg_attr(feature = "lints", allow(explicit_iter_loop))]
#![cfg_attr(feature = "lints", allow(should_implement_trait))]
#![cfg_attr(feature = "lints", deny(warnings))]
#![cfg_attr(not(any(feature = "lints", feature = "nightly")), deny(unstable_features))]
#![deny(missing_docs,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts, trivial_numeric_casts,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications)]
@ -641,12 +666,14 @@ extern crate ansi_term;
extern crate yaml_rust;
#[macro_use]
extern crate bitflags;
extern crate vec_map;
#[cfg(feature = "yaml")]
pub use yaml_rust::YamlLoader;
pub use args::{Arg, ArgGroup, ArgMatches, SubCommand};
pub use app::{App, AppSettings, ClapError, ClapErrorType};
pub use app::{App, AppSettings};
pub use fmt::Format;
pub use errors::{ClapError, ClapErrorType};
#[macro_use]
mod macros;
@ -654,3 +681,5 @@ mod app;
mod args;
mod usageparser;
mod fmt;
mod suggestions;
mod errors;

View file

@ -44,90 +44,7 @@ macro_rules! load_yaml {
);
}
macro_rules! write_spaces {
($num:expr, $w:ident) => ({
for _ in 0..$num {
try!(write!($w, " "));
}
})
}
// convenience macro for remove an item from a vec
macro_rules! vec_remove {
($vec:expr, $to_rem:ident) => {
{
let mut ix = None;
$vec.dedup();
for (i, val) in $vec.iter().enumerate() {
if val == $to_rem {
ix = Some(i);
break;
}
}
if let Some(i) = ix {
$vec.remove(i);
}
}
}
}
macro_rules! remove_overriden {
($me:ident, $name:expr) => ({
if let Some(ref o) = $me.opts.get($name) {
if let Some(ref ora) = o.requires {
for a in ora {
vec_remove!($me.required, a);
}
}
if let Some(ref ora) = o.blacklist {
for a in ora {
vec_remove!($me.blacklist, a);
}
}
if let Some(ref ora) = o.overrides {
for a in ora {
vec_remove!($me.overrides, a);
}
}
} else if let Some(ref o) = $me.flags.get($name) {
if let Some(ref ora) = o.requires {
for a in ora {
vec_remove!($me.required, a);
}
}
if let Some(ref ora) = o.blacklist {
for a in ora {
vec_remove!($me.blacklist, a);
}
}
if let Some(ref ora) = o.overrides {
for a in ora {
vec_remove!($me.overrides, a);
}
}
} else if let Some(p) = $me.positionals_name.get($name) {
if let Some(ref o) = $me.positionals_idx.get(p) {
if let Some(ref ora) = o.requires {
for a in ora {
vec_remove!($me.required, a);
}
}
if let Some(ref ora) = o.blacklist {
for a in ora {
vec_remove!($me.blacklist, a);
}
}
if let Some(ref ora) = o.overrides {
for a in ora {
vec_remove!($me.overrides, a);
}
}
}
}
})
}
// De-duplication macro used in src/app.rs
// used in src/args/arg_builder/option.rs
macro_rules! print_opt_help {
($opt:ident, $spc:expr, $w:ident) => {
if let Some(h) = $opt.help {
@ -136,7 +53,7 @@ macro_rules! print_opt_help {
if let Some(part) = hel.next() {
try!(write!($w, "{}", part));
}
while let Some(part) = hel.next() {
for part in hel {
try!(write!($w, "\n"));
write_spaces!($spc, $w);
try!(write!($w, "{}", part));
@ -155,38 +72,35 @@ macro_rules! print_opt_help {
};
}
// De-duplication macro used in src/app.rs
macro_rules! parse_group_reqs {
($me:ident, $arg:ident) => {
for ag in $me.groups.values() {
let mut found = false;
for name in ag.args.iter() {
if name == &$arg.name {
vec_remove!($me.required, name);
if let Some(ref reqs) = ag.requires {
for r in reqs {
$me.required.push(r);
}
}
if let Some(ref bl) = ag.conflicts {
for b in bl {
$me.blacklist.push(b);
}
}
found = true;
// Helper/deduplication macro for printing the correct number of spaces in help messages
// used in:
// src/args/arg_builder/*.rs
// src/app/mod.rs
macro_rules! write_spaces {
($num:expr, $w:ident) => ({
for _ in 0..$num {
try!(write!($w, " "));
}
})
}
// convenience macro for remove an item from a vec
macro_rules! vec_remove {
($vec:expr, $to_rem:ident) => {
{
let mut ix = None;
$vec.dedup();
for (i, val) in $vec.iter().enumerate() {
if &val == &$to_rem {
ix = Some(i);
break;
}
}
if found {
for name in ag.args.iter() {
if name == &$arg.name { continue }
vec_remove!($me.required, name);
$me.blacklist.push(name);
}
if let Some(i) = ix {
$vec.remove(i);
}
}
};
}
}
// Thanks to bluss and flan3002 in #rust IRC

View file

@ -1,6 +1,8 @@
#[cfg(feature = "suggestions")]
use strsim;
use 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
@ -34,6 +36,34 @@ pub fn did_you_mean<'a, T, I>(_: &str, _: I) -> Option<&'a str>
None
}
/// Returns a suffix that can be empty, or is the standard 'did you mean phrase
#[cfg_attr(feature = "lints", allow(needless_lifetimes))]
pub fn did_you_mean_suffix<'z, T, I>(arg: &str,
values: I,
style: DidYouMeanMessageStyle)
-> (String, Option<&'z str>)
where T: AsRef<str> + 'z,
I: IntoIterator<Item = &'z T>
{
match did_you_mean(arg, values) {
Some(candidate) => {
let mut suffix = "\n\tDid you mean ".to_owned();
match style {
DidYouMeanMessageStyle::LongFlag =>
suffix.push_str(&*format!("{}", Format::Good("--"))),
DidYouMeanMessageStyle::EnumValue => suffix.push('\''),
}
suffix.push_str(&Format::Good(candidate).to_string()[..]);
if let DidYouMeanMessageStyle::EnumValue = style {
suffix.push('\'');
}
suffix.push_str(" ?");
(suffix, Some(candidate))
}
None => (String::new(), None),
}
}
/// A helper to determine message formatting
pub enum DidYouMeanMessageStyle {
/// Suggested value is a long flag

View file

@ -25,7 +25,107 @@ impl<'u> UsageParser<'u> {
}
}
#[cfg_attr(feature = "lints", allow(while_let_on_iterator))]
fn name(&mut self, c: char) -> Option<UsageToken<'u>> {
if self.e != 0 {
self.e += 1;
}
self.s = self.e + 1;
let closing = match c {
'[' => ']',
'<' => '>',
_ => unreachable!(),
};
while let Some(c) = self.chars.next() {
self.e += 1;
if c == closing {
break;
}
}
if self.e > self.usage.len() {
return None;
}
let name = &self.usage[self.s..self.e];
Some(UsageToken::Name(name,
if c == '<' {
Some(true)
} else {
None
}))
}
#[cfg_attr(feature = "lints", allow(while_let_on_iterator))]
fn help(&mut self) -> Option<UsageToken<'u>> {
self.s = self.e + 2;
self.e = self.usage.len() - 1;
while let Some(_) = self.chars.next() {
continue;
}
Some(UsageToken::Help(&self.usage[self.s..self.e]))
}
#[cfg_attr(feature = "lints", allow(while_let_on_iterator))]
fn long_arg(&mut self) -> Option<UsageToken<'u>> {
if self.e != 1 {
self.e += 1;
}
self.s = self.e + 1;
while let Some(c) = self.chars.next() {
self.e += 1;
if c == ' ' || c == '=' || c == '.' {
break;
}
}
if self.e > self.usage.len() {
return None;
} else if self.e == self.usage.len() - 1 {
return Some(UsageToken::Long(&self.usage[self.s..]));
}
Some(UsageToken::Long(&self.usage[self.s..self.e]))
}
fn short_arg(&mut self, c: char) -> Option<UsageToken<'u>> {
// When short is first don't increment e
if self.e != 1 {
self.e += 1;
}
if !c.is_alphanumeric() {
return None;
}
Some(UsageToken::Short(c))
}
fn multiple(&mut self) -> bool {
self.e += 1;
let mut mult = false;
for _ in 0..2 {
self.e += 1;
match self.chars.next() {
// longs consume one '.' so they match '.. ' whereas shorts can
// match '...'
Some('.') | Some(' ') => {
mult = true;
}
_ => {
// if there is no help or following space all we can match is '..'
if self.e == self.usage.len() - 1 {
mult = true;
}
break;
}
}
}
mult
}
}
impl<'u> Iterator for UsageParser<'u> {
@ -35,80 +135,19 @@ impl<'u> Iterator for UsageParser<'u> {
loop {
match self.chars.next() {
Some(c) if c == '[' || c == '<' => {
// self.s = self.e + 1;
if self.e != 0 {
self.e += 1;
}
self.s = self.e + 1;
let closing = match c {
'[' => ']',
'<' => '>',
_ => unreachable!(),
};
while let Some(c) = self.chars.next() {
self.e += 1;
if c == closing {
break;
}
}
if self.e > self.usage.len() {
return None;
}
let name = &self.usage[self.s..self.e];
return Some(UsageToken::Name(name,
if c == '<' {
Some(true)
} else {
None
}));
return self.name(c);
}
Some('\'') => {
self.s = self.e + 2;
self.e = self.usage.len() - 1;
while let Some(_) = self.chars.next() {
continue;
}
return Some(UsageToken::Help(&self.usage[self.s..self.e]));
return self.help();
}
Some('-') => {
self.e += 1;
match self.chars.next() {
Some('-') => {
if self.e != 1 {
self.e += 1;
}
self.s = self.e + 1;
while let Some(c) = self.chars.next() {
self.e += 1;
if c == ' ' || c == '=' || c == '.' {
break;
}
}
if self.e > self.usage.len() {
return None;
}
if self.e == self.usage.len() - 1 {
return Some(UsageToken::Long(&self.usage[self.s..]));
}
return Some(UsageToken::Long(&self.usage[self.s..self.e]));
return self.long_arg();
}
Some(c) => {
// When short is first don't increment e
if self.e != 1 {
self.e += 1;
}
// Short
if !c.is_alphanumeric() {
return None;
}
return Some(UsageToken::Short(c));
return self.short_arg(c);
}
_ => {
return None;
@ -116,26 +155,7 @@ impl<'u> Iterator for UsageParser<'u> {
}
}
Some('.') => {
self.e += 1;
let mut mult = false;
for _ in 0..2 {
self.e += 1;
match self.chars.next() {
// longs consume one '.' so they match '.. ' whereas shorts can
// match '...'
Some('.') | Some(' ') => {
mult = true;
}
_ => {
// if there is no help or following space all we can match is '..'
if self.e == self.usage.len() - 1 {
mult = true;
}
break;
}
}
}
if mult {
if self.multiple() {
return Some(UsageToken::Multiple);
}
}

View file

@ -43,12 +43,11 @@ fn arg_required_else_help() {
let result = App::new("arg_required")
.setting(AppSettings::ArgRequiredElseHelp)
.arg(Arg::with_name("test")
.required(true)
.index(1))
.get_matches_from_safe(vec![""]);
assert!(result.is_err());
let err = result.err().unwrap();
assert_eq!(err.error_type, ClapErrorType::MissingRequiredArgument);
assert_eq!(err.error_type, ClapErrorType::MissingArgumentOrSubcommand);
}
#[test]
@ -109,4 +108,4 @@ fn app_settings_fromstr() {
assert_eq!("subcommandrequiredelsehelp".parse::<AppSettings>().ok().unwrap(), AppSettings::SubcommandRequiredElseHelp);
assert_eq!("hidden".parse::<AppSettings>().ok().unwrap(), AppSettings::Hidden);
assert!("hahahaha".parse::<AppSettings>().is_err());
}
}

View file

@ -26,6 +26,10 @@ fn group_single_value() {
.get_matches_from(vec!["", "-c", "blue"]);
assert!(m.is_present("grp"));
assert_eq!(m.value_of("grp").unwrap(), "blue");
}
#[test]
fn group_single_flag() {
let m = App::new("group")
.args_from_usage("-f, --flag 'some flag'
-c, --color [color] 'some option'")
@ -34,6 +38,10 @@ fn group_single_value() {
.get_matches_from(vec!["", "-f"]);
assert!(m.is_present("grp"));
assert!(m.value_of("grp").is_none());
}
#[test]
fn group_empty() {
let m = App::new("group")
.args_from_usage("-f, --flag 'some flag'
-c, --color [color] 'some option'")
@ -44,6 +52,20 @@ fn group_single_value() {
assert!(m.value_of("grp").is_none());
}
#[test]
fn group_reqired_flags_empty() {
let result = App::new("group")
.args_from_usage("-f, --flag 'some flag'
-c, --color 'some option'")
.arg_group(ArgGroup::with_name("grp")
.required(true)
.add_all(&["flag", "color"]))
.get_matches_from_safe(vec![""]);
assert!(result.is_err());
let err = result.err().unwrap();
assert_eq!(err.error_type, ClapErrorType::MissingRequiredArgument);
}
#[test]
fn group_multi_value_single_arg() {
let m = App::new("group")

View file

@ -150,17 +150,15 @@ fn conflict_overriden_4() {
}
#[test]
fn require_overriden() {
fn pos_required_overridden_by_flag() {
let result = App::new("require_overriden")
.arg(Arg::with_name("flag")
.arg(Arg::with_name("pos")
.index(1)
.required(true))
.arg(Arg::from_usage("-c, --color 'other flag'")
.mutually_overrides_with("flag"))
.get_matches_from_safe(vec!["", "flag", "-c"]);
assert!(result.is_ok());
// let err = result.err().unwrap();
// assert_eq!(err.error_type, ClapErrorType::MissingRequiredArgument);
.arg(Arg::from_usage("-c, --color 'some flag'")
.mutually_overrides_with("pos"))
.get_matches_from_safe(vec!["", "test", "-c"]);
assert!(result.is_ok(), "{:?}", result.unwrap_err());
}
#[test]
@ -202,4 +200,4 @@ fn require_overriden_4() {
assert!(result.is_err());
let err = result.err().unwrap();
assert_eq!(err.error_type, ClapErrorType::MissingRequiredArgument);
}
}

View file

@ -11,8 +11,8 @@ fn invalid_unicode_safe() {
.get_matches_from_safe(vec![OsString::from_vec(vec![0x20]),
OsString::from_vec(vec![0xe9])]);
assert!(m.is_err());
if let Err(e) = m {
assert_eq!(e.error_type, ClapErrorType::InvalidUnicode);
if let Err(err) = m {
assert_eq!(err.error_type, ClapErrorType::InvalidUnicode);
}
}