diff --git a/README.md b/README.md index c1ae9e04..df99a869 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/app.rs b/src/app.rs index f4b860be..7de39259 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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(mut self, itr: I) + -> ArgMatches<'ar, 'ar> + where I: IntoIterator, + T: AsRef { 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 + 'z, - I: IntoIterator { - 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) { + // The actual parsing function + fn get_matches_with(&mut self, matches: &mut ArgMatches<'ar, 'ar>, it: &mut I) + where I: Iterator, + T: AsRef { 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 + 'z, + I: IntoIterator { + 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), + } + } } diff --git a/src/macros.rs b/src/macros.rs index fde4f7f9..a8b799bd 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -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),)+