Merge branch 'master' into flag-subcommands

This commit is contained in:
Nick Hackman 2020-07-09 21:31:13 -04:00 committed by GitHub
commit 27441329f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 335 additions and 279 deletions

View file

@ -67,7 +67,7 @@ maintenance = {status = "actively-developed"}
[dependencies] [dependencies]
bitflags = "1.2" bitflags = "1.2"
unicode-width = "0.1" unicode-width = "0.1"
textwrap = "0.11" textwrap = "0.12"
indexmap = "1.0" indexmap = "1.0"
os_str_bytes = { version = "2.3", features = ["raw"] } os_str_bytes = { version = "2.3", features = ["raw"] }
vec_map = "0.8" vec_map = "0.8"
@ -75,7 +75,7 @@ strsim = { version = "0.10", optional = true }
yaml-rust = { version = "0.4.1", optional = true } yaml-rust = { version = "0.4.1", optional = true }
atty = { version = "0.2", optional = true } atty = { version = "0.2", optional = true }
termcolor = { version = "1.1", optional = true } termcolor = { version = "1.1", optional = true }
term_size = { version = "1.0.0-beta1", optional = true } terminal_size = { version = "0.1.12", optional = true }
lazy_static = { version = "1", optional = true } lazy_static = { version = "1", optional = true }
clap_derive = { path = "./clap_derive", version = "3.0.0-beta.1", optional = true } clap_derive = { path = "./clap_derive", version = "3.0.0-beta.1", optional = true }
@ -90,7 +90,7 @@ default = ["suggestions", "color", "derive", "std", "cargo"]
std = [] # support for no_std in a backwards-compatible way std = [] # support for no_std in a backwards-compatible way
suggestions = ["strsim"] suggestions = ["strsim"]
color = ["atty", "termcolor"] color = ["atty", "termcolor"]
wrap_help = ["term_size", "textwrap/term_size"] wrap_help = ["terminal_size", "textwrap/terminal_size"]
derive = ["clap_derive", "lazy_static"] derive = ["clap_derive", "lazy_static"]
yaml = ["yaml-rust"] yaml = ["yaml-rust"]
cargo = [] # Disable if you're not using Cargo, enables Cargo-env-var-dependent macros cargo = [] # Disable if you're not using Cargo, enables Cargo-env-var-dependent macros

View file

@ -350,12 +350,12 @@ fn main() {
(about: "Does awesome things") (about: "Does awesome things")
(@arg CONFIG: -c --config +takes_value "Sets a custom config file") (@arg CONFIG: -c --config +takes_value "Sets a custom config file")
(@arg INPUT: +required "Sets the input file to use") (@arg INPUT: +required "Sets the input file to use")
(@arg debug: -d ... "Sets the level of debugging information") (@arg verbose: -v --verbose "Print test information verbosely")
(@subcommand test => (@subcommand test =>
(about: "controls testing features") (about: "controls testing features")
(version: "1.3") (version: "1.3")
(author: "Someone E. <someone_else@other.com>") (author: "Someone E. <someone_else@other.com>")
(@arg verbose: -v --verbose "Print test information verbosely") (@arg debug: -d ... "Sets the level of debugging information")
) )
).get_matches(); ).get_matches();
@ -410,7 +410,7 @@ To try out the pre-built [examples][examples], use the following steps:
To test out `clap`'s default auto-generated help/version follow these steps: To test out `clap`'s default auto-generated help/version follow these steps:
* Create a new cargo project `$ cargo new fake --bin && cd fake` * Create a new cargo project `$ cargo new fake --bin && cd fake`
* Writer your program as described in the quick example section. * Write your program as described in the quick example section.
* Build your program `$ cargo build --release` * Build your program `$ cargo build --release`
* Run with help or version `$ ./target/release/fake --help` or `$ ./target/release/fake --version` * Run with help or version `$ ./target/release/fake --help` or `$ ./target/release/fake --version`

View file

@ -30,7 +30,7 @@ struct Opt {
// the long option will be translated by default to kebab case, // the long option will be translated by default to kebab case,
// i.e. `--nb-cars`. // i.e. `--nb-cars`.
/// Number of cars /// Number of cars
#[clap(short = "c", long)] #[clap(short = 'c', long)]
nb_cars: Option<i32>, nb_cars: Option<i32>,
/// admin_level to consider /// admin_level to consider

View file

@ -26,7 +26,7 @@ struct Opt {
// but this makes adding an argument after the values impossible: // but this makes adding an argument after the values impossible:
// my_program -D a=1 -D b=2 my_input_file // my_program -D a=1 -D b=2 my_input_file
// becomes invalid. // becomes invalid.
#[clap(short = "D", parse(try_from_str = parse_key_val), number_of_values = 1)] #[clap(short = 'D', parse(try_from_str = parse_key_val), number_of_values = 1)]
defines: Vec<(String, i32)>, defines: Vec<(String, i32)>,
} }

View file

@ -147,11 +147,7 @@ impl ToTokens for Method {
fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) { fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
let Method { ref name, ref args } = self; let Method { ref name, ref args } = self;
let tokens = if name == "short" { let tokens = quote!( .#name(#args) );
quote!( .#name(#args.chars().nth(0).unwrap()) )
} else {
quote!( .#name(#args) )
};
tokens.to_tokens(ts); tokens.to_tokens(ts);
} }
@ -242,6 +238,28 @@ impl Name {
} }
} }
} }
pub fn translate_char(self, style: CasingStyle) -> TokenStream {
use CasingStyle::*;
match self {
Name::Assigned(tokens) => quote!( (#tokens).chars().next().unwrap() ),
Name::Derived(ident) => {
let s = ident.unraw().to_string();
let s = match style {
Pascal => s.to_camel_case(),
Kebab => s.to_kebab_case(),
Camel => s.to_mixed_case(),
ScreamingSnake => s.to_shouty_snake_case(),
Snake => s.to_snake_case(),
Verbatim => s,
};
let s = s.chars().next().unwrap();
quote_spanned!(ident.span()=> #s)
}
}
}
} }
impl Attrs { impl Attrs {
@ -284,7 +302,11 @@ impl Attrs {
for attr in parse_clap_attributes(attrs) { for attr in parse_clap_attributes(attrs) {
match attr { match attr {
Short(ident) | Long(ident) => { Short(ident) => {
self.push_method(ident, self.name.clone().translate_char(*self.casing));
}
Long(ident) => {
self.push_method(ident, self.name.clone().translate(*self.casing)); self.push_method(ident, self.name.clone().translate(*self.casing));
} }

View file

@ -188,12 +188,18 @@ fn gen_from_subcommand(
quote!(::std::string::String), quote!(::std::string::String),
quote!(values_of), quote!(values_of),
) )
} else { } else if is_simple_ty(subty, "OsString") {
( (
subty.span(), subty.span(),
quote!(::std::ffi::OsString), quote!(::std::ffi::OsString),
quote!(values_of_os), quote!(values_of_os),
) )
} else {
abort!(
ty.span(),
"The type must be either `Vec<String>` or `Vec<OsString>` \
to be used with `external_subcommand`."
);
} }
} }

View file

@ -44,7 +44,10 @@ pub fn sub_type(ty: &syn::Type) -> Option<&syn::Type> {
subty_if(ty, |_| true) subty_if(ty, |_| true)
} }
fn only_last_segment(ty: &syn::Type) -> Option<&PathSegment> { fn only_last_segment(mut ty: &syn::Type) -> Option<&PathSegment> {
while let syn::Type::Group(syn::TypeGroup { elem, .. }) = ty {
ty = elem;
}
match ty { match ty {
Type::Path(TypePath { Type::Path(TypePath {
qself: None, qself: None,

View file

@ -99,7 +99,7 @@ fn test_standalone_short_generates_kebab_case() {
fn test_custom_short_overwrites_default_name() { fn test_custom_short_overwrites_default_name() {
#[derive(Clap, Debug, PartialEq)] #[derive(Clap, Debug, PartialEq)]
struct Opt { struct Opt {
#[clap(short = "o")] #[clap(short = 'o')]
foo_option: bool, foo_option: bool,
} }

View file

@ -18,7 +18,7 @@ use clap::Clap;
fn basic() { fn basic() {
#[derive(Clap, PartialEq, Debug)] #[derive(Clap, PartialEq, Debug)]
struct Opt { struct Opt {
#[clap(short = "a", long = "arg")] #[clap(short = 'a', long = "arg")]
arg: Vec<i32>, arg: Vec<i32>,
} }
assert_eq!(Opt { arg: vec![24] }, Opt::parse_from(&["test", "-a24"])); assert_eq!(Opt { arg: vec![24] }, Opt::parse_from(&["test", "-a24"]));

View file

@ -32,7 +32,7 @@ struct PathOpt {
#[clap(short, parse(from_os_str))] #[clap(short, parse(from_os_str))]
option_path_1: Option<PathBuf>, option_path_1: Option<PathBuf>,
#[clap(short = "q", parse(from_os_str))] #[clap(short = 'q', parse(from_os_str))]
option_path_2: Option<PathBuf>, option_path_2: Option<PathBuf>,
} }
@ -179,7 +179,7 @@ struct Occurrences {
#[clap(short, parse(from_occurrences))] #[clap(short, parse(from_occurrences))]
unsigned: usize, unsigned: usize,
#[clap(short = "r", parse(from_occurrences))] #[clap(short = 'r', parse(from_occurrences))]
little_unsigned: u8, little_unsigned: u8,
#[clap(short, long, parse(from_occurrences = foo))] #[clap(short, long, parse(from_occurrences = foo))]

View file

@ -7,7 +7,7 @@ use utils::*;
fn explicit_short_long_no_rename() { fn explicit_short_long_no_rename() {
#[derive(Clap, PartialEq, Debug)] #[derive(Clap, PartialEq, Debug)]
struct Opt { struct Opt {
#[clap(short = ".", long = ".foo")] #[clap(short = '.', long = ".foo")]
foo: Vec<String>, foo: Vec<String>,
} }

View file

@ -0,0 +1,33 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use clap::Clap;
// Tests that clap_derive properly detects an `Option` field
// that results from a macro expansion
#[test]
fn use_option() {
macro_rules! expand_ty {
($name:ident: $ty:ty) => {
#[derive(Clap)]
struct Outer {
#[clap(short, long)]
#[allow(dead_code)]
$name: $ty,
}
};
}
expand_ty!(my_field: Option<String>);
}

View file

@ -30,7 +30,7 @@ struct Opt {
)] )]
x: i32, x: i32,
#[clap(short = "l", long = "level", aliases = &["set-level", "lvl"])] #[clap(short = 'l', long = "level", aliases = &["set-level", "lvl"])]
level: String, level: String,
#[clap(long("values"))] #[clap(long("values"))]
@ -129,7 +129,7 @@ fn parse_hex(input: &str) -> Result<u64, ParseIntError> {
#[derive(Clap, PartialEq, Debug)] #[derive(Clap, PartialEq, Debug)]
struct HexOpt { struct HexOpt {
#[clap(short = "n", parse(try_from_str = parse_hex))] #[clap(short, parse(try_from_str = parse_hex))]
number: u64, number: u64,
} }

View file

@ -1,3 +1,9 @@
error: The type must be either `Vec<String>` or `Vec<OsString>` to be used with `external_subcommand`.
--> $DIR/external_subcommand_wrong_type.rs:7:11
|
7 | Other(Vec<CString>),
| ^^^
error: The type must be either `Vec<String>` or `Vec<OsString>` to be used with `external_subcommand`. error: The type must be either `Vec<String>` or `Vec<OsString>` to be used with `external_subcommand`.
--> $DIR/external_subcommand_wrong_type.rs:13:11 --> $DIR/external_subcommand_wrong_type.rs:13:11
| |
@ -10,12 +16,3 @@ error: The enum variant marked with `external_attribute` must be a single-typed
18 | / #[clap(external_subcommand)] 18 | / #[clap(external_subcommand)]
19 | | Other { a: String }, 19 | | Other { a: String },
| |_______________________^ | |_______________________^
error[E0308]: mismatched types
--> $DIR/external_subcommand_wrong_type.rs:7:15
|
7 | Other(Vec<CString>),
| ^^^^^^^ expected struct `std::ffi::CString`, found struct `std::ffi::OsString`
|
= note: expected struct `std::vec::Vec<std::ffi::CString>`
found struct `std::vec::Vec<std::ffi::OsString>`

View file

@ -5,7 +5,7 @@
// Accept and endure. Do not touch. // Accept and endure. Do not touch.
#![allow(unused)] #![allow(unused)]
use clap::{find_subcmd_mut, match_alias, IntoApp}; use clap::IntoApp;
pub fn get_help<T: IntoApp>() -> String { pub fn get_help<T: IntoApp>() -> String {
let mut output = Vec::new(); let mut output = Vec::new();
@ -33,7 +33,9 @@ pub fn get_long_help<T: IntoApp>() -> String {
pub fn get_subcommand_long_help<T: IntoApp>(subcmd: &str) -> String { pub fn get_subcommand_long_help<T: IntoApp>(subcmd: &str) -> String {
let mut output = Vec::new(); let mut output = Vec::new();
find_subcmd_mut!(<T as IntoApp>::into_app(), subcmd) <T as IntoApp>::into_app()
.get_subcommands_mut()
.find(|s| s.get_name() == subcmd)
.unwrap() .unwrap()
.write_long_help(&mut output) .write_long_help(&mut output)
.unwrap(); .unwrap();

View file

@ -4,7 +4,7 @@ mod shells;
use std::io::Write; use std::io::Write;
// Internal // Internal
use clap::{find_subcmd, flags, match_alias, App, AppSettings, Arg}; use clap::{App, AppSettings, Arg};
pub use shells::*; pub use shells::*;
/// Generator trait which can be used to write generators /// Generator trait which can be used to write generators
@ -61,11 +61,7 @@ pub trait Generator {
fn all_subcommands(app: &App) -> Vec<(String, String)> { fn all_subcommands(app: &App) -> Vec<(String, String)> {
let mut subcmds: Vec<_> = Self::subcommands(app); let mut subcmds: Vec<_> = Self::subcommands(app);
for sc_v in app for sc_v in app.get_subcommands().map(|s| Self::all_subcommands(&s)) {
.get_subcommands()
.iter()
.map(|s| Self::all_subcommands(&s))
{
subcmds.extend(sc_v); subcmds.extend(sc_v);
} }
@ -81,7 +77,7 @@ pub trait Generator {
let mut app = p; let mut app = p;
for sc in path { for sc in path {
app = find_subcmd!(app, sc).unwrap(); app = app.find_subcommand(sc).unwrap();
} }
app app
@ -123,7 +119,6 @@ pub trait Generator {
let mut shorts: Vec<char> = p let mut shorts: Vec<char> = p
.get_arguments() .get_arguments()
.iter()
.filter_map(|a| { .filter_map(|a| {
if a.get_index().is_none() && a.get_short().is_some() { if a.get_index().is_none() && a.get_short().is_some() {
Some(a.get_short().unwrap()) Some(a.get_short().unwrap())
@ -151,7 +146,6 @@ pub trait Generator {
let mut longs: Vec<String> = p let mut longs: Vec<String> = p
.get_arguments() .get_arguments()
.iter()
.filter_map(|a| { .filter_map(|a| {
if a.get_index().is_none() && a.get_long().is_some() { if a.get_index().is_none() && a.get_long().is_some() {
Some(a.get_long().unwrap().to_string()) Some(a.get_long().unwrap().to_string())
@ -179,7 +173,7 @@ pub trait Generator {
fn flags<'b>(p: &'b App<'b>) -> Vec<Arg> { fn flags<'b>(p: &'b App<'b>) -> Vec<Arg> {
debug!("flags: name={}", p.get_name()); debug!("flags: name={}", p.get_name());
let mut flags: Vec<_> = flags!(p).cloned().collect(); let mut flags: Vec<_> = p.get_flags_no_heading().cloned().collect();
if flags.iter().find(|x| x.get_name() == "help").is_none() { if flags.iter().find(|x| x.get_name() == "help").is_none() {
flags.push( flags.push(

View file

@ -146,7 +146,7 @@ fn option_details_for_path(app: &App, path: &str) -> String {
let p = Bash::find_subcommand_with_path(app, path.split("__").skip(1).collect()); let p = Bash::find_subcommand_with_path(app, path.split("__").skip(1).collect());
let mut opts = String::new(); let mut opts = String::new();
for o in opts!(p) { for o in p.get_opts_no_heading() {
if let Some(l) = o.get_long() { if let Some(l) = o.get_long() {
opts = format!( opts = format!(
"{} "{}
@ -201,7 +201,9 @@ fn all_options_for_path(app: &App, path: &str) -> String {
longs = Bash::longs(p) longs = Bash::longs(p)
.iter() .iter()
.fold(String::new(), |acc, l| format!("{} --{}", acc, l)), .fold(String::new(), |acc, l| format!("{} --{}", acc, l)),
pos = positionals!(p).fold(String::new(), |acc, p| format!("{} {}", acc, p)), pos = p
.get_positionals()
.fold(String::new(), |acc, p| format!("{} {}", acc, p)),
subcmds = scs.join(" "), subcmds = scs.join(" "),
); );

View file

@ -77,7 +77,7 @@ fn generate_inner<'b>(
let mut completions = String::new(); let mut completions = String::new();
let preamble = String::from("\n cand "); let preamble = String::from("\n cand ");
for option in opts!(p) { for option in p.get_opts_no_heading() {
if let Some(data) = option.get_short() { if let Some(data) = option.get_short() {
let tooltip = get_tooltip(option.get_about(), data); let tooltip = get_tooltip(option.get_about(), data);

View file

@ -54,7 +54,7 @@ fn gen_fish_inner(root_command: &str, app: &App, buffer: &mut String) {
debug!("gen_fish_inner: bin_name={}", bin_name); debug!("gen_fish_inner: bin_name={}", bin_name);
for option in opts!(app) { for option in app.get_opts_no_heading() {
let mut template = basic_template.clone(); let mut template = basic_template.clone();
if let Some(data) = option.get_short() { if let Some(data) = option.get_short() {

View file

@ -84,7 +84,7 @@ fn generate_inner<'b>(
let mut completions = String::new(); let mut completions = String::new();
let preamble = String::from("\n [CompletionResult]::new("); let preamble = String::from("\n [CompletionResult]::new(");
for option in opts!(p) { for option in p.get_opts_no_heading() {
if let Some(data) = option.get_short() { if let Some(data) = option.get_short() {
let tooltip = get_tooltip(option.get_about(), data); let tooltip = get_tooltip(option.get_about(), data);

View file

@ -248,7 +248,7 @@ esac",
name = p.get_name(), name = p.get_name(),
name_hyphen = p.get_bin_name().unwrap().replace(" ", "-"), name_hyphen = p.get_bin_name().unwrap().replace(" ", "-"),
subcommands = subcmds.join("\n"), subcommands = subcmds.join("\n"),
pos = positionals!(p).count() + 1 pos = p.get_positionals().count() + 1
) )
} }
@ -260,7 +260,7 @@ fn parser_of<'b>(p: &'b App<'b>, mut sc: &str) -> &'b App<'b> {
} }
sc = sc.split(' ').last().unwrap(); sc = sc.split(' ').last().unwrap();
find_subcmd!(p, sc).expect(INTERNAL_ERROR_MSG) p.find_subcommand(sc).expect(INTERNAL_ERROR_MSG)
} }
// Writes out the args section, which ends up being the flags, opts and postionals, and a jump to // Writes out the args section, which ends up being the flags, opts and postionals, and a jump to
@ -354,7 +354,7 @@ fn write_opts_of(p: &App) -> String {
let mut ret = vec![]; let mut ret = vec![];
for o in opts!(p) { for o in p.get_opts_no_heading() {
debug!("write_opts_of:iter: o={}", o.get_name()); debug!("write_opts_of:iter: o={}", o.get_name());
let help = o.get_about().map_or(String::new(), escape_help); let help = o.get_about().map_or(String::new(), escape_help);
@ -490,7 +490,7 @@ fn write_positionals_of(p: &App) -> String {
let mut ret = vec![]; let mut ret = vec![];
for arg in positionals!(p) { for arg in p.get_positionals() {
debug!("write_positionals_of:iter: arg={}", arg.get_name()); debug!("write_positionals_of:iter: arg={}", arg.get_name());
let optional = if !arg.is_set(ArgSettings::Required) { let optional = if !arg.is_set(ArgSettings::Required) {

View file

@ -25,8 +25,8 @@ run-tests:
cargo bench cargo bench
@lint: @lint:
rustup add component clippy rustup component add clippy
rustup add component rustfmt rustup component add rustfmt
cargo clippy --lib --features "yaml unstable" -- -D warnings cargo clippy --lib --features "yaml unstable" -- -D warnings
cargo clippy --tests --examples --features "yaml unstable" cargo clippy --tests --examples --features "yaml unstable"
cargo fmt -- --check cargo fmt -- --check

View file

@ -165,20 +165,40 @@ impl<'b> App<'b> {
/// Get the list of subcommands /// Get the list of subcommands
#[inline] #[inline]
pub fn get_subcommands(&self) -> &[App<'b>] { pub fn get_subcommands(&self) -> impl Iterator<Item = &App<'b>> {
&self.subcommands self.subcommands.iter()
} }
/// Get the list of subcommands /// Iterate through the set of subcommands, getting a mutable reference to each.
#[inline] #[inline]
pub fn get_subcommands_mut(&mut self) -> &mut [App<'b>] { pub fn get_subcommands_mut(&mut self) -> impl Iterator<Item = &mut App<'b>> {
&mut self.subcommands self.subcommands.iter_mut()
} }
/// Get the list of arguments /// Iterate through the set of arguments
#[inline] #[inline]
pub fn get_arguments(&self) -> &[Arg<'b>] { pub fn get_arguments(&self) -> impl Iterator<Item = &Arg<'b>> {
&self.args.args self.args.args.iter()
}
/// Get the list of *positional* arguments.
#[inline]
pub fn get_positionals(&self) -> impl Iterator<Item = &Arg<'b>> {
self.get_arguments().filter(|a| a.is_positional())
}
/// Iterate through the *flags* that don't have custom heading.
pub fn get_flags_no_heading(&self) -> impl Iterator<Item = &Arg<'b>> {
self.get_arguments()
.filter(|a| !a.is_set(ArgSettings::TakesValue) && a.get_index().is_none())
.filter(|a| a.get_help_heading().is_none())
}
/// Iterate through the *options* that don't have custom heading.
pub fn get_opts_no_heading(&self) -> impl Iterator<Item = &Arg<'b>> {
self.get_arguments()
.filter(|a| a.is_set(ArgSettings::TakesValue) && a.get_index().is_none())
.filter(|a| a.get_help_heading().is_none())
} }
/// Get the list of arguments the given argument conflicts with /// Get the list of arguments the given argument conflicts with
@ -210,6 +230,15 @@ impl<'b> App<'b> {
pub fn has_subcommands(&self) -> bool { pub fn has_subcommands(&self) -> bool {
!self.subcommands.is_empty() !self.subcommands.is_empty()
} }
/// Find subcommand such that its name or one of aliases equals `name`.
#[inline]
pub fn find_subcommand<T>(&self, name: &T) -> Option<&App<'b>>
where
T: PartialEq<str> + ?Sized,
{
self.get_subcommands().find(|s| s.aliases_to(name))
}
} }
impl<'b> App<'b> { impl<'b> App<'b> {
@ -2204,6 +2233,7 @@ impl<'b> App<'b> {
self.args.args.iter().find(|a| a.id == *arg_id) self.args.args.iter().find(|a| a.id == *arg_id)
} }
#[inline]
// Should we color the output? // Should we color the output?
pub(crate) fn color(&self) -> ColorChoice { pub(crate) fn color(&self) -> ColorChoice {
debug!("App::color: Color setting..."); debug!("App::color: Color setting...");
@ -2220,6 +2250,7 @@ impl<'b> App<'b> {
} }
} }
#[inline]
pub(crate) fn contains_short(&self, s: char) -> bool { pub(crate) fn contains_short(&self, s: char) -> bool {
if !self.is_set(AppSettings::Built) { if !self.is_set(AppSettings::Built) {
panic!("If App::_build hasn't been called, manually search through Arg shorts"); panic!("If App::_build hasn't been called, manually search through Arg shorts");
@ -2228,24 +2259,29 @@ impl<'b> App<'b> {
self.args.contains(s) self.args.contains(s)
} }
#[inline]
pub(crate) fn set(&mut self, s: AppSettings) { pub(crate) fn set(&mut self, s: AppSettings) {
self.settings.set(s) self.settings.set(s)
} }
#[inline]
pub(crate) fn unset(&mut self, s: AppSettings) { pub(crate) fn unset(&mut self, s: AppSettings) {
self.settings.unset(s) self.settings.unset(s)
} }
#[inline]
pub(crate) fn has_args(&self) -> bool { pub(crate) fn has_args(&self) -> bool {
!self.args.is_empty() !self.args.is_empty()
} }
#[inline]
pub(crate) fn has_opts(&self) -> bool { pub(crate) fn has_opts(&self) -> bool {
opts!(self).count() > 0 self.get_opts_no_heading().count() > 0
} }
#[inline]
pub(crate) fn has_flags(&self) -> bool { pub(crate) fn has_flags(&self) -> bool {
flags!(self).count() > 0 self.get_flags_no_heading().count() > 0
} }
pub(crate) fn has_visible_subcommands(&self) -> bool { pub(crate) fn has_visible_subcommands(&self) -> bool {
@ -2255,11 +2291,40 @@ impl<'b> App<'b> {
.any(|sc| !sc.is_set(AppSettings::Hidden)) .any(|sc| !sc.is_set(AppSettings::Hidden))
} }
/// Check if this subcommand can be referred to as `name`. In other words,
/// check if `name` is the name of this subcommand or is one of its aliases.
#[inline]
pub(crate) fn aliases_to<T>(&self, name: &T) -> bool
where
T: PartialEq<str> + ?Sized,
{
*name == *self.get_name() || self.get_all_aliases().any(|alias| *name == *alias)
}
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
pub(crate) fn id_exists(&self, id: &Id) -> bool { pub(crate) fn id_exists(&self, id: &Id) -> bool {
self.args.args.iter().any(|x| x.id == *id) || self.groups.iter().any(|x| x.id == *id) self.args.args.iter().any(|x| x.id == *id) || self.groups.iter().any(|x| x.id == *id)
} }
/// Iterate through the groups this arg is member of.
pub(crate) fn groups_for_arg<'a>(&'a self, arg: &'_ Id) -> impl Iterator<Item = Id> + 'a {
debug!("App::groups_for_arg: id={:?}", arg);
let arg = arg.clone();
self.groups
.iter()
.filter(move |grp| grp.args.iter().any(|a| a == &arg))
.map(|grp| grp.id.clone())
}
/// Iterate through all the names of all subcommands (not recursively), including aliases.
/// Used for suggestions.
pub(crate) fn all_subcommand_names(&self) -> impl Iterator<Item = &str> {
self.get_subcommands().map(|s| s.get_name()).chain(
self.get_subcommands()
.flat_map(|s| s.aliases.iter().map(|&(n, _)| n)),
)
}
pub(crate) fn unroll_args_in_group(&self, group: &Id) -> Vec<Id> { pub(crate) fn unroll_args_in_group(&self, group: &Id) -> Vec<Id> {
debug!("App::unroll_args_in_group: group={:?}", group); debug!("App::unroll_args_in_group: group={:?}", group);
let mut g_vec = vec![group]; let mut g_vec = vec![group];

View file

@ -3005,8 +3005,8 @@ impl<'help> Arg<'help> {
/// ///
/// **WARNING**: When building your CLIs, consider the effects of allowing leading hyphens and /// **WARNING**: When building your CLIs, consider the effects of allowing leading hyphens and
/// the user passing in a value that matches a valid short. For example `prog -opt -F` where /// the user passing in a value that matches a valid short. For example `prog -opt -F` where
/// `-F` is supposed to be a value, yet `-F` is *also* a valid short for another arg. Care should /// `-F` is supposed to be a value, yet `-F` is *also* a valid short for another arg.
/// should be taken when designing these args. This is compounded by the ability to "stack" /// Care should be taken when designing these args. This is compounded by the ability to "stack"
/// short args. I.e. if `-val` is supposed to be a value, but `-v`, `-a`, and `-l` are all valid /// short args. I.e. if `-val` is supposed to be a value, but `-v`, `-a`, and `-l` are all valid
/// shorts. /// shorts.
/// ///

View file

@ -272,7 +272,7 @@ macro_rules! app_from_crate {
/// ///
/// # Alternative form for non-ident values /// # Alternative form for non-ident values
/// ///
/// Certain places that normally accept an `ident`, will optionally accept an alternative of `("expr enclosed by parens")` /// Certain places that normally accept an `ident` also optionally accept an alternative of `("expr enclosed by parens")`
/// * `(@arg something: --something)` could also be `(@arg ("something-else"): --("something-else"))` /// * `(@arg something: --something)` could also be `(@arg ("something-else"): --("something-else"))`
/// * `(@subcommand something => ...)` could also be `(@subcommand ("something-else") => ...)` /// * `(@subcommand something => ...)` could also be `(@subcommand ("something-else") => ...)`
/// ///
@ -552,128 +552,3 @@ macro_rules! debug {
macro_rules! debug { macro_rules! debug {
($($arg:tt)*) => {}; ($($arg:tt)*) => {};
} }
#[macro_export]
#[doc(hidden)]
macro_rules! flags {
($app:expr, $how:ident) => {{
$app.get_arguments()
.$how()
.filter(|a| !a.is_set($crate::ArgSettings::TakesValue) && a.get_index().is_none())
.filter(|a| !a.get_help_heading().is_some())
}};
($app:expr) => {
$crate::flags!($app, iter)
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! opts {
($app:expr, $how:ident) => {{
$app.get_arguments()
.$how()
.filter(|a| a.is_set($crate::ArgSettings::TakesValue) && a.get_index().is_none())
.filter(|a| !a.get_help_heading().is_some())
}};
($app:expr) => {
opts!($app, iter)
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! positionals {
($app:expr) => {{
$app.get_arguments()
.iter()
.filter(|a| !(a.get_short().is_some() || a.get_long().is_some()))
}};
}
macro_rules! groups_for_arg {
($app:expr, $grp:expr) => {{
debug!("groups_for_arg: name={:?}", $grp);
$app.groups
.iter()
.filter(|grp| grp.args.iter().any(|a| a == $grp))
.map(|grp| grp.id.clone())
}};
}
macro_rules! find_subcmd_cloned {
($app:expr, $sc:expr) => {{
$app.get_subcommands()
.iter()
.cloned()
.find(|a| match_alias!(a, $sc, a.get_name()))
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! find_subcmd {
($app:expr, $sc:expr) => {{
$app.get_subcommands()
.iter()
.find(|a| match_alias!(a, $sc, a.get_name()))
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! find_subcmd_mut {
($app:expr, $sc:expr) => {{
$app.get_subcommands_mut()
.iter_mut()
.find(|a| match_alias!(a, $sc, a.get_name()))
}};
}
macro_rules! longs {
($app:expr, $how:ident) => {{
use crate::mkeymap::KeyType;
$app.args.keys.iter().map(|x| &x.key).filter_map(|a| {
if let KeyType::Long(v) = a {
Some(v)
} else {
None
}
})
}};
($app:expr) => {
longs!($app, iter)
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! names {
(@args $app:expr) => {{
$app.get_arguments().iter().map(|a| &*a.get_name())
}};
(@sc $app:expr) => {{
$app.get_subcommands().iter().map(|s| &*s.get_name()).chain(
$app.get_subcommands()
.iter()
.filter(|s| !s.aliases.is_empty()) // REFACTOR
.flat_map(|s| s.aliases.iter().map(|&(n, _)| n)),
)
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! sc_names {
($app:expr) => {{
names!(@sc $app)
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! match_alias {
($a:expr, $to:expr, $what:expr) => {{
$what == $to || $a.get_all_aliases().any(|alias| alias == $to)
}};
}

View file

@ -23,11 +23,12 @@ use crate::{
use indexmap::IndexSet; use indexmap::IndexSet;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
#[cfg(not(feature = "wrap_help"))]
mod term_size {
pub(crate) fn dimensions() -> Option<(usize, usize)> { pub(crate) fn dimensions() -> Option<(usize, usize)> {
None #[cfg(not(feature = "wrap_help"))]
} return None;
#[cfg(feature = "wrap_help")]
terminal_size::terminal_size().map(|(w, h)| (w.0.into(), h.0.into()))
} }
fn str_width(s: &str) -> usize { fn str_width(s: &str) -> usize {
@ -80,7 +81,7 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> {
Some(0) => usize::MAX, Some(0) => usize::MAX,
Some(w) => w, Some(w) => w,
None => cmp::min( None => cmp::min(
term_size::dimensions().map_or(100, |(w, _)| w), dimensions().map_or(100, |(w, _)| w),
match parser.app.max_w { match parser.app.max_w {
None | Some(0) => usize::MAX, None | Some(0) => usize::MAX,
Some(mw) => mw, Some(mw) => mw,
@ -663,15 +664,18 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> {
pub(crate) fn write_all_args(&mut self) -> ClapResult<()> { pub(crate) fn write_all_args(&mut self) -> ClapResult<()> {
debug!("Help::write_all_args"); debug!("Help::write_all_args");
let flags = self.parser.has_flags(); let flags = self.parser.has_flags();
// Strange filter/count vs fold... https://github.com/rust-lang/rust/issues/33038 // FIXME: Strange filter/count vs fold... https://github.com/rust-lang/rust/issues/33038
let pos = positionals!(self.parser.app).fold(0, |acc, arg| { let pos = self.parser.app.get_positionals().fold(0, |acc, arg| {
if should_show_arg(self.use_long, arg) { if should_show_arg(self.use_long, arg) {
acc + 1 acc + 1
} else { } else {
acc acc
} }
}) > 0; }) > 0;
let opts = opts!(self.parser.app) let opts = self
.parser
.app
.get_opts_no_heading()
.filter(|arg| should_show_arg(self.use_long, arg)) .filter(|arg| should_show_arg(self.use_long, arg))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let subcmds = self.parser.has_visible_subcommands(); let subcmds = self.parser.has_visible_subcommands();
@ -687,7 +691,7 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> {
let mut first = if pos { let mut first = if pos {
self.warning("ARGS:\n")?; self.warning("ARGS:\n")?;
self.write_args_unsorted(&*positionals!(self.parser.app).collect::<Vec<_>>())?; self.write_args_unsorted(&self.parser.app.get_positionals().collect::<Vec<_>>())?;
false false
} else { } else {
true true
@ -716,8 +720,8 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> {
self.none("\n\n")?; self.none("\n\n")?;
} }
self.warning("FLAGS:\n")?; self.warning("FLAGS:\n")?;
let flags_v: Vec<_> = flags!(self.parser.app).collect(); let flags_v: Vec<_> = self.parser.app.get_flags_no_heading().collect();
self.write_args(&*flags_v)?; self.write_args(&flags_v)?;
first = false; first = false;
} }
if !opts.is_empty() { if !opts.is_empty() {
@ -1066,16 +1070,16 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> {
.iter() .iter()
.filter(|a| a.has_switch()) .filter(|a| a.has_switch())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
self.write_args(&*opts_flags)?; self.write_args(&opts_flags)?;
} }
b"flags" => { b"flags" => {
self.write_args(&*flags!(self.parser.app).collect::<Vec<_>>())?; self.write_args(&self.parser.app.get_flags_no_heading().collect::<Vec<_>>())?;
} }
b"options" => { b"options" => {
self.write_args(&*opts!(self.parser.app).collect::<Vec<_>>())?; self.write_args(&self.parser.app.get_opts_no_heading().collect::<Vec<_>>())?;
} }
b"positionals" => { b"positionals" => {
self.write_args(&*positionals!(self.parser.app).collect::<Vec<_>>())?; self.write_args(&self.parser.app.get_positionals().collect::<Vec<_>>())?;
} }
b"subcommands" => { b"subcommands" => {
self.write_subcommands(self.parser.app)?; self.write_subcommands(self.parser.app)?;

View file

@ -71,7 +71,10 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
usage.push_str(" [OPTIONS]"); usage.push_str(" [OPTIONS]");
} }
if !self.p.is_set(AS::UnifiedHelpMessage) if !self.p.is_set(AS::UnifiedHelpMessage)
&& opts!(self.p.app) && self
.p
.app
.get_opts_no_heading()
.any(|o| !o.is_set(ArgSettings::Required) && !o.is_set(ArgSettings::Hidden)) .any(|o| !o.is_set(ArgSettings::Required) && !o.is_set(ArgSettings::Hidden))
{ {
usage.push_str(" [OPTIONS]"); usage.push_str(" [OPTIONS]");
@ -79,11 +82,23 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
usage.push_str(&req_string[..]); usage.push_str(&req_string[..]);
let has_last = positionals!(self.p.app).any(|p| p.is_set(ArgSettings::Last)); let has_last = self
.p
.app
.get_positionals()
.any(|p| p.is_set(ArgSettings::Last));
// places a '--' in the usage string if there are args and options // places a '--' in the usage string if there are args and options
// supporting multiple values // supporting multiple values
if opts!(self.p.app).any(|o| o.is_set(ArgSettings::MultipleValues)) if self
&& positionals!(self.p.app).any(|p| !p.is_set(ArgSettings::Required)) .p
.app
.get_opts_no_heading()
.any(|o| o.is_set(ArgSettings::MultipleValues))
&& self
.p
.app
.get_positionals()
.any(|p| !p.is_set(ArgSettings::Required))
&& !(self.p.app.has_visible_subcommands() && !(self.p.app.has_visible_subcommands()
|| self.p.is_set(AS::AllowExternalSubcommands)) || self.p.is_set(AS::AllowExternalSubcommands))
&& !has_last && !has_last
@ -94,19 +109,28 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
(!p.is_set(ArgSettings::Required) || p.is_set(ArgSettings::Last)) (!p.is_set(ArgSettings::Required) || p.is_set(ArgSettings::Last))
&& !p.is_set(ArgSettings::Hidden) && !p.is_set(ArgSettings::Hidden)
}; };
if positionals!(self.p.app).any(not_req_or_hidden) { if self.p.app.get_positionals().any(not_req_or_hidden) {
if let Some(args_tag) = self.get_args_tag(incl_reqs) { if let Some(args_tag) = self.get_args_tag(incl_reqs) {
usage.push_str(&*args_tag); usage.push_str(&*args_tag);
} else { } else {
usage.push_str(" [ARGS]"); usage.push_str(" [ARGS]");
} }
if has_last && incl_reqs { if has_last && incl_reqs {
let pos = positionals!(self.p.app) let pos = self
.p
.app
.get_positionals()
.find(|p| p.is_set(ArgSettings::Last)) .find(|p| p.is_set(ArgSettings::Last))
.expect(INTERNAL_ERROR_MSG); .expect(INTERNAL_ERROR_MSG);
debug!("Usage::create_help_usage: '{}' has .last(true)", pos.name); debug!("Usage::create_help_usage: '{}' has .last(true)", pos.name);
let req = pos.is_set(ArgSettings::Required); let req = pos.is_set(ArgSettings::Required);
if req && positionals!(self.p.app).any(|p| !p.is_set(ArgSettings::Required)) { if req
&& self
.p
.app
.get_positionals()
.any(|p| !p.is_set(ArgSettings::Required))
{
usage.push_str(" -- <"); usage.push_str(" -- <");
} else if req { } else if req {
usage.push_str(" [--] <"); usage.push_str(" [--] <");
@ -181,13 +205,16 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
fn get_args_tag(&self, incl_reqs: bool) -> Option<String> { fn get_args_tag(&self, incl_reqs: bool) -> Option<String> {
debug!("Usage::get_args_tag; incl_reqs = {:?}", incl_reqs); debug!("Usage::get_args_tag; incl_reqs = {:?}", incl_reqs);
let mut count = 0; let mut count = 0;
'outer: for pos in positionals!(self.p.app) 'outer: for pos in self
.p
.app
.get_positionals()
.filter(|pos| !pos.is_set(ArgSettings::Required)) .filter(|pos| !pos.is_set(ArgSettings::Required))
.filter(|pos| !pos.is_set(ArgSettings::Hidden)) .filter(|pos| !pos.is_set(ArgSettings::Hidden))
.filter(|pos| !pos.is_set(ArgSettings::Last)) .filter(|pos| !pos.is_set(ArgSettings::Last))
{ {
debug!("Usage::get_args_tag:iter:{}", pos.name); debug!("Usage::get_args_tag:iter:{}", pos.name);
for grp_s in groups_for_arg!(self.p.app, &pos.id) { for grp_s in self.p.app.groups_for_arg(&pos.id) {
debug!("Usage::get_args_tag:iter:{:?}:iter:{:?}", pos.name, grp_s); debug!("Usage::get_args_tag:iter:{:?}:iter:{:?}", pos.name, grp_s);
// if it's part of a required group we don't want to count it // if it's part of a required group we don't want to count it
if self if self
@ -210,7 +237,10 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
debug!("Usage::get_args_tag:iter: More than one, returning [ARGS]"); debug!("Usage::get_args_tag:iter: More than one, returning [ARGS]");
return None; // [ARGS] return None; // [ARGS]
} else if count == 1 && incl_reqs { } else if count == 1 && incl_reqs {
let pos = positionals!(self.p.app) let pos = self
.p
.app
.get_positionals()
.find(|pos| { .find(|pos| {
!pos.is_set(ArgSettings::Required) !pos.is_set(ArgSettings::Required)
&& !pos.is_set(ArgSettings::Hidden) && !pos.is_set(ArgSettings::Hidden)
@ -232,7 +262,9 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
{ {
debug!("Usage::get_args_tag:iter: Don't collapse returning all"); debug!("Usage::get_args_tag:iter: Don't collapse returning all");
return Some( return Some(
positionals!(self.p.app) self.p
.app
.get_positionals()
.filter(|pos| !pos.is_set(ArgSettings::Required)) .filter(|pos| !pos.is_set(ArgSettings::Required))
.filter(|pos| !pos.is_set(ArgSettings::Hidden)) .filter(|pos| !pos.is_set(ArgSettings::Hidden))
.filter(|pos| !pos.is_set(ArgSettings::Last)) .filter(|pos| !pos.is_set(ArgSettings::Last))
@ -242,7 +274,10 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
); );
} else if !incl_reqs { } else if !incl_reqs {
debug!("Usage::get_args_tag:iter: incl_reqs=false, building secondary usage string"); debug!("Usage::get_args_tag:iter: incl_reqs=false, building secondary usage string");
let highest_req_pos = positionals!(self.p.app) let highest_req_pos = self
.p
.app
.get_positionals()
.filter_map(|pos| { .filter_map(|pos| {
if pos.is_set(ArgSettings::Required) && !pos.is_set(ArgSettings::Last) { if pos.is_set(ArgSettings::Required) && !pos.is_set(ArgSettings::Last) {
Some(pos.index) Some(pos.index)
@ -251,16 +286,12 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
} }
}) })
.max() .max()
.unwrap_or_else(|| Some(positionals!(self.p.app).count() as u64)); .unwrap_or_else(|| Some(self.p.app.get_positionals().count() as u64));
return Some( return Some(
positionals!(self.p.app) self.p
.filter_map(|pos| { .app
if pos.index <= highest_req_pos { .get_positionals()
Some(pos) .filter(|pos| pos.index <= highest_req_pos)
} else {
None
}
})
.filter(|pos| !pos.is_set(ArgSettings::Required)) .filter(|pos| !pos.is_set(ArgSettings::Required))
.filter(|pos| !pos.is_set(ArgSettings::Hidden)) .filter(|pos| !pos.is_set(ArgSettings::Hidden))
.filter(|pos| !pos.is_set(ArgSettings::Last)) .filter(|pos| !pos.is_set(ArgSettings::Last))
@ -275,7 +306,7 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
// Determines if we need the `[FLAGS]` tag in the usage string // Determines if we need the `[FLAGS]` tag in the usage string
fn needs_flags_tag(&self) -> bool { fn needs_flags_tag(&self) -> bool {
debug!("Usage::needs_flags_tag"); debug!("Usage::needs_flags_tag");
'outer: for f in flags!(self.p.app) { 'outer: for f in self.p.app.get_flags_no_heading() {
debug!("Usage::needs_flags_tag:iter: f={}", f.name); debug!("Usage::needs_flags_tag:iter: f={}", f.name);
if let Some(l) = f.long { if let Some(l) = f.long {
if l == "help" || l == "version" { if l == "help" || l == "version" {
@ -283,7 +314,7 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
continue; continue;
} }
} }
for grp_s in groups_for_arg!(self.p.app, &f.id) { for grp_s in self.p.app.groups_for_arg(&f.id) {
debug!("Usage::needs_flags_tag:iter:iter: grp_s={:?}", grp_s); debug!("Usage::needs_flags_tag:iter:iter: grp_s={:?}", grp_s);
if self if self
.p .p
@ -356,7 +387,7 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
unrolled_reqs unrolled_reqs
.iter() .iter()
.chain(incls.iter()) .chain(incls.iter())
.filter(|a| positionals!(self.p.app).any(|p| &&p.id == a)) .filter(|a| self.p.app.get_positionals().any(|p| &&p.id == a))
.filter(|&pos| !m.contains(pos)) .filter(|&pos| !m.contains(pos))
.filter_map(|pos| self.p.app.find(pos)) .filter_map(|pos| self.p.app.find(pos))
.filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last)) .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last))
@ -367,7 +398,7 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
unrolled_reqs unrolled_reqs
.iter() .iter()
.chain(incls.iter()) .chain(incls.iter())
.filter(|a| positionals!(self.p.app).any(|p| &&p.id == a)) .filter(|a| self.p.app.get_positionals().any(|p| &&p.id == a))
.filter_map(|pos| self.p.app.find(pos)) .filter_map(|pos| self.p.app.find(pos))
.filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last)) .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last))
.filter(|pos| !args_in_groups.contains(&pos.id)) .filter(|pos| !args_in_groups.contains(&pos.id))
@ -383,7 +414,7 @@ impl<'b, 'c, 'z> Usage<'b, 'c, 'z> {
for a in unrolled_reqs for a in unrolled_reqs
.iter() .iter()
.chain(incls.iter()) .chain(incls.iter())
.filter(|name| !positionals!(self.p.app).any(|p| &&p.id == name)) .filter(|name| !self.p.app.get_positionals().any(|p| &&p.id == name))
.filter(|name| !self.p.app.groups.iter().any(|g| &&g.id == name)) .filter(|name| !self.p.app.groups.iter().any(|g| &&g.id == name))
.filter(|name| !args_in_groups.contains(name)) .filter(|name| !args_in_groups.contains(name))
.filter(|name| !(matcher.is_some() && matcher.as_ref().unwrap().contains(name))) .filter(|name| !(matcher.is_some() && matcher.as_ref().unwrap().contains(name)))

View file

@ -42,27 +42,31 @@ where
T: AsRef<str>, T: AsRef<str>,
I: IntoIterator<Item = T>, I: IntoIterator<Item = T>,
{ {
use crate::mkeymap::KeyType;
match did_you_mean(arg, longs).pop() { match did_you_mean(arg, longs).pop() {
Some(ref candidate) => { Some(candidate) => Some((candidate, None)),
return Some((candidate.to_owned(), None));
}
None => { None => {
for subcommand in subcommands { for subcommand in subcommands {
subcommand._build(); subcommand._build();
if let Some(ref candidate) = did_you_mean(
arg,
longs!(subcommand).map(|x| x.to_string_lossy().into_owned()),
)
.pop()
{
return Some((candidate.to_owned(), Some(subcommand.get_name().to_owned())));
}
}
}
}
let longs = subcommand.args.keys.iter().map(|x| &x.key).filter_map(|a| {
if let KeyType::Long(v) = a {
Some(v.to_string_lossy().into_owned())
} else {
None None
} }
});
if let Some(candidate) = did_you_mean(arg, longs).pop() {
return Some((candidate, Some(subcommand.get_name().to_string())));
}
}
None
}
}
}
#[cfg(all(test, features = "suggestions"))] #[cfg(all(test, features = "suggestions"))]
mod test { mod test {

View file

@ -171,7 +171,7 @@ where
let only_highest = |a: &Arg| { let only_highest = |a: &Arg| {
a.is_set(ArgSettings::MultipleValues) && (a.index.unwrap_or(0) != highest_idx) a.is_set(ArgSettings::MultipleValues) && (a.index.unwrap_or(0) != highest_idx)
}; };
if positionals!(self.app).any(only_highest) { if self.app.get_positionals().any(only_highest) {
// First we make sure if there is a positional that allows multiple values // First we make sure if there is a positional that allows multiple values
// the one before it (second to last) has one of these: // the one before it (second to last) has one of these:
// * a value terminator // * a value terminator
@ -206,7 +206,9 @@ where
); );
// Next we check how many have both Multiple and not a specific number of values set // Next we check how many have both Multiple and not a specific number of values set
let count = positionals!(self.app) let count = self
.app
.get_positionals()
.filter(|p| p.settings.is_set(ArgSettings::MultipleValues) && p.num_vals.is_none()) .filter(|p| p.settings.is_set(ArgSettings::MultipleValues) && p.num_vals.is_none())
.count(); .count();
let ok = count <= 1 let ok = count <= 1
@ -227,7 +229,7 @@ where
let mut found = false; let mut found = false;
let mut foundx2 = false; let mut foundx2 = false;
for p in positionals!(self.app) { for p in self.app.get_positionals() {
if foundx2 && !p.is_set(ArgSettings::Required) { if foundx2 && !p.is_set(ArgSettings::Required) {
assert!( assert!(
p.is_set(ArgSettings::Required), p.is_set(ArgSettings::Required),
@ -283,13 +285,16 @@ where
} }
} }
assert!( assert!(
positionals!(self.app) self.app
.get_positionals()
.filter(|p| p.is_set(ArgSettings::Last)) .filter(|p| p.is_set(ArgSettings::Last))
.count() .count()
< 2, < 2,
"Only one positional argument may have last(true) set. Found two." "Only one positional argument may have last(true) set. Found two."
); );
if positionals!(self.app) if self
.app
.get_positionals()
.any(|p| p.is_set(ArgSettings::Last) && p.is_set(ArgSettings::Required)) .any(|p| p.is_set(ArgSettings::Last) && p.is_set(ArgSettings::Required))
&& self.has_subcommands() && self.has_subcommands()
&& !self.is_set(AS::SubcommandsNegateReqs) && !self.is_set(AS::SubcommandsNegateReqs)
@ -336,7 +341,7 @@ where
self._verify_positionals(); self._verify_positionals();
// Set the LowIndexMultiple flag if required // Set the LowIndexMultiple flag if required
if positionals!(self.app).any(|a| { if self.app.get_positionals().any(|a| {
a.is_set(ArgSettings::MultipleValues) a.is_set(ArgSettings::MultipleValues)
&& (a.index.unwrap_or(0) as usize && (a.index.unwrap_or(0) as usize
!= self != self
@ -346,7 +351,7 @@ where
.iter() .iter()
.filter(|x| x.key.is_position()) .filter(|x| x.key.is_position())
.count()) .count())
}) && positionals!(self.app).last().map_or(false, |p_name| { }) && self.app.get_positionals().last().map_or(false, |p_name| {
!self.app[&p_name.id].is_set(ArgSettings::Last) !self.app[&p_name.id].is_set(ArgSettings::Last)
}) { }) {
self.app.settings.set(AS::LowIndexMultiplePositional); self.app.settings.set(AS::LowIndexMultiplePositional);
@ -533,8 +538,10 @@ where
|| self.is_set(AS::AllowExternalSubcommands) || self.is_set(AS::AllowExternalSubcommands)
|| self.is_set(AS::InferSubcommands)) || self.is_set(AS::InferSubcommands))
{ {
let cands = let cands = suggestions::did_you_mean(
suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self.app)); &*arg_os.to_string_lossy(),
self.app.all_subcommand_names(),
);
if !cands.is_empty() { if !cands.is_empty() {
let cands: Vec<_> = let cands: Vec<_> =
cands.iter().map(|cand| format!("'{}'", cand)).collect(); cands.iter().map(|cand| format!("'{}'", cand)).collect();
@ -578,8 +585,10 @@ where
ParseResult::ValuesDone(id) => ParseResult::ValuesDone(id), ParseResult::ValuesDone(id) => ParseResult::ValuesDone(id),
_ => { _ => {
if let Some(p) = if let Some(p) = self
positionals!(self.app).find(|p| p.index == Some(pos_counter as u64)) .app
.get_positionals()
.find(|p| p.index == Some(pos_counter as u64))
{ {
ParseResult::Pos(p.id.clone()) ParseResult::Pos(p.id.clone())
} else { } else {
@ -593,7 +602,10 @@ where
if self.is_new_arg(&n, &needs_val_of) if self.is_new_arg(&n, &needs_val_of)
|| sc_match || sc_match
|| !suggestions::did_you_mean(&n.to_string_lossy(), sc_names!(self.app)) || !suggestions::did_you_mean(
&n.to_string_lossy(),
self.app.all_subcommand_names(),
)
.is_empty() .is_empty()
{ {
debug!("Parser::get_matches_with: Bumping the positional counter..."); debug!("Parser::get_matches_with: Bumping the positional counter...");
@ -653,7 +665,7 @@ where
self.add_val_to_arg(p, &arg_os, matcher, ValueType::CommandLine)?; self.add_val_to_arg(p, &arg_os, matcher, ValueType::CommandLine)?;
matcher.inc_occurrence_of(&p.id); matcher.inc_occurrence_of(&p.id);
for grp in groups_for_arg!(self.app, &p.id) { for grp in self.app.groups_for_arg(&p.id) {
matcher.inc_occurrence_of(&grp); matcher.inc_occurrence_of(&grp);
} }
@ -712,8 +724,10 @@ where
self.app.color(), self.app.color(),
)?); )?);
} else if !has_args || self.is_set(AS::InferSubcommands) && self.has_subcommands() { } else if !has_args || self.is_set(AS::InferSubcommands) && self.has_subcommands() {
let cands = let cands = suggestions::did_you_mean(
suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self.app)); &*arg_os.to_string_lossy(),
self.app.all_subcommand_names(),
);
if !cands.is_empty() { if !cands.is_empty() {
let cands: Vec<_> = cands.iter().map(|cand| format!("'{}'", cand)).collect(); let cands: Vec<_> = cands.iter().map(|cand| format!("'{}'", cand)).collect();
return Err(ClapError::invalid_subcommand( return Err(ClapError::invalid_subcommand(
@ -742,7 +756,9 @@ where
if !external_subcommand { if !external_subcommand {
if let Some(ref pos_sc_name) = subcmd_name { if let Some(ref pos_sc_name) = subcmd_name {
let sc_name = find_subcmd!(self.app, *pos_sc_name) let sc_name = self
.app
.find_subcommand(pos_sc_name)
.expect(INTERNAL_ERROR_MSG) .expect(INTERNAL_ERROR_MSG)
.name .name
.clone(); .clone();
@ -816,7 +832,7 @@ where
} }
debug!("No"); debug!("No");
for group in groups_for_arg!(self.app, &arg.id) { for group in self.app.groups_for_arg(&arg.id) {
debug!("Parser::maybe_inc_pos_counter: group={:?}", group); debug!("Parser::maybe_inc_pos_counter: group={:?}", group);
let group = self.app.groups.iter().find(|g| g.id == group); let group = self.app.groups.iter().find(|g| g.id == group);
@ -842,7 +858,9 @@ where
} }
if self.is_set(AS::InferSubcommands) { if self.is_set(AS::InferSubcommands) {
let v = sc_names!(self.app) let v = self
.app
.all_subcommand_names()
.filter(|s| arg_os.is_prefix_of(s)) .filter(|s| arg_os.is_prefix_of(s))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -855,7 +873,7 @@ where
return Some(sc); return Some(sc);
} }
} }
} else if let Some(sc) = find_subcmd!(self.app, arg_os) { } else if let Some(sc) = self.app.find_subcommand(arg_os) {
return Some(&sc.name); return Some(&sc.name);
} }
@ -914,18 +932,18 @@ where
help_help = true; help_help = true;
break; // Maybe? break; // Maybe?
} }
if let Some(id) = find_subcmd!(sc, cmd).map(|x| x.id.clone()) { if let Some(id) = sc.find_subcommand(cmd).map(|x| x.id.clone()) {
sc._propagate(Propagation::To(id)); sc._propagate(Propagation::To(id));
} }
if let Some(mut c) = find_subcmd_cloned!(sc, cmd) { if let Some(mut c) = sc.find_subcommand(cmd).cloned() {
c._build(); c._build();
sc = c; sc = c;
if i == cmds.len() - 1 { if i == cmds.len() - 1 {
break; break;
} }
} else if let Some(mut c) = find_subcmd_cloned!(sc, &cmd.to_string_lossy()) { } else if let Some(mut c) = sc.find_subcommand(&cmd.to_string_lossy()).cloned() {
c._build(); c._build();
sc = c; sc = c;
@ -1045,7 +1063,7 @@ where
mid_string.push_str(" "); mid_string.push_str(" ");
if let Some(x) = find_subcmd!(self.app, sc_name) { if let Some(x) = self.app.find_subcommand(sc_name) {
let id = x.id.clone(); let id = x.id.clone();
self.app._propagate(Propagation::To(id)); self.app._propagate(Propagation::To(id));
} }
@ -1387,7 +1405,7 @@ where
matcher.inc_occurrence_of(&opt.id); matcher.inc_occurrence_of(&opt.id);
// Increment or create the group "args" // Increment or create the group "args"
for grp in groups_for_arg!(self.app, &opt.id) { for grp in self.app.groups_for_arg(&opt.id) {
matcher.inc_occurrence_of(&grp); matcher.inc_occurrence_of(&grp);
} }
@ -1464,7 +1482,7 @@ where
matcher.add_index_to(&arg.id, self.cur_idx.get(), ty); matcher.add_index_to(&arg.id, self.cur_idx.get(), ty);
// Increment or create the group "args" // Increment or create the group "args"
for grp in groups_for_arg!(self.app, &arg.id) { for grp in self.app.groups_for_arg(&arg.id) {
matcher.add_val_to(&grp, v.to_os_string(), ty); matcher.add_val_to(&grp, v.to_os_string(), ty);
} }
@ -1480,7 +1498,7 @@ where
matcher.inc_occurrence_of(&flag.id); matcher.inc_occurrence_of(&flag.id);
matcher.add_index_to(&flag.id, self.cur_idx.get(), ValueType::CommandLine); matcher.add_index_to(&flag.id, self.cur_idx.get(), ValueType::CommandLine);
// Increment or create the group "args" // Increment or create the group "args"
for grp in groups_for_arg!(self.app, &flag.id) { for grp in self.app.groups_for_arg(&flag.id) {
matcher.inc_occurrence_of(&grp); matcher.inc_occurrence_of(&grp);
} }
@ -1560,12 +1578,12 @@ where
pub(crate) fn add_defaults(&mut self, matcher: &mut ArgMatcher) -> ClapResult<()> { pub(crate) fn add_defaults(&mut self, matcher: &mut ArgMatcher) -> ClapResult<()> {
debug!("Parser::add_defaults"); debug!("Parser::add_defaults");
for o in opts!(self.app) { for o in self.app.get_opts_no_heading() {
debug!("Parser::add_defaults:iter:{}:", o.name); debug!("Parser::add_defaults:iter:{}:", o.name);
self.add_value(o, matcher, ValueType::DefaultValue)?; self.add_value(o, matcher, ValueType::DefaultValue)?;
} }
for p in positionals!(self.app) { for p in self.app.get_positionals() {
debug!("Parser::add_defaults:iter:{}:", p.name); debug!("Parser::add_defaults:iter:{}:", p.name);
self.add_value(p, matcher, ValueType::DefaultValue)?; self.add_value(p, matcher, ValueType::DefaultValue)?;
} }
@ -1693,7 +1711,7 @@ where
.args .args
.get(&KeyType::Long(OsString::from(name.0.clone()))) .get(&KeyType::Long(OsString::from(name.0.clone())))
{ {
for g in groups_for_arg!(self.app, &opt.id) { for g in self.app.groups_for_arg(&opt.id) {
matcher.inc_occurrence_of(&g); matcher.inc_occurrence_of(&g);
} }
matcher.insert(&opt.id); matcher.insert(&opt.id);

View file

@ -309,7 +309,7 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> {
// args in that group to the conflicts, as well as any args those args conflict // args in that group to the conflicts, as well as any args those args conflict
// with // with
for grp in groups_for_arg!(self.p.app, name) { for grp in self.p.app.groups_for_arg(&name) {
if let Some(g) = self if let Some(g) = self
.p .p
.app .app

View file

@ -64,7 +64,7 @@ pub fn compare_output2(l: App, args: &str, right1: &str, right2: &str, stderr: b
compare(&*left, right1) || compare(&*left, right2) compare(&*left, right1) || compare(&*left, right2)
} }
// Legacy tests from the pyhton script days // Legacy tests from the python script days
pub fn complex_app() -> App<'static> { pub fn complex_app() -> App<'static> {
let opt3_vals = ["fast", "slow"]; let opt3_vals = ["fast", "slow"];