Merge pull request #150 from kbknapp/issue-149

Issue 149
This commit is contained in:
Kevin K. 2015-07-07 20:14:38 -04:00
commit 6bb0576a72
3 changed files with 116 additions and 51 deletions

View file

@ -8,7 +8,22 @@ It is a simple to use, efficient, and full featured library for parsing command
## What's New
If you're already familiar with `clap` but just want to see some new highlights as of **1.0.0-beta**
If you're already familiar with `clap` but just want to see some new highlights as of **1.0.0**
* **Args can now be parsed from arbitrary locations!** This makes testing immensly easier. Thanks to [cristicbz](https://github.com/cristicbz) for the idea!
Example...
```rust
let v = vec!["my_prog", "some_arg", "-f"];
let m = App::new("my_prog")
// Normal configuration goes here...
.get_matches_from(v);
// Use matches like normal...
```
As of **1.0.0-beta**
* **Deprecated Functions Removed** - In an effort to start a 1.x all deprecated functions have been removed, see the deprecations sections below to update your code (very minimal)
* **Can fully override help** - This allows you fully override the auto-generated help if you so choose

View file

@ -6,7 +6,6 @@ use std::collections::VecDeque;
use std::env;
use std::io::{self, BufRead};
use std::path::Path;
use std::vec::IntoIter;
use std::process;
use args::{ ArgMatches, Arg, SubCommand, MatchedArg};
@ -1427,18 +1426,56 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
if quit { self.exit(1); }
}
// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls
// the real parsing function for subcommands
pub fn get_matches(mut self) -> ArgMatches<'ar, 'ar> {
/// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls
/// the real parsing function for all subcommands
///
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg};
/// let matches = App::new("myprog")
/// // Args and options go here...
/// .get_matches();
/// ```
pub fn get_matches(self) -> ArgMatches<'ar, 'ar> {
let args: Vec<_> = env::args().collect();
self.get_matches_from(args)
}
/// Starts the parsing process. Called on top level parent app **ONLY** then recursively calls
/// the real parsing function for all subcommands
///
/// **NOTE:** The first argument will be parsed as the binary name.
///
/// **NOTE:** This method should only be used when absolutely necessary, such as needing to
/// parse arguments from something other than `std::env::args()`. If you are unsure, use
/// `App::get_matches()`
///
///
/// # Example
///
/// ```no_run
/// # use clap::{App, Arg};
/// let arg_vec = vec!["my_prog", "some", "args", "to", "parse"];
///
/// let matches = App::new("myprog")
/// // Args and options go here...
/// .get_matches_from(arg_vec);
/// ```
pub fn get_matches_from<I, T>(mut self, itr: I)
-> ArgMatches<'ar, 'ar>
where I: IntoIterator<Item=T>,
T: AsRef<str> {
self.verify_positionals();
self.propogate_globals();
let mut matches = ArgMatches::new();
let args: Vec<_> = env::args().collect();
let mut it = args.into_iter();
let mut it = itr.into_iter();
if let Some(name) = it.next() {
let p = Path::new(&name[..]);
let p = Path::new(name.as_ref());
if let Some(f) = p.file_name() {
if let Ok(s) = f.to_os_string().into_string() {
if let None = self.bin_name {
@ -1447,7 +1484,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
}
}
}
self.get_matches_from(&mut matches, &mut it );
self.get_matches_with(&mut matches, &mut it);
matches
}
@ -1502,31 +1539,12 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
}
}
/// Returns a suffix that can be empty, or is the standard 'did you mean phrase
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_string();
match style {
DidYouMeanMessageStyle::LongFlag => suffix.push_str(&Format::Good("--").to_string()[..]),
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),
}
}
fn possible_values_error(&self, arg: &str, opt: &str, p_vals: &BTreeSet<&str>,
matches: &ArgMatches<'ar, 'ar>) {
fn possible_values_error(&self,
arg: &str,
opt: &str,
p_vals: &BTreeSet<&str>,
matches: &ArgMatches<'ar, 'ar>) {
let suffix = App::did_you_mean_suffix(arg, p_vals.iter(),
DidYouMeanMessageStyle::EnumValue);
@ -1544,7 +1562,10 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
Some(matches.args.keys().map(|k| *k).collect()));
}
fn get_matches_from(&mut self, matches: &mut ArgMatches<'ar, 'ar>, it: &mut IntoIter<String>) {
// The actual parsing function
fn get_matches_with<I, T>(&mut self, matches: &mut ArgMatches<'ar, 'ar>, it: &mut I)
where I: Iterator<Item=T>,
T: AsRef<str> {
self.create_help_and_version();
let mut pos_only = false;
@ -1552,7 +1573,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
let mut needs_val_of: Option<&str> = None;
let mut pos_counter = 1;
while let Some(arg) = it.next() {
let arg_slice = &arg[..];
let arg_slice = arg.as_ref();
let mut skip = false;
let new_arg = if arg_slice.starts_with("-") {
if arg_slice.len() == 1 { false } else { true }
@ -1592,7 +1613,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
if !opt.empty_vals &&
matches.args.contains_key(opt.name) &&
arg.is_empty() {
arg_slice.is_empty() {
self.report_error(format!("The argument '{}' does not allow empty \
values, but one was found.", Format::Warning(opt.to_string())),
true,
@ -1604,7 +1625,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
// Options have values, so we can unwrap()
if let Some(ref mut vals) = o.values {
let len = vals.len() as u8 + 1;
vals.insert(len, arg.clone());
vals.insert(len, arg_slice.to_owned());
}
// if it's multiple the occurrences are increased when originall found
@ -1651,24 +1672,24 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
continue;
}
// Single flag, or option long version
needs_val_of = self.parse_long_arg(matches, &arg);
needs_val_of = self.parse_long_arg(matches, arg_slice);
} else if arg_slice.starts_with("-") && arg_slice.len() != 1 && ! pos_only {
needs_val_of = self.parse_short_arg(matches, &arg);
needs_val_of = self.parse_short_arg(matches, arg_slice);
} else {
// Positional or Subcommand
// If the user pased `--` we don't check for subcommands, because the argument they
// may be trying to pass might match a subcommand name
if !pos_only {
if self.subcommands.contains_key(&arg) {
if self.subcommands.contains_key(arg_slice) {
if arg_slice == "help" {
self.print_help();
}
subcmd_name = Some(arg.clone());
subcmd_name = Some(arg_slice.to_owned());
break;
}
if let Some(candidate_subcommand) = did_you_mean(&arg,
self.subcommands.keys()) {
if let Some(candidate_subcommand) = did_you_mean(arg_slice,
self.subcommands.keys()) {
self.report_error(
format!("The subcommand '{}' isn't valid\n\tDid you mean '{}' ?\n\n\
If you received this message in error, try \
@ -1677,7 +1698,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
Format::Good(candidate_subcommand),
self.bin_name.clone().unwrap_or(self.name.clone()),
Format::Good("--"),
arg),
arg_slice),
true,
true,
None);
@ -1738,7 +1759,8 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
}
}
}
if !p.empty_vals && matches.args.contains_key(p.name) && arg.is_empty() {
if !p.empty_vals && matches.args.contains_key(p.name)
&& arg_slice.is_empty() {
self.report_error(format!("The argument '{}' does not allow empty \
values, but one was found.", Format::Warning(p.to_string())),
true,
@ -1752,7 +1774,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
pos.occurrences += 1;
if let Some(ref mut vals) = pos.values {
let len = (vals.len() + 1) as u8;
vals.insert(len, arg.clone());
vals.insert(len, arg_slice.to_owned());
}
}
} else {
@ -1762,7 +1784,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
// Was an update made, or is this the first occurrence?
if !done {
let mut bm = BTreeMap::new();
if !p.empty_vals && arg.is_empty() {
if !p.empty_vals && arg_slice.is_empty() {
self.report_error(format!("The argument '{}' does not allow empty \
values, but one was found.", Format::Warning(p.to_string())),
true,
@ -1770,7 +1792,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
Some(matches.args.keys()
.map(|k| *k).collect()));
}
bm.insert(1, arg.clone());
bm.insert(1, arg_slice.to_owned());
matches.args.insert(p.name, MatchedArg{
occurrences: 1,
values: Some(bm),
@ -1894,7 +1916,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
""
},
sc.name.clone()));
sc.get_matches_from(&mut new_matches, it);
sc.get_matches_with(&mut new_matches, it);
matches.subcommand = Some(Box::new(SubCommand {
name: sc.name_slice,
matches: new_matches
@ -2017,7 +2039,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
}
}
fn parse_long_arg(&mut self, matches: &mut ArgMatches<'ar, 'ar> ,full_arg: &String)
fn parse_long_arg(&mut self, matches: &mut ArgMatches<'ar, 'ar> ,full_arg: &str)
-> Option<&'ar str> {
let mut arg = full_arg.trim_left_matches(|c| c == '-');
@ -2246,7 +2268,7 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
unreachable!();
}
fn parse_short_arg(&mut self, matches: &mut ArgMatches<'ar, 'ar> ,full_arg: &String)
fn parse_short_arg(&mut self, matches: &mut ArgMatches<'ar, 'ar> ,full_arg: &str)
-> Option<&'ar str> {
let arg = &full_arg[..].trim_left_matches(|c| c == '-');
if arg.len() > 1 {
@ -2585,4 +2607,27 @@ impl<'a, 'v, 'ab, 'u, 'h, 'ar> App<'a, 'v, 'ab, 'u, 'h, 'ar>{
}
true
}
/// Returns a suffix that can be empty, or is the standard 'did you mean phrase
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_string();
match style {
DidYouMeanMessageStyle::LongFlag => suffix.push_str(&Format::Good("--").to_string()[..]),
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),
}
}
}

View file

@ -345,6 +345,7 @@ macro_rules! simple_enum {
}
impl $e {
#[allow(dead_code)]
pub fn variants() -> Vec<&'static str> {
vec![
$(stringify!($v),)+
@ -427,6 +428,7 @@ macro_rules! arg_enum {
}
impl $e {
#[allow(dead_code)]
fn variants() -> Vec<&'static str> {
vec![
$(stringify!($v),)+
@ -469,6 +471,7 @@ macro_rules! arg_enum {
}
impl $e {
#[allow(dead_code)]
pub fn variants() -> Vec<&'static str> {
vec![
$(stringify!($v),)+
@ -512,6 +515,7 @@ macro_rules! arg_enum {
}
impl $e {
#[allow(dead_code)]
pub fn variants() -> Vec<&'static str> {
vec![
$(stringify!($v),)+
@ -555,6 +559,7 @@ macro_rules! arg_enum {
}
impl $e {
#[allow(dead_code)]
pub fn variants() -> Vec<&'static str> {
vec![
$(stringify!($v),)+