From 3ec065a12a4360400c17a99569515ee3d0e81be8 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Fri, 3 Feb 2017 01:03:51 +0100 Subject: [PATCH 0001/3337] Init project --- .gitignore | 3 +++ .travis.yml | 9 +++++++++ COPYING | 14 ++++++++++++++ Cargo.toml | 17 +++++++++++++++++ README.md | 9 +++++++++ examples/basic.rs | 24 ++++++++++++++++++++++++ src/lib.rs | 16 ++++++++++++++++ structopt-derive/Cargo.toml | 17 +++++++++++++++++ structopt-derive/src/lib.rs | 36 ++++++++++++++++++++++++++++++++++++ 9 files changed, 145 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 COPYING create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 examples/basic.rs create mode 100644 src/lib.rs create mode 100644 structopt-derive/Cargo.toml create mode 100644 structopt-derive/src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..f0ff2599d09 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target +Cargo.lock +*~ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000000..45793de3b62 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: rust +cache: cargo +rust: + - stable + - beta + - nightly +matrix: + allow_failures: + - rust: nightly diff --git a/COPYING b/COPYING new file mode 100644 index 00000000000..ee7d6a54e91 --- /dev/null +++ b/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000000..7c966b29905 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "structopt" +version = "0.0.0" +authors = ["Guillaume Pinot "] +description = "Parse command line argument by defining a struct." +documentation = "https://docs.rs/structopt" +repository = "https://github.com/TeXitoi/structopt" +keywords = ["clap", "cli", "derive", "docopt"] +categories = ["command-line-interface"] +license = "WTFPL" +readme = "README.md" + +[dependencies] +clap = "2.20.1" +structopt-derive = { path = "structopt-derive", version = "0.0.0" } + +[workspace] diff --git a/README.md b/README.md new file mode 100644 index 00000000000..d93da90bb00 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# StructOpt [![Build status](https://travis-ci.org/TeXitoi/structopt.svg?branch=master)](https://travis-ci.org/TeXitoi/structopt) [![](https://img.shields.io/crates/v/structopt.svg)](https://crates.io/crates/structopt) [![](https://docs.rs/structopt/badge.svg)](https://docs.rs/structopt) + +Parse command line argument by defining a struct. + +Work in progress. + +## Documentation + +Find it on [Docs.rs](https://docs.rs/structopt) diff --git a/examples/basic.rs b/examples/basic.rs new file mode 100644 index 00000000000..9016c0f5aee --- /dev/null +++ b/examples/basic.rs @@ -0,0 +1,24 @@ +// Copyright (c) 2017 Guillaume Pinot +// +// This work is free. You can redistribute it and/or modify it under +// the terms of the Do What The Fuck You Want To Public License, +// Version 2, as published by Sam Hocevar. See the COPYING file for +// more details. + +extern crate structopt; +#[macro_use] +extern crate structopt_derive; +#[macro_use] +extern crate clap; + +use structopt::StructOpt; + +#[derive(StructOpt, Default)] +struct Opt { + i: bool, +} + +fn main() { + let opt = Opt::from_args(); + println!("i = {}", opt.i); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000000..87e6f1b25eb --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,16 @@ +// Copyright (c) 2017 Guillaume Pinot +// +// This work is free. You can redistribute it and/or modify it under +// the terms of the Do What The Fuck You Want To Public License, +// Version 2, as published by Sam Hocevar. See the COPYING file for +// more details. + +extern crate clap; + +pub trait StructOpt { + fn clap<'a, 'b>() -> clap::App<'a, 'b>; + fn from_clap(clap::App) -> Self; + fn from_args() -> Self where Self: Sized { + Self::from_clap(Self::clap()) + } +} diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml new file mode 100644 index 00000000000..6c62670e046 --- /dev/null +++ b/structopt-derive/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "structopt-derive" +version = "0.0.0" +authors = ["Guillaume Pinot "] +description = "Parse command line argument by defining a struct, derive crate." +documentation = "https://docs.rs/structopt-derive" +repository = "https://github.com/TeXitoi/structopt" +keywords = ["clap", "cli", "derive", "docopt"] +categories = ["command-line-interface"] +license = "WTFPL" + +[dependencies] +syn = "0.11.4" +quote = "0.3.12" + +[lib] +proc-macro = true diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs new file mode 100644 index 00000000000..09981e35b7e --- /dev/null +++ b/structopt-derive/src/lib.rs @@ -0,0 +1,36 @@ +// Copyright (c) 2017 Guillaume Pinot +// +// This work is free. You can redistribute it and/or modify it under +// the terms of the Do What The Fuck You Want To Public License, +// Version 2, as published by Sam Hocevar. See the COPYING file for +// more details. + +extern crate proc_macro; +extern crate syn; +#[macro_use] +extern crate quote; + +use proc_macro::TokenStream; + +#[proc_macro_derive(StructOpt)] +pub fn structopt(input: TokenStream) -> TokenStream { + let s = input.to_string(); + let ast = syn::parse_macro_input(&s).unwrap(); + let gen = impl_structopt(&ast); + gen.parse().unwrap() +} + +fn impl_structopt(ast: &syn::MacroInput) -> quote::Tokens { + let name = &ast.ident; + quote! { + impl StructOpt for #name { + fn clap<'a, 'b>() -> clap::App<'a, 'b> { + app_from_crate!() + } + fn from_clap(app: clap::App) -> Self { + let _ = app.get_matches(); + Self::default() + } + } + } +} From 36b39727b1c636c65c2205956ecab69fd04d0ae2 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Sat, 4 Feb 2017 01:43:44 +0100 Subject: [PATCH 0002/3337] support different options --- examples/basic.rs | 10 ++-- structopt-derive/src/lib.rs | 99 +++++++++++++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 9016c0f5aee..3b3493fef2a 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -13,12 +13,16 @@ extern crate clap; use structopt::StructOpt; -#[derive(StructOpt, Default)] +#[derive(StructOpt, Debug)] struct Opt { - i: bool, + debug: bool, + verbose: u64, + speed: Option, + output: String, + level: Vec, } fn main() { let opt = Opt::from_args(); - println!("i = {}", opt.i); + println!("{:?}", opt); } diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 09981e35b7e..ebbf832600f 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -15,21 +15,114 @@ use proc_macro::TokenStream; #[proc_macro_derive(StructOpt)] pub fn structopt(input: TokenStream) -> TokenStream { let s = input.to_string(); - let ast = syn::parse_macro_input(&s).unwrap(); + let ast = syn::parse_derive_input(&s).unwrap(); let gen = impl_structopt(&ast); gen.parse().unwrap() } +enum Ty { + Bool, + U64, + Vec, + Option, + Other, +} + +fn ty(t: &syn::Ty) -> Ty { + if let syn::Ty::Path(None, syn::Path { segments: ref segs, .. }) = *t { + match segs.last().unwrap().ident.as_ref() { + "bool" => Ty::Bool, + "u64" => Ty::U64, + "Option" => Ty::Option, + "Vec" => Ty::Vec, + _ => Ty::Other, + } + } else { + Ty::Other + } +} + fn impl_structopt(ast: &syn::MacroInput) -> quote::Tokens { + use syn::{Body, VariantData}; let name = &ast.ident; + let s = if let Body::Struct(VariantData::Struct(ref s)) = ast.body { + s + } else { + panic!("Only struct is supported") + }; + let args = s.iter().map(|f| { + let ident = f.ident.as_ref().unwrap(); + let modifier = match ty(&f.ty) { + Ty::Bool => quote! { + .max_values(0) + .takes_value(false) + .multiple(false) + }, + Ty::U64 => quote! { + .max_values(0) + .takes_value(false) + .multiple(true) + }, + Ty::Option => quote! { + .takes_value(true) + .multiple(false) + }, + Ty::Vec => quote! { + .takes_value(true) + .multiple(true) + }, + Ty::Other => quote!{ + .takes_value(true) + .multiple(false) + .required(true) + }, + }; + quote! { + .arg(Arg::with_name(stringify!(#ident)) + .long(stringify!(#ident)) + #modifier) + } + }); + let fields = s.iter().map(|f| { + let ident = f.ident.as_ref().unwrap(); + let convert = match ty(&f.ty) { + Ty::Bool => quote!(is_present(stringify!(#ident))), + Ty::U64 => quote!(occurrences_of(stringify!(#ident))), + Ty::Option => quote! { + value_of(stringify!(#ident)) + .as_ref() + .map(|s| s.parse().unwrap()) + }, + Ty::Vec => quote! { + values_of(stringify!(#ident)) + .map(|v| v.map(|s| s.parse().unwrap()).collect()) + .unwrap_or_else(Vec::new) + }, + Ty::Other => quote! { + value_of(stringify!(#ident)) + .as_ref() + .unwrap() + .parse() + .unwrap() + }, + }; + quote! { + #ident: matches.#convert, + } + }); + quote! { impl StructOpt for #name { fn clap<'a, 'b>() -> clap::App<'a, 'b> { + use ::clap::Arg; app_from_crate!() + #( #args )* } fn from_clap(app: clap::App) -> Self { - let _ = app.get_matches(); - Self::default() + let matches = app.get_matches(); + #name { + #( #fields )* + } } } } From 1196571909afa1894558838eef644589a950444a Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Sun, 5 Feb 2017 02:06:39 +0100 Subject: [PATCH 0003/3337] use attributes --- examples/basic.rs | 7 +++++++ structopt-derive/src/lib.rs | 26 +++++++++++++++++++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 3b3493fef2a..049488e25e7 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -15,10 +15,17 @@ use structopt::StructOpt; #[derive(StructOpt, Debug)] struct Opt { + #[structopt(short = "d", long = "debug", help = "Activate debug mode")] debug: bool, + #[structopt(short = "v", long = "verbose", help = "Verbose mode")] verbose: u64, + #[structopt(short = "s", long = "speed", help = "Set speed")] speed: Option, + #[structopt(short = "o", long = "output", help = "Output file")] output: String, + #[structopt(short = "l", + long = "level", + help = "admin_level to consider")] level: Vec, } diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index ebbf832600f..943d912b4eb 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -12,7 +12,7 @@ extern crate quote; use proc_macro::TokenStream; -#[proc_macro_derive(StructOpt)] +#[proc_macro_derive(StructOpt, attributes(structopt))] pub fn structopt(input: TokenStream) -> TokenStream { let s = input.to_string(); let ast = syn::parse_derive_input(&s).unwrap(); @@ -42,8 +42,8 @@ fn ty(t: &syn::Ty) -> Ty { } } -fn impl_structopt(ast: &syn::MacroInput) -> quote::Tokens { - use syn::{Body, VariantData}; +fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { + use syn::{Body, VariantData, MetaItem, NestedMetaItem}; let name = &ast.ident; let s = if let Body::Struct(VariantData::Struct(ref s)) = ast.body { s @@ -68,6 +68,7 @@ fn impl_structopt(ast: &syn::MacroInput) -> quote::Tokens { .multiple(false) }, Ty::Vec => quote! { + .use_delimiter(true) .takes_value(true) .multiple(true) }, @@ -77,10 +78,25 @@ fn impl_structopt(ast: &syn::MacroInput) -> quote::Tokens { .required(true) }, }; + let from_attr = f.attrs.iter() + .filter_map(|attr| { + if let MetaItem::List(ref i, ref v) = attr.value { + if i.as_ref() == "structopt" { + return Some(v) + } + } + None + }).flat_map(|v| v.iter().filter_map(|mi| { + if let NestedMetaItem::MetaItem(MetaItem::NameValue(ref i, ref l)) = *mi { + Some(quote!(.#i(#l))) + } else { + None + } + })); quote! { .arg(Arg::with_name(stringify!(#ident)) - .long(stringify!(#ident)) - #modifier) + #modifier + #(#from_attr)*) } }); let fields = s.iter().map(|f| { From edc9d69f030524072645938a566e6c0ca07e47a6 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Mon, 6 Feb 2017 01:00:27 +0100 Subject: [PATCH 0004/3337] support attrs in struct --- examples/basic.rs | 2 +- structopt-derive/src/lib.rs | 61 ++++++++++++++++++++++--------------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 049488e25e7..dd45c6821e1 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -8,12 +8,12 @@ extern crate structopt; #[macro_use] extern crate structopt_derive; -#[macro_use] extern crate clap; use structopt::StructOpt; #[derive(StructOpt, Debug)] +#[structopt(name = "basic", about = "A basic example")] struct Opt { #[structopt(short = "d", long = "debug", help = "Activate debug mode")] debug: bool, diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 943d912b4eb..5c55bc4ac10 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -11,6 +11,7 @@ extern crate syn; extern crate quote; use proc_macro::TokenStream; +use syn::{Attribute, Body, VariantData, MetaItem, NestedMetaItem, Ident, Lit, StrStyle}; #[proc_macro_derive(StructOpt, attributes(structopt))] pub fn structopt(input: TokenStream) -> TokenStream { @@ -42,14 +43,39 @@ fn ty(t: &syn::Ty) -> Ty { } } +fn extract_attrs<'a>(attrs: &'a [Attribute]) -> Box + 'a> { + let iter = attrs.iter() + .filter_map(|attr| match attr.value { + MetaItem::List(ref i, ref v) if i.as_ref() == "structopt" => Some(v), + _ => None, + }).flat_map(|v| v.iter().filter_map(|mi| match *mi { + NestedMetaItem::MetaItem(MetaItem::NameValue(ref i, ref l)) => Some((i, l)), + _ => None, + })); + Box::new(iter) +} + +fn from_attr_or(attrs: &[(&Ident, &Lit)], key: &str, default: &str) -> Lit { + attrs.iter() + .find(|&&(i, _)| i.as_ref() == key) + .map(|&(_, l)| l.clone()) + .unwrap_or_else(|| Lit::Str(default.into(), StrStyle::Cooked)) + } + fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { - use syn::{Body, VariantData, MetaItem, NestedMetaItem}; - let name = &ast.ident; + let struct_name = &ast.ident; let s = if let Body::Struct(VariantData::Struct(ref s)) = ast.body { s } else { panic!("Only struct is supported") }; + + let struct_attrs: Vec<_> = extract_attrs(&ast.attrs).collect(); + let name = from_attr_or(&struct_attrs, "name", env!("CARGO_PKG_NAME")); + let version = from_attr_or(&struct_attrs, "version", env!("CARGO_PKG_VERSION")); + let author = from_attr_or(&struct_attrs, "author", env!("CARGO_PKG_AUTHORS")); + let about = from_attr_or(&struct_attrs, "about", env!("CARGO_PKG_DESCRIPTION")); + let args = s.iter().map(|f| { let ident = f.ident.as_ref().unwrap(); let modifier = match ty(&f.ty) { @@ -78,21 +104,7 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { .required(true) }, }; - let from_attr = f.attrs.iter() - .filter_map(|attr| { - if let MetaItem::List(ref i, ref v) = attr.value { - if i.as_ref() == "structopt" { - return Some(v) - } - } - None - }).flat_map(|v| v.iter().filter_map(|mi| { - if let NestedMetaItem::MetaItem(MetaItem::NameValue(ref i, ref l)) = *mi { - Some(quote!(.#i(#l))) - } else { - None - } - })); + let from_attr = extract_attrs(&f.attrs).map(|(i, l)| quote!(.#i(#l))); quote! { .arg(Arg::with_name(stringify!(#ident)) #modifier @@ -122,21 +134,22 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { .unwrap() }, }; - quote! { - #ident: matches.#convert, - } + quote!( #ident: matches.#convert, ) }); quote! { - impl StructOpt for #name { + impl StructOpt for #struct_name { fn clap<'a, 'b>() -> clap::App<'a, 'b> { - use ::clap::Arg; - app_from_crate!() + use clap::{App, Arg}; + App::new(#name) + .version(#version) + .author(#author) + .about(#about) #( #args )* } fn from_clap(app: clap::App) -> Self { let matches = app.get_matches(); - #name { + #struct_name { #( #fields )* } } From afd812cf5a1b94932401b588eb7fc19132f6af9a Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Mon, 6 Feb 2017 23:55:46 +0100 Subject: [PATCH 0005/3337] validator + default value --- examples/basic.rs | 11 +++++---- structopt-derive/src/lib.rs | 46 ++++++++++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index dd45c6821e1..ec059a5438c 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -19,13 +19,14 @@ struct Opt { debug: bool, #[structopt(short = "v", long = "verbose", help = "Verbose mode")] verbose: u64, - #[structopt(short = "s", long = "speed", help = "Set speed")] - speed: Option, + #[structopt(short = "s", long = "speed", help = "Set speed", default_value = "42")] + speed: f64, #[structopt(short = "o", long = "output", help = "Output file")] output: String, - #[structopt(short = "l", - long = "level", - help = "admin_level to consider")] + #[structopt(short = "c", long = "car", help = "Number of car")] + car: Option, + #[structopt(short = "l", long = "level")] + #[structopt(help = "admin_level to consider")] level: Vec, } diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 5c55bc4ac10..4737cea20af 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -11,7 +11,7 @@ extern crate syn; extern crate quote; use proc_macro::TokenStream; -use syn::{Attribute, Body, VariantData, MetaItem, NestedMetaItem, Ident, Lit, StrStyle}; +use syn::*; #[proc_macro_derive(StructOpt, attributes(structopt))] pub fn structopt(input: TokenStream) -> TokenStream { @@ -43,6 +43,21 @@ fn ty(t: &syn::Ty) -> Ty { } } +fn sub_type(t: &syn::Ty) -> Option<&syn::Ty> { + let segs = match *t { + syn::Ty::Path(None, syn::Path { ref segments, .. }) => segments, + _ => return None, + }; + match *segs.last().unwrap() { + PathSegment { + parameters: PathParameters::AngleBracketed( + AngleBracketedParameterData { ref types, .. }), + .. + } if !types.is_empty() => Some(&types[0]), + _ => None, + } +} + fn extract_attrs<'a>(attrs: &'a [Attribute]) -> Box + 'a> { let iter = attrs.iter() .filter_map(|attr| match attr.value { @@ -78,7 +93,17 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { let args = s.iter().map(|f| { let ident = f.ident.as_ref().unwrap(); - let modifier = match ty(&f.ty) { + let cur_type = ty(&f.ty); + let convert_type = match cur_type { + Ty::Vec | Ty::Option => sub_type(&f.ty).unwrap_or(&f.ty), + _ => &f.ty, + }; + let validator = quote! { + validator(|s| s.parse::<#convert_type>() + .map(|_| ()) + .map_err(|e| e.description().into())) + }; + let modifier = match cur_type { Ty::Bool => quote! { .max_values(0) .takes_value(false) @@ -92,16 +117,24 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { Ty::Option => quote! { .takes_value(true) .multiple(false) + .#validator }, Ty::Vec => quote! { .use_delimiter(true) .takes_value(true) .multiple(true) + .#validator }, - Ty::Other => quote!{ - .takes_value(true) - .multiple(false) - .required(true) + Ty::Other => { + let required = extract_attrs(&f.attrs) + .find(|&(i, _)| i.as_ref() == "default_value") + .is_none(); + quote! { + .takes_value(true) + .multiple(false) + .required(#required) + .#validator + } }, }; let from_attr = extract_attrs(&f.attrs).map(|(i, l)| quote!(.#i(#l))); @@ -141,6 +174,7 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { impl StructOpt for #struct_name { fn clap<'a, 'b>() -> clap::App<'a, 'b> { use clap::{App, Arg}; + use std::error::Error; App::new(#name) .version(#version) .author(#author) From 4e4bbabfa7f7d1877f6dbb16c7829ef972e2373b Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Tue, 7 Feb 2017 00:19:18 +0100 Subject: [PATCH 0006/3337] manage not require extern crate clap from user --- examples/basic.rs | 1 - structopt-derive/src/lib.rs | 47 +++++++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index ec059a5438c..aaf68d2e7c6 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -8,7 +8,6 @@ extern crate structopt; #[macro_use] extern crate structopt_derive; -extern crate clap; use structopt::StructOpt; diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 4737cea20af..bb9b483d85d 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -139,11 +139,22 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { }; let from_attr = extract_attrs(&f.attrs).map(|(i, l)| quote!(.#i(#l))); quote! { - .arg(Arg::with_name(stringify!(#ident)) + .arg(_clap::Arg::with_name(stringify!(#ident)) #modifier #(#from_attr)*) } }); + let clap = quote! { + fn clap<'a, 'b>() -> _clap::App<'a, 'b> { + use std::error::Error; + _clap::App::new(#name) + .version(#version) + .author(#author) + .about(#about) + #( #args )* + } + }; + let fields = s.iter().map(|f| { let ident = f.ident.as_ref().unwrap(); let convert = match ty(&f.ty) { @@ -169,24 +180,24 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { }; quote!( #ident: matches.#convert, ) }); - - quote! { - impl StructOpt for #struct_name { - fn clap<'a, 'b>() -> clap::App<'a, 'b> { - use clap::{App, Arg}; - use std::error::Error; - App::new(#name) - .version(#version) - .author(#author) - .about(#about) - #( #args )* - } - fn from_clap(app: clap::App) -> Self { - let matches = app.get_matches(); - #struct_name { - #( #fields )* - } + let from_clap = quote! { + fn from_clap(app: _clap::App) -> Self { + let matches = app.get_matches(); + #struct_name { + #( #fields )* } } + }; + let dummy_const = Ident::new(format!("_IMPL_STRUCTOPT_FOR_{}", struct_name)); + quote! { + #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] + const #dummy_const: () = { + extern crate clap as _clap; + extern crate structopt as _structopt; + impl _structopt::StructOpt for #struct_name { + #clap + #from_clap + } + }; } } From fdc18d07bb1849bf7ca02d748ff8808704edc972 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Tue, 7 Feb 2017 01:41:16 +0100 Subject: [PATCH 0007/3337] add flag tests --- Cargo.toml | 2 + src/lib.rs | 4 +- structopt-derive/src/lib.rs | 5 +-- tests/flags.rs | 78 +++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 tests/flags.rs diff --git a/Cargo.toml b/Cargo.toml index 7c966b29905..55a3943e48b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ readme = "README.md" [dependencies] clap = "2.20.1" + +[dev-dependencies] structopt-derive = { path = "structopt-derive", version = "0.0.0" } [workspace] diff --git a/src/lib.rs b/src/lib.rs index 87e6f1b25eb..fba497a3fb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,8 @@ extern crate clap; pub trait StructOpt { fn clap<'a, 'b>() -> clap::App<'a, 'b>; - fn from_clap(clap::App) -> Self; + fn from_clap(clap::ArgMatches) -> Self; fn from_args() -> Self where Self: Sized { - Self::from_clap(Self::clap()) + Self::from_clap(Self::clap().get_matches()) } } diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index bb9b483d85d..a61298fbcdc 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -181,8 +181,7 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { quote!( #ident: matches.#convert, ) }); let from_clap = quote! { - fn from_clap(app: _clap::App) -> Self { - let matches = app.get_matches(); + fn from_clap(matches: _clap::ArgMatches) -> Self { #struct_name { #( #fields )* } @@ -190,7 +189,7 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { }; let dummy_const = Ident::new(format!("_IMPL_STRUCTOPT_FOR_{}", struct_name)); quote! { - #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] + #[allow(non_upper_case_globals, unused_attributes, unused_imports)] const #dummy_const: () = { extern crate clap as _clap; extern crate structopt as _structopt; diff --git a/tests/flags.rs b/tests/flags.rs new file mode 100644 index 00000000000..40026e3deba --- /dev/null +++ b/tests/flags.rs @@ -0,0 +1,78 @@ +// Copyright (c) 2017 Guillaume Pinot +// +// This work is free. You can redistribute it and/or modify it under +// the terms of the Do What The Fuck You Want To Public License, +// Version 2, as published by Sam Hocevar. See the COPYING file for +// more details. + +extern crate structopt; +#[macro_use] +extern crate structopt_derive; + +use structopt::StructOpt; + +#[test] +fn unique_flag() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short = "a", long = "alice")] + alice: bool, + } + + assert_eq!(Opt { alice: false }, + Opt::from_clap(Opt::clap().get_matches_from(&["test"]))); + assert_eq!(Opt { alice: true }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-a"]))); + assert_eq!(Opt { alice: true }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "--alice"]))); + assert!(Opt::clap().get_matches_from_safe(&["test", "-i"]).is_err()); + assert!(Opt::clap().get_matches_from_safe(&["test", "-a", "foo"]).is_err()); + assert!(Opt::clap().get_matches_from_safe(&["test", "-a", "-a"]).is_err()); + assert!(Opt::clap().get_matches_from_safe(&["test", "-a", "--alice"]).is_err()); +} + +#[test] +fn multiple_flag() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short = "a", long = "alice")] + alice: u64, + } + + assert_eq!(Opt { alice: 0 }, + Opt::from_clap(Opt::clap().get_matches_from(&["test"]))); + assert_eq!(Opt { alice: 1 }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-a"]))); + assert_eq!(Opt { alice: 2 }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-a", "-a"]))); + assert_eq!(Opt { alice: 2 }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-a", "--alice"]))); + assert_eq!(Opt { alice: 3 }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-aaa"]))); + assert!(Opt::clap().get_matches_from_safe(&["test", "-i"]).is_err()); + assert!(Opt::clap().get_matches_from_safe(&["test", "-a", "foo"]).is_err()); +} + +#[test] +fn combined_flags() { + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(short = "a", long = "alice")] + alice: bool, + #[structopt(short = "b", long = "bob")] + bob: u64, + } + + assert_eq!(Opt { alice: false, bob: 0 }, + Opt::from_clap(Opt::clap().get_matches_from(&["test"]))); + assert_eq!(Opt { alice: true, bob: 0 }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-a"]))); + assert_eq!(Opt { alice: true, bob: 0 }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-a"]))); + assert_eq!(Opt { alice: false, bob: 1 }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-b"]))); + assert_eq!(Opt { alice: true, bob: 1 }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "--alice", "--bob"]))); + assert_eq!(Opt { alice: true, bob: 4 }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-bb", "-a", "-bb"]))); +} From b0cf3af9c990c8b2c5468fb04a7b2748e900a760 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Tue, 7 Feb 2017 01:52:26 +0100 Subject: [PATCH 0008/3337] example with args --- examples/basic.rs | 2 ++ structopt-derive/src/lib.rs | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index aaf68d2e7c6..739e1f2e50d 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -27,6 +27,8 @@ struct Opt { #[structopt(short = "l", long = "level")] #[structopt(help = "admin_level to consider")] level: Vec, + #[structopt(help = "Files to process")] + files: Vec, } fn main() { diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index a61298fbcdc..d3220b975ba 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -105,12 +105,10 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { }; let modifier = match cur_type { Ty::Bool => quote! { - .max_values(0) .takes_value(false) .multiple(false) }, Ty::U64 => quote! { - .max_values(0) .takes_value(false) .multiple(true) }, @@ -120,7 +118,6 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { .#validator }, Ty::Vec => quote! { - .use_delimiter(true) .takes_value(true) .multiple(true) .#validator @@ -187,6 +184,7 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { } } }; + let dummy_const = Ident::new(format!("_IMPL_STRUCTOPT_FOR_{}", struct_name)); quote! { #[allow(non_upper_case_globals, unused_attributes, unused_imports)] From 8d095c609ded196f43e24f03341fe5bae6485f48 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Tue, 7 Feb 2017 23:03:13 +0100 Subject: [PATCH 0009/3337] refactor --- structopt-derive/src/lib.rs | 122 +++++++++++++++--------------------- 1 file changed, 52 insertions(+), 70 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index d3220b975ba..9871cedba56 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -75,16 +75,44 @@ fn from_attr_or(attrs: &[(&Ident, &Lit)], key: &str, default: &str) -> Lit { .find(|&&(i, _)| i.as_ref() == key) .map(|&(_, l)| l.clone()) .unwrap_or_else(|| Lit::Str(default.into(), StrStyle::Cooked)) - } +} -fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { - let struct_name = &ast.ident; - let s = if let Body::Struct(VariantData::Struct(ref s)) = ast.body { - s - } else { - panic!("Only struct is supported") - }; +fn gen_from_clap(struct_name: &Ident, s: &[Field]) -> quote::Tokens { + let fields = s.iter().map(|f| { + let ident = f.ident.as_ref().unwrap(); + let convert = match ty(&f.ty) { + Ty::Bool => quote!(is_present(stringify!(#ident))), + Ty::U64 => quote!(occurrences_of(stringify!(#ident))), + Ty::Option => quote! { + value_of(stringify!(#ident)) + .as_ref() + .map(|s| s.parse().unwrap()) + }, + Ty::Vec => quote! { + values_of(stringify!(#ident)) + .map(|v| v.map(|s| s.parse().unwrap()).collect()) + .unwrap_or_else(Vec::new) + }, + Ty::Other => quote! { + value_of(stringify!(#ident)) + .as_ref() + .unwrap() + .parse() + .unwrap() + }, + }; + quote!( #ident: matches.#convert, ) + }); + quote! { + fn from_clap(matches: _clap::ArgMatches) -> Self { + #struct_name { + #( #fields )* + } + } + } +} +fn gen_clap(ast: &DeriveInput, s: &[Field]) -> quote::Tokens { let struct_attrs: Vec<_> = extract_attrs(&ast.attrs).collect(); let name = from_attr_or(&struct_attrs, "name", env!("CARGO_PKG_NAME")); let version = from_attr_or(&struct_attrs, "version", env!("CARGO_PKG_VERSION")); @@ -104,44 +132,21 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { .map_err(|e| e.description().into())) }; let modifier = match cur_type { - Ty::Bool => quote! { - .takes_value(false) - .multiple(false) - }, - Ty::U64 => quote! { - .takes_value(false) - .multiple(true) - }, - Ty::Option => quote! { - .takes_value(true) - .multiple(false) - .#validator - }, - Ty::Vec => quote! { - .takes_value(true) - .multiple(true) - .#validator - }, + Ty::Bool => quote!( .takes_value(false).multiple(false) ), + Ty::U64 => quote!( .takes_value(false).multiple(true) ), + Ty::Option => quote!( .takes_value(true).multiple(false).#validator ), + Ty::Vec => quote!( .takes_value(true).multiple(true).#validator ), Ty::Other => { let required = extract_attrs(&f.attrs) .find(|&(i, _)| i.as_ref() == "default_value") .is_none(); - quote! { - .takes_value(true) - .multiple(false) - .required(#required) - .#validator - } + quote!( .takes_value(true).multiple(false).required(#required).#validator ) }, }; let from_attr = extract_attrs(&f.attrs).map(|(i, l)| quote!(.#i(#l))); - quote! { - .arg(_clap::Arg::with_name(stringify!(#ident)) - #modifier - #(#from_attr)*) - } + quote!( .arg(_clap::Arg::with_name(stringify!(#ident)) #modifier #(#from_attr)*) ) }); - let clap = quote! { + quote! { fn clap<'a, 'b>() -> _clap::App<'a, 'b> { use std::error::Error; _clap::App::new(#name) @@ -150,41 +155,18 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { .about(#about) #( #args )* } - }; + } +} - let fields = s.iter().map(|f| { - let ident = f.ident.as_ref().unwrap(); - let convert = match ty(&f.ty) { - Ty::Bool => quote!(is_present(stringify!(#ident))), - Ty::U64 => quote!(occurrences_of(stringify!(#ident))), - Ty::Option => quote! { - value_of(stringify!(#ident)) - .as_ref() - .map(|s| s.parse().unwrap()) - }, - Ty::Vec => quote! { - values_of(stringify!(#ident)) - .map(|v| v.map(|s| s.parse().unwrap()).collect()) - .unwrap_or_else(Vec::new) - }, - Ty::Other => quote! { - value_of(stringify!(#ident)) - .as_ref() - .unwrap() - .parse() - .unwrap() - }, - }; - quote!( #ident: matches.#convert, ) - }); - let from_clap = quote! { - fn from_clap(matches: _clap::ArgMatches) -> Self { - #struct_name { - #( #fields )* - } - } +fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { + let struct_name = &ast.ident; + let s = match ast.body { + Body::Struct(VariantData::Struct(ref s)) => s, + _ => panic!("Only struct is supported"), }; + let clap = gen_clap(ast, s); + let from_clap = gen_from_clap(struct_name, s); let dummy_const = Ident::new(format!("_IMPL_STRUCTOPT_FOR_{}", struct_name)); quote! { #[allow(non_upper_case_globals, unused_attributes, unused_imports)] From e3492f74fa8b03f8ddc61774596e791a4654929f Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Tue, 7 Feb 2017 23:34:40 +0100 Subject: [PATCH 0010/3337] support custom name --- examples/basic.rs | 2 +- structopt-derive/src/lib.rs | 47 +++++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 739e1f2e50d..8427c08e0b1 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -27,7 +27,7 @@ struct Opt { #[structopt(short = "l", long = "level")] #[structopt(help = "admin_level to consider")] level: Vec, - #[structopt(help = "Files to process")] + #[structopt(name = "FILE", help = "Files to process")] files: Vec, } diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 9871cedba56..85cc90ed3e8 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -77,31 +77,42 @@ fn from_attr_or(attrs: &[(&Ident, &Lit)], key: &str, default: &str) -> Lit { .unwrap_or_else(|| Lit::Str(default.into(), StrStyle::Cooked)) } +fn gen_name(field: &Field) -> Ident { + extract_attrs(&field.attrs) + .find(|&(i, _)| i.as_ref() == "name") + .and_then(|(_, l)| match *l { + Lit::Str(ref s, _) => Some(Ident::new(s.clone())), + _ => None, + }) + .unwrap_or(field.ident.as_ref().unwrap().clone()) +} + fn gen_from_clap(struct_name: &Ident, s: &[Field]) -> quote::Tokens { - let fields = s.iter().map(|f| { - let ident = f.ident.as_ref().unwrap(); - let convert = match ty(&f.ty) { - Ty::Bool => quote!(is_present(stringify!(#ident))), - Ty::U64 => quote!(occurrences_of(stringify!(#ident))), + let fields = s.iter().map(|field| { + let field_name = field.ident.as_ref().unwrap(); + let name = gen_name(field); + let convert = match ty(&field.ty) { + Ty::Bool => quote!(is_present(stringify!(#name))), + Ty::U64 => quote!(occurrences_of(stringify!(#name))), Ty::Option => quote! { - value_of(stringify!(#ident)) + value_of(stringify!(#name)) .as_ref() .map(|s| s.parse().unwrap()) }, Ty::Vec => quote! { - values_of(stringify!(#ident)) + values_of(stringify!(#name)) .map(|v| v.map(|s| s.parse().unwrap()).collect()) .unwrap_or_else(Vec::new) }, Ty::Other => quote! { - value_of(stringify!(#ident)) + value_of(stringify!(#name)) .as_ref() .unwrap() .parse() .unwrap() }, }; - quote!( #ident: matches.#convert, ) + quote!( #field_name: matches.#convert, ) }); quote! { fn from_clap(matches: _clap::ArgMatches) -> Self { @@ -119,12 +130,12 @@ fn gen_clap(ast: &DeriveInput, s: &[Field]) -> quote::Tokens { let author = from_attr_or(&struct_attrs, "author", env!("CARGO_PKG_AUTHORS")); let about = from_attr_or(&struct_attrs, "about", env!("CARGO_PKG_DESCRIPTION")); - let args = s.iter().map(|f| { - let ident = f.ident.as_ref().unwrap(); - let cur_type = ty(&f.ty); + let args = s.iter().map(|field| { + let name = gen_name(field); + let cur_type = ty(&field.ty); let convert_type = match cur_type { - Ty::Vec | Ty::Option => sub_type(&f.ty).unwrap_or(&f.ty), - _ => &f.ty, + Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty), + _ => &field.ty, }; let validator = quote! { validator(|s| s.parse::<#convert_type>() @@ -137,14 +148,16 @@ fn gen_clap(ast: &DeriveInput, s: &[Field]) -> quote::Tokens { Ty::Option => quote!( .takes_value(true).multiple(false).#validator ), Ty::Vec => quote!( .takes_value(true).multiple(true).#validator ), Ty::Other => { - let required = extract_attrs(&f.attrs) + let required = extract_attrs(&field.attrs) .find(|&(i, _)| i.as_ref() == "default_value") .is_none(); quote!( .takes_value(true).multiple(false).required(#required).#validator ) }, }; - let from_attr = extract_attrs(&f.attrs).map(|(i, l)| quote!(.#i(#l))); - quote!( .arg(_clap::Arg::with_name(stringify!(#ident)) #modifier #(#from_attr)*) ) + let from_attr = extract_attrs(&field.attrs) + .filter(|&(i, _)| i.as_ref() != "name") + .map(|(i, l)| quote!(.#i(#l))); + quote!( .arg(_clap::Arg::with_name(stringify!(#name)) #modifier #(#from_attr)*) ) }); quote! { fn clap<'a, 'b>() -> _clap::App<'a, 'b> { From c9e7ad90df2fe4a5e2bfeac96f53caf6d99bfb91 Mon Sep 17 00:00:00 2001 From: Guillaume P Date: Thu, 9 Feb 2017 22:31:06 +0100 Subject: [PATCH 0011/3337] Presentation in README.md --- README.md | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d93da90bb00..fe3a75aeec1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,86 @@ # StructOpt [![Build status](https://travis-ci.org/TeXitoi/structopt.svg?branch=master)](https://travis-ci.org/TeXitoi/structopt) [![](https://img.shields.io/crates/v/structopt.svg)](https://crates.io/crates/structopt) [![](https://docs.rs/structopt/badge.svg)](https://docs.rs/structopt) -Parse command line argument by defining a struct. - -Work in progress. +Parse command line argument by defining a struct. It combines [clap](https://crates.io/crates/clap) with custom derive. ## Documentation Find it on [Docs.rs](https://docs.rs/structopt) + +## Example + +```rust +extern crate structopt; +#[macro_use] +extern crate structopt_derive; + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "example", about = "An example of StructOpt usage.")] +struct Opt { + /// A flag, true if used in the command line. + #[structopt(short = "d", long = "debug", help = "Activate debug mode")] + debug: bool, + + /// An argument of type float, with a default value. + #[structopt(short = "s", long = "speed", help = "Set speed", default_value = "42")] + speed: f64, + + /// Needed parameter, the first on the command line. + #[structopt(help = "Input file")] + input: String, + + /// An optional parameter, will be `None` if not present on the + /// command line. + #[structopt(help = "Output file, stdout if not present")] + output: Option, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} +``` + +Using this example: +``` +$ ./example +error: The following required arguments were not provided: + + +USAGE: + example [FLAGS] [OPTIONS] [ARGS] + +For more information try --help +$ ./example --help +example 0.0.0 +Guillaume Pinot +An example of StructOpt usage. + +USAGE: + example [FLAGS] [OPTIONS] [ARGS] + +FLAGS: + -d, --debug Activate debug mode + -h, --help Prints help information + -V, --version Prints version information + +OPTIONS: + -s, --speed Set speed [default: 42] + +ARGS: + Input file + Output file, stdout if not present +$ ./example foo +Opt { debug: false, speed: 42, input: "foo", output: None } +$ ./example -ds 1337 foo bar +Opt { debug: true, speed: 1337, input: "foo", output: Some("bar") } +``` + +## Why + +I use [docopt](https://crates.io/crates/docopt) since long time. I really like the fact that you have a structure with the parsed argumentThat's like going back to the : no need to convert `String` to `f64`, no useless `unwrap`. But in another hand, I don't like to write by hand the usage string. That's like going back to the golden age of WYSIWYG editors. + +Today, the new standard to read command line arguments in Rust is [clap](https://crates.io/crates/clap). This library is so feature full! But I think there is one downside: even if you can validate arument, expressing that an argument is required, you still need to transform something looking like a hashmap of string vectors to something useful for your application. + +Now, there is stable custom derive. Thus I can add to clap the automatic conversion that I miss. Here is the result. From d3872e09849be9480fdb07229d9eaf954133cf58 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Thu, 9 Feb 2017 22:51:41 +0100 Subject: [PATCH 0012/3337] add example.rs --- examples/example.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 examples/example.rs diff --git a/examples/example.rs b/examples/example.rs new file mode 100644 index 00000000000..17881488123 --- /dev/null +++ b/examples/example.rs @@ -0,0 +1,38 @@ +// Copyright (c) 2017 Guillaume Pinot +// +// This work is free. You can redistribute it and/or modify it under +// the terms of the Do What The Fuck You Want To Public License, +// Version 2, as published by Sam Hocevar. See the COPYING file for +// more details. + +extern crate structopt; +#[macro_use] +extern crate structopt_derive; + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "example", about = "An example of StructOpt usage.")] +struct Opt { + /// A flag, true if used in the command line. + #[structopt(short = "d", long = "debug", help = "Activate debug mode")] + debug: bool, + + /// An argument of type float, with a default value. + #[structopt(short = "s", long = "speed", help = "Set speed", default_value = "42")] + speed: f64, + + /// Needed parameter, the first on the command line. + #[structopt(help = "Input file")] + input: String, + + /// An optional parameter, will be `None` if not present on the + /// command line. + #[structopt(help = "Output file, stdout if not present")] + output: Option, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} From 258a85c710dbb280fa2d2da6420040ce82ea29ad Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Fri, 10 Feb 2017 01:14:39 +0100 Subject: [PATCH 0013/3337] document structop-derive --- Cargo.toml | 4 +-- README.md | 4 +-- src/lib.rs | 15 ++++++++ structopt-derive/Cargo.toml | 2 +- structopt-derive/src/lib.rs | 71 +++++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 55a3943e48b..63768304c07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.0.0" +version = "0.0.1" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" @@ -14,6 +14,6 @@ readme = "README.md" clap = "2.20.1" [dev-dependencies] -structopt-derive = { path = "structopt-derive", version = "0.0.0" } +structopt-derive = { path = "structopt-derive", version = "0.0.1" } [workspace] diff --git a/README.md b/README.md index fe3a75aeec1..b483e396973 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# StructOpt [![Build status](https://travis-ci.org/TeXitoi/structopt.svg?branch=master)](https://travis-ci.org/TeXitoi/structopt) [![](https://img.shields.io/crates/v/structopt.svg)](https://crates.io/crates/structopt) [![](https://docs.rs/structopt/badge.svg)](https://docs.rs/structopt) +# StructOpt [![Build status](https://travis-ci.org/TeXitoi/structopt.svg?branch=master)](https://travis-ci.org/TeXitoi/structopt) [![](https://img.shields.io/crates/v/structopt.svg)](https://crates.io/crates/structopt) [![](https://docs.rs/structopt/badge.svg)](https://docs.rs/structopt-derive) Parse command line argument by defining a struct. It combines [clap](https://crates.io/crates/clap) with custom derive. ## Documentation -Find it on [Docs.rs](https://docs.rs/structopt) +Find it on Docs.rs: [structopt-derive](https://docs.rs/structopt-derive) and [structopt](https://docs.rs/structopt). ## Example diff --git a/src/lib.rs b/src/lib.rs index fba497a3fb2..b27e21a7fae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,11 +5,26 @@ // Version 2, as published by Sam Hocevar. See the COPYING file for // more details. +#![deny(missing_docs)] + +//! `StructOpt` trait definition +//! +//! This crate defines the `StructOpt` trait. Alone, this crate is of +//! little interest. See the `structopt-derive` crate to +//! automatically generate implementation of this trait. + extern crate clap; +/// A struct that is converted from command line arguments. pub trait StructOpt { + /// Returns the corresponding `clap::App`. fn clap<'a, 'b>() -> clap::App<'a, 'b>; + + /// Creates the struct from `clap::ArgMatches`. fn from_clap(clap::ArgMatches) -> Self; + + /// Gets the struct from the command line arguments. Print the + /// error message and quit the program in case of failure. fn from_args() -> Self where Self: Sized { Self::from_clap(Self::clap().get_matches()) } diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index 6c62670e046..c5627f24a60 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt-derive" -version = "0.0.0" +version = "0.0.1" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct, derive crate." documentation = "https://docs.rs/structopt-derive" diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 85cc90ed3e8..f23faf233f1 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -5,6 +5,76 @@ // Version 2, as published by Sam Hocevar. See the COPYING file for // more details. +//! How to `derive(StructOpt)` +//! +//! First, look at an example: +//! +//! ``` +//! #[derive(StructOpt)] +//! #[structopt(name = "example", about = "An example of StructOpt usage.")] +//! struct Opt { +//! #[structopt(short = "d", long = "debug", help = "Activate debug mode")] +//! debug: bool, +//! #[structopt(short = "s", long = "speed", help = "Set speed", default_value = "42")] +//! speed: f64, +//! #[structopt(help = "Input file")] +//! input: String, +//! #[structopt(help = "Output file, stdout if not present")] +//! output: Option, +//! } +//! ``` +//! +//! So, `derive(StructOpt)` do the job, and `structopt` attribute is +//! used for additional parameters. +//! +//! First, define a struct, whatever its name. This structure will +//! correspond to a `clap::App`. Every method of `clap::App` in the +//! form of `fn function_name(self, &str)` can be use in the form of +//! attributes. Our example call for example something like +//! `app.about("An example of StructOpt usage.")`. There is some +//! special attributes: +//! +//! - `name`: correspond to the creation of the `App` object. Our +//! example does `clap::App::new("example")`. Default to +//! the crate name given by cargo. +//! - `version`: default to the crate version given by cargo. +//! - `author`: default to the crate version given by cargo. +//! - `about`: default to the crate version given by cargo. +//! +//! Then, each field of the struct correspond to a `clap::Arg`. As +//! for the struct attributes, every method of `clap::Arg` in the form +//! of `fn function_name(self, &str)` can be use in the form of +//! attributes. The `name` attribute can be used to customize the +//! `Arg::with_name()` call (default to the field name). +//! +//! The type of the field gives the kind of argument: +//! +//! Type | Effect | Added method call to `clap::Arg` +//! ---------------------|-------------------|-------------------------------------- +//! `bool` | `true` if present | `.takes_value(false).multiple(false)` +//! `u64` | number of params | `.takes_value(false).multiple(true)` +//! `Option` | optional argument | `.takes_value(true).multiple(false)` +//! `Vec` | list of arguments | `.takes_value(true).multiple(true)` +//! `T: FromStr` | required argument | `.takes_value(true).multiple(false).required(!has_default)` +//! +//! The `FromStr` trait is used to convert the argument to the given +//! type, and the `Arg::validator` method is setted to a method using +//! `FromStr::Error::description()`. +//! +//! Thus, the `speed` argument is generated as: +//! +//! ``` +//! clap::Arg::with_name("speed") +//! .takes_value(true) +//! .multiple(false) +//! .required(false) +//! .validator(parse_validator::) +//! .short("s") +//! .long("debug") +//! .help("Set speed") +//! .default_value("42") +//! ``` + extern crate proc_macro; extern crate syn; #[macro_use] @@ -13,6 +83,7 @@ extern crate quote; use proc_macro::TokenStream; use syn::*; +/// Generates the `StructOpt` impl. #[proc_macro_derive(StructOpt, attributes(structopt))] pub fn structopt(input: TokenStream) -> TokenStream { let s = input.to_string(); From 72d9c576d3e3c497f03858b016b20d4ce87a3495 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Fri, 10 Feb 2017 01:18:47 +0100 Subject: [PATCH 0014/3337] desactivate doctest on structopt-derive doc --- structopt-derive/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index f23faf233f1..36e140396c2 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -9,7 +9,7 @@ //! //! First, look at an example: //! -//! ``` +//! ```ignore //! #[derive(StructOpt)] //! #[structopt(name = "example", about = "An example of StructOpt usage.")] //! struct Opt { @@ -63,7 +63,7 @@ //! //! Thus, the `speed` argument is generated as: //! -//! ``` +//! ```ignore //! clap::Arg::with_name("speed") //! .takes_value(true) //! .multiple(false) From 6d73942385d9de51f824d871cae6accdd530dcda Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Fri, 10 Feb 2017 01:19:16 +0100 Subject: [PATCH 0015/3337] add travis badge to cargo --- Cargo.toml | 3 +++ structopt-derive/Cargo.toml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 63768304c07..d025beb00fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ categories = ["command-line-interface"] license = "WTFPL" readme = "README.md" +[badges] +travis-ci = { repository = "TeXitoi/structopt" } + [dependencies] clap = "2.20.1" diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index c5627f24a60..af9609d7789 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -9,6 +9,9 @@ keywords = ["clap", "cli", "derive", "docopt"] categories = ["command-line-interface"] license = "WTFPL" +[badges] +travis-ci = { repository = "TeXitoi/structopt" } + [dependencies] syn = "0.11.4" quote = "0.3.12" From 36cfbc4c108b8e0ee42e61a0fc347ab5ef892db4 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Fri, 10 Feb 2017 01:23:09 +0100 Subject: [PATCH 0016/3337] docopt badge on structopt-derive in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b483e396973..75b7ec1e7d0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# StructOpt [![Build status](https://travis-ci.org/TeXitoi/structopt.svg?branch=master)](https://travis-ci.org/TeXitoi/structopt) [![](https://img.shields.io/crates/v/structopt.svg)](https://crates.io/crates/structopt) [![](https://docs.rs/structopt/badge.svg)](https://docs.rs/structopt-derive) +# StructOpt [![Build status](https://travis-ci.org/TeXitoi/structopt.svg?branch=master)](https://travis-ci.org/TeXitoi/structopt) [![](https://img.shields.io/crates/v/structopt.svg)](https://crates.io/crates/structopt) [![](https://docs.rs/structopt-derive/badge.svg)](https://docs.rs/structopt-derive) Parse command line argument by defining a struct. It combines [clap](https://crates.io/crates/clap) with custom derive. From 0646262757c543f9de47131ea1b2df6d74c095ef Mon Sep 17 00:00:00 2001 From: Guillaume P Date: Fri, 10 Feb 2017 01:28:00 +0100 Subject: [PATCH 0017/3337] corrections on README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 75b7ec1e7d0..d2fac42c5ff 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,8 @@ Opt { debug: true, speed: 1337, input: "foo", output: Some("bar") } ## Why -I use [docopt](https://crates.io/crates/docopt) since long time. I really like the fact that you have a structure with the parsed argumentThat's like going back to the : no need to convert `String` to `f64`, no useless `unwrap`. But in another hand, I don't like to write by hand the usage string. That's like going back to the golden age of WYSIWYG editors. +I use [docopt](https://crates.io/crates/docopt) since a long time (pre rust 1.0). I really like the fact that you have a structure with the parsed argument: no need to convert `String` to `f64`, no useless `unwrap`. But on the other hand, I don't like to write by hand the usage string. That's like going back to the golden age of WYSIWYG editors. Field naming is also a bit artificial. -Today, the new standard to read command line arguments in Rust is [clap](https://crates.io/crates/clap). This library is so feature full! But I think there is one downside: even if you can validate arument, expressing that an argument is required, you still need to transform something looking like a hashmap of string vectors to something useful for your application. +Today, the new standard to read command line arguments in Rust is [clap](https://crates.io/crates/clap). This library is so feature full! But I think there is one downside: even if you can validate argument and expressing that an argument is required, you still need to transform something looking like a hashmap of string vectors to something useful for your application. Now, there is stable custom derive. Thus I can add to clap the automatic conversion that I miss. Here is the result. From 6d4aa55c60c974a36cbf641940f1d46e13cef56d Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Sat, 11 Feb 2017 00:32:01 +0100 Subject: [PATCH 0018/3337] Correct default values using cargo environment variables --- Cargo.toml | 6 +++--- README.md | 9 +++++++++ structopt-derive/Cargo.toml | 2 +- structopt-derive/src/lib.rs | 13 +++++++------ 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d025beb00fb..433a47830de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.0.1" +version = "0.0.2" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" @@ -14,9 +14,9 @@ readme = "README.md" travis-ci = { repository = "TeXitoi/structopt" } [dependencies] -clap = "2.20.1" +clap = "2.20" [dev-dependencies] -structopt-derive = { path = "structopt-derive", version = "0.0.1" } +structopt-derive = { path = "structopt-derive", version = "0.0.2" } [workspace] diff --git a/README.md b/README.md index d2fac42c5ff..dfe455c7f0f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,15 @@ Find it on Docs.rs: [structopt-derive](https://docs.rs/structopt-derive) and [st ## Example +Add `clap`, `structopt` and `structop-derive` to your dependencies of your `Cargo.toml`: +```toml +[dependencies] +clap = "2.20" +structopt = "0.0.2" +structopt-derive = "0.0.2" +``` + +And then, in your rust file: ```rust extern crate structopt; #[macro_use] diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index af9609d7789..f0a339c4d08 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt-derive" -version = "0.0.1" +version = "0.0.2" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct, derive crate." documentation = "https://docs.rs/structopt-derive" diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 36e140396c2..1f40fe60def 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -141,11 +141,12 @@ fn extract_attrs<'a>(attrs: &'a [Attribute]) -> Box Lit { +fn from_attr_or_env(attrs: &[(&Ident, &Lit)], key: &str, env: &str) -> Lit { + let default = std::env::var(env).unwrap_or("".into()); attrs.iter() .find(|&&(i, _)| i.as_ref() == key) .map(|&(_, l)| l.clone()) - .unwrap_or_else(|| Lit::Str(default.into(), StrStyle::Cooked)) + .unwrap_or_else(|| Lit::Str(default, StrStyle::Cooked)) } fn gen_name(field: &Field) -> Ident { @@ -196,10 +197,10 @@ fn gen_from_clap(struct_name: &Ident, s: &[Field]) -> quote::Tokens { fn gen_clap(ast: &DeriveInput, s: &[Field]) -> quote::Tokens { let struct_attrs: Vec<_> = extract_attrs(&ast.attrs).collect(); - let name = from_attr_or(&struct_attrs, "name", env!("CARGO_PKG_NAME")); - let version = from_attr_or(&struct_attrs, "version", env!("CARGO_PKG_VERSION")); - let author = from_attr_or(&struct_attrs, "author", env!("CARGO_PKG_AUTHORS")); - let about = from_attr_or(&struct_attrs, "about", env!("CARGO_PKG_DESCRIPTION")); + let name = from_attr_or_env(&struct_attrs, "name", "CARGO_PKG_NAME"); + let version = from_attr_or_env(&struct_attrs, "version", "CARGO_PKG_VERSION"); + let author = from_attr_or_env(&struct_attrs, "author", "CARGO_PKG_AUTHORS"); + let about = from_attr_or_env(&struct_attrs, "about", "CARGO_PKG_DESCRIPTION"); let args = s.iter().map(|field| { let name = gen_name(field); From 66e75fe3cc05f54a06810ca18942dbe276b78d82 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Sat, 11 Feb 2017 13:39:09 +0100 Subject: [PATCH 0019/3337] Don't ask to the user to import the good clap version --- Cargo.toml | 4 ++-- README.md | 5 ++--- src/lib.rs | 7 ++++++- structopt-derive/Cargo.toml | 2 +- structopt-derive/src/lib.rs | 9 ++++----- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 433a47830de..33a20bc23b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.0.2" +version = "0.0.3" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" @@ -17,6 +17,6 @@ travis-ci = { repository = "TeXitoi/structopt" } clap = "2.20" [dev-dependencies] -structopt-derive = { path = "structopt-derive", version = "0.0.2" } +structopt-derive = { path = "structopt-derive", version = "0.0.3" } [workspace] diff --git a/README.md b/README.md index dfe455c7f0f..708ee4041dc 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,8 @@ Find it on Docs.rs: [structopt-derive](https://docs.rs/structopt-derive) and [st Add `clap`, `structopt` and `structop-derive` to your dependencies of your `Cargo.toml`: ```toml [dependencies] -clap = "2.20" -structopt = "0.0.2" -structopt-derive = "0.0.2" +structopt = "0.0.3" +structopt-derive = "0.0.3" ``` And then, in your rust file: diff --git a/src/lib.rs b/src/lib.rs index b27e21a7fae..e6de4a53102 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,12 @@ //! little interest. See the `structopt-derive` crate to //! automatically generate implementation of this trait. -extern crate clap; +extern crate clap as _clap; + +/// Re-export of clap +pub mod clap { + pub use _clap::*; +} /// A struct that is converted from command line arguments. pub trait StructOpt { diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index f0a339c4d08..c6777af3b9a 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt-derive" -version = "0.0.2" +version = "0.0.3" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct, derive crate." documentation = "https://docs.rs/structopt-derive" diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 1f40fe60def..b93bafc126d 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -187,7 +187,7 @@ fn gen_from_clap(struct_name: &Ident, s: &[Field]) -> quote::Tokens { quote!( #field_name: matches.#convert, ) }); quote! { - fn from_clap(matches: _clap::ArgMatches) -> Self { + fn from_clap(matches: _structopt::clap::ArgMatches) -> Self { #struct_name { #( #fields )* } @@ -229,12 +229,12 @@ fn gen_clap(ast: &DeriveInput, s: &[Field]) -> quote::Tokens { let from_attr = extract_attrs(&field.attrs) .filter(|&(i, _)| i.as_ref() != "name") .map(|(i, l)| quote!(.#i(#l))); - quote!( .arg(_clap::Arg::with_name(stringify!(#name)) #modifier #(#from_attr)*) ) + quote!( .arg(_structopt::clap::Arg::with_name(stringify!(#name)) #modifier #(#from_attr)*) ) }); quote! { - fn clap<'a, 'b>() -> _clap::App<'a, 'b> { + fn clap<'a, 'b>() -> _structopt::clap::App<'a, 'b> { use std::error::Error; - _clap::App::new(#name) + _structopt::clap::App::new(#name) .version(#version) .author(#author) .about(#about) @@ -256,7 +256,6 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { quote! { #[allow(non_upper_case_globals, unused_attributes, unused_imports)] const #dummy_const: () = { - extern crate clap as _clap; extern crate structopt as _structopt; impl _structopt::StructOpt for #struct_name { #clap From 52d2550d4e5bea30cf3a689abef208f654bff5c4 Mon Sep 17 00:00:00 2001 From: Guillaume P Date: Sat, 11 Feb 2017 13:56:11 +0100 Subject: [PATCH 0020/3337] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 708ee4041dc..daaf1b7e8fd 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Find it on Docs.rs: [structopt-derive](https://docs.rs/structopt-derive) and [st ## Example -Add `clap`, `structopt` and `structop-derive` to your dependencies of your `Cargo.toml`: +Add `structopt` and `structop-derive` to your dependencies of your `Cargo.toml`: ```toml [dependencies] structopt = "0.0.3" From c66413a0822b81355fddc8c1ffe0d2844c044bca Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Thu, 15 Jun 2017 15:18:17 +0200 Subject: [PATCH 0021/3337] WIP: Allow doc comment to set help text This treats doc comments as `help`/`about` attributes, which may lead to concatenating the original help text with the doc comment. --- examples/basic.rs | 29 ++++++++++++----- structopt-derive/src/lib.rs | 63 +++++++++++++++++++++++++++---------- 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 8427c08e0b1..4197da52b3f 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -11,23 +11,36 @@ extern crate structopt_derive; use structopt::StructOpt; +/// A basic example #[derive(StructOpt, Debug)] -#[structopt(name = "basic", about = "A basic example")] +#[structopt(name = "basic")] struct Opt { - #[structopt(short = "d", long = "debug", help = "Activate debug mode")] + /// Activate debug mode + #[structopt(short = "d", long = "debug")] debug: bool, - #[structopt(short = "v", long = "verbose", help = "Verbose mode")] + + /// Verbose mode + #[structopt(short = "v", long = "verbose")] verbose: u64, - #[structopt(short = "s", long = "speed", help = "Set speed", default_value = "42")] + + /// Set speed + #[structopt(short = "s", long = "speed", default_value = "42")] speed: f64, - #[structopt(short = "o", long = "output", help = "Output file")] + + /// Output file + #[structopt(short = "o", long = "output")] output: String, - #[structopt(short = "c", long = "car", help = "Number of car")] + + /// Number of car + #[structopt(short = "c", long = "car")] car: Option, + + /// admin_level to consider #[structopt(short = "l", long = "level")] - #[structopt(help = "admin_level to consider")] level: Vec, - #[structopt(name = "FILE", help = "Files to process")] + + /// Files to process + #[structopt(name = "FILE")] files: Vec, } diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index b93bafc126d..fbad4183088 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -129,31 +129,62 @@ fn sub_type(t: &syn::Ty) -> Option<&syn::Ty> { } } -fn extract_attrs<'a>(attrs: &'a [Attribute]) -> Box + 'a> { - let iter = attrs.iter() +#[derive(Debug, Clone, Copy)] +enum AttrSource { Struct, Field, } + +fn extract_attrs<'a>(attrs: &'a [Attribute], attr_source: AttrSource) -> Box + 'a> { + let settings_attrs = attrs.iter() .filter_map(|attr| match attr.value { MetaItem::List(ref i, ref v) if i.as_ref() == "structopt" => Some(v), _ => None, }).flat_map(|v| v.iter().filter_map(|mi| match *mi { - NestedMetaItem::MetaItem(MetaItem::NameValue(ref i, ref l)) => Some((i, l)), + NestedMetaItem::MetaItem(MetaItem::NameValue(ref i, ref l)) => + Some((i.clone(), l.clone())), _ => None, })); - Box::new(iter) + + let doc_comments = attrs.iter() + .filter_map(move |attr| { + if let Attribute { + value: MetaItem::NameValue(ref name, Lit::Str(ref value, StrStyle::Cooked)), + is_sugared_doc: true, + .. + } = *attr { + if name != "doc" { return None; } + let text = value.trim_left_matches("//!") + .trim_left_matches("///") + .trim_left_matches("/*!") + .trim_left_matches("/**") + .trim(); + + // Clap's `App` has an `about` method to set a description, + // it's `Field`s have a `help` method instead. + if let AttrSource::Struct = attr_source { + Some(("about".into(), text.into())) + } else { + Some(("help".into(), text.into())) + } + } else { + None + } + }); + + Box::new(settings_attrs.chain(doc_comments)) } -fn from_attr_or_env(attrs: &[(&Ident, &Lit)], key: &str, env: &str) -> Lit { +fn from_attr_or_env<'a>(attrs: &[(Ident, Lit)], key: &str, env: &str) -> Lit { let default = std::env::var(env).unwrap_or("".into()); attrs.iter() - .find(|&&(i, _)| i.as_ref() == key) - .map(|&(_, l)| l.clone()) + .find(|&&(ref i, _)| i.as_ref() == key) + .map(|&(_, ref l)| l.clone()) .unwrap_or_else(|| Lit::Str(default, StrStyle::Cooked)) } fn gen_name(field: &Field) -> Ident { - extract_attrs(&field.attrs) - .find(|&(i, _)| i.as_ref() == "name") - .and_then(|(_, l)| match *l { - Lit::Str(ref s, _) => Some(Ident::new(s.clone())), + extract_attrs(&field.attrs, AttrSource::Field) + .find(|&(ref i, _)| i.as_ref() == "name") + .and_then(|(_, ref l)| match l { + &Lit::Str(ref s, _) => Some(Ident::new(s.clone())), _ => None, }) .unwrap_or(field.ident.as_ref().unwrap().clone()) @@ -196,7 +227,7 @@ fn gen_from_clap(struct_name: &Ident, s: &[Field]) -> quote::Tokens { } fn gen_clap(ast: &DeriveInput, s: &[Field]) -> quote::Tokens { - let struct_attrs: Vec<_> = extract_attrs(&ast.attrs).collect(); + let struct_attrs: Vec<_> = extract_attrs(&ast.attrs, AttrSource::Struct).collect(); let name = from_attr_or_env(&struct_attrs, "name", "CARGO_PKG_NAME"); let version = from_attr_or_env(&struct_attrs, "version", "CARGO_PKG_VERSION"); let author = from_attr_or_env(&struct_attrs, "author", "CARGO_PKG_AUTHORS"); @@ -220,14 +251,14 @@ fn gen_clap(ast: &DeriveInput, s: &[Field]) -> quote::Tokens { Ty::Option => quote!( .takes_value(true).multiple(false).#validator ), Ty::Vec => quote!( .takes_value(true).multiple(true).#validator ), Ty::Other => { - let required = extract_attrs(&field.attrs) - .find(|&(i, _)| i.as_ref() == "default_value") + let required = extract_attrs(&field.attrs, AttrSource::Field) + .find(|&(ref i, _)| i.as_ref() == "default_value") .is_none(); quote!( .takes_value(true).multiple(false).required(#required).#validator ) }, }; - let from_attr = extract_attrs(&field.attrs) - .filter(|&(i, _)| i.as_ref() != "name") + let from_attr = extract_attrs(&field.attrs, AttrSource::Field) + .filter(|&(ref i, _)| i.as_ref() != "name") .map(|(i, l)| quote!(.#i(#l))); quote!( .arg(_structopt::clap::Arg::with_name(stringify!(#name)) #modifier #(#from_attr)*) ) }); From 2075ee832806188b47b76bb1bd0b7b7641dbca0c Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Thu, 15 Jun 2017 20:14:16 +0200 Subject: [PATCH 0022/3337] Favor help argument over doc comments This changes the behavior of `from_attr_or_env` (used for clap application settings) to favor the last occurrence of the requested attribute. This is what repeatedly calling the same methods to set field properties does implicitly. --- structopt-derive/src/lib.rs | 8 +++--- tests/doc-comments-help.rs | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 tests/doc-comments-help.rs diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index fbad4183088..32116795e87 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -169,20 +169,22 @@ fn extract_attrs<'a>(attrs: &'a [Attribute], attr_source: AttrSource) -> Box(attrs: &[(Ident, Lit)], key: &str, env: &str) -> Lit { let default = std::env::var(env).unwrap_or("".into()); attrs.iter() - .find(|&&(ref i, _)| i.as_ref() == key) + .filter(|&&(ref i, _)| i.as_ref() == key) + .last() .map(|&(_, ref l)| l.clone()) .unwrap_or_else(|| Lit::Str(default, StrStyle::Cooked)) } fn gen_name(field: &Field) -> Ident { extract_attrs(&field.attrs, AttrSource::Field) - .find(|&(ref i, _)| i.as_ref() == "name") + .filter(|&(ref i, _)| i.as_ref() == "name") + .last() .and_then(|(_, ref l)| match l { &Lit::Str(ref s, _) => Some(Ident::new(s.clone())), _ => None, diff --git a/tests/doc-comments-help.rs b/tests/doc-comments-help.rs new file mode 100644 index 00000000000..4d7da7c8360 --- /dev/null +++ b/tests/doc-comments-help.rs @@ -0,0 +1,51 @@ +// Copyright (c) 2017 structopt Developers +// +// This work is free. You can redistribute it and/or modify it under +// the terms of the Do What The Fuck You Want To Public License, +// Version 2, as published by Sam Hocevar. See the COPYING file for +// more details. + +extern crate structopt; +#[macro_use] +extern crate structopt_derive; + +use structopt::StructOpt; + +#[test] +fn commets_intead_of_actual_help() { + /// Lorem ipsum + #[derive(StructOpt, PartialEq, Debug)] + struct LoremIpsum { + /// Fooify a bar + #[structopt(short = "f", long = "foo")] + foo: bool, + } + + let mut output = Vec::new(); + LoremIpsum::clap().write_long_help(&mut output).unwrap(); + let output = String::from_utf8(output).unwrap(); + + assert!(output.contains("Lorem ipsum")); + assert!(output.contains("Fooify a bar")); +} + +#[test] +fn help_is_better_than_comments() { + /// Lorem ipsum + #[derive(StructOpt, PartialEq, Debug)] + #[structopt(name = "lorem-ipsum", about = "Dolor sit amet")] + struct LoremIpsum { + /// Fooify a bar + #[structopt(short = "f", long = "foo", help = "DO NOT PASS A BAR UNDER ANY CIRCUMSTANCES")] + foo: bool, + } + + let mut output = Vec::new(); + LoremIpsum::clap().write_long_help(&mut output).unwrap(); + let output = String::from_utf8(output).unwrap(); + + println!("{}", output); + assert!(output.contains("Dolor sit amet")); + assert!(!output.contains("Lorem ipsum")); + assert!(output.contains("DO NOT PASS A BAR")); +} From 483396dc9762927509bc5a9dc415da168f2c6140 Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Fri, 16 Jun 2017 11:47:40 +0200 Subject: [PATCH 0023/3337] Remove unneeded lifetime annotation --- structopt-derive/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 32116795e87..62a127b6845 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -172,7 +172,7 @@ fn extract_attrs<'a>(attrs: &'a [Attribute], attr_source: AttrSource) -> Box(attrs: &[(Ident, Lit)], key: &str, env: &str) -> Lit { +fn from_attr_or_env(attrs: &[(Ident, Lit)], key: &str, env: &str) -> Lit { let default = std::env::var(env).unwrap_or("".into()); attrs.iter() .filter(|&&(ref i, _)| i.as_ref() == key) From 51e0c36278a7f42e20eca5396f4f3ce18d7c6262 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Fri, 16 Jun 2017 13:40:59 +0200 Subject: [PATCH 0024/3337] update to 0.0.4 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 33a20bc23b5..6ed0d41055f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.0.3" +version = "0.0.4" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" From 859ac77f4e51d8852e863f8ae2cea265693320d3 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Fri, 16 Jun 2017 17:12:21 +0200 Subject: [PATCH 0025/3337] Update to 0.0.5 (oups...) --- Cargo.toml | 4 ++-- structopt-derive/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6ed0d41055f..e787c873219 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.0.4" +version = "0.0.5" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" @@ -17,6 +17,6 @@ travis-ci = { repository = "TeXitoi/structopt" } clap = "2.20" [dev-dependencies] -structopt-derive = { path = "structopt-derive", version = "0.0.3" } +structopt-derive = { path = "structopt-derive", version = "0.0.5" } [workspace] diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index c6777af3b9a..77fa9d6d4bf 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt-derive" -version = "0.0.3" +version = "0.0.5" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct, derive crate." documentation = "https://docs.rs/structopt-derive" From a4826404f3a96a8e5a87eaf4cc5b410451990e0c Mon Sep 17 00:00:00 2001 From: William Yao Date: Wed, 28 Jun 2017 19:16:31 -0500 Subject: [PATCH 0026/3337] separate creating app and adding arguments --- src/lib.rs | 3 +++ structopt-derive/src/lib.rs | 27 +++++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e6de4a53102..b67860d6468 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,9 @@ pub trait StructOpt { /// Returns the corresponding `clap::App`. fn clap<'a, 'b>() -> clap::App<'a, 'b>; + /// Add this app's arguments/subcommands to another `clap::App`. + fn augment_clap<'a, 'b>(clap::App<'a, 'b>) -> clap::App<'a, 'b>; + /// Creates the struct from `clap::ArgMatches`. fn from_clap(clap::ArgMatches) -> Self; diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 62a127b6845..403ec4daf61 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -228,13 +228,25 @@ fn gen_from_clap(struct_name: &Ident, s: &[Field]) -> quote::Tokens { } } -fn gen_clap(ast: &DeriveInput, s: &[Field]) -> quote::Tokens { +fn gen_clap(ast: &DeriveInput) -> quote::Tokens { let struct_attrs: Vec<_> = extract_attrs(&ast.attrs, AttrSource::Struct).collect(); let name = from_attr_or_env(&struct_attrs, "name", "CARGO_PKG_NAME"); let version = from_attr_or_env(&struct_attrs, "version", "CARGO_PKG_VERSION"); let author = from_attr_or_env(&struct_attrs, "author", "CARGO_PKG_AUTHORS"); let about = from_attr_or_env(&struct_attrs, "about", "CARGO_PKG_DESCRIPTION"); + quote! { + fn clap<'a, 'b>() -> _structopt::clap::App<'a, 'b> { + let app = _structopt::clap::App::new(#name) + .version(#version) + .author(#author) + .about(#about); + Self::augment_clap(app) + } + } +} + +fn gen_augment_clap(s: &[Field]) -> quote::Tokens { let args = s.iter().map(|field| { let name = gen_name(field); let cur_type = ty(&field.ty); @@ -264,14 +276,11 @@ fn gen_clap(ast: &DeriveInput, s: &[Field]) -> quote::Tokens { .map(|(i, l)| quote!(.#i(#l))); quote!( .arg(_structopt::clap::Arg::with_name(stringify!(#name)) #modifier #(#from_attr)*) ) }); + quote! { - fn clap<'a, 'b>() -> _structopt::clap::App<'a, 'b> { + fn augment_clap<'a, 'b>(app: _structopt::clap::App<'a, 'b>) -> _structopt::clap::App<'a, 'b> { use std::error::Error; - _structopt::clap::App::new(#name) - .version(#version) - .author(#author) - .about(#about) - #( #args )* + app #( #args )* } } } @@ -283,7 +292,8 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { _ => panic!("Only struct is supported"), }; - let clap = gen_clap(ast, s); + let clap = gen_clap(ast); + let augment_clap = gen_augment_clap(s); let from_clap = gen_from_clap(struct_name, s); let dummy_const = Ident::new(format!("_IMPL_STRUCTOPT_FOR_{}", struct_name)); quote! { @@ -292,6 +302,7 @@ fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { extern crate structopt as _structopt; impl _structopt::StructOpt for #struct_name { #clap + #augment_clap #from_clap } }; From 00f313b5d12861fa74d704d9d7f1d6123303fff8 Mon Sep 17 00:00:00 2001 From: William Yao Date: Wed, 28 Jun 2017 19:33:32 -0500 Subject: [PATCH 0027/3337] split `impl_structopt` into struct and enum branches --- structopt-derive/src/lib.rs | 43 +++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 403ec4daf61..48c4534274b 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -228,8 +228,8 @@ fn gen_from_clap(struct_name: &Ident, s: &[Field]) -> quote::Tokens { } } -fn gen_clap(ast: &DeriveInput) -> quote::Tokens { - let struct_attrs: Vec<_> = extract_attrs(&ast.attrs, AttrSource::Struct).collect(); +fn gen_clap(struct_attrs: &[Attribute]) -> quote::Tokens { + let struct_attrs: Vec<_> = extract_attrs(struct_attrs, AttrSource::Struct).collect(); let name = from_attr_or_env(&struct_attrs, "name", "CARGO_PKG_NAME"); let version = from_attr_or_env(&struct_attrs, "version", "CARGO_PKG_VERSION"); let author = from_attr_or_env(&struct_attrs, "author", "CARGO_PKG_AUTHORS"); @@ -285,26 +285,41 @@ fn gen_augment_clap(s: &[Field]) -> quote::Tokens { } } -fn impl_structopt(ast: &syn::DeriveInput) -> quote::Tokens { +fn impl_structopt_for_struct(name: &Ident, fields: &[Field], attrs: &[Attribute]) -> quote::Tokens { + let clap = gen_clap(attrs); + let augment_clap = gen_augment_clap(fields); + let from_clap = gen_from_clap(name, fields); + + quote! { + impl _structopt::StructOpt for #name { + #clap + #augment_clap + #from_clap + } + } +} + +fn impl_structopt_for_enum(_variants: &[Variant]) -> quote::Tokens { + quote!() +} + +fn impl_structopt(ast: &DeriveInput) -> quote::Tokens { let struct_name = &ast.ident; - let s = match ast.body { - Body::Struct(VariantData::Struct(ref s)) => s, - _ => panic!("Only struct is supported"), + let inner_impl = match ast.body { + Body::Struct(VariantData::Struct(ref fields)) => + impl_structopt_for_struct(struct_name, fields, &ast.attrs), + Body::Enum(ref variants) => + impl_structopt_for_enum(variants), + _ => panic!("structopt only supports non-tuple structs and enums") }; - let clap = gen_clap(ast); - let augment_clap = gen_augment_clap(s); - let from_clap = gen_from_clap(struct_name, s); let dummy_const = Ident::new(format!("_IMPL_STRUCTOPT_FOR_{}", struct_name)); quote! { #[allow(non_upper_case_globals, unused_attributes, unused_imports)] const #dummy_const: () = { extern crate structopt as _structopt; - impl _structopt::StructOpt for #struct_name { - #clap - #augment_clap - #from_clap - } + use structopt::StructOpt; + #inner_impl }; } } From 08aeb9b1fb95e540dc29d784c9abb0c2fddd111c Mon Sep 17 00:00:00 2001 From: William Yao Date: Wed, 28 Jun 2017 19:40:55 -0500 Subject: [PATCH 0028/3337] skeleton of enum impl --- structopt-derive/src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 48c4534274b..a4c441d4b02 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -300,7 +300,15 @@ fn impl_structopt_for_struct(name: &Ident, fields: &[Field], attrs: &[Attribute] } fn impl_structopt_for_enum(_variants: &[Variant]) -> quote::Tokens { - quote!() + quote! { + impl _structopt::StructOpt for #name { + } + + impl #name { + fn from_subcommand<'a, 'b>(sub: (&'b str, Option<&'b _structopt::clap::ArgMatches<'a, 'b>>)) -> Option { + } + } + } } fn impl_structopt(ast: &DeriveInput) -> quote::Tokens { From fd67b014667b5cc87b1cc40bb38b36ba50c0f84d Mon Sep 17 00:00:00 2001 From: William Yao Date: Thu, 29 Jun 2017 09:00:04 -0500 Subject: [PATCH 0029/3337] factor out code generation that's needed for enums --- structopt-derive/src/lib.rs | 120 +++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 51 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index a4c441d4b02..8ef9145cb24 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -181,19 +181,48 @@ fn from_attr_or_env(attrs: &[(Ident, Lit)], key: &str, env: &str) -> Lit { .unwrap_or_else(|| Lit::Str(default, StrStyle::Cooked)) } -fn gen_name(field: &Field) -> Ident { - extract_attrs(&field.attrs, AttrSource::Field) - .filter(|&(ref i, _)| i.as_ref() == "name") - .last() - .and_then(|(_, ref l)| match l { - &Lit::Str(ref s, _) => Some(Ident::new(s.clone())), - _ => None, - }) - .unwrap_or(field.ident.as_ref().unwrap().clone()) +/// Generate a block of code to add arguments/subcommands corresponding to +/// the `fields` to an app. +fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens { + let args = fields.iter().map(|field| { + let name = gen_name(field); + let cur_type = ty(&field.ty); + let convert_type = match cur_type { + Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty), + _ => &field.ty, + }; + let validator = quote! { + validator(|s| s.parse::<#convert_type>() + .map(|_| ()) + .map_err(|e| e.description().into())) + }; + let modifier = match cur_type { + Ty::Bool => quote!( .takes_value(false).multiple(false) ), + Ty::U64 => quote!( .takes_value(false).multiple(true) ), + Ty::Option => quote!( .takes_value(true).multiple(false).#validator ), + Ty::Vec => quote!( .takes_value(true).multiple(true).#validator ), + Ty::Other => { + let required = extract_attrs(&field.attrs, AttrSource::Field) + .find(|&(ref i, _)| i.as_ref() == "default_value") + .is_none(); + quote!( .takes_value(true).multiple(false).required(#required).#validator ) + }, + }; + let from_attr = extract_attrs(&field.attrs, AttrSource::Field) + .filter(|&(ref i, _)| i.as_ref() != "name") + .map(|(i, l)| quote!(.#i(#l))); + quote!( .arg(_structopt::clap::Arg::with_name(stringify!(#name)) #modifier #(#from_attr)*) ) + }); + + quote! {{ + use std::error::Error; + let #app_var = #app_var #( #args )* ; + #app_var + }} } -fn gen_from_clap(struct_name: &Ident, s: &[Field]) -> quote::Tokens { - let fields = s.iter().map(|field| { +fn gen_constructor(fields: &[Field]) -> quote::Tokens { + let fields = fields.iter().map(|field| { let field_name = field.ident.as_ref().unwrap(); let name = gen_name(field); let convert = match ty(&field.ty) { @@ -217,13 +246,31 @@ fn gen_from_clap(struct_name: &Ident, s: &[Field]) -> quote::Tokens { .unwrap() }, }; - quote!( #field_name: matches.#convert, ) + quote!( #field_name: matches.#convert ) }); + + quote! {{ + #( #fields ),* + }} +} + +fn gen_name(field: &Field) -> Ident { + extract_attrs(&field.attrs, AttrSource::Field) + .filter(|&(ref i, _)| i.as_ref() == "name") + .last() + .and_then(|(_, ref l)| match l { + &Lit::Str(ref s, _) => Some(Ident::new(s.clone())), + _ => None, + }) + .unwrap_or(field.ident.as_ref().unwrap().clone()) +} + +fn gen_from_clap(struct_name: &Ident, fields: &[Field]) -> quote::Tokens { + let field_block = gen_constructor(fields); + quote! { fn from_clap(matches: _structopt::clap::ArgMatches) -> Self { - #struct_name { - #( #fields )* - } + #struct_name #field_block } } } @@ -246,41 +293,12 @@ fn gen_clap(struct_attrs: &[Attribute]) -> quote::Tokens { } } -fn gen_augment_clap(s: &[Field]) -> quote::Tokens { - let args = s.iter().map(|field| { - let name = gen_name(field); - let cur_type = ty(&field.ty); - let convert_type = match cur_type { - Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty), - _ => &field.ty, - }; - let validator = quote! { - validator(|s| s.parse::<#convert_type>() - .map(|_| ()) - .map_err(|e| e.description().into())) - }; - let modifier = match cur_type { - Ty::Bool => quote!( .takes_value(false).multiple(false) ), - Ty::U64 => quote!( .takes_value(false).multiple(true) ), - Ty::Option => quote!( .takes_value(true).multiple(false).#validator ), - Ty::Vec => quote!( .takes_value(true).multiple(true).#validator ), - Ty::Other => { - let required = extract_attrs(&field.attrs, AttrSource::Field) - .find(|&(ref i, _)| i.as_ref() == "default_value") - .is_none(); - quote!( .takes_value(true).multiple(false).required(#required).#validator ) - }, - }; - let from_attr = extract_attrs(&field.attrs, AttrSource::Field) - .filter(|&(ref i, _)| i.as_ref() != "name") - .map(|(i, l)| quote!(.#i(#l))); - quote!( .arg(_structopt::clap::Arg::with_name(stringify!(#name)) #modifier #(#from_attr)*) ) - }); - +fn gen_augment_clap(fields: &[Field]) -> quote::Tokens { + let app_var = Ident::new("app"); + let augmentation = gen_augmentation(fields, &app_var); quote! { - fn augment_clap<'a, 'b>(app: _structopt::clap::App<'a, 'b>) -> _structopt::clap::App<'a, 'b> { - use std::error::Error; - app #( #args )* + fn augment_clap<'a, 'b>(#app_var: _structopt::clap::App<'a, 'b>) -> _structopt::clap::App<'a, 'b> { + #augmentation } } } @@ -299,7 +317,7 @@ fn impl_structopt_for_struct(name: &Ident, fields: &[Field], attrs: &[Attribute] } } -fn impl_structopt_for_enum(_variants: &[Variant]) -> quote::Tokens { +fn impl_structopt_for_enum(name: &Ident, _variants: &[Variant]) -> quote::Tokens { quote! { impl _structopt::StructOpt for #name { } @@ -317,7 +335,7 @@ fn impl_structopt(ast: &DeriveInput) -> quote::Tokens { Body::Struct(VariantData::Struct(ref fields)) => impl_structopt_for_struct(struct_name, fields, &ast.attrs), Body::Enum(ref variants) => - impl_structopt_for_enum(variants), + impl_structopt_for_enum(struct_name, variants), _ => panic!("structopt only supports non-tuple structs and enums") }; From 30a3b3145c88fc98d1a4c8a8513c7c5df5c73658 Mon Sep 17 00:00:00 2001 From: William Yao Date: Thu, 29 Jun 2017 10:25:07 -0500 Subject: [PATCH 0030/3337] prototype enum stuff? --- structopt-derive/src/lib.rs | 127 ++++++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 4 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 8ef9145cb24..0c3525f9425 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -181,6 +181,20 @@ fn from_attr_or_env(attrs: &[(Ident, Lit)], key: &str, env: &str) -> Lit { .unwrap_or_else(|| Lit::Str(default, StrStyle::Cooked)) } +fn is_subcommand(field: &Field) -> bool { + field.attrs.iter() + .map(|attr| &attr.value) + .any(|meta| if let MetaItem::List(ref i, ref l) = *meta { + if i != "structopt" { return false; } + match l.first() { + Some(&NestedMetaItem::MetaItem(MetaItem::Word(ref inner))) => inner == "subcommand", + _ => false + } + } else { + false + }) +} + /// Generate a block of code to add arguments/subcommands corresponding to /// the `fields` to an app. fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens { @@ -303,6 +317,97 @@ fn gen_augment_clap(fields: &[Field]) -> quote::Tokens { } } +fn gen_clap_enum(enum_attrs: &[Attribute]) -> quote::Tokens { + let enum_attrs: Vec<_> = extract_attrs(enum_attrs, AttrSource::Struct).collect(); + let name = from_attr_or_env(&enum_attrs, "name", "CARGO_PKG_NAME"); + let version = from_attr_or_env(&enum_attrs, "version", "CARGO_PKG_VERSION"); + let author = from_attr_or_env(&enum_attrs, "author", "CARGO_PKG_AUTHORS"); + let about = from_attr_or_env(&enum_attrs, "about", "CARGO_PKG_DESCRIPTION"); + + quote! { + fn clap<'a, 'b>() -> _structopt::clap::App<'a, 'b> { + let app = _structopt::clap::App::new(#name) + .version(#version) + .author(#author) + .about(#about) + .setting(_structopt::clap::AppSettings::SubcommandRequired); + Self::augment_clap(app) + } + } +} + +fn gen_augment_clap_enum(variants: &[Variant]) -> quote::Tokens { + let subcommands = variants.iter().map(|variant| { + let name = extract_attrs(&variant.attrs, AttrSource::Struct) + .filter_map(|attr| match attr { + (ref i, Lit::Str(ref s, ..)) if i == "name" => + Some(Ident::new(s as &str)), + _ => None + }) + .next() + .unwrap_or_else(|| variant.ident.clone()); + let app_var = Ident::new("subcommand"); + let arg_block = match variant.data { + VariantData::Struct(ref fields) => gen_augmentation(fields, &app_var), + _ => unreachable!() + }; + + quote! { + .subcommand({ + let #app_var = _structopt::clap::SubCommand::with_name( stringify!(#name) ); + #arg_block + }) + } + }); + + quote! { + fn augment_clap<'a, 'b>(app: _structopt::clap::App<'a, 'b>) -> _structopt::clap::App<'a, 'b> { + app #( #subcommands )* + } + } +} + +fn gen_from_clap_enum(name: &Ident) -> quote::Tokens { + quote! { + fn from_clap(matches: _structopt::clap::ArgMatches) -> Self { + #name ::from_subcommand(matches.subcommand()) + .unwrap() + } + } +} + +fn gen_from_subcommand(name: &Ident, variants: &[Variant]) -> quote::Tokens { + let match_arms = variants.iter().map(|variant| { + let sub_name = extract_attrs(&variant.attrs, AttrSource::Struct) + .filter_map(|attr| match attr { + (ref i, Lit::Str(ref s, ..)) if i == "name" => + Some(Ident::new(s as &str)), + _ => None + }) + .next() + .unwrap_or_else(|| variant.ident.clone()); + let variant_name = &variant.ident; + let constructor_block = match variant.data { + VariantData::Struct(ref fields) => gen_constructor(fields), + _ => unreachable!() + }; + + quote! { + (stringify!(#sub_name), Some(matches)) => + Some(#name :: #variant_name #constructor_block) + } + }); + + quote! { + fn from_subcommand<'a, 'b>(sub: (&'b str, Option<&'b _structopt::clap::ArgMatches<'a, 'b>>)) -> Option { + match sub { + #( #match_arms ),*, + _ => None + } + } + } +} + fn impl_structopt_for_struct(name: &Ident, fields: &[Field], attrs: &[Attribute]) -> quote::Tokens { let clap = gen_clap(attrs); let augment_clap = gen_augment_clap(fields); @@ -317,14 +422,28 @@ fn impl_structopt_for_struct(name: &Ident, fields: &[Field], attrs: &[Attribute] } } -fn impl_structopt_for_enum(name: &Ident, _variants: &[Variant]) -> quote::Tokens { +fn impl_structopt_for_enum(name: &Ident, variants: &[Variant], attrs: &[Attribute]) -> quote::Tokens { + if variants.iter().any(|variant| { + if let VariantData::Struct(..) = variant.data { false } else { true } + }) + { + panic!("enum variants must use curly braces"); + } + + let clap = gen_clap_enum(attrs); + let augment_clap = gen_augment_clap_enum(variants); + let from_clap = gen_from_clap_enum(name); + let from_subcommand = gen_from_subcommand(name, variants); + quote! { impl _structopt::StructOpt for #name { + #clap + #augment_clap + #from_clap } impl #name { - fn from_subcommand<'a, 'b>(sub: (&'b str, Option<&'b _structopt::clap::ArgMatches<'a, 'b>>)) -> Option { - } + #from_subcommand } } } @@ -335,7 +454,7 @@ fn impl_structopt(ast: &DeriveInput) -> quote::Tokens { Body::Struct(VariantData::Struct(ref fields)) => impl_structopt_for_struct(struct_name, fields, &ast.attrs), Body::Enum(ref variants) => - impl_structopt_for_enum(struct_name, variants), + impl_structopt_for_enum(struct_name, variants, &ast.attrs), _ => panic!("structopt only supports non-tuple structs and enums") }; From 00c659b9c6681a0e58f75f86027c24ece92a99f3 Mon Sep 17 00:00:00 2001 From: William Yao Date: Thu, 29 Jun 2017 10:52:01 -0500 Subject: [PATCH 0031/3337] lifetime fix --- structopt-derive/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 0c3525f9425..184ec5d60e9 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -399,7 +399,7 @@ fn gen_from_subcommand(name: &Ident, variants: &[Variant]) -> quote::Tokens { }); quote! { - fn from_subcommand<'a, 'b>(sub: (&'b str, Option<&'b _structopt::clap::ArgMatches<'a, 'b>>)) -> Option { + fn from_subcommand<'a, 'b>(sub: (&'b str, Option<&'b _structopt::clap::ArgMatches<'a>>)) -> Option { match sub { #( #match_arms ),*, _ => None From e00e263f23c63830f382196828ca714bbfbc29dc Mon Sep 17 00:00:00 2001 From: William Yao Date: Thu, 29 Jun 2017 10:52:11 -0500 Subject: [PATCH 0032/3337] add test for subcommand parsing (texitoi/structopt#1) --- tests/subcommands.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/subcommands.rs diff --git a/tests/subcommands.rs b/tests/subcommands.rs new file mode 100644 index 00000000000..e2aa442352e --- /dev/null +++ b/tests/subcommands.rs @@ -0,0 +1,57 @@ +// Copyright (c) 2017 Guillaume Pinot +// +// This work is free. You can redistribute it and/or modify it under +// the terms of the Do What The Fuck You Want To Public License, +// Version 2, as published by Sam Hocevar. See the COPYING file for +// more details. + +extern crate structopt; +#[macro_use] extern crate structopt_derive; + +use structopt::StructOpt; + +#[derive(StructOpt, PartialEq, Debug)] +enum Opt { + #[structopt(name = "fetch")] + Fetch { + #[structopt(long = "all")] + all: bool, + #[structopt(short = "f", long = "force")] + /// Overwrite local branches. + force: bool, + repo: String + }, + + #[structopt(name = "add")] + Add { + #[structopt(short = "i", long = "interactive")] + interactive: bool, + #[structopt(short = "v", long = "verbose")] + verbose: bool + } +} + +#[test] +fn test_fetch() { + assert_eq!(Opt::Fetch { all: true, force: false, repo: "origin".to_string() }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "fetch", "--all", "origin"]))); + assert_eq!(Opt::Fetch { all: false, force: true, repo: "origin".to_string() }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "fetch", "-f", "origin"]))); +} + +#[test] +fn test_add() { + assert_eq!(Opt::Add { interactive: false, verbose: false }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "add"]))); + assert_eq!(Opt::Add { interactive: true, verbose: true }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "add", "-i", "-v"]))); +} + +#[test] +fn test_no_parse() { + let result = Opt::clap().get_matches_from_safe(&["test", "badcmd", "-i", "-v"]); + assert!(result.is_err()); + + let result = Opt::clap().get_matches_from_safe(&["test", "add", "--badoption"]); + assert!(result.is_err()); +} From c010cf0266dd43e84748fa450fc1da5ce1a45605 Mon Sep 17 00:00:00 2001 From: William Yao Date: Thu, 29 Jun 2017 19:37:52 -0500 Subject: [PATCH 0033/3337] add ability to put in nested subcommands (texitoi/structopt#1) --- structopt-derive/src/lib.rs | 147 +++++++++++++++++++++++------------- 1 file changed, 93 insertions(+), 54 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 184ec5d60e9..62bc8b39339 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -92,6 +92,7 @@ pub fn structopt(input: TokenStream) -> TokenStream { gen.parse().unwrap() } +#[derive(Copy, Clone)] enum Ty { Bool, U64, @@ -198,39 +199,56 @@ fn is_subcommand(field: &Field) -> bool { /// Generate a block of code to add arguments/subcommands corresponding to /// the `fields` to an app. fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens { - let args = fields.iter().map(|field| { - let name = gen_name(field); - let cur_type = ty(&field.ty); - let convert_type = match cur_type { - Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty), - _ => &field.ty, - }; - let validator = quote! { - validator(|s| s.parse::<#convert_type>() - .map(|_| ()) - .map_err(|e| e.description().into())) - }; - let modifier = match cur_type { - Ty::Bool => quote!( .takes_value(false).multiple(false) ), - Ty::U64 => quote!( .takes_value(false).multiple(true) ), - Ty::Option => quote!( .takes_value(true).multiple(false).#validator ), - Ty::Vec => quote!( .takes_value(true).multiple(true).#validator ), - Ty::Other => { - let required = extract_attrs(&field.attrs, AttrSource::Field) - .find(|&(ref i, _)| i.as_ref() == "default_value") - .is_none(); - quote!( .takes_value(true).multiple(false).required(#required).#validator ) - }, - }; - let from_attr = extract_attrs(&field.attrs, AttrSource::Field) - .filter(|&(ref i, _)| i.as_ref() != "name") - .map(|(i, l)| quote!(.#i(#l))); - quote!( .arg(_structopt::clap::Arg::with_name(stringify!(#name)) #modifier #(#from_attr)*) ) - }); + let subcmds: Vec = fields.iter() + .filter(|&field| is_subcommand(field)) + .map(|field| { + let cur_type = ty(&field.ty); + let subcmd_type = match (cur_type, sub_type(&field.ty)) { + (Ty::Option, Some(sub_type)) => sub_type, + _ => &field.ty + }; + + quote!( let #app_var = #subcmd_type ::augment_clap( #app_var ); ) + }) + .collect(); + let args = fields.iter() + .filter(|&field| !is_subcommand(field)) + .map(|field| { + let name = gen_name(field); + let cur_type = ty(&field.ty); + let convert_type = match cur_type { + Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty), + _ => &field.ty, + }; + let validator = quote! { + validator(|s| s.parse::<#convert_type>() + .map(|_| ()) + .map_err(|e| e.description().into())) + }; + let modifier = match cur_type { + Ty::Bool => quote!( .takes_value(false).multiple(false) ), + Ty::U64 => quote!( .takes_value(false).multiple(true) ), + Ty::Option => quote!( .takes_value(true).multiple(false).#validator ), + Ty::Vec => quote!( .takes_value(true).multiple(true).#validator ), + Ty::Other => { + let required = extract_attrs(&field.attrs, AttrSource::Field) + .find(|&(ref i, _)| i.as_ref() == "default_value") + .is_none(); + quote!( .takes_value(true).multiple(false).required(#required).#validator ) + }, + }; + let from_attr = extract_attrs(&field.attrs, AttrSource::Field) + .filter(|&(ref i, _)| i.as_ref() != "name") + .map(|(i, l)| quote!(.#i(#l))); + quote!( .arg(_structopt::clap::Arg::with_name(stringify!(#name)) #modifier #(#from_attr)*) ) + }); + + assert!(subcmds.len() <= 1, "cannot have more than one nested subcommand"); quote! {{ use std::error::Error; let #app_var = #app_var #( #args )* ; + #( #subcmds )* #app_var }} } @@ -239,28 +257,41 @@ fn gen_constructor(fields: &[Field]) -> quote::Tokens { let fields = fields.iter().map(|field| { let field_name = field.ident.as_ref().unwrap(); let name = gen_name(field); - let convert = match ty(&field.ty) { - Ty::Bool => quote!(is_present(stringify!(#name))), - Ty::U64 => quote!(occurrences_of(stringify!(#name))), - Ty::Option => quote! { - value_of(stringify!(#name)) - .as_ref() - .map(|s| s.parse().unwrap()) - }, - Ty::Vec => quote! { - values_of(stringify!(#name)) - .map(|v| v.map(|s| s.parse().unwrap()).collect()) - .unwrap_or_else(Vec::new) - }, - Ty::Other => quote! { - value_of(stringify!(#name)) - .as_ref() - .unwrap() - .parse() - .unwrap() - }, - }; - quote!( #field_name: matches.#convert ) + if is_subcommand(field) { + let cur_type = ty(&field.ty); + let subcmd_type = match (cur_type, sub_type(&field.ty)) { + (Ty::Option, Some(sub_type)) => sub_type, + _ => &field.ty + }; + let unwrapper = match cur_type { + Ty::Option => quote!(), + _ => quote!( .unwrap() ) + }; + quote!( #field_name: #subcmd_type ::from_subcommand(matches.subcommand()) #unwrapper ) + } else { + let convert = match ty(&field.ty) { + Ty::Bool => quote!(is_present(stringify!(#name))), + Ty::U64 => quote!(occurrences_of(stringify!(#name))), + Ty::Option => quote! { + value_of(stringify!(#name)) + .as_ref() + .map(|s| s.parse().unwrap()) + }, + Ty::Vec => quote! { + values_of(stringify!(#name)) + .map(|v| v.map(|s| s.parse().unwrap()).collect()) + .unwrap_or_else(Vec::new) + }, + Ty::Other => quote! { + value_of(stringify!(#name)) + .as_ref() + .unwrap() + .parse() + .unwrap() + }, + }; + quote!( #field_name: matches.#convert ) + } }); quote! {{ @@ -289,19 +320,26 @@ fn gen_from_clap(struct_name: &Ident, fields: &[Field]) -> quote::Tokens { } } -fn gen_clap(struct_attrs: &[Attribute]) -> quote::Tokens { +fn gen_clap(struct_attrs: &[Attribute], subcmd_required: bool) -> quote::Tokens { let struct_attrs: Vec<_> = extract_attrs(struct_attrs, AttrSource::Struct).collect(); let name = from_attr_or_env(&struct_attrs, "name", "CARGO_PKG_NAME"); let version = from_attr_or_env(&struct_attrs, "version", "CARGO_PKG_VERSION"); let author = from_attr_or_env(&struct_attrs, "author", "CARGO_PKG_AUTHORS"); let about = from_attr_or_env(&struct_attrs, "about", "CARGO_PKG_DESCRIPTION"); + let setting = if subcmd_required { + quote!( .setting(_structopt::clap::AppSettings::SubcommandRequired) ) + } else { + quote!() + }; quote! { fn clap<'a, 'b>() -> _structopt::clap::App<'a, 'b> { let app = _structopt::clap::App::new(#name) .version(#version) .author(#author) - .about(#about); + .about(#about) + #setting + ; Self::augment_clap(app) } } @@ -409,7 +447,8 @@ fn gen_from_subcommand(name: &Ident, variants: &[Variant]) -> quote::Tokens { } fn impl_structopt_for_struct(name: &Ident, fields: &[Field], attrs: &[Attribute]) -> quote::Tokens { - let clap = gen_clap(attrs); + let subcmd_required = fields.iter().any(is_subcommand); + let clap = gen_clap(attrs, subcmd_required); let augment_clap = gen_augment_clap(fields); let from_clap = gen_from_clap(name, fields); From baa96165f1627dc0ec6b1b54389661acc2b38f28 Mon Sep 17 00:00:00 2001 From: William Yao Date: Thu, 29 Jun 2017 19:44:34 -0500 Subject: [PATCH 0034/3337] ignore unused matches for empty subcommands (texitoi/structopt#1) --- structopt-derive/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 62bc8b39339..abd0db0caa4 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -499,7 +499,8 @@ fn impl_structopt(ast: &DeriveInput) -> quote::Tokens { let dummy_const = Ident::new(format!("_IMPL_STRUCTOPT_FOR_{}", struct_name)); quote! { - #[allow(non_upper_case_globals, unused_attributes, unused_imports)] + #[allow(non_upper_case_globals)] + #[allow(unused_attributes, unused_imports, unused_variables)] const #dummy_const: () = { extern crate structopt as _structopt; use structopt::StructOpt; From 91e2d28e81c4d1e6d7257c9e0d6cb27e9268fda3 Mon Sep 17 00:00:00 2001 From: William Yao Date: Thu, 29 Jun 2017 19:53:07 -0500 Subject: [PATCH 0035/3337] allow for optional subcommands (texitoi/structopt#1) --- structopt-derive/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index abd0db0caa4..92b0b61962c 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -447,7 +447,13 @@ fn gen_from_subcommand(name: &Ident, variants: &[Variant]) -> quote::Tokens { } fn impl_structopt_for_struct(name: &Ident, fields: &[Field], attrs: &[Attribute]) -> quote::Tokens { - let subcmd_required = fields.iter().any(is_subcommand); + let subcmd_required = fields.iter().any(|field| { + let cur_type = ty(&field.ty); + match cur_type { + Ty::Option => false, + _ => is_subcommand(field) + } + }); let clap = gen_clap(attrs, subcmd_required); let augment_clap = gen_augment_clap(fields); let from_clap = gen_from_clap(name, fields); From b295234d5d9597b3a64ebc8b34ede60302012e7f Mon Sep 17 00:00:00 2001 From: William Yao Date: Thu, 29 Jun 2017 20:02:59 -0500 Subject: [PATCH 0036/3337] add test suite for nested subcommands (texitoi/structopt#1) --- tests/nested-subcommands.rs | 116 ++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 tests/nested-subcommands.rs diff --git a/tests/nested-subcommands.rs b/tests/nested-subcommands.rs new file mode 100644 index 00000000000..d978982e7b3 --- /dev/null +++ b/tests/nested-subcommands.rs @@ -0,0 +1,116 @@ +// Copyright (c) 2017 Guillaume Pinot +// +// This work is free. You can redistribute it and/or modify it under +// the terms of the Do What The Fuck You Want To Public License, +// Version 2, as published by Sam Hocevar. See the COPYING file for +// more details. + +extern crate structopt; +#[macro_use] extern crate structopt_derive; + +use structopt::StructOpt; + +#[derive(StructOpt, PartialEq, Debug)] +struct Opt { + #[structopt(short = "f", long = "force")] + force: bool, + #[structopt(short = "v", long = "verbose")] + verbose: u64, + #[structopt(subcommand)] + cmd: Sub +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Sub { + #[structopt(name = "fetch")] + Fetch {}, + #[structopt(name = "add")] + Add {} +} + +#[derive(StructOpt, PartialEq, Debug)] +struct Opt2 { + #[structopt(short = "f", long = "force")] + force: bool, + #[structopt(short = "v", long = "verbose")] + verbose: u64, + #[structopt(subcommand)] + cmd: Option +} + +#[test] +fn test_no_cmd() { + let result = Opt::clap().get_matches_from_safe(&["test"]); + assert!(result.is_err()); + + assert_eq!(Opt2 { force: false, verbose: 0, cmd: None }, + Opt2::from_clap(Opt2::clap().get_matches_from(&["test"]))); +} + +#[test] +fn test_fetch() { + assert_eq!(Opt { force: false, verbose: 3, cmd: Sub::Fetch {} }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-vvv", "fetch"]))); + assert_eq!(Opt { force: true, verbose: 0, cmd: Sub::Fetch {} }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "--force", "fetch"]))); +} + +#[test] +fn test_add() { + assert_eq!(Opt { force: false, verbose: 0, cmd: Sub::Add {} }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "add"]))); + assert_eq!(Opt { force: false, verbose: 2, cmd: Sub::Add {} }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-vv", "add"]))); +} + +#[test] +fn test_badinput() { + let result = Opt::clap().get_matches_from_safe(&["test", "badcmd"]); + assert!(result.is_err()); + let result = Opt::clap().get_matches_from_safe(&["test", "add", "--verbose"]); + assert!(result.is_err()); + let result = Opt::clap().get_matches_from_safe(&["test", "--badopt", "add"]); + assert!(result.is_err()); + let result = Opt::clap().get_matches_from_safe(&["test", "add", "--badopt"]); + assert!(result.is_err()); +} + +#[derive(StructOpt, PartialEq, Debug)] +struct Opt3 { + #[structopt(short = "a", long = "all")] + all: bool, + #[structopt(subcommand)] + cmd: Sub2 +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Sub2 { + #[structopt(name = "foo")] + Foo { + file: String, + #[structopt(subcommand)] + cmd: Sub3 + }, + #[structopt(name = "bar")] + Bar { + } +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Sub3 { + #[structopt(name = "baz")] + Baz {}, + #[structopt(name = "quux")] + Quux {} +} + +#[test] +fn test_subsubcommand() { + assert_eq!( + Opt3 { + all: true, + cmd: Sub2::Foo { file: "lib.rs".to_string(), cmd: Sub3::Quux {} } + }, + Opt3::from_clap(Opt3::clap().get_matches_from(&["test", "--all", "foo", "lib.rs", "quux"])) + ); +} From a20a9e7be0208f06698746c6f699da71b8e38df2 Mon Sep 17 00:00:00 2001 From: William Yao Date: Mon, 3 Jul 2017 13:29:37 -0500 Subject: [PATCH 0037/3337] fix hyphenated subcommands --- structopt-derive/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 92b0b61962c..1c198d2fc91 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -379,11 +379,11 @@ fn gen_augment_clap_enum(variants: &[Variant]) -> quote::Tokens { let name = extract_attrs(&variant.attrs, AttrSource::Struct) .filter_map(|attr| match attr { (ref i, Lit::Str(ref s, ..)) if i == "name" => - Some(Ident::new(s as &str)), + Some(s.to_string()), _ => None }) .next() - .unwrap_or_else(|| variant.ident.clone()); + .unwrap_or_else(|| variant.ident.to_string()); let app_var = Ident::new("subcommand"); let arg_block = match variant.data { VariantData::Struct(ref fields) => gen_augmentation(fields, &app_var), @@ -392,7 +392,7 @@ fn gen_augment_clap_enum(variants: &[Variant]) -> quote::Tokens { quote! { .subcommand({ - let #app_var = _structopt::clap::SubCommand::with_name( stringify!(#name) ); + let #app_var = _structopt::clap::SubCommand::with_name( #name ); #arg_block }) } From f9fc92399316ba339883901acda627eae9611879 Mon Sep 17 00:00:00 2001 From: William Yao Date: Mon, 3 Jul 2017 13:36:36 -0500 Subject: [PATCH 0038/3337] add link to structopt-derive documentation from structopt crate --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b67860d6468..68905506b40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,8 +10,9 @@ //! `StructOpt` trait definition //! //! This crate defines the `StructOpt` trait. Alone, this crate is of -//! little interest. See the `structopt-derive` crate to -//! automatically generate implementation of this trait. +//! little interest. See the +//! [`structopt-derive`](https://docs.rs/structopt-derive) crate to +//! automatically generate an implementation of this trait. extern crate clap as _clap; From f68a35c3a18511452a0efd104e87f504924b0df5 Mon Sep 17 00:00:00 2001 From: William Yao Date: Mon, 3 Jul 2017 13:44:17 -0500 Subject: [PATCH 0039/3337] preliminary editing of structopt-derive documentation --- structopt-derive/src/lib.rs | 42 +++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 1c198d2fc91..11f666e866b 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -5,9 +5,9 @@ // Version 2, as published by Sam Hocevar. See the COPYING file for // more details. -//! How to `derive(StructOpt)` +//! ## How to `derive(StructOpt)` //! -//! First, look at an example: +//! First, let's look at an example: //! //! ```ignore //! #[derive(StructOpt)] @@ -24,28 +24,30 @@ //! } //! ``` //! -//! So, `derive(StructOpt)` do the job, and `structopt` attribute is +//! So `derive(StructOpt)` tells Rust to generate a command line parser, +//! and the various `structopt` attributes are simply //! used for additional parameters. //! //! First, define a struct, whatever its name. This structure will //! correspond to a `clap::App`. Every method of `clap::App` in the -//! form of `fn function_name(self, &str)` can be use in the form of -//! attributes. Our example call for example something like -//! `app.about("An example of StructOpt usage.")`. There is some -//! special attributes: +//! form of `fn function_name(self, &str)` can be use through attributes +//! placed on the struct. In our example above, the `about` attribute +//! will become an `.about("An example of StructOpt usage.")` call on the +//! generated `clap::App`. There are a few attributes that will default +//! if not specified: //! -//! - `name`: correspond to the creation of the `App` object. Our -//! example does `clap::App::new("example")`. Default to -//! the crate name given by cargo. -//! - `version`: default to the crate version given by cargo. -//! - `author`: default to the crate version given by cargo. -//! - `about`: default to the crate version given by cargo. +//! - `name`: The binary name displayed in help messages. Defaults + to the crate name given by Cargo. +//! - `version`: Defaults to the crate version given by Cargo. +//! - `author`: Defaults to the crate author name given by Cargo. +//! - `about`: Defaults to the crate description given by Cargo. //! -//! Then, each field of the struct correspond to a `clap::Arg`. As -//! for the struct attributes, every method of `clap::Arg` in the form -//! of `fn function_name(self, &str)` can be use in the form of -//! attributes. The `name` attribute can be used to customize the -//! `Arg::with_name()` call (default to the field name). +//! Then, each field of the struct not marked as a subcommand corresponds +//! to a `clap::Arg`. As with the struct attributes, every method of +//! `clap::Arg`in the form of `fn function_name(self, &str)` can be used +//! through specifying it as an attribute. +//! The `name` attribute can be used to customize the +//! `Arg::with_name()` call (defaults to the field name). //! //! The type of the field gives the kind of argument: //! @@ -55,10 +57,10 @@ //! `u64` | number of params | `.takes_value(false).multiple(true)` //! `Option` | optional argument | `.takes_value(true).multiple(false)` //! `Vec` | list of arguments | `.takes_value(true).multiple(true)` -//! `T: FromStr` | required argument | `.takes_value(true).multiple(false).required(!has_default)` +//! `T: FromStr` | required argument | `.takes_value(true).multiple(false).required(!has_default)` //! //! The `FromStr` trait is used to convert the argument to the given -//! type, and the `Arg::validator` method is setted to a method using +//! type, and the `Arg::validator` method is set to a method using //! `FromStr::Error::description()`. //! //! Thus, the `speed` argument is generated as: From a8e466c7bb41f960f67e9585cdc69558f6014171 Mon Sep 17 00:00:00 2001 From: William Yao Date: Mon, 3 Jul 2017 14:18:31 -0500 Subject: [PATCH 0040/3337] add documentation in structopt-derive about how to use subcommands --- structopt-derive/src/lib.rs | 105 ++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 11f666e866b..80370abcd9f 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -76,6 +76,111 @@ //! .help("Set speed") //! .default_value("42") //! ``` +//! +//! ## Subcomamnds +//! +//! Some applications, like `git`, support "subcommands;" an extra command that +//! is used to differentiate what the application should do. With `git`, these +//! would be `add`, `init`, `fetch`, `commit`, for a few examples. +//! +//! `clap` has this functionality, so `structopt` supports this through enums: +//! +//! ```ignore +//! #[derive(StructOpt)] +//! #[structopt(name = "git", about = "the stupid content tracker")] +//! enum Git { +//! #[structopt(name = "add")] +//! Add { +//! #[structopt(short = "i")] +//! interactive: bool, +//! #[structopt(short = "p")] +//! patch: bool, +//! files: Vec +//! }, +//! #[structopt(name = "fetch")] +//! Fetch { +//! #[structopt(long = "dry-run")] +//! dry_run: bool, +//! #[structopt(long = "all")] +//! all: bool, +//! repository: Option +//! }, +//! #[structopt(name = "commit")] +//! Commit { +//! #[structopt(short = "m")] +//! message: Option, +//! #[structopt(short = "a")] +//! all: bool +//! } +//! } +//! ``` +//! +//! Using `derive(StructOpt)` on an enum instead of a struct will produce +//! a `clap::App` that only takes subcommands. So `git add`, `git fetch`, +//! and `git commit` would be commands allowed for the above example. +//! +//! `structopt` also provides support for applications where certain flags +//! need to apply to all subcommands, as well as nested subcommands: +//! +//! ```ignore +//! #[derive(StructOpt)] +//! #[structopt(name = "make-cookie")] +//! struct MakeCookie { +//! #[structopt(name = "supervisor", default_value = "Puck")] +//! supervising_faerie: Option, +//! #[structopt(name = "tree")] +//! /// The faerie tree this cookie is being made in. +//! tree: Option, +//! #[structopt(subcommand)] // Note that we mark a field as a subcommand +//! cmd: Command +//! } +//! +//! #[derive(StructOpt)] +//! enum Command { +//! #[structopt(name = "pound")] +//! /// Pound acorns into flour for cookie dough. +//! Pound { +//! acorns: u32 +//! }, +//! #[structopt(name = "sparkle")] +//! /// Add magical sparkles -- the secret ingredient! +//! Sparkle { +//! #[structopt(short = "m")] +//! magicality: u64, +//! #[structopt(short = "c")] +//! color: String +//! }, +//! #[structopt(name = "finish")] +//! Finish { +//! #[structopt(short = "t")] +//! time: u32, +//! #[structopt(subcommand)] // Note that we mark a field as a subcommand +//! type: FinishType +//! } +//! } +//! +//! #[derive(StructOpt)] +//! enum FinishType { +//! #[structopt(name = "glaze")] +//! Glaze { +//! applications: u32 +//! }, +//! #[structopt(name = "powder")] +//! Powder { +//! flavor: String, +//! dips: u32 +//! } +//! } +//! ``` +//! +//! Marking a field with `structopt(subcommand)` will add the subcommands of the +//! designated enum to the current `clap::App`. The designated enum *must* also +//! be derived `StructOpt`. So the above example would take the following +//! commands: +//! +//! + `make-cookie pound 50` +//! + `make-cookie sparkle -mmm --color "green"` +//! + `make-cookie finish 130 glaze 3` extern crate proc_macro; extern crate syn; From 12e88e1ea8fee78aa51587eab1f478e6e6dddb4b Mon Sep 17 00:00:00 2001 From: William Yao Date: Mon, 3 Jul 2017 14:22:17 -0500 Subject: [PATCH 0041/3337] note about optional subcommands --- structopt-derive/src/lib.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 80370abcd9f..07c758e79da 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -181,6 +181,25 @@ //! + `make-cookie pound 50` //! + `make-cookie sparkle -mmm --color "green"` //! + `make-cookie finish 130 glaze 3` +//! +//! ### Optional subcommands +//! +//! A nested subcommand can be marked optional: +//! +//! #[derive(StructOpt)] +//! #[structopt(name = "foo")] +//! struct Foo { +//! file: String, +//! #[structopt(subcommand)] +//! cmd: Option +//! } +//! +//! #[derive(StructOpt)] +//! enum Command { +//! Bar {}, +//! Baz {}, +//! Quux {} +//! } extern crate proc_macro; extern crate syn; From a21a01acb237e3feaff5cf7078d3b985781281e8 Mon Sep 17 00:00:00 2001 From: William Yao Date: Mon, 3 Jul 2017 14:26:03 -0500 Subject: [PATCH 0042/3337] add note about using doc comments for help messages (texitoi/structopt#1) --- structopt-derive/src/lib.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 07c758e79da..a29b7628e37 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -37,7 +37,7 @@ //! if not specified: //! //! - `name`: The binary name displayed in help messages. Defaults - to the crate name given by Cargo. +//! to the crate name given by Cargo. //! - `version`: Defaults to the crate version given by Cargo. //! - `author`: Defaults to the crate author name given by Cargo. //! - `about`: Defaults to the crate description given by Cargo. @@ -77,6 +77,26 @@ //! .default_value("42") //! ``` //! +//! ## Help messages +//! +//! Help messages for the whole binary or individual arguments can be +//! specified using the `about` attribute on the struct/field, as we've +//! already seen. For convenience, they can also be specified using +//! doc comments. For example: +//! +//! ```ignore +//! #[derive(StructOpt)] +//! #[structopt(name = "foo")] +//! /// The help message that will be displayed when passing `--help`. +//! struct Foo { +//! ... +//! #[structopt(short = "b")] +//! /// The description for the arg that will be displayed when passing `--help`. +//! bar: String +//! ... +//! } +//! ``` +//! //! ## Subcomamnds //! //! Some applications, like `git`, support "subcommands;" an extra command that From 593f042c770f2b18a8403a2defc436bb826d0966 Mon Sep 17 00:00:00 2001 From: William Yao Date: Mon, 3 Jul 2017 14:30:14 -0500 Subject: [PATCH 0043/3337] bump versions of structopt and structopt-derive --- Cargo.toml | 2 +- structopt-derive/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e787c873219..461f5f7a011 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.0.5" +version = "0.0.6" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index 77fa9d6d4bf..830448c2503 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt-derive" -version = "0.0.5" +version = "0.0.6" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct, derive crate." documentation = "https://docs.rs/structopt-derive" From 6f385b4adbef825b0e3c26a663549f45436c8267 Mon Sep 17 00:00:00 2001 From: William Yao Date: Mon, 3 Jul 2017 14:49:01 -0500 Subject: [PATCH 0044/3337] add missing backticks (oops) --- structopt-derive/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index a29b7628e37..bc104669ebd 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -206,6 +206,7 @@ //! //! A nested subcommand can be marked optional: //! +//! ```ignore //! #[derive(StructOpt)] //! #[structopt(name = "foo")] //! struct Foo { @@ -220,6 +221,7 @@ //! Baz {}, //! Quux {} //! } +//! ``` extern crate proc_macro; extern crate syn; From 5be4fb38e4296d0205b04c5370747f629155fd27 Mon Sep 17 00:00:00 2001 From: William Yao Date: Mon, 3 Jul 2017 14:59:09 -0500 Subject: [PATCH 0045/3337] bump versions of structopt in README, fix typo --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index daaf1b7e8fd..e0acfe57e7c 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ Find it on Docs.rs: [structopt-derive](https://docs.rs/structopt-derive) and [st ## Example -Add `structopt` and `structop-derive` to your dependencies of your `Cargo.toml`: +Add `structopt` and `structopt-derive` to your dependencies of your `Cargo.toml`: ```toml [dependencies] -structopt = "0.0.3" -structopt-derive = "0.0.3" +structopt = "0.0.6" +structopt-derive = "0.0.6" ``` And then, in your rust file: From 746f8e6a346645764b69db13456e032b09f24f32 Mon Sep 17 00:00:00 2001 From: William Yao Date: Mon, 3 Jul 2017 15:22:53 -0500 Subject: [PATCH 0046/3337] bump versions to 0.1.0 --- Cargo.toml | 4 ++-- README.md | 4 ++-- structopt-derive/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 461f5f7a011..e18bbbb4163 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.0.6" +version = "0.1.0" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" @@ -17,6 +17,6 @@ travis-ci = { repository = "TeXitoi/structopt" } clap = "2.20" [dev-dependencies] -structopt-derive = { path = "structopt-derive", version = "0.0.5" } +structopt-derive = { path = "structopt-derive", version = "0.1.0" } [workspace] diff --git a/README.md b/README.md index e0acfe57e7c..64dc3f79eb9 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ Find it on Docs.rs: [structopt-derive](https://docs.rs/structopt-derive) and [st Add `structopt` and `structopt-derive` to your dependencies of your `Cargo.toml`: ```toml [dependencies] -structopt = "0.0.6" -structopt-derive = "0.0.6" +structopt = "0.1.0" +structopt-derive = "0.1.0" ``` And then, in your rust file: diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index 830448c2503..32892387e88 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt-derive" -version = "0.0.6" +version = "0.1.0" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct, derive crate." documentation = "https://docs.rs/structopt-derive" From c3815cdd812085f7203d45bda41146a508a0eb7b Mon Sep 17 00:00:00 2001 From: William Yao Date: Mon, 3 Jul 2017 16:01:00 -0500 Subject: [PATCH 0047/3337] remove unnecessary Option in documentation [ci skip] --- structopt-derive/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index bc104669ebd..d8e9ab32662 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -147,7 +147,7 @@ //! #[structopt(name = "make-cookie")] //! struct MakeCookie { //! #[structopt(name = "supervisor", default_value = "Puck")] -//! supervising_faerie: Option, +//! supervising_faerie: String, //! #[structopt(name = "tree")] //! /// The faerie tree this cookie is being made in. //! tree: Option, From e05c792f658787096b2b0b7095df7b973c1f9a7d Mon Sep 17 00:00:00 2001 From: William Yao Date: Mon, 3 Jul 2017 16:05:17 -0500 Subject: [PATCH 0048/3337] fix typoes --- structopt-derive/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index d8e9ab32662..305fafffc11 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -44,7 +44,7 @@ //! //! Then, each field of the struct not marked as a subcommand corresponds //! to a `clap::Arg`. As with the struct attributes, every method of -//! `clap::Arg`in the form of `fn function_name(self, &str)` can be used +//! `clap::Arg` in the form of `fn function_name(self, &str)` can be used //! through specifying it as an attribute. //! The `name` attribute can be used to customize the //! `Arg::with_name()` call (defaults to the field name). @@ -97,7 +97,7 @@ //! } //! ``` //! -//! ## Subcomamnds +//! ## Subcommands //! //! Some applications, like `git`, support "subcommands;" an extra command that //! is used to differentiate what the application should do. With `git`, these From 3772285091732c7a8b25f3333599f2eea5553078 Mon Sep 17 00:00:00 2001 From: William Yao Date: Mon, 3 Jul 2017 16:08:18 -0500 Subject: [PATCH 0049/3337] add extra Arg methods in doc so examples still work --- structopt-derive/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 305fafffc11..1423b39e32f 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -146,7 +146,7 @@ //! #[derive(StructOpt)] //! #[structopt(name = "make-cookie")] //! struct MakeCookie { -//! #[structopt(name = "supervisor", default_value = "Puck")] +//! #[structopt(name = "supervisor", default_value = "Puck", required = false, long = "supervisor")] //! supervising_faerie: String, //! #[structopt(name = "tree")] //! /// The faerie tree this cookie is being made in. From 91fb4a40e4da7458a5f779b13d359fc428b213ba Mon Sep 17 00:00:00 2001 From: William Yao Date: Tue, 4 Jul 2017 16:59:27 -0500 Subject: [PATCH 0050/3337] allow for documentation to be placed on subcommands --- examples/git.rs | 40 +++++++++++++++++++++++++++++++++++++ structopt-derive/src/lib.rs | 12 +++++++---- tests/subcommands.rs | 18 ++++++++++++++++- 3 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 examples/git.rs diff --git a/examples/git.rs b/examples/git.rs new file mode 100644 index 00000000000..f25d66cec04 --- /dev/null +++ b/examples/git.rs @@ -0,0 +1,40 @@ +//! `git.rs` serves as a demonstration of how to use subcommands, +//! as well as a demonstration of adding documentation to subcommands. +//! Documentation can be added either through doc comments or the +//! `about` attribute. + +extern crate structopt; +#[macro_use] extern crate structopt_derive; + +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = "git")] +/// the stupid content tracker +enum Opt { + #[structopt(name = "fetch")] + /// fetch branches from remote repository + Fetch { + #[structopt(long = "dry-run")] + dry_run: bool, + #[structopt(long = "all")] + all: bool, + #[structopt(default_value = "origin")] + repository: String + }, + #[structopt(name = "add")] + /// add files to the staging area + Add { + #[structopt(short = "i")] + interactive: bool, + #[structopt(short = "a")] + all: bool, + files: Vec + } +} + +fn main() { + let matches = Opt::from_args(); + + println!("{:?}", matches); +} diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 1423b39e32f..700524b8557 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -537,10 +537,14 @@ fn gen_augment_clap_enum(variants: &[Variant]) -> quote::Tokens { VariantData::Struct(ref fields) => gen_augmentation(fields, &app_var), _ => unreachable!() }; + let from_attr = extract_attrs(&variant.attrs, AttrSource::Struct) + .filter(|&(ref i, _)| i != "name") + .map(|(i, l)| quote!( .#i(#l) )); quote! { .subcommand({ - let #app_var = _structopt::clap::SubCommand::with_name( #name ); + let #app_var = _structopt::clap::SubCommand::with_name( #name ) + #( #from_attr )* ; #arg_block }) } @@ -567,11 +571,11 @@ fn gen_from_subcommand(name: &Ident, variants: &[Variant]) -> quote::Tokens { let sub_name = extract_attrs(&variant.attrs, AttrSource::Struct) .filter_map(|attr| match attr { (ref i, Lit::Str(ref s, ..)) if i == "name" => - Some(Ident::new(s as &str)), + Some(s.to_string()), _ => None }) .next() - .unwrap_or_else(|| variant.ident.clone()); + .unwrap_or_else(|| variant.ident.as_ref().to_string()); let variant_name = &variant.ident; let constructor_block = match variant.data { VariantData::Struct(ref fields) => gen_constructor(fields), @@ -579,7 +583,7 @@ fn gen_from_subcommand(name: &Ident, variants: &[Variant]) -> quote::Tokens { }; quote! { - (stringify!(#sub_name), Some(matches)) => + (#sub_name, Some(matches)) => Some(#name :: #variant_name #constructor_block) } }); diff --git a/tests/subcommands.rs b/tests/subcommands.rs index e2aa442352e..999c8ddd24e 100644 --- a/tests/subcommands.rs +++ b/tests/subcommands.rs @@ -12,7 +12,7 @@ use structopt::StructOpt; #[derive(StructOpt, PartialEq, Debug)] enum Opt { - #[structopt(name = "fetch")] + #[structopt(name = "fetch", about = "Fetch stuff from GitHub.")] Fetch { #[structopt(long = "all")] all: bool, @@ -55,3 +55,19 @@ fn test_no_parse() { let result = Opt::clap().get_matches_from_safe(&["test", "add", "--badoption"]); assert!(result.is_err()); } + +#[derive(StructOpt, PartialEq, Debug)] +enum Opt2 { + #[structopt(name = "do-something")] + DoSomething { + arg: String + } +} + +#[test] +/// This test is specifically to make sure that hyphenated subcommands get +/// processed correctly. +fn test_hyphenated_subcommands() { + assert_eq!(Opt2::DoSomething { arg: "blah".to_string() }, + Opt2::from_clap(Opt2::clap().get_matches_from(&["test", "do-something", "blah"]))); +} From 810831cfe158b25c6f41bd82db170bb6f025a56d Mon Sep 17 00:00:00 2001 From: William Yao Date: Tue, 4 Jul 2017 17:09:54 -0500 Subject: [PATCH 0051/3337] add support for unit-variant subcommands --- structopt-derive/src/lib.rs | 6 ++++-- tests/subcommands.rs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 700524b8557..e79d9df34ba 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -535,6 +535,7 @@ fn gen_augment_clap_enum(variants: &[Variant]) -> quote::Tokens { let app_var = Ident::new("subcommand"); let arg_block = match variant.data { VariantData::Struct(ref fields) => gen_augmentation(fields, &app_var), + VariantData::Unit => quote!( #app_var ), _ => unreachable!() }; let from_attr = extract_attrs(&variant.attrs, AttrSource::Struct) @@ -579,6 +580,7 @@ fn gen_from_subcommand(name: &Ident, variants: &[Variant]) -> quote::Tokens { let variant_name = &variant.ident; let constructor_block = match variant.data { VariantData::Struct(ref fields) => gen_constructor(fields), + VariantData::Unit => quote!(), // empty _ => unreachable!() }; @@ -621,10 +623,10 @@ fn impl_structopt_for_struct(name: &Ident, fields: &[Field], attrs: &[Attribute] fn impl_structopt_for_enum(name: &Ident, variants: &[Variant], attrs: &[Attribute]) -> quote::Tokens { if variants.iter().any(|variant| { - if let VariantData::Struct(..) = variant.data { false } else { true } + if let VariantData::Tuple(..) = variant.data { true } else { false } }) { - panic!("enum variants must use curly braces"); + panic!("enum variants cannot be tuples"); } let clap = gen_clap_enum(attrs); diff --git a/tests/subcommands.rs b/tests/subcommands.rs index 999c8ddd24e..14ad5413be0 100644 --- a/tests/subcommands.rs +++ b/tests/subcommands.rs @@ -71,3 +71,20 @@ fn test_hyphenated_subcommands() { assert_eq!(Opt2::DoSomething { arg: "blah".to_string() }, Opt2::from_clap(Opt2::clap().get_matches_from(&["test", "do-something", "blah"]))); } + +#[derive(StructOpt, PartialEq, Debug)] +enum Opt3 { + #[structopt(name = "add")] + Add, + #[structopt(name = "init")] + Init, + #[structopt(name = "fetch")] + Fetch +} + +#[test] +fn test_null_commands() { + assert_eq!(Opt3::Add, Opt3::from_clap(Opt3::clap().get_matches_from(&["test", "add"]))); + assert_eq!(Opt3::Init, Opt3::from_clap(Opt3::clap().get_matches_from(&["test", "init"]))); + assert_eq!(Opt3::Fetch, Opt3::from_clap(Opt3::clap().get_matches_from(&["test", "fetch"]))); +} From f6bd33d5deff61f165bae300e5e3d4c65f6e87f3 Mon Sep 17 00:00:00 2001 From: William Yao Date: Tue, 4 Jul 2017 17:11:30 -0500 Subject: [PATCH 0052/3337] update structopt-derive documentation to reflect unit subcommands change --- structopt-derive/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index e79d9df34ba..6535669ddd6 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -217,9 +217,9 @@ //! //! #[derive(StructOpt)] //! enum Command { -//! Bar {}, -//! Baz {}, -//! Quux {} +//! Bar, +//! Baz, +//! Quux //! } //! ``` From 443cc3e1458bae566be9dd925b04de65f3a528c0 Mon Sep 17 00:00:00 2001 From: William Yao Date: Fri, 7 Jul 2017 12:40:10 -0500 Subject: [PATCH 0053/3337] move `augment_clap` into inherent impl --- src/lib.rs | 3 --- structopt-derive/src/lib.rs | 7 +++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 68905506b40..36477c37788 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,9 +26,6 @@ pub trait StructOpt { /// Returns the corresponding `clap::App`. fn clap<'a, 'b>() -> clap::App<'a, 'b>; - /// Add this app's arguments/subcommands to another `clap::App`. - fn augment_clap<'a, 'b>(clap::App<'a, 'b>) -> clap::App<'a, 'b>; - /// Creates the struct from `clap::ArgMatches`. fn from_clap(clap::ArgMatches) -> Self; diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 6535669ddd6..9178842eae8 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -615,9 +615,12 @@ fn impl_structopt_for_struct(name: &Ident, fields: &[Field], attrs: &[Attribute] quote! { impl _structopt::StructOpt for #name { #clap - #augment_clap #from_clap } + + impl #name { + #augment_clap + } } } @@ -637,11 +640,11 @@ fn impl_structopt_for_enum(name: &Ident, variants: &[Variant], attrs: &[Attribut quote! { impl _structopt::StructOpt for #name { #clap - #augment_clap #from_clap } impl #name { + #augment_clap #from_subcommand } } From a7224b5255afd4c168b7c7678d6544f00eea3e54 Mon Sep 17 00:00:00 2001 From: William Yao Date: Fri, 7 Jul 2017 12:41:55 -0500 Subject: [PATCH 0054/3337] move assert --- structopt-derive/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 9178842eae8..3db8e81f2d6 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -359,6 +359,9 @@ fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens { quote!( let #app_var = #subcmd_type ::augment_clap( #app_var ); ) }) .collect(); + + assert!(subcmds.len() <= 1, "cannot have more than one nested subcommand"); + let args = fields.iter() .filter(|&field| !is_subcommand(field)) .map(|field| { @@ -391,8 +394,6 @@ fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens { quote!( .arg(_structopt::clap::Arg::with_name(stringify!(#name)) #modifier #(#from_attr)*) ) }); - assert!(subcmds.len() <= 1, "cannot have more than one nested subcommand"); - quote! {{ use std::error::Error; let #app_var = #app_var #( #args )* ; From 7c60c62ac549a180347a1f88412d5453f65efbdb Mon Sep 17 00:00:00 2001 From: Tshepang Lekhonkhobe Date: Wed, 19 Jul 2017 00:46:54 +0200 Subject: [PATCH 0055/3337] doc: improve explanation of "subcommands" --- structopt-derive/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 3db8e81f2d6..57a94246485 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -99,11 +99,13 @@ //! //! ## Subcommands //! -//! Some applications, like `git`, support "subcommands;" an extra command that -//! is used to differentiate what the application should do. With `git`, these -//! would be `add`, `init`, `fetch`, `commit`, for a few examples. +//! Some applications, especially large ones, split their functionality +//! through the use of "subcommands". Each of these act somewhat like a separate +//! command, but is part of the larger group. +//! One example is `git`, which has subcommands such as `add`, `commit`, +//! and `clone`, to mention just a few. //! -//! `clap` has this functionality, so `structopt` supports this through enums: +//! `clap` has this functionality, and `structopt` supports it through enums: //! //! ```ignore //! #[derive(StructOpt)] From b4494747c7889d97ae7aca9689688f45b779032f Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Sun, 27 Aug 2017 18:19:32 +0200 Subject: [PATCH 0056/3337] Remove some trailing spaces --- structopt-derive/src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 57a94246485..d2fac396d9c 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -24,7 +24,7 @@ //! } //! ``` //! -//! So `derive(StructOpt)` tells Rust to generate a command line parser, +//! So `derive(StructOpt)` tells Rust to generate a command line parser, //! and the various `structopt` attributes are simply //! used for additional parameters. //! @@ -98,7 +98,7 @@ //! ``` //! //! ## Subcommands -//! +//! //! Some applications, especially large ones, split their functionality //! through the use of "subcommands". Each of these act somewhat like a separate //! command, but is part of the larger group. @@ -156,7 +156,7 @@ //! #[structopt(subcommand)] // Note that we mark a field as a subcommand //! cmd: Command //! } -//! +//! //! #[derive(StructOpt)] //! enum Command { //! #[structopt(name = "pound")] @@ -180,7 +180,7 @@ //! type: FinishType //! } //! } -//! +//! //! #[derive(StructOpt)] //! enum FinishType { //! #[structopt(name = "glaze")] @@ -199,7 +199,7 @@ //! designated enum to the current `clap::App`. The designated enum *must* also //! be derived `StructOpt`. So the above example would take the following //! commands: -//! +//! //! + `make-cookie pound 50` //! + `make-cookie sparkle -mmm --color "green"` //! + `make-cookie finish 130 glaze 3` @@ -216,7 +216,7 @@ //! #[structopt(subcommand)] //! cmd: Option //! } -//! +//! //! #[derive(StructOpt)] //! enum Command { //! Bar, @@ -529,7 +529,7 @@ fn gen_augment_clap_enum(variants: &[Variant]) -> quote::Tokens { let subcommands = variants.iter().map(|variant| { let name = extract_attrs(&variant.attrs, AttrSource::Struct) .filter_map(|attr| match attr { - (ref i, Lit::Str(ref s, ..)) if i == "name" => + (ref i, Lit::Str(ref s, ..)) if i == "name" => Some(s.to_string()), _ => None }) @@ -553,7 +553,7 @@ fn gen_augment_clap_enum(variants: &[Variant]) -> quote::Tokens { }) } }); - + quote! { fn augment_clap<'a, 'b>(app: _structopt::clap::App<'a, 'b>) -> _structopt::clap::App<'a, 'b> { app #( #subcommands )* @@ -574,7 +574,7 @@ fn gen_from_subcommand(name: &Ident, variants: &[Variant]) -> quote::Tokens { let match_arms = variants.iter().map(|variant| { let sub_name = extract_attrs(&variant.attrs, AttrSource::Struct) .filter_map(|attr| match attr { - (ref i, Lit::Str(ref s, ..)) if i == "name" => + (ref i, Lit::Str(ref s, ..)) if i == "name" => Some(s.to_string()), _ => None }) @@ -586,7 +586,7 @@ fn gen_from_subcommand(name: &Ident, variants: &[Variant]) -> quote::Tokens { VariantData::Unit => quote!(), // empty _ => unreachable!() }; - + quote! { (#sub_name, Some(matches)) => Some(#name :: #variant_name #constructor_block) From 89f0078a0fd61c56662f0c593f8a9a08c86289ef Mon Sep 17 00:00:00 2001 From: Pascal Hertleif Date: Sun, 27 Aug 2017 18:19:19 +0200 Subject: [PATCH 0057/3337] Format multiple authors Cargo's `CARGO_PKG_AUTHORS` env var separates author names with a colon, which is not very pretty to look at. So, let's replace it with a comma. Clap has the same functionality it its `crate_authors!` macro but as it's only a few lines of code I didn't want to call that. --- structopt-derive/src/lib.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index d2fac396d9c..a91bca3cd60 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -471,11 +471,20 @@ fn gen_from_clap(struct_name: &Ident, fields: &[Field]) -> quote::Tokens { } } +fn format_author(raw_authors: Lit) -> Lit { + let raw_authors = match raw_authors { + Lit::Str(x, _) => x, + x => return x, + }; + let authors = raw_authors.replace(":", ", "); + Lit::Str(authors, StrStyle::Cooked) +} + fn gen_clap(struct_attrs: &[Attribute], subcmd_required: bool) -> quote::Tokens { let struct_attrs: Vec<_> = extract_attrs(struct_attrs, AttrSource::Struct).collect(); let name = from_attr_or_env(&struct_attrs, "name", "CARGO_PKG_NAME"); let version = from_attr_or_env(&struct_attrs, "version", "CARGO_PKG_VERSION"); - let author = from_attr_or_env(&struct_attrs, "author", "CARGO_PKG_AUTHORS"); + let author = format_author(from_attr_or_env(&struct_attrs, "author", "CARGO_PKG_AUTHORS")); let about = from_attr_or_env(&struct_attrs, "about", "CARGO_PKG_DESCRIPTION"); let setting = if subcmd_required { quote!( .setting(_structopt::clap::AppSettings::SubcommandRequired) ) @@ -510,7 +519,7 @@ fn gen_clap_enum(enum_attrs: &[Attribute]) -> quote::Tokens { let enum_attrs: Vec<_> = extract_attrs(enum_attrs, AttrSource::Struct).collect(); let name = from_attr_or_env(&enum_attrs, "name", "CARGO_PKG_NAME"); let version = from_attr_or_env(&enum_attrs, "version", "CARGO_PKG_VERSION"); - let author = from_attr_or_env(&enum_attrs, "author", "CARGO_PKG_AUTHORS"); + let author = format_author(from_attr_or_env(&enum_attrs, "author", "CARGO_PKG_AUTHORS")); let about = from_attr_or_env(&enum_attrs, "about", "CARGO_PKG_DESCRIPTION"); quote! { From ebde32aa5b344c5dc9a1c8d499a35978696f0456 Mon Sep 17 00:00:00 2001 From: Seebi Date: Wed, 1 Nov 2017 08:41:58 +0100 Subject: [PATCH 0058/3337] Add raw attributes (#26) * Add raw attributes This makes it possible to call clap functions that don't take strings, but any arbitrary value. Even functions that take more than one argument can be called. All attributes that are called `attribute_name_raw` are augmented to `attribute_name(value)` without quoting `value`. * Add tests for raw attributes and fix spelling --- examples/raw_attributes.rs | 37 ++++++++++++++++++ structopt-derive/src/lib.rs | 76 ++++++++++++++++++++++++------------- tests/raw_attributes.rs | 65 +++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 27 deletions(-) create mode 100644 examples/raw_attributes.rs create mode 100644 tests/raw_attributes.rs diff --git a/examples/raw_attributes.rs b/examples/raw_attributes.rs new file mode 100644 index 00000000000..a1ff6be3df7 --- /dev/null +++ b/examples/raw_attributes.rs @@ -0,0 +1,37 @@ +// Copyright (c) 2017 Guillaume Pinot +// +// This work is free. You can redistribute it and/or modify it under +// the terms of the Do What The Fuck You Want To Public License, +// Version 2, as published by Sam Hocevar. See the COPYING file for +// more details. + +extern crate structopt; +#[macro_use] +extern crate structopt_derive; + +use structopt::StructOpt; +use structopt::clap::AppSettings; + +/// An example of raw attributes +#[derive(StructOpt, Debug)] +#[structopt(global_settings_raw = "&[AppSettings::ColoredHelp, AppSettings::VersionlessSubcommands]")] +struct Opt { + /// Output file + #[structopt(short = "o", long = "output")] + output: String, + + /// admin_level to consider + #[structopt(short = "l", long = "level", aliases_raw = "&[\"set-level\", \"lvl\"]")] + level: Vec, + + /// Files to process + /// + /// `level` is required if a file is called `FILE`. + #[structopt(name = "FILE", requires_if_raw = "\"FILE\", \"level\"")] + files: Vec, +} + +fn main() { + let opt = Opt::from_args(); + println!("{:?}", opt); +} diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index a91bca3cd60..8e2ece8cd7b 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -48,6 +48,8 @@ //! through specifying it as an attribute. //! The `name` attribute can be used to customize the //! `Arg::with_name()` call (defaults to the field name). +//! For functions that do not take a `&str` as argument, the attribute can be +//! called `function_name_raw`, e. g. `aliases_raw = "&[\"alias\"]"`. //! //! The type of the field gives the kind of argument: //! @@ -385,14 +387,15 @@ fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens { Ty::Vec => quote!( .takes_value(true).multiple(true).#validator ), Ty::Other => { let required = extract_attrs(&field.attrs, AttrSource::Field) - .find(|&(ref i, _)| i.as_ref() == "default_value") + .find(|&(ref i, _)| i.as_ref() == "default_value" + || i.as_ref() == "default_value_raw") .is_none(); quote!( .takes_value(true).multiple(false).required(#required).#validator ) }, }; let from_attr = extract_attrs(&field.attrs, AttrSource::Field) .filter(|&(ref i, _)| i.as_ref() != "name") - .map(|(i, l)| quote!(.#i(#l))); + .map(|(i, l)| gen_attr_call(&i, &l)); quote!( .arg(_structopt::clap::Arg::with_name(stringify!(#name)) #modifier #(#from_attr)*) ) }); @@ -404,6 +407,21 @@ fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens { }} } +/// Interpret the value of `*_raw` attributes as code and the rest as strings. +fn gen_attr_call(key: &syn::Ident, val: &syn::Lit) -> quote::Tokens { + if let Lit::Str(ref val, _) = *val { + let key = key.as_ref(); + if key.ends_with("_raw") { + let key = Ident::from(&key[..(key.len() - 4)]); + // Call method without quoting the string + let ts = syn::parse_token_trees(val) + .expect(&format!("bad parameter {} = {}: the parameter must be valid rust code", key, val)); + return quote!(.#key(#(#ts)*)); + } + } + quote!(.#key(#val)) +} + fn gen_constructor(fields: &[Field]) -> quote::Tokens { let fields = fields.iter().map(|field| { let field_name = field.ident.as_ref().unwrap(); @@ -480,12 +498,28 @@ fn format_author(raw_authors: Lit) -> Lit { Lit::Str(authors, StrStyle::Cooked) } -fn gen_clap(struct_attrs: &[Attribute], subcmd_required: bool) -> quote::Tokens { - let struct_attrs: Vec<_> = extract_attrs(struct_attrs, AttrSource::Struct).collect(); - let name = from_attr_or_env(&struct_attrs, "name", "CARGO_PKG_NAME"); - let version = from_attr_or_env(&struct_attrs, "version", "CARGO_PKG_VERSION"); - let author = format_author(from_attr_or_env(&struct_attrs, "author", "CARGO_PKG_AUTHORS")); - let about = from_attr_or_env(&struct_attrs, "about", "CARGO_PKG_DESCRIPTION"); +fn gen_clap(attrs: &[Attribute]) -> quote::Tokens { + let attrs: Vec<_> = extract_attrs(attrs, AttrSource::Struct).collect(); + let name = from_attr_or_env(&attrs, "name", "CARGO_PKG_NAME"); + let version = from_attr_or_env(&attrs, "version", "CARGO_PKG_VERSION"); + let author = format_author(from_attr_or_env(&attrs, "author", "CARGO_PKG_AUTHORS")); + let about = from_attr_or_env(&attrs, "about", "CARGO_PKG_DESCRIPTION"); + let settings = attrs.iter() + .filter(|&&(ref i, _)| !["name", "version", "author", "about"].contains(&i.as_ref())) + .map(|&(ref i, ref l)| gen_attr_call(i, l)) + .collect::>(); + + quote! { + _structopt::clap::App::new(#name) + .version(#version) + .author(#author) + .about(#about) + #( #settings )* + } +} + +fn gen_clap_struct(struct_attrs: &[Attribute], subcmd_required: bool) -> quote::Tokens { + let gen = gen_clap(struct_attrs); let setting = if subcmd_required { quote!( .setting(_structopt::clap::AppSettings::SubcommandRequired) ) } else { @@ -494,12 +528,8 @@ fn gen_clap(struct_attrs: &[Attribute], subcmd_required: bool) -> quote::Tokens quote! { fn clap<'a, 'b>() -> _structopt::clap::App<'a, 'b> { - let app = _structopt::clap::App::new(#name) - .version(#version) - .author(#author) - .about(#about) - #setting - ; + let app = #gen + #setting; Self::augment_clap(app) } } @@ -516,19 +546,11 @@ fn gen_augment_clap(fields: &[Field]) -> quote::Tokens { } fn gen_clap_enum(enum_attrs: &[Attribute]) -> quote::Tokens { - let enum_attrs: Vec<_> = extract_attrs(enum_attrs, AttrSource::Struct).collect(); - let name = from_attr_or_env(&enum_attrs, "name", "CARGO_PKG_NAME"); - let version = from_attr_or_env(&enum_attrs, "version", "CARGO_PKG_VERSION"); - let author = format_author(from_attr_or_env(&enum_attrs, "author", "CARGO_PKG_AUTHORS")); - let about = from_attr_or_env(&enum_attrs, "about", "CARGO_PKG_DESCRIPTION"); - + let gen = gen_clap(enum_attrs); quote! { fn clap<'a, 'b>() -> _structopt::clap::App<'a, 'b> { - let app = _structopt::clap::App::new(#name) - .version(#version) - .author(#author) - .about(#about) - .setting(_structopt::clap::AppSettings::SubcommandRequired); + let app = #gen + .setting(_structopt::clap::AppSettings::SubcommandRequiredElseHelp); Self::augment_clap(app) } } @@ -552,7 +574,7 @@ fn gen_augment_clap_enum(variants: &[Variant]) -> quote::Tokens { }; let from_attr = extract_attrs(&variant.attrs, AttrSource::Struct) .filter(|&(ref i, _)| i != "name") - .map(|(i, l)| quote!( .#i(#l) )); + .map(|(i, l)| gen_attr_call(&i, &l)); quote! { .subcommand({ @@ -620,7 +642,7 @@ fn impl_structopt_for_struct(name: &Ident, fields: &[Field], attrs: &[Attribute] _ => is_subcommand(field) } }); - let clap = gen_clap(attrs, subcmd_required); + let clap = gen_clap_struct(attrs, subcmd_required); let augment_clap = gen_augment_clap(fields); let from_clap = gen_from_clap(name, fields); diff --git a/tests/raw_attributes.rs b/tests/raw_attributes.rs new file mode 100644 index 00000000000..6ce485a8c12 --- /dev/null +++ b/tests/raw_attributes.rs @@ -0,0 +1,65 @@ +// Copyright (c) 2017 Guillaume Pinot +// +// This work is free. You can redistribute it and/or modify it under +// the terms of the Do What The Fuck You Want To Public License, +// Version 2, as published by Sam Hocevar. See the COPYING file for +// more details. + +extern crate structopt; +#[macro_use] +extern crate structopt_derive; + +use structopt::StructOpt; +use structopt::clap::AppSettings; + +// Check if the global settings compile +#[derive(StructOpt, Debug, PartialEq, Eq)] +#[structopt(global_settings_raw = "&[AppSettings::ColoredHelp]")] +struct Opt { + #[structopt(long = "x", display_order_raw = "2", next_line_help_raw = "true", + default_value_raw = "\"0\"", require_equals_raw = "true")] + x: i32, + + #[structopt(short = "l", long = "level", aliases_raw = "&[\"set-level\", \"lvl\"]")] + level: String, + + #[structopt(long = "values")] + values: Vec, + + #[structopt(name = "FILE", requires_if_raw = "\"FILE\", \"values\"")] + files: Vec, +} + +#[test] +fn test_raw_slice() { + assert_eq!(Opt { x: 0, level: "1".to_string(), files: Vec::new(), values: vec![] }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-l", "1"]))); + assert_eq!(Opt { x: 0, level: "1".to_string(), files: Vec::new(), values: vec![] }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "--level", "1"]))); + assert_eq!(Opt { x: 0, level: "1".to_string(), files: Vec::new(), values: vec![] }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "--set-level", "1"]))); + assert_eq!(Opt { x: 0, level: "1".to_string(), files: Vec::new(), values: vec![] }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "--lvl", "1"]))); +} + +#[test] +fn test_raw_multi_args() { + assert_eq!(Opt { x: 0, level: "1".to_string(), files: vec!["file".to_string()], values: vec![] }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-l", "1", "file"]))); + assert_eq!(Opt { x: 0, level: "1".to_string(), files: vec!["FILE".to_string()], values: vec![1] }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-l", "1", "--values", "1", "--", "FILE"]))); +} + +#[test] +fn test_raw_multi_args_fail() { + let result = Opt::clap().get_matches_from_safe(&["test", "-l", "1", "--", "FILE"]); + assert!(result.is_err()); +} + +#[test] +fn test_raw_bool() { + assert_eq!(Opt { x: 1, level: "1".to_string(), files: vec![], values: vec![] }, + Opt::from_clap(Opt::clap().get_matches_from(&["test", "-l", "1", "--x=1"]))); + let result = Opt::clap().get_matches_from_safe(&["test", "-l", "1", "--x", "1"]); + assert!(result.is_err()); +} From f7a4be13ca98fe839727f63f7321951ec7baa921 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Fri, 22 Sep 2017 22:21:07 +0200 Subject: [PATCH 0059/3337] update to 0.1.1 --- Cargo.toml | 4 ++-- structopt-derive/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e18bbbb4163..438aeb75177 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.1.0" +version = "0.1.1" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" @@ -17,6 +17,6 @@ travis-ci = { repository = "TeXitoi/structopt" } clap = "2.20" [dev-dependencies] -structopt-derive = { path = "structopt-derive", version = "0.1.0" } +structopt-derive = { path = "structopt-derive", version = "0.1.1" } [workspace] diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index 32892387e88..62634575c58 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt-derive" -version = "0.1.0" +version = "0.1.1" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct, derive crate." documentation = "https://docs.rs/structopt-derive" From 4880ccb02540afe17b7000ca4aa673dea0f9b282 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Wed, 1 Nov 2017 21:30:46 +0100 Subject: [PATCH 0060/3337] Fix a privacy bug Fix #24 --- structopt-derive/src/lib.rs | 8 +++++--- tests/privacy.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 tests/privacy.rs diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 8e2ece8cd7b..b0bbb333d6e 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -539,7 +539,7 @@ fn gen_augment_clap(fields: &[Field]) -> quote::Tokens { let app_var = Ident::new("app"); let augmentation = gen_augmentation(fields, &app_var); quote! { - fn augment_clap<'a, 'b>(#app_var: _structopt::clap::App<'a, 'b>) -> _structopt::clap::App<'a, 'b> { + pub fn augment_clap<'a, 'b>(#app_var: _structopt::clap::App<'a, 'b>) -> _structopt::clap::App<'a, 'b> { #augmentation } } @@ -586,7 +586,7 @@ fn gen_augment_clap_enum(variants: &[Variant]) -> quote::Tokens { }); quote! { - fn augment_clap<'a, 'b>(app: _structopt::clap::App<'a, 'b>) -> _structopt::clap::App<'a, 'b> { + pub fn augment_clap<'a, 'b>(app: _structopt::clap::App<'a, 'b>) -> _structopt::clap::App<'a, 'b> { app #( #subcommands )* } } @@ -594,6 +594,7 @@ fn gen_augment_clap_enum(variants: &[Variant]) -> quote::Tokens { fn gen_from_clap_enum(name: &Ident) -> quote::Tokens { quote! { + #[doc(hidden)] fn from_clap(matches: _structopt::clap::ArgMatches) -> Self { #name ::from_subcommand(matches.subcommand()) .unwrap() @@ -625,7 +626,8 @@ fn gen_from_subcommand(name: &Ident, variants: &[Variant]) -> quote::Tokens { }); quote! { - fn from_subcommand<'a, 'b>(sub: (&'b str, Option<&'b _structopt::clap::ArgMatches<'a>>)) -> Option { + #[doc(hidden)] + pub fn from_subcommand<'a, 'b>(sub: (&'b str, Option<&'b _structopt::clap::ArgMatches<'a>>)) -> Option { match sub { #( #match_arms ),*, _ => None diff --git a/tests/privacy.rs b/tests/privacy.rs new file mode 100644 index 00000000000..14a86c4b287 --- /dev/null +++ b/tests/privacy.rs @@ -0,0 +1,29 @@ +// Copyright (c) 2017 Guillaume Pinot +// +// This work is free. You can redistribute it and/or modify it under +// the terms of the Do What The Fuck You Want To Public License, +// Version 2, as published by Sam Hocevar. See the COPYING file for +// more details. + +extern crate structopt; +#[macro_use] +extern crate structopt_derive; + +mod options { + #[derive(Debug, StructOpt)] + pub struct Options { + #[structopt(subcommand)] + pub subcommand: ::subcommands::SubCommand, + } +} + +mod subcommands { + #[derive(Debug, StructOpt)] + pub enum SubCommand { + #[structopt(name = "foo", about = "foo")] + Foo { + #[structopt(help = "foo")] + bars: Vec, + }, + } +} From 96a726aeb746219b9c5b750b06f91ff28a82c84f Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Wed, 1 Nov 2017 22:00:15 +0100 Subject: [PATCH 0061/3337] Handle multiline doc comment as help message fix #25 --- structopt-derive/src/lib.rs | 28 +++++++++++++++++----------- tests/doc-comments-help.rs | 3 ++- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index b0bbb333d6e..7a1a4e872b4 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -296,7 +296,7 @@ fn extract_attrs<'a>(attrs: &'a [Attribute], attr_source: AttrSource) -> Box None, })); - let doc_comments = attrs.iter() + let doc_comments: Vec = attrs.iter() .filter_map(move |attr| { if let Attribute { value: MetaItem::NameValue(ref name, Lit::Str(ref value, StrStyle::Cooked)), @@ -309,20 +309,26 @@ fn extract_attrs<'a>(attrs: &'a [Attribute], attr_source: AttrSource) -> Box Lit { diff --git a/tests/doc-comments-help.rs b/tests/doc-comments-help.rs index 4d7da7c8360..1d4368a310e 100644 --- a/tests/doc-comments-help.rs +++ b/tests/doc-comments-help.rs @@ -17,6 +17,7 @@ fn commets_intead_of_actual_help() { #[derive(StructOpt, PartialEq, Debug)] struct LoremIpsum { /// Fooify a bar + /// and a baz #[structopt(short = "f", long = "foo")] foo: bool, } @@ -26,7 +27,7 @@ fn commets_intead_of_actual_help() { let output = String::from_utf8(output).unwrap(); assert!(output.contains("Lorem ipsum")); - assert!(output.contains("Fooify a bar")); + assert!(output.contains("Fooify a bar and a baz")); } #[test] From 5ba275680b31dae96899c7fa218efcbbfdbe7893 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Wed, 1 Nov 2017 22:12:44 +0100 Subject: [PATCH 0062/3337] new release --- Cargo.toml | 4 ++-- structopt-derive/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 438aeb75177..a5c7a8b52ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.1.1" +version = "0.1.2" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" @@ -17,6 +17,6 @@ travis-ci = { repository = "TeXitoi/structopt" } clap = "2.20" [dev-dependencies] -structopt-derive = { path = "structopt-derive", version = "0.1.1" } +structopt-derive = { path = "structopt-derive", version = "0.1.2" } [workspace] diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index 62634575c58..06b742b1754 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt-derive" -version = "0.1.1" +version = "0.1.2" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct, derive crate." documentation = "https://docs.rs/structopt-derive" From 29a642b10d153d5ce140c3bb7976e2c82cf0d644 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Wed, 1 Nov 2017 22:31:33 +0100 Subject: [PATCH 0063/3337] fix #12 --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 36477c37788..95d9d7e4070 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,8 @@ pub trait StructOpt { /// Returns the corresponding `clap::App`. fn clap<'a, 'b>() -> clap::App<'a, 'b>; - /// Creates the struct from `clap::ArgMatches`. + /// Creates the struct from `clap::ArgMatches`. It cannot fail + /// with a parameter generated by `clap` by construction. fn from_clap(clap::ArgMatches) -> Self; /// Gets the struct from the command line arguments. Print the From d1170466df238708b6dd4fb0a09894594a9033fa Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Wed, 1 Nov 2017 22:32:32 +0100 Subject: [PATCH 0064/3337] fix #9 --- structopt-derive/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 7a1a4e872b4..655486aa6ff 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -63,7 +63,8 @@ //! //! The `FromStr` trait is used to convert the argument to the given //! type, and the `Arg::validator` method is set to a method using -//! `FromStr::Error::description()`. +//! `FromStr::Err::description()` (`FromStr::Err` must implement +//! `std::Error::Error`). //! //! Thus, the `speed` argument is generated as: //! From 43710cf96951838b12663b581aaeb21879f63e32 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Wed, 1 Nov 2017 22:35:27 +0100 Subject: [PATCH 0065/3337] update to 0.1.3 --- Cargo.toml | 4 ++-- structopt-derive/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5c7a8b52ca..65c3e04cff3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.1.2" +version = "0.1.3" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" @@ -17,6 +17,6 @@ travis-ci = { repository = "TeXitoi/structopt" } clap = "2.20" [dev-dependencies] -structopt-derive = { path = "structopt-derive", version = "0.1.2" } +structopt-derive = { path = "structopt-derive", version = "0.1.3" } [workspace] diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index 06b742b1754..f5bbfc06fbe 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt-derive" -version = "0.1.2" +version = "0.1.3" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct, derive crate." documentation = "https://docs.rs/structopt-derive" From 13fc4fb66a5894b6b37623fb0cc45631171235ae Mon Sep 17 00:00:00 2001 From: kennytm Date: Thu, 9 Nov 2017 17:23:02 +0800 Subject: [PATCH 0066/3337] Implement custom string parser from either `&str` or `&OsStr`. (#28) * Implement custom string parser from either &str or &OsStr. Fix #2. Fix #3. * Addressed review comments. --- structopt-derive/src/lib.rs | 204 ++++++++++++++++++++++++++++++--- tests/custom-string-parsers.rs | 159 +++++++++++++++++++++++++ 2 files changed, 344 insertions(+), 19 deletions(-) create mode 100644 tests/custom-string-parsers.rs diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 655486aa6ff..b6de4b39ed7 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -63,8 +63,9 @@ //! //! The `FromStr` trait is used to convert the argument to the given //! type, and the `Arg::validator` method is set to a method using -//! `FromStr::Err::description()` (`FromStr::Err` must implement -//! `std::Error::Error`). +//! `to_string()` (`FromStr::Err` must implement `std::fmt::Display`). +//! If you would like to use a custom string parser other than `FromStr`, see +//! the [same titled section](#custom-string-parsers) below. //! //! Thus, the `speed` argument is generated as: //! @@ -227,6 +228,52 @@ //! Quux //! } //! ``` +//! +//! ## Custom string parsers +//! +//! If the field type does not have a `FromStr` implementation, or you would +//! like to provide a custom parsing scheme other than `FromStr`, you may +//! provide a custom string parser using `parse(...)` like this: +//! +//! ```ignore +//! use std::num::ParseIntError; +//! use std::path::PathBuf; +//! +//! fn parse_hex(src: &str) -> Result { +//! u32::from_str_radix(src, 16) +//! } +//! +//! #[derive(StructOpt)] +//! struct HexReader { +//! #[structopt(short = "n", parse(try_from_str = "parse_hex"))] +//! number: u32, +//! #[structopt(short = "o", parse(from_os_str))] +//! output: PathBuf, +//! } +//! ``` +//! +//! There are four kinds custom string parsers: +//! +//! | Kind | Signature | Default | +//! |-------------------|---------------------------------------|---------------------------------| +//! | `from_str` | `fn(&str) -> T` | `::std::convert::From::from` | +//! | `try_from_str` | `fn(&str) -> Result` | `::std::str::FromStr::from_str` | +//! | `from_os_str` | `fn(&OsStr) -> T` | `::std::convert::From::from` | +//! | `try_from_os_str` | `fn(&OsStr) -> Result` | (no default function) | +//! +//! When supplying a custom string parser, `bool` and `u64` will not be treated +//! specially: +//! +//! Type | Effect | Added method call to `clap::Arg` +//! ------------|-------------------|-------------------------------------- +//! `Option` | optional argument | `.takes_value(true).multiple(false)` +//! `Vec` | list of arguments | `.takes_value(true).multiple(true)` +//! `T` | required argument | `.takes_value(true).multiple(false).required(!has_default)` +//! +//! In the `try_from_*` variants, the function will run twice on valid input: +//! once to validate, and once to parse. Hence, make sure the function is +//! side-effect-free. + extern crate proc_macro; extern crate syn; @@ -286,6 +333,19 @@ fn sub_type(t: &syn::Ty) -> Option<&syn::Ty> { #[derive(Debug, Clone, Copy)] enum AttrSource { Struct, Field, } +#[derive(Debug)] +enum Parser { + /// Parse an option to using a `fn(&str) -> T` function. The function should never fail. + FromStr, + /// Parse an option to using a `fn(&str) -> Result` function. The error will be + /// converted to a string using `.to_string()`. + TryFromStr, + /// Parse an option to using a `fn(&OsStr) -> T` function. The function should never fail. + FromOsStr, + /// Parse an option to using a `fn(&OsStr) -> Result` function. + TryFromOsStr, +} + fn extract_attrs<'a>(attrs: &'a [Attribute], attr_source: AttrSource) -> Box + 'a> { let settings_attrs = attrs.iter() .filter_map(|attr| match attr.value { @@ -355,6 +415,71 @@ fn is_subcommand(field: &Field) -> bool { }) } +fn get_default_parser() -> (Parser, quote::Tokens) { + (Parser::TryFromStr, quote!(::std::str::FromStr::from_str)) +} + +fn get_parser(field: &Field) -> Option<(Parser, quote::Tokens)> { + field.attrs.iter() + .flat_map(|attr| { + if let MetaItem::List(ref i, ref l) = attr.value { + if i == "structopt" { + return &**l; + } + } + &[] + }) + .filter_map(|attr| { + if let NestedMetaItem::MetaItem(MetaItem::List(ref i, ref l)) = *attr { + if i == "parse" { + return l.first(); + } + } + None + }) + .map(|attr| { + match *attr { + NestedMetaItem::MetaItem(MetaItem::NameValue(ref i, Lit::Str(ref v, _))) => { + let function = parse_path(v).expect("parser function path"); + let parser = if i == "from_str" { + Parser::FromStr + } else if i == "try_from_str" { + Parser::TryFromStr + } else if i == "from_os_str" { + Parser::FromOsStr + } else if i == "try_from_os_str" { + Parser::TryFromOsStr + } else { + panic!("unsupported parser {}", i); + }; + (parser, quote!(#function)) + } + NestedMetaItem::MetaItem(MetaItem::Word(ref i)) => { + if i == "from_str" { + (Parser::FromStr, quote!(::std::convert::From::from)) + } else if i == "try_from_str" { + (Parser::TryFromStr, quote!(::std::str::FromStr::from_str)) + } else if i == "from_os_str" { + (Parser::FromOsStr, quote!(::std::convert::From::from)) + } else if i == "try_from_os_str" { + panic!("cannot omit parser function name with `try_from_os_str`") + } else { + panic!("unsupported parser {}", i); + } + } + _ => panic!("unknown value parser specification"), + } + }) + .next() +} + +fn convert_with_custom_parse(cur_type: Ty) -> Ty { + match cur_type { + Ty::Bool | Ty::U64 => Ty::Other, + rest => rest, + } +} + /// Generate a block of code to add arguments/subcommands corresponding to /// the `fields` to an app. fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens { @@ -377,27 +502,41 @@ fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens { .filter(|&field| !is_subcommand(field)) .map(|field| { let name = gen_name(field); - let cur_type = ty(&field.ty); + let mut cur_type = ty(&field.ty); let convert_type = match cur_type { Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty), _ => &field.ty, }; - let validator = quote! { - validator(|s| s.parse::<#convert_type>() - .map(|_| ()) - .map_err(|e| e.description().into())) + + let parser = get_parser(field); + if parser.is_some() { + cur_type = convert_with_custom_parse(cur_type); + } + let validator = match parser.unwrap_or_else(get_default_parser) { + (Parser::TryFromStr, f) => quote! { + .validator(|s| { + #f(&s) + .map(|_: #convert_type| ()) + .map_err(|e| e.to_string()) + }) + }, + (Parser::TryFromOsStr, f) => quote! { + .validator_os(|s| #f(&s).map(|_: #convert_type| ())) + }, + _ => quote! {}, }; + let modifier = match cur_type { Ty::Bool => quote!( .takes_value(false).multiple(false) ), Ty::U64 => quote!( .takes_value(false).multiple(true) ), - Ty::Option => quote!( .takes_value(true).multiple(false).#validator ), - Ty::Vec => quote!( .takes_value(true).multiple(true).#validator ), + Ty::Option => quote!( .takes_value(true).multiple(false) #validator ), + Ty::Vec => quote!( .takes_value(true).multiple(true) #validator ), Ty::Other => { let required = extract_attrs(&field.attrs, AttrSource::Field) .find(|&(ref i, _)| i.as_ref() == "default_value" || i.as_ref() == "default_value_raw") .is_none(); - quote!( .takes_value(true).multiple(false).required(#required).#validator ) + quote!( .takes_value(true).multiple(false).required(#required) #validator ) }, }; let from_attr = extract_attrs(&field.attrs, AttrSource::Field) @@ -445,24 +584,51 @@ fn gen_constructor(fields: &[Field]) -> quote::Tokens { }; quote!( #field_name: #subcmd_type ::from_subcommand(matches.subcommand()) #unwrapper ) } else { - let convert = match ty(&field.ty) { + let mut cur_type = ty(&field.ty); + let parser = get_parser(field); + if parser.is_some() { + cur_type = convert_with_custom_parse(cur_type); + } + + let (value_of, values_of, parse) = match parser.unwrap_or_else(get_default_parser) { + (Parser::FromStr, f) => ( + quote!(value_of), + quote!(values_of), + f, + ), + (Parser::TryFromStr, f) => ( + quote!(value_of), + quote!(values_of), + quote!(|s| #f(s).unwrap()), + ), + (Parser::FromOsStr, f) => ( + quote!(value_of_os), + quote!(values_of_os), + f, + ), + (Parser::TryFromOsStr, f) => ( + quote!(value_of_os), + quote!(values_of_os), + quote!(|s| #f(s).unwrap()), + ), + }; + + let convert = match cur_type { Ty::Bool => quote!(is_present(stringify!(#name))), Ty::U64 => quote!(occurrences_of(stringify!(#name))), Ty::Option => quote! { - value_of(stringify!(#name)) + #value_of(stringify!(#name)) .as_ref() - .map(|s| s.parse().unwrap()) + .map(#parse) }, Ty::Vec => quote! { - values_of(stringify!(#name)) - .map(|v| v.map(|s| s.parse().unwrap()).collect()) + #values_of(stringify!(#name)) + .map(|v| v.map(#parse).collect()) .unwrap_or_else(Vec::new) }, Ty::Other => quote! { - value_of(stringify!(#name)) - .as_ref() - .unwrap() - .parse() + #value_of(stringify!(#name)) + .map(#parse) .unwrap() }, }; diff --git a/tests/custom-string-parsers.rs b/tests/custom-string-parsers.rs new file mode 100644 index 00000000000..9db105d747f --- /dev/null +++ b/tests/custom-string-parsers.rs @@ -0,0 +1,159 @@ +// Copyright (c) 2017 structopt Developers +// +// This work is free. You can redistribute it and/or modify it under +// the terms of the Do What The Fuck You Want To Public License, +// Version 2, as published by Sam Hocevar. See the COPYING file for +// more details. + +extern crate structopt; +#[macro_use] +extern crate structopt_derive; + +use structopt::StructOpt; + +use std::path::PathBuf; +use std::num::ParseIntError; +use std::ffi::{OsStr, OsString}; + +#[derive(StructOpt, PartialEq, Debug)] +struct PathOpt { + #[structopt(short = "p", long = "path", parse(from_os_str))] + path: PathBuf, + + #[structopt(short = "d", default_value = "../", parse(from_os_str))] + default_path: PathBuf, + + #[structopt(short = "v", parse(from_os_str))] + vector_path: Vec, + + #[structopt(short = "o", parse(from_os_str))] + option_path_1: Option, + + #[structopt(short = "q", parse(from_os_str))] + option_path_2: Option, +} + +#[test] +fn test_path_opt_simple() { + assert_eq!( + PathOpt { + path: PathBuf::from("/usr/bin"), + default_path: PathBuf::from("../"), + vector_path: vec![ + PathBuf::from("/a/b/c"), + PathBuf::from("/d/e/f"), + PathBuf::from("/g/h/i"), + ], + option_path_1: None, + option_path_2: Some(PathBuf::from("j.zip")), + }, + PathOpt::from_clap(PathOpt::clap().get_matches_from(&[ + "test", + "-p", "/usr/bin", + "-v", "/a/b/c", + "-v", "/d/e/f", + "-v", "/g/h/i", + "-q", "j.zip", + ])) + ); +} + + + + +fn parse_hex(input: &str) -> Result { + u64::from_str_radix(input, 16) +} + +#[derive(StructOpt, PartialEq, Debug)] +struct HexOpt { + #[structopt(short = "n", parse(try_from_str = "parse_hex"))] + number: u64, +} + +#[test] +fn test_parse_hex() { + assert_eq!( + HexOpt { number: 5 }, + HexOpt::from_clap(HexOpt::clap().get_matches_from(&["test", "-n", "5"])) + ); + assert_eq!( + HexOpt { number: 0xabcdef }, + HexOpt::from_clap(HexOpt::clap().get_matches_from(&["test", "-n", "abcdef"])) + ); + + let err = HexOpt::clap().get_matches_from_safe(&["test", "-n", "gg"]).unwrap_err(); + assert!(err.message.contains("invalid digit found in string"), err); +} + + + + +fn custom_parser_1(_: &str) -> &'static str { + "A" +} +fn custom_parser_2(_: &str) -> Result<&'static str, u32> { + Ok("B") +} +fn custom_parser_3(_: &OsStr) -> &'static str { + "C" +} +fn custom_parser_4(_: &OsStr) -> Result<&'static str, OsString> { + Ok("D") +} + +#[derive(StructOpt, PartialEq, Debug)] +struct NoOpOpt { + #[structopt(short = "a", parse(from_str = "custom_parser_1"))] + a: &'static str, + #[structopt(short = "b", parse(try_from_str = "custom_parser_2"))] + b: &'static str, + #[structopt(short = "c", parse(from_os_str = "custom_parser_3"))] + c: &'static str, + #[structopt(short = "d", parse(try_from_os_str = "custom_parser_4"))] + d: &'static str, +} + +#[test] +fn test_every_custom_parser() { + assert_eq!( + NoOpOpt { a: "A", b: "B", c: "C", d: "D" }, + NoOpOpt::from_clap(NoOpOpt::clap().get_matches_from(&[ + "test", "-a=?", "-b=?", "-c=?", "-d=?", + ])) + ); +} + + +// Note: can't use `Vec` directly, as structopt would instead look for +// conversion function from `&str` to `u8`. +type Bytes = Vec; + +#[derive(StructOpt, PartialEq, Debug)] +struct DefaultedOpt { + #[structopt(short = "b", parse(from_str))] + bytes: Bytes, + + #[structopt(short = "i", parse(try_from_str))] + integer: u64, + + #[structopt(short = "p", parse(from_os_str))] + path: PathBuf, +} + +#[test] +fn test_parser_with_default_value() { + assert_eq!( + DefaultedOpt { + bytes: b"E\xc2\xb2=p\xc2\xb2c\xc2\xb2+m\xc2\xb2c\xe2\x81\xb4".to_vec(), + integer: 9000, + path: PathBuf::from("src/lib.rs"), + }, + DefaultedOpt::from_clap(DefaultedOpt::clap().get_matches_from(&[ + "test", + "-b", "E²=p²c²+m²c⁴", + "-i", "9000", + "-p", "src/lib.rs", + ])) + ); +} From f24fcb19a8fb97707d770a734c87535eb1d9cda8 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Thu, 9 Nov 2017 10:24:18 +0100 Subject: [PATCH 0067/3337] v0.1.4 --- Cargo.toml | 4 ++-- structopt-derive/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 65c3e04cff3..756b51e51a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.1.3" +version = "0.1.4" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" @@ -17,6 +17,6 @@ travis-ci = { repository = "TeXitoi/structopt" } clap = "2.20" [dev-dependencies] -structopt-derive = { path = "structopt-derive", version = "0.1.3" } +structopt-derive = { path = "structopt-derive", version = "0.1.4" } [workspace] diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index f5bbfc06fbe..b31cbeb5f62 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt-derive" -version = "0.1.3" +version = "0.1.4" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct, derive crate." documentation = "https://docs.rs/structopt-derive" From d2baf8b70c1bb60ff4e8ca1bb6a7c61691820186 Mon Sep 17 00:00:00 2001 From: Andrew Hobden Date: Sun, 12 Nov 2017 19:02:56 +0100 Subject: [PATCH 0068/3337] init --- .gitignore | 3 +++ .travis.yml | 11 ++++++++ Cargo.toml | 28 +++++++++++++++++++ LICENSE | 21 +++++++++++++++ README.md | 36 +++++++++++++++++++++++++ appveyor.yml | 23 ++++++++++++++++ examples/basic.rs | 27 +++++++++++++++++++ src/arg_enum.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++ src/errors.rs | 19 +++++++++++++ src/helpers.rs | 10 +++++++ src/lib.rs | 50 ++++++++++++++++++++++++++++++++++ 11 files changed, 296 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 appveyor.yml create mode 100644 examples/basic.rs create mode 100644 src/arg_enum.rs create mode 100644 src/errors.rs create mode 100644 src/helpers.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..6aa106405a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000000..9149c8e1ed4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: rust +sudo: false +env: + global: + - RUST_BACKTRACE=1 +cache: cargo + +matrix: + include: + - rust: nightly + - rust: stable \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000000..cb4f53142d7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "clap_derive" +description = """ +Custom derives for Clap. +""" +version = "0.0.5" +authors = ["hoverbear "] +license = "MIT" + +categories = ["development-tools::procedural-macro-helpers"] +keywords = ["macro", "clap", "command-line"] +readme = "README.md" +repository = "https://github.com/Hoverbear/clap-derives" + +[badges] +travis-ci = { repository = "https://github.com/Hoverbear/clap-derives" } +appveyor = { repository = "https://github.com/Hoverbear/clap-derives", service = "github" } + +[lib] +proc-macro = true + +[dependencies] +quote = "*" +syn = "*" +error-chain = "*" + +[dev-dependencies] +clap = "*" \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..8aa26455d23 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000000..6467d2578f0 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# clap-derives + +[![Build Status](https://travis-ci.org/Hoverbear/clap-derives.svg?branch=master)](https://travis-ci.org/Hoverbear/clap-derives) +[![Build status](https://ci.appveyor.com/api/projects/status/w8v2poyjwsy5d05k?svg=true)](https://ci.appveyor.com/project/Hoverbear/clap-derives) + +Clap custom derives. + +```rust +#[macro_use] +extern crate clap; +#[macro_use] +extern crate clap_derive; + +use clap::{App, Arg}; + +#[derive(ArgEnum, Debug)] +enum ArgChoice { + Foo, + Bar, + Baz, +} + +fn main() { + let matches = App::new(env!("CARGO_PKG_NAME")) + .arg(Arg::with_name("arg") + .required(true) + .takes_value(true) + .possible_values(&ArgChoice::variants()) + ).get_matches(); + + let t = value_t!(matches.value_of("arg"), ArgChoice) + .unwrap_or_else(|e| e.exit()); + + println!("{:?}", t); +} +``` \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000000..91f614f551d --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,23 @@ +environment: + matrix: + - TARGET: x86_64-pc-windows-gnu + RUST_VERSION: stable + - TARGET: x86_64-pc-windows-gnu + RUST_VERSION: nightly + +install: + - curl -sSf -o rustup-init.exe https://win.rustup.rs/ + - rustup-init.exe -y --default-host %TARGET% --default-toolchain %RUST_VERSION% + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + - rustc -Vv + - cargo -V + +# Building is done in the test phase, so we disable Appveyor's build phase. +build: false + +cache: + - C:\Users\appveyor\.cargo\registry + - target + +test_script: + - cargo test \ No newline at end of file diff --git a/examples/basic.rs b/examples/basic.rs new file mode 100644 index 00000000000..d4ee1558e35 --- /dev/null +++ b/examples/basic.rs @@ -0,0 +1,27 @@ +#[macro_use] +extern crate clap; +#[macro_use] +extern crate clap_derive; + +use clap::{App, Arg}; + +#[derive(ArgEnum, Debug)] +enum ArgChoice { + Foo, + Bar, + Baz, +} + +fn main() { + let matches = App::new(env!("CARGO_PKG_NAME")) + .arg(Arg::with_name("arg") + .required(true) + .takes_value(true) + .possible_values(&ArgChoice::variants()) + ).get_matches(); + + let t = value_t!(matches.value_of("arg"), ArgChoice) + .unwrap_or_else(|e| e.exit()); + + println!("{:?}", t); +} \ No newline at end of file diff --git a/src/arg_enum.rs b/src/arg_enum.rs new file mode 100644 index 00000000000..44e00504661 --- /dev/null +++ b/src/arg_enum.rs @@ -0,0 +1,68 @@ +use syn::DeriveInput; +use quote::Tokens; +use ClapDerive; +use helpers; +use errors::*; + +pub struct ArgEnum; + +impl ClapDerive for ArgEnum { + fn generate_from(ast: &DeriveInput) -> Result { + let from_str_block = impl_from_str(ast)?; + let variants_block = impl_variants(ast)?; + + Ok(quote! { + #from_str_block + #variants_block + }) + } +} + +fn impl_from_str(ast: &DeriveInput) -> Result { + let ident = &ast.ident; + let variants = helpers::variants(ast)?; + + let strings = variants.iter() + .map(|ref variant| String::from(variant.ident.as_ref())) + .collect::>(); + + // Yes, we actually need to do this. + let ident_slice = [ident.clone()]; + let idents = ident_slice.iter().cycle(); + + let for_error_message = strings.clone(); + + Ok(quote! { + impl ::std::str::FromStr for #ident { + type Err = String; + + fn from_str(input: &str) -> ::std::result::Result { + match input { + #(val if ::std::ascii::AsciiExt::eq_ignore_ascii_case(val, #strings) => Ok(#idents::#variants),)* + _ => Err({ + let v = #for_error_message; + format!("valid values: {}", + v.join(" ,")) + }), + } + } + } + }) +} + +fn impl_variants(ast: &DeriveInput) -> Result { + let ident = &ast.ident; + let variants = helpers::variants(ast)? + .iter() + .map(|ref variant| String::from(variant.ident.as_ref())) + .collect::>(); + let length = variants.len(); + + Ok(quote! { + impl #ident { + fn variants() -> [&'static str; #length] { + #variants + } + } + }) +} \ No newline at end of file diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 00000000000..bfc7737d3d6 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,19 @@ +use proc_macro; + +// Unfortunately `proc_macro` and `syn` don't have a good error handling story. +error_chain! { + errors { + WrongBodyType(expected: &'static str) { + description("The wrong type for the derived structure was provided.") + display("Wrong type for derive structure: {:?} expected", expected) + } + ParseError(error: String) { + description("A parsing failure.") + display("A parsing failure happened: {:?}", error) + } + ProcLexError(error: proc_macro::LexError) { + description("A proc_macro lex failure.") + display("A proc_macro lex failure happened: {:?}", error) + } + } +} \ No newline at end of file diff --git a/src/helpers.rs b/src/helpers.rs new file mode 100644 index 00000000000..2b64ccd4d11 --- /dev/null +++ b/src/helpers.rs @@ -0,0 +1,10 @@ +use syn::{DeriveInput, Variant}; +use syn::Body::Enum; +use errors::*; + +pub fn variants(ast: &DeriveInput) -> Result<&Vec> { + match ast.body { + Enum(ref variants) => Ok(variants), + _ => Err(ErrorKind::WrongBodyType("enum"))?, + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000000..7e2f5772974 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,50 @@ +/*! +Derives for clap-rs. +*/ +#![recursion_limit="256"] + +extern crate proc_macro; +extern crate syn; +#[macro_use] +extern crate quote; +#[macro_use] +extern crate error_chain; + +use proc_macro::TokenStream; +use syn::DeriveInput; +use quote::Tokens; +use errors::*; + +mod arg_enum; +use arg_enum::ArgEnum; +mod helpers; +mod errors; + +trait ClapDerive { + /// Generate the output from a given input. + fn generate_from(ast: &DeriveInput) -> Result; + + /// Wraps around `generate_from` and does some pre/post processing. + fn derive(input: TokenStream) -> Result { + let derive_input = Self::parse_input(input)?; + let generated_output = Self::generate_from(&derive_input)?; + let stream = generated_output.parse() + .map_err(|e| ErrorKind::ProcLexError(e))?; + Ok(stream) + } + /// Parses the inputted stream. + fn parse_input(input: TokenStream) -> Result { + // Construct a string representation of the type definition + let as_string = input.to_string(); + // Parse the string representation + let parsed = syn::parse_derive_input(&as_string) + .map_err(|e| ErrorKind::ParseError(e))?; + Ok(parsed) + } +} + +/// It is required to have this seperate and specificly defined. +#[proc_macro_derive(ArgEnum)] +pub fn derive_arg_enum(input: TokenStream) -> TokenStream { + ArgEnum::derive(input).unwrap() +} \ No newline at end of file From 8073b57c2a8820d94ef5a598188024d3aabe1fb9 Mon Sep 17 00:00:00 2001 From: Andrew Hobden Date: Sun, 12 Nov 2017 20:58:36 +0100 Subject: [PATCH 0069/3337] Update Cargo.toml metadata. Point to new repo. Closes #2. --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cb4f53142d7..1e1ff62a418 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,11 @@ license = "MIT" categories = ["development-tools::procedural-macro-helpers"] keywords = ["macro", "clap", "command-line"] readme = "README.md" -repository = "https://github.com/Hoverbear/clap-derives" +repository = "https://github.com/kbknapp/clap-derives" [badges] -travis-ci = { repository = "https://github.com/Hoverbear/clap-derives" } -appveyor = { repository = "https://github.com/Hoverbear/clap-derives", service = "github" } +travis-ci = { repository = "https://github.com/kbknapp/clap-derives" } +appveyor = { repository = "https://github.com/kbknapp/clap-derives", service = "github" } [lib] proc-macro = true @@ -25,4 +25,4 @@ syn = "*" error-chain = "*" [dev-dependencies] -clap = "*" \ No newline at end of file +clap = "*" From 227d1fa73c375b0479e182fb041b1a8630d29f0a Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Tue, 14 Nov 2017 15:18:13 +0100 Subject: [PATCH 0070/3337] fix bug with optional subsubcommand and Enum --- structopt-derive/src/lib.rs | 31 +++++++++++--------------- tests/nested-subcommands.rs | 43 +++++++++++++++++++++++++++++++++++++ tests/subcommands.rs | 3 +++ 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index b6de4b39ed7..19b586dc849 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -292,7 +292,7 @@ pub fn structopt(input: TokenStream) -> TokenStream { gen.parse().unwrap() } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq)] enum Ty { Bool, U64, @@ -491,8 +491,16 @@ fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens { (Ty::Option, Some(sub_type)) => sub_type, _ => &field.ty }; + let required = if cur_type == Ty::Option { + quote!() + } else { + quote!( let #app_var = #app_var.setting(_structopt::clap::AppSettings::SubcommandRequiredElseHelp); ) + }; - quote!( let #app_var = #subcmd_type ::augment_clap( #app_var ); ) + quote!{ + let #app_var = #subcmd_type ::augment_clap( #app_var ); + #required + } }) .collect(); @@ -691,18 +699,12 @@ fn gen_clap(attrs: &[Attribute]) -> quote::Tokens { } } -fn gen_clap_struct(struct_attrs: &[Attribute], subcmd_required: bool) -> quote::Tokens { +fn gen_clap_struct(struct_attrs: &[Attribute]) -> quote::Tokens { let gen = gen_clap(struct_attrs); - let setting = if subcmd_required { - quote!( .setting(_structopt::clap::AppSettings::SubcommandRequired) ) - } else { - quote!() - }; quote! { fn clap<'a, 'b>() -> _structopt::clap::App<'a, 'b> { - let app = #gen - #setting; + let app = #gen; Self::augment_clap(app) } } @@ -810,14 +812,7 @@ fn gen_from_subcommand(name: &Ident, variants: &[Variant]) -> quote::Tokens { } fn impl_structopt_for_struct(name: &Ident, fields: &[Field], attrs: &[Attribute]) -> quote::Tokens { - let subcmd_required = fields.iter().any(|field| { - let cur_type = ty(&field.ty); - match cur_type { - Ty::Option => false, - _ => is_subcommand(field) - } - }); - let clap = gen_clap_struct(attrs, subcmd_required); + let clap = gen_clap_struct(attrs); let augment_clap = gen_augment_clap(fields); let from_clap = gen_from_clap(name, fields); diff --git a/tests/nested-subcommands.rs b/tests/nested-subcommands.rs index d978982e7b3..db2b4097782 100644 --- a/tests/nested-subcommands.rs +++ b/tests/nested-subcommands.rs @@ -114,3 +114,46 @@ fn test_subsubcommand() { Opt3::from_clap(Opt3::clap().get_matches_from(&["test", "--all", "foo", "lib.rs", "quux"])) ); } + +#[derive(StructOpt, PartialEq, Debug)] +enum SubSubCmdWithOption { + #[structopt(name = "remote")] + Remote { + #[structopt(subcommand)] + cmd: Option + }, + #[structopt(name = "stash")] + Stash { + #[structopt(subcommand)] + cmd: Stash + }, +} +#[derive(StructOpt, PartialEq, Debug)] +enum Remote { + #[structopt(name = "add")] + Add { name: String, url: String }, + #[structopt(name = "remove")] + Remove { name: String }, +} + +#[derive(StructOpt, PartialEq, Debug)] +enum Stash { + #[structopt(name = "save")] + Save, + #[structopt(name = "pop")] + Pop, +} + +#[test] +fn sub_sub_cmd_with_option() { + fn make(args: &[&str]) -> Option { + SubSubCmdWithOption::clap().get_matches_from_safe(args).ok().map(SubSubCmdWithOption::from_clap) + } + assert_eq!(Some(SubSubCmdWithOption::Remote { cmd: None }), make(&["", "remote"])); + assert_eq!( + Some(SubSubCmdWithOption::Remote { cmd: Some(Remote::Add { name: "origin".into(), url: "http".into() }) }), + make(&["", "remote", "add", "origin", "http"]) + ); + assert_eq!(Some(SubSubCmdWithOption::Stash { cmd: Stash::Save }), make(&["", "stash", "save"])); + assert_eq!(None, make(&["", "stash"])); +} diff --git a/tests/subcommands.rs b/tests/subcommands.rs index 14ad5413be0..f6c102d2381 100644 --- a/tests/subcommands.rs +++ b/tests/subcommands.rs @@ -54,6 +54,9 @@ fn test_no_parse() { let result = Opt::clap().get_matches_from_safe(&["test", "add", "--badoption"]); assert!(result.is_err()); + + let result = Opt::clap().get_matches_from_safe(&["test"]); + assert!(result.is_err()); } #[derive(StructOpt, PartialEq, Debug)] From 1173955d37aabbc60a548c73a2004e1754763b11 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Tue, 14 Nov 2017 15:19:33 +0100 Subject: [PATCH 0071/3337] v0.1.5 --- Cargo.toml | 4 ++-- structopt-derive/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 756b51e51a9..935412e691c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.1.4" +version = "0.1.5" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" @@ -17,6 +17,6 @@ travis-ci = { repository = "TeXitoi/structopt" } clap = "2.20" [dev-dependencies] -structopt-derive = { path = "structopt-derive", version = "0.1.4" } +structopt-derive = { path = "structopt-derive", version = "0.1.5" } [workspace] diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index b31cbeb5f62..7ad73f6102f 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt-derive" -version = "0.1.4" +version = "0.1.5" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct, derive crate." documentation = "https://docs.rs/structopt-derive" From 5ee70bbe99c459bc3fc0f4e0993485cb3afde47e Mon Sep 17 00:00:00 2001 From: Andrew Hobden Date: Wed, 15 Nov 2017 18:49:55 +0100 Subject: [PATCH 0072/3337] Add case sensitive option for ArgEnum --- examples/{basic.rs => arg_enum_basic.rs} | 0 examples/arg_enum_case_sensitive.rs | 28 +++++++++++++++ src/arg_enum.rs | 11 ++++-- src/lib.rs | 2 +- tests/arg_enum_basic.rs | 45 ++++++++++++++++++++++++ tests/arg_enum_case_sensitive.rs | 45 ++++++++++++++++++++++++ 6 files changed, 128 insertions(+), 3 deletions(-) rename examples/{basic.rs => arg_enum_basic.rs} (100%) create mode 100644 examples/arg_enum_case_sensitive.rs create mode 100644 tests/arg_enum_basic.rs create mode 100644 tests/arg_enum_case_sensitive.rs diff --git a/examples/basic.rs b/examples/arg_enum_basic.rs similarity index 100% rename from examples/basic.rs rename to examples/arg_enum_basic.rs diff --git a/examples/arg_enum_case_sensitive.rs b/examples/arg_enum_case_sensitive.rs new file mode 100644 index 00000000000..0c425cfcff8 --- /dev/null +++ b/examples/arg_enum_case_sensitive.rs @@ -0,0 +1,28 @@ +#[macro_use] +extern crate clap; +#[macro_use] +extern crate clap_derive; + +use clap::{App, Arg}; + +#[derive(ArgEnum, Debug)] +#[case_sensitive] +enum ArgChoice { + Foo, + Bar, + Baz, +} + +fn main() { + let matches = App::new(env!("CARGO_PKG_NAME")) + .arg(Arg::with_name("arg") + .required(true) + .takes_value(true) + .possible_values(&ArgChoice::variants()) + ).get_matches(); + + let t = value_t!(matches.value_of("arg"), ArgChoice) + .unwrap_or_else(|e| e.exit()); + + println!("{:?}", t); +} \ No newline at end of file diff --git a/src/arg_enum.rs b/src/arg_enum.rs index 44e00504661..967a2acbcef 100644 --- a/src/arg_enum.rs +++ b/src/arg_enum.rs @@ -20,25 +20,32 @@ impl ClapDerive for ArgEnum { fn impl_from_str(ast: &DeriveInput) -> Result { let ident = &ast.ident; + let is_case_sensitive = ast.attrs.iter().any(|v| v.name() == "case_sensitive"); let variants = helpers::variants(ast)?; let strings = variants.iter() .map(|ref variant| String::from(variant.ident.as_ref())) .collect::>(); - // Yes, we actually need to do this. + // All of these need to be iterators. let ident_slice = [ident.clone()]; let idents = ident_slice.iter().cycle(); let for_error_message = strings.clone(); + let condition_function_slice = [match is_case_sensitive { + true => quote! { str::eq }, + false => quote! { ::std::ascii::AsciiExt::eq_ignore_ascii_case }, + }]; + let condition_function = condition_function_slice.iter().cycle(); + Ok(quote! { impl ::std::str::FromStr for #ident { type Err = String; fn from_str(input: &str) -> ::std::result::Result { match input { - #(val if ::std::ascii::AsciiExt::eq_ignore_ascii_case(val, #strings) => Ok(#idents::#variants),)* + #(val if #condition_function(val, #strings) => Ok(#idents::#variants),)* _ => Err({ let v = #for_error_message; format!("valid values: {}", diff --git a/src/lib.rs b/src/lib.rs index 7e2f5772974..32facc15f6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,7 @@ trait ClapDerive { } /// It is required to have this seperate and specificly defined. -#[proc_macro_derive(ArgEnum)] +#[proc_macro_derive(ArgEnum, attributes(case_sensitive))] pub fn derive_arg_enum(input: TokenStream) -> TokenStream { ArgEnum::derive(input).unwrap() } \ No newline at end of file diff --git a/tests/arg_enum_basic.rs b/tests/arg_enum_basic.rs new file mode 100644 index 00000000000..8de3eb472d0 --- /dev/null +++ b/tests/arg_enum_basic.rs @@ -0,0 +1,45 @@ +#[macro_use] +extern crate clap; +#[macro_use] +extern crate clap_derive; + +use clap::{App, Arg}; + +#[derive(ArgEnum, Debug, PartialEq)] +enum ArgChoice { + Foo, + Bar, + Baz, +} + +#[test] +fn when_lowercase() { + let matches = App::new(env!("CARGO_PKG_NAME")) + .arg(Arg::with_name("arg") + .required(true) + .takes_value(true) + .possible_values(&ArgChoice::variants()) + ).get_matches_from_safe(vec![ + "", + "foo", + ]).unwrap(); + let t = value_t!(matches.value_of("arg"), ArgChoice); + assert!(t.is_ok()); + assert_eq!(t.unwrap(), ArgChoice::Foo); +} + +#[test] +fn when_capitalized() { + let matches = App::new(env!("CARGO_PKG_NAME")) + .arg(Arg::with_name("arg") + .required(true) + .takes_value(true) + .possible_values(&ArgChoice::variants()) + ).get_matches_from_safe(vec![ + "", + "Foo", + ]).unwrap(); + let t = value_t!(matches.value_of("arg"), ArgChoice); + assert!(t.is_ok()); + assert_eq!(t.unwrap(), ArgChoice::Foo); +} \ No newline at end of file diff --git a/tests/arg_enum_case_sensitive.rs b/tests/arg_enum_case_sensitive.rs new file mode 100644 index 00000000000..17df4ee1728 --- /dev/null +++ b/tests/arg_enum_case_sensitive.rs @@ -0,0 +1,45 @@ +#[macro_use] +extern crate clap; +#[macro_use] +extern crate clap_derive; + +use clap::{App, Arg}; + +#[derive(ArgEnum, Debug, PartialEq)] +#[case_sensitive] +enum ArgChoice { + Foo, + Bar, + Baz, +} + +#[test] +fn when_lowercase() { + let matches = App::new(env!("CARGO_PKG_NAME")) + .arg(Arg::with_name("arg") + .required(true) + .takes_value(true) + .possible_values(&ArgChoice::variants()) + ).get_matches_from_safe(vec![ + "", + "foo", + ]); // We expect this to fail. + assert!(matches.is_err()); + assert_eq!(matches.unwrap_err().kind, clap::ErrorKind::InvalidValue); +} + +#[test] +fn when_capitalized() { + let matches = App::new(env!("CARGO_PKG_NAME")) + .arg(Arg::with_name("arg") + .required(true) + .takes_value(true) + .possible_values(&ArgChoice::variants()) + ).get_matches_from_safe(vec![ + "", + "Foo", + ]).unwrap(); + let t = value_t!(matches.value_of("arg"), ArgChoice); + assert!(t.is_ok()); + assert_eq!(t.unwrap(), ArgChoice::Foo); +} \ No newline at end of file From dca4b8daab2bbf395ad55b5dfa140399b7f7634e Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Thu, 23 Nov 2017 16:03:50 +0100 Subject: [PATCH 0073/3337] Improve documentation. Fix #30 --- structopt-derive/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 19b586dc849..81be7a4ea6f 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -53,13 +53,13 @@ //! //! The type of the field gives the kind of argument: //! -//! Type | Effect | Added method call to `clap::Arg` -//! ---------------------|-------------------|-------------------------------------- -//! `bool` | `true` if present | `.takes_value(false).multiple(false)` -//! `u64` | number of params | `.takes_value(false).multiple(true)` -//! `Option` | optional argument | `.takes_value(true).multiple(false)` -//! `Vec` | list of arguments | `.takes_value(true).multiple(true)` -//! `T: FromStr` | required argument | `.takes_value(true).multiple(false).required(!has_default)` +//! Type | Effect | Added method call to `clap::Arg` +//! ---------------------|--------------------------------------|-------------------------------------- +//! `bool` | `true` if present | `.takes_value(false).multiple(false)` +//! `u64` | number of times the argument is used | `.takes_value(false).multiple(true)` +//! `Option` | optional argument | `.takes_value(true).multiple(false)` +//! `Vec` | list of arguments | `.takes_value(true).multiple(true)` +//! `T: FromStr` | required argument | `.takes_value(true).multiple(false).required(!has_default)` //! //! The `FromStr` trait is used to convert the argument to the given //! type, and the `Arg::validator` method is set to a method using @@ -76,7 +76,7 @@ //! .required(false) //! .validator(parse_validator::) //! .short("s") -//! .long("debug") +//! .long("speed") //! .help("Set speed") //! .default_value("42") //! ``` From 5bf0d2ffb579bd7750473fd21fe47cf698344f6f Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Sat, 25 Nov 2017 23:10:11 +0100 Subject: [PATCH 0074/3337] Don't call author, version and about if empty argument fix #31 --- structopt-derive/src/lib.rs | 38 ++++++++++++++++++++------------ tests/no_author_version_about.rs | 24 ++++++++++++++++++++ 2 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 tests/no_author_version_about.rs diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 81be7a4ea6f..0ab193e600f 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -392,13 +392,16 @@ fn extract_attrs<'a>(attrs: &'a [Attribute], attr_source: AttrSource) -> Box Lit { +fn from_attr_or_env(attrs: &[(Ident, Lit)], key: &str, env: &str) -> String { let default = std::env::var(env).unwrap_or("".into()); attrs.iter() .filter(|&&(ref i, _)| i.as_ref() == key) .last() - .map(|&(_, ref l)| l.clone()) - .unwrap_or_else(|| Lit::Str(default, StrStyle::Cooked)) + .and_then(|&(_, ref l)| match *l { + Lit::Str(ref s, _) => Some(s.clone()), + _ => None + }) + .unwrap_or(default) } fn is_subcommand(field: &Field) -> bool { @@ -670,21 +673,28 @@ fn gen_from_clap(struct_name: &Ident, fields: &[Field]) -> quote::Tokens { } } -fn format_author(raw_authors: Lit) -> Lit { - let raw_authors = match raw_authors { - Lit::Str(x, _) => x, - x => return x, - }; - let authors = raw_authors.replace(":", ", "); - Lit::Str(authors, StrStyle::Cooked) +fn format_author(raw_authors: String) -> String { + raw_authors.replace(":", ", ") +} + +fn method_if_arg(method: &str, arg: &str) -> Option { + if arg.is_empty() { + None + } else { + let method: Ident = method.into(); + Some(quote!(.#method(#arg))) + } } fn gen_clap(attrs: &[Attribute]) -> quote::Tokens { let attrs: Vec<_> = extract_attrs(attrs, AttrSource::Struct).collect(); - let name = from_attr_or_env(&attrs, "name", "CARGO_PKG_NAME"); + let name: Lit = from_attr_or_env(&attrs, "name", "CARGO_PKG_NAME").into(); let version = from_attr_or_env(&attrs, "version", "CARGO_PKG_VERSION"); + let version = method_if_arg("version", &version); let author = format_author(from_attr_or_env(&attrs, "author", "CARGO_PKG_AUTHORS")); + let author = method_if_arg("author", &author); let about = from_attr_or_env(&attrs, "about", "CARGO_PKG_DESCRIPTION"); + let about = method_if_arg("about", &about); let settings = attrs.iter() .filter(|&&(ref i, _)| !["name", "version", "author", "about"].contains(&i.as_ref())) .map(|&(ref i, ref l)| gen_attr_call(i, l)) @@ -692,9 +702,9 @@ fn gen_clap(attrs: &[Attribute]) -> quote::Tokens { quote! { _structopt::clap::App::new(#name) - .version(#version) - .author(#author) - .about(#about) + #version + #author + #about #( #settings )* } } diff --git a/tests/no_author_version_about.rs b/tests/no_author_version_about.rs new file mode 100644 index 00000000000..98637be00a5 --- /dev/null +++ b/tests/no_author_version_about.rs @@ -0,0 +1,24 @@ +// Copyright (c) 2017 Guillaume Pinot +// +// This work is free. You can redistribute it and/or modify it under +// the terms of the Do What The Fuck You Want To Public License, +// Version 2, as published by Sam Hocevar. See the COPYING file for +// more details. + +extern crate structopt; +#[macro_use] extern crate structopt_derive; + +use structopt::StructOpt; + +#[test] +fn no_author_version_about() { + #[derive(StructOpt, PartialEq, Debug)] + #[structopt(name = "foo", about = "", author = "", version = "")] + struct Opt {} + + let mut output = Vec::new(); + Opt::clap().write_long_help(&mut output).unwrap(); + let output = String::from_utf8(output).unwrap(); + + assert!(output.starts_with("foo \n\nUSAGE:")); +} From 3186513b5709b741fd5722504f54df1591cf9657 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Sat, 25 Nov 2017 23:38:37 +0100 Subject: [PATCH 0075/3337] Improve doc about positional arguments fix #33 --- structopt-derive/src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 0ab193e600f..486404be8f2 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -53,13 +53,13 @@ //! //! The type of the field gives the kind of argument: //! -//! Type | Effect | Added method call to `clap::Arg` -//! ---------------------|--------------------------------------|-------------------------------------- -//! `bool` | `true` if present | `.takes_value(false).multiple(false)` -//! `u64` | number of times the argument is used | `.takes_value(false).multiple(true)` -//! `Option` | optional argument | `.takes_value(true).multiple(false)` -//! `Vec` | list of arguments | `.takes_value(true).multiple(true)` -//! `T: FromStr` | required argument | `.takes_value(true).multiple(false).required(!has_default)` +//! Type | Effect | Added method call to `clap::Arg` +//! ---------------------|---------------------------------------------------|-------------------------------------- +//! `bool` | `true` if the flag is present | `.takes_value(false).multiple(false)` +//! `u64` | number of times the flag is used | `.takes_value(false).multiple(true)` +//! `Option` | optional positional argument or option | `.takes_value(true).multiple(false)` +//! `Vec` | list of options or the other positional arguments | `.takes_value(true).multiple(true)` +//! `T: FromStr` | required option or positional argument | `.takes_value(true).multiple(false).required(!has_default)` //! //! The `FromStr` trait is used to convert the argument to the given //! type, and the `Arg::validator` method is set to a method using From 374f9080a8d4db50df9261a51331f143baef2d31 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Sat, 25 Nov 2017 23:45:02 +0100 Subject: [PATCH 0076/3337] v0.1.6 --- Cargo.toml | 4 ++-- structopt-derive/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 935412e691c..eadd04b972c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.1.5" +version = "0.1.6" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" @@ -17,6 +17,6 @@ travis-ci = { repository = "TeXitoi/structopt" } clap = "2.20" [dev-dependencies] -structopt-derive = { path = "structopt-derive", version = "0.1.5" } +structopt-derive = { path = "structopt-derive", version = "0.1.6" } [workspace] diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index 7ad73f6102f..4e191131f42 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt-derive" -version = "0.1.5" +version = "0.1.6" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct, derive crate." documentation = "https://docs.rs/structopt-derive" From cc6dfd2e542440e4f194ce7823add4bf09ba9c22 Mon Sep 17 00:00:00 2001 From: Xavier Bestel Date: Tue, 5 Dec 2017 15:20:17 +0100 Subject: [PATCH 0077/3337] Add doc on colored help (#40) Added a note about `clap::App`'s _raw methods, and an example specifically about colored help text. --- structopt-derive/src/lib.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 486404be8f2..cdda3078ddd 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -42,6 +42,32 @@ //! - `author`: Defaults to the crate author name given by Cargo. //! - `about`: Defaults to the crate description given by Cargo. //! +//! Methods from `clap::App` that don't take an &str can be called by +//! adding _raw to their name, e.g. to activate colored help text: +//! +//! ```ignore +//! extern crate clap; +//! extern crate structopt; +//! #[macro_use] +//! extern crate structopt_derive; +//! +//! use structopt::StructOpt; +//! +//! #[derive(StructOpt, Debug)] +//! #[structopt(setting_raw = "clap::AppSettings::ColoredHelp")] +//! struct Opt { +//! #[structopt(short = "s")] +//! speed: bool, +//! #[structopt(short = "d")] +//! debug: bool, +//! } +//! +//! fn main() { +//! let opt = Opt::from_args(); +//! println!("{:?}", opt); +//! } +//! ``` +//! //! Then, each field of the struct not marked as a subcommand corresponds //! to a `clap::Arg`. As with the struct attributes, every method of //! `clap::Arg` in the form of `fn function_name(self, &str)` can be used From 00a1e13fea3fce60a7dcefefa779a037b4d6b6e3 Mon Sep 17 00:00:00 2001 From: Sune Kirkeby Date: Tue, 23 Jan 2018 15:47:06 +0100 Subject: [PATCH 0078/3337] Allow opting out of clap default features. (#46) --- Cargo.toml | 7 +++++-- src/lib.rs | 9 +++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eadd04b972c..86fb3cfd7be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "structopt" -version = "0.1.6" +version = "0.1.7" authors = ["Guillaume Pinot "] description = "Parse command line argument by defining a struct." documentation = "https://docs.rs/structopt" @@ -10,11 +10,14 @@ categories = ["command-line-interface"] license = "WTFPL" readme = "README.md" +[features] +default = ["clap/default"] + [badges] travis-ci = { repository = "TeXitoi/structopt" } [dependencies] -clap = "2.20" +clap = { version = "2.20", default-features = false } [dev-dependencies] structopt-derive = { path = "structopt-derive", version = "0.1.6" } diff --git a/src/lib.rs b/src/lib.rs index 95d9d7e4070..fdac1bf9db3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,15 @@ //! little interest. See the //! [`structopt-derive`](https://docs.rs/structopt-derive) crate to //! automatically generate an implementation of this trait. +//! +//! If you want to disable all the `clap` features (colors, +//! suggestions, ..) add `default-features = false` to the `structopt` +//! dependency: +//! ```toml +//! [dependencies] +//! structopt = { version = "0.1.0", default-features = false } +//! structopt-derive = "0.1.0" +//! ``` extern crate clap as _clap; From acdbd47152102607b7f4c6702cc076caca771280 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 22 Jan 2018 18:19:53 -0500 Subject: [PATCH 0079/3337] wip: changes to builders in prep for v3 --- src/app/help.rs | 337 ++++-- src/app/meta.rs | 33 - src/app/mod.rs | 610 +++++++--- src/app/parser.rs | 1818 +++++++++++----------------- src/app/usage.rs | 822 +++++++------ src/app/validator.rs | 219 ++-- src/args/any_arg.rs | 74 -- src/args/arg.rs | 539 +++++++-- src/args/arg_builder/base.rs | 38 - src/args/arg_builder/flag.rs | 159 --- src/args/arg_builder/mod.rs | 13 - src/args/arg_builder/option.rs | 244 ---- src/args/arg_builder/positional.rs | 229 ---- src/args/arg_builder/switched.rs | 38 - src/args/arg_builder/valued.rs | 67 - src/args/arg_matcher.rs | 25 +- src/args/mod.rs | 8 +- src/completions/bash.rs | 64 +- src/completions/fish.rs | 43 +- src/completions/macros.rs | 10 +- src/completions/mod.rs | 74 +- src/completions/powershell.rs | 35 +- src/completions/zsh.rs | 100 +- src/errors.rs | 34 +- src/macros.rs | 320 +++-- src/suggestions.rs | 16 +- src/usage_parser.rs | 892 +++++++------- 27 files changed, 3090 insertions(+), 3771 deletions(-) delete mode 100644 src/app/meta.rs delete mode 100644 src/args/any_arg.rs delete mode 100644 src/args/arg_builder/base.rs delete mode 100644 src/args/arg_builder/flag.rs delete mode 100644 src/args/arg_builder/mod.rs delete mode 100644 src/args/arg_builder/option.rs delete mode 100644 src/args/arg_builder/positional.rs delete mode 100644 src/args/arg_builder/switched.rs delete mode 100644 src/args/arg_builder/valued.rs diff --git a/src/app/help.rs b/src/app/help.rs index fc118a9e9ae..ea0357a8d18 100644 --- a/src/app/help.rs +++ b/src/app/help.rs @@ -2,17 +2,16 @@ use std::borrow::Cow; use std::cmp; use std::collections::BTreeMap; -use std::fmt::Display; use std::io::{self, Cursor, Read, Write}; use std::usize; // Internal use app::{App, AppSettings}; use app::parser::Parser; -use args::{AnyArg, ArgSettings, DispOrder}; +use args::{ArgSettings, DispOrder, Arg}; use errors::{Error, Result as ClapResult}; use fmt::{Colorizer, ColorizerOption, Format}; -use app::usage; +use app::usage::Usage; use map::VecMap; use INTERNAL_ERROR_MSG; @@ -31,29 +30,16 @@ fn str_width(s: &str) -> usize { UnicodeWidthStr::width(s) } const TAB: &'static str = " "; -// These are just convenient traits to make the code easier to read. -trait ArgWithDisplay<'b, 'c>: AnyArg<'b, 'c> + Display {} -impl<'b, 'c, T> ArgWithDisplay<'b, 'c> for T -where - T: AnyArg<'b, 'c> + Display, -{ -} - -trait ArgWithOrder<'b, 'c>: ArgWithDisplay<'b, 'c> + DispOrder { - fn as_base(&self) -> &ArgWithDisplay<'b, 'c>; -} -impl<'b, 'c, T> ArgWithOrder<'b, 'c> for T -where - T: ArgWithDisplay<'b, 'c> + DispOrder, -{ - fn as_base(&self) -> &ArgWithDisplay<'b, 'c> { self } -} - -fn as_arg_trait<'a, 'b, T: ArgWithOrder<'a, 'b>>(x: &T) -> &ArgWithOrder<'a, 'b> { x } - -impl<'b, 'c> DispOrder for App<'b, 'c> { - fn disp_ord(&self) -> usize { 999 } -} +// trait ArgWithOrder<'b, 'c>: Display + DispOrder { +// fn as_base(&self) -> &Arg<'b, 'c>; +// } +// impl<'b, 'c, T> ArgWithOrder<'b, 'c> for T +// where +// T: Display + DispOrder, +// { +// fn as_base(&self) -> &Arg<'b, 'c> { self } +// } +// fn as_arg_trait<'w, 'b, T: ArgWithOrder<'w, 'b>>(x: &T) -> &ArgWithOrder<'w, 'b> { x } macro_rules! color { ($_self:ident, $s:expr, $c:ident) => { @@ -75,8 +61,8 @@ macro_rules! color { /// `clap` Help Writer. /// /// Wraps a writer stream providing different methods to generate help for `clap` objects. -pub struct Help<'a> { - writer: &'a mut Write, +pub struct Help<'w> { + writer: &'w mut Write, next_line_help: bool, hide_pv: bool, term_w: usize, @@ -88,11 +74,11 @@ pub struct Help<'a> { } // Public Functions -impl<'a> Help<'a> { +impl<'w> Help<'w> { /// Create a new `Help` instance. #[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))] pub fn new( - w: &'a mut Write, + w: &'w mut Write, next_line_help: bool, hide_pv: bool, color: bool, @@ -130,14 +116,15 @@ impl<'a> Help<'a> { /// Reads help settings from an App /// and write its help to the wrapped stream. - pub fn write_app_help(w: &'a mut Write, app: &App, use_long: bool) -> ClapResult<()> { + pub fn write_app_help(w: &'w mut Write, app: &App, use_long: bool) -> ClapResult<()> { debugln!("Help::write_app_help;"); - Self::write_parser_help(w, &app.p, use_long) + let p = RefParser::new(app); + Self::write_parser_help(w, &p, use_long) } /// Reads help settings from a Parser /// and write its help to the wrapped stream. - pub fn write_parser_help(w: &'a mut Write, parser: &Parser, use_long: bool) -> ClapResult<()> { + pub fn write_parser_help(w: &'w mut Write, parser: &Parser, use_long: bool) -> ClapResult<()> { debugln!("Help::write_parser_help;"); Self::_write_parser_help(w, parser, false, use_long) } @@ -145,14 +132,14 @@ impl<'a> Help<'a> { /// Reads help settings from a Parser /// and write its help to the wrapped stream which will be stderr. This method prevents /// formatting when required. - pub fn write_parser_help_to_stderr(w: &'a mut Write, parser: &Parser) -> ClapResult<()> { + pub fn write_parser_help_to_stderr(w: &'w mut Write, parser: &Parser) -> ClapResult<()> { debugln!("Help::write_parser_help;"); Self::_write_parser_help(w, parser, true, false) } #[doc(hidden)] pub fn _write_parser_help( - w: &'a mut Write, + w: &'w mut Write, parser: &Parser, stderr: bool, use_long: bool, @@ -163,7 +150,7 @@ impl<'a> Help<'a> { let color = parser.is_set(AppSettings::ColoredHelp); let cizer = Colorizer::new(ColorizerOption { use_stderr: stderr, - when: parser.color(), + when: parser.app.color(), }); Self::new( w, @@ -171,8 +158,8 @@ impl<'a> Help<'a> { hide_v, color, cizer, - parser.meta.term_w, - parser.meta.max_w, + parser.app.term_w, + parser.app.max_w, use_long, ).write_help(parser) } @@ -180,9 +167,9 @@ impl<'a> Help<'a> { /// Writes the parser help to the wrapped stream. pub fn write_help(&mut self, parser: &Parser) -> ClapResult<()> { debugln!("Help::write_help;"); - if let Some(h) = parser.meta.help_str { + if let Some(h) = parser.app.help_str { write!(self.writer, "{}", h).map_err(Error::from)?; - } else if let Some(tmpl) = parser.meta.template { + } else if let Some(tmpl) = parser.app.template { self.write_templated_help(parser, tmpl)?; } else { self.write_default_help(parser)?; @@ -191,12 +178,13 @@ impl<'a> Help<'a> { } } -// Methods to write AnyArg help. -impl<'a> Help<'a> { +// Methods to write Arg help. +impl<'w> Help<'w> { /// Writes help for each argument in the order they were declared to the wrapped stream. - fn write_args_unsorted<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> + fn write_args_unsorted<'a, 'b, I>(&mut self, args: I) -> io::Result<()> where - I: Iterator>, + 'a: 'b, + I: Iterator>, { debugln!("Help::write_args_unsorted;"); // The shortest an arg can legally be is 2 (i.e. '-x') @@ -217,15 +205,16 @@ impl<'a> Help<'a> { } else { self.writer.write_all(b"\n")?; } - self.write_arg(arg.as_base())?; + self.write_arg(arg)?; } Ok(()) } /// Sorts arguments by length and display order and write their help to the wrapped stream. - fn write_args<'b: 'd, 'c: 'd, 'd, I: 'd>(&mut self, args: I) -> io::Result<()> + fn write_args<'a, 'b, I>(&mut self, args: I) -> io::Result<()> where - I: Iterator>, + 'a: 'b, + I: Iterator>, { debugln!("Help::write_args;"); // The shortest an arg can legally be is 2 (i.e. '-x') @@ -244,7 +233,7 @@ impl<'a> Help<'a> { debugln!("Help::write_args: New Longest...{}", self.longest); } let btm = ord_m.entry(arg.disp_ord()).or_insert(BTreeMap::new()); - btm.insert(arg.name(), arg); + btm.insert(arg.name, arg); } let mut first = true; for btm in ord_m.values() { @@ -254,14 +243,14 @@ impl<'a> Help<'a> { } else { self.writer.write_all(b"\n")?; } - self.write_arg(arg.as_base())?; + self.write_arg(arg)?; } } Ok(()) } /// Writes help for an argument to the wrapped stream. - fn write_arg<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { + fn write_arg<'b, 'c>(&mut self, arg: &Arg<'b, 'c>) -> io::Result<()> { debugln!("Help::write_arg;"); self.short(arg)?; self.long(arg)?; @@ -271,10 +260,10 @@ impl<'a> Help<'a> { } /// Writes argument's short command to the wrapped stream. - fn short<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { + fn short<'b, 'c>(&mut self, arg: &Arg<'b, 'c>) -> io::Result<()> { debugln!("Help::short;"); write!(self.writer, "{}", TAB)?; - if let Some(s) = arg.short() { + if let Some(s) = arg.short { color!(self, "-{}", s, good) } else if arg.has_switch() { write!(self.writer, "{}", TAB) @@ -284,14 +273,14 @@ impl<'a> Help<'a> { } /// Writes argument's long command to the wrapped stream. - fn long<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> io::Result<()> { + fn long<'b, 'c>(&mut self, arg: &Arg<'b, 'c>) -> io::Result<()> { debugln!("Help::long;"); if !arg.has_switch() { return Ok(()); } - if arg.takes_value() { - if let Some(l) = arg.long() { - if arg.short().is_some() { + if arg.is_set(ArgSettings::TakesValue) { + if let Some(l) = arg.long { + if arg.short.is_some() { write!(self.writer, ", ")?; } color!(self, "--{}", l, good)? @@ -303,8 +292,8 @@ impl<'a> Help<'a> { " " }; write!(self.writer, "{}", sep)?; - } else if let Some(l) = arg.long() { - if arg.short().is_some() { + } else if let Some(l) = arg.long { + if arg.short.is_some() { write!(self.writer, ", ")?; } color!(self, "--{}", l, good)?; @@ -313,15 +302,15 @@ impl<'a> Help<'a> { } /// Writes argument's possible values to the wrapped stream. - fn val<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>) -> Result { + fn val<'b, 'c>(&mut self, arg: &Arg<'b, 'c>) -> Result { debugln!("Help::val: arg={}", arg); - if arg.takes_value() { + if arg.is_set(ArgSettings::TakesValue) { let delim = if arg.is_set(ArgSettings::RequireDelimiter) { - arg.val_delim().expect(INTERNAL_ERROR_MSG) + arg.val_delim.expect(INTERNAL_ERROR_MSG) } else { ' ' }; - if let Some(vec) = arg.val_names() { + if let Some(vec) = arg.val_names { let mut it = vec.iter().peekable(); while let Some((_, val)) = it.next() { color!(self, "<{}>", val, good)?; @@ -333,10 +322,10 @@ impl<'a> Help<'a> { if arg.is_set(ArgSettings::Multiple) && num == 1 { color!(self, "...", good)?; } - } else if let Some(num) = arg.num_vals() { + } else if let Some(num) = arg.num_vals { let mut it = (0..num).peekable(); while let Some(_) = it.next() { - color!(self, "<{}>", arg.name(), good)?; + color!(self, "<{}>", arg.name, good)?; if it.peek().is_some() { write!(self.writer, "{}", delim)?; } @@ -345,7 +334,7 @@ impl<'a> Help<'a> { color!(self, "...", good)?; } } else if arg.has_switch() { - color!(self, "<{}>", arg.name(), good)?; + color!(self, "<{}>", arg.name, good)?; if arg.is_set(ArgSettings::Multiple) { color!(self, "...", good)?; } @@ -355,7 +344,7 @@ impl<'a> Help<'a> { } let spec_vals = self.spec_vals(arg); - let h = arg.help().unwrap_or(""); + let h = arg.help.unwrap_or(""); let h_w = str_width(h) + str_width(&*spec_vals); let nlh = self.next_line_help || arg.is_set(ArgSettings::NextLineHelp); let taken = self.longest + 12; @@ -384,7 +373,7 @@ impl<'a> Help<'a> { let mut spcs = self.longest - self_len; // Since we're writing spaces from the tab point we first need to know if we // had a long and short, or just short - if arg.long().is_some() { + if arg.long.is_some() { // Only account 4 after the val spcs += 4; } else { @@ -440,12 +429,12 @@ impl<'a> Help<'a> { } /// Writes argument's help to the wrapped stream. - fn help<'b, 'c>(&mut self, arg: &ArgWithDisplay<'b, 'c>, spec_vals: &str) -> io::Result<()> { + fn help<'b, 'c>(&mut self, arg: &Arg<'b, 'c>, spec_vals: &str) -> io::Result<()> { debugln!("Help::help;"); let h = if self.use_long { - arg.long_help().unwrap_or_else(|| arg.help().unwrap_or("")) + arg.long_help.unwrap_or_else(|| arg.help.unwrap_or("")) } else { - arg.help().unwrap_or_else(|| arg.long_help().unwrap_or("")) + arg.help.unwrap_or_else(|| arg.long_help.unwrap_or("")) }; let mut help = String::from(h) + spec_vals; let nlh = self.next_line_help || arg.is_set(ArgSettings::NextLineHelp) || self.use_long; @@ -496,10 +485,10 @@ impl<'a> Help<'a> { Ok(()) } - fn spec_vals(&self, a: &ArgWithDisplay) -> String { + fn spec_vals(&self, a: &Arg) -> String { debugln!("Help::spec_vals: a={}", a); let mut spec_vals = vec![]; - if let Some(ref env) = a.env() { + if let Some(ref env) = a.env { debugln!( "Help::spec_vals: Found environment variable...[{:?}:{:?}]", env.0, @@ -518,7 +507,7 @@ impl<'a> Help<'a> { spec_vals.push(env_info); } if !a.is_set(ArgSettings::HideDefaultValue) { - if let Some(pv) = a.default_val() { + if let Some(pv) = a.default_val { debugln!("Help::spec_vals: Found default value...[{:?}]", pv); spec_vals.push(format!( " [default: {}]", @@ -530,23 +519,24 @@ impl<'a> Help<'a> { )); } } - if let Some(ref aliases) = a.aliases() { + if let Some(ref aliases) = a.aliases { debugln!("Help::spec_vals: Found aliases...{:?}", aliases); spec_vals.push(format!( " [aliases: {}]", if self.color { aliases .iter() - .map(|v| format!("{}", self.cizer.good(v))) + .filter(|&als| als.1) // visible + .map(|&als| format!("{}", self.cizer.good(als.0))) // name .collect::>() .join(", ") } else { - aliases.join(", ") + aliases.iter().filter(|&als| als.1).map(|&als| als.0).collect::>().join(", ") } )); } if !self.hide_pv && !a.is_set(ArgSettings::HidePossibleValues) { - if let Some(pv) = a.possible_vals() { + if let Some(pv) = a.possible_vals { debugln!("Help::spec_vals: Found possible vals...{:?}", pv); spec_vals.push(if self.color { format!( @@ -565,9 +555,117 @@ impl<'a> Help<'a> { } } +/// Methods to write a single subcommand +impl<'w> Help<'w> { + fn write_subcommand<'a, 'b>(&mut self, app: &App<'a, 'b>) -> io::Result<()> { + debugln!("Help::write_subcommand;"); + let spec_vals = self.sc_val(app)?; + self.sc_help(app, &*spec_vals)?; + Ok(()) + } + + fn sc_val<'a, 'b>(&mut self, app: &App<'a, 'b>) -> Result { + debugln!("Help::sc_val: app={}", app.name); + + let spec_vals = self.sc_spec_vals(app); + let h = app.about.unwrap_or(""); + let h_w = str_width(h) + str_width(&*spec_vals); + let nlh = self.next_line_help; + let taken = self.longest + 12; + self.force_next_line = !nlh && self.term_w >= taken + && (taken as f32 / self.term_w as f32) > 0.40 + && h_w > (self.term_w - taken); + + if !(nlh || self.force_next_line) { + write_nspaces!( + self.writer, + self.longest + 4 - (str_width(app.to_string().as_str())) + ); + } + Ok(spec_vals) + } + + fn sc_spec_vals(&self, a: &App) -> String { + debugln!("Help::sc_spec_vals: a={}", a.name); + let mut spec_vals = vec![]; + if let Some(ref aliases) = a.aliases { + debugln!("Help::spec_vals: Found aliases...{:?}", aliases); + spec_vals.push(format!( + " [aliases: {}]", + if self.color { + aliases + .iter() + .filter(|&als| als.1) // visible + .map(|&als| format!("{}", self.cizer.good(als.0))) // name + .collect::>() + .join(", ") + } else { + aliases.iter().filter(|&als| als.1).map(|&als| als.0).collect::>().join(", ") + } + )); + } + spec_vals.join(" ") + } + + fn sc_help<'a, 'b>(&mut self, app: &App<'a, 'b>, spec_vals: &str) -> io::Result<()> { + debugln!("Help::sc_help;"); + let h = if self.use_long { + app.long_about.unwrap_or_else(|| app.about.unwrap_or("")) + } else { + app.about.unwrap_or_else(|| app.long_about.unwrap_or("")) + }; + let mut help = String::from(h) + spec_vals; + let nlh = self.next_line_help || self.use_long; + debugln!("Help::sc_help: Next Line...{:?}", nlh); + + let spcs = if nlh || self.force_next_line { + 12 // "tab" * 3 + } else { + self.longest + 12 + }; + + let too_long = spcs + str_width(h) + str_width(&*spec_vals) >= self.term_w; + + // Is help on next line, if so then indent + if nlh || self.force_next_line { + write!(self.writer, "\n{}{}{}", TAB, TAB, TAB)?; + } + + debug!("Help::sc_help: Too long..."); + if too_long && spcs <= self.term_w || h.contains("{n}") { + sdebugln!("Yes"); + debugln!("Help::sc_help: help...{}", help); + debugln!("Help::sc_help: help width...{}", str_width(&*help)); + // Determine how many newlines we need to insert + let avail_chars = self.term_w - spcs; + debugln!("Help::sc_help: Usable space...{}", avail_chars); + help = wrap_help(&help.replace("{n}", "\n"), avail_chars); + } else { + sdebugln!("No"); + } + if let Some(part) = help.lines().next() { + write!(self.writer, "{}", part)?; + } + for part in help.lines().skip(1) { + write!(self.writer, "\n")?; + if nlh || self.force_next_line { + write!(self.writer, "{}{}{}", TAB, TAB, TAB)?; + } else { + write_nspaces!(self.writer, self.longest + 8); + } + write!(self.writer, "{}", part)?; + } + if !help.contains('\n') && (nlh || self.force_next_line) { + write!(self.writer, "\n")?; + } + Ok(()) + } + +} + // Methods to write Parser help. -impl<'a> Help<'a> { +impl<'w> Help<'w> { /// Writes help for all arguments (options, flags, args, subcommands) /// including titles of a Parser Object to the wrapped stream. #[cfg_attr(feature = "lints", allow(useless_let_if_seq))] @@ -575,8 +673,7 @@ impl<'a> Help<'a> { pub fn write_all_args(&mut self, parser: &Parser) -> ClapResult<()> { debugln!("Help::write_all_args;"); let flags = parser.has_flags(); - let pos = parser - .positionals() + let pos = positionals!(parser.app) .filter(|arg| !arg.is_set(ArgSettings::Hidden)) .count() > 0; let opts = parser.has_opts(); @@ -587,17 +684,14 @@ impl<'a> Help<'a> { let mut first = true; if unified_help && (flags || opts) { - let opts_flags = parser - .flags() - .map(as_arg_trait) - .chain(parser.opts().map(as_arg_trait)); + let opts_flags = parser.app.args.iter().filter(|a| a.has_switch()); color!(self, "OPTIONS:\n", warning)?; self.write_args(opts_flags)?; first = false; } else { if flags { color!(self, "FLAGS:\n", warning)?; - self.write_args(parser.flags().map(as_arg_trait))?; + self.write_args(flags!(parser.app))?; first = false; } if opts { @@ -605,7 +699,7 @@ impl<'a> Help<'a> { self.writer.write_all(b"\n\n")?; } color!(self, "OPTIONS:\n", warning)?; - self.write_args(parser.opts().map(as_arg_trait))?; + self.write_args(opts!(parser.app))?; first = false; } } @@ -615,7 +709,7 @@ impl<'a> Help<'a> { self.writer.write_all(b"\n\n")?; } color!(self, "ARGS:\n", warning)?; - self.write_args_unsorted(parser.positionals().map(as_arg_trait))?; + self.write_args_unsorted(positionals!(parser.app))?; first = false; } @@ -624,27 +718,25 @@ impl<'a> Help<'a> { self.writer.write_all(b"\n\n")?; } color!(self, "SUBCOMMANDS:\n", warning)?; - self.write_subcommands(parser)?; + self.write_subcommands(&parser.app)?; } Ok(()) } /// Writes help for subcommands of a Parser Object to the wrapped stream. - fn write_subcommands(&mut self, parser: &Parser) -> io::Result<()> { + fn write_subcommands(&mut self, app: &App) -> io::Result<()> { debugln!("Help::write_subcommands;"); // The shortest an arg can legally be is 2 (i.e. '-x') self.longest = 2; let mut ord_m = VecMap::new(); - for sc in parser - .subcommands - .iter() - .filter(|s| !s.p.is_set(AppSettings::Hidden)) + for sc in subcommands!(app) + .filter(|s| !s.is_set(AppSettings::Hidden)) { - let btm = ord_m.entry(sc.p.meta.disp_ord).or_insert(BTreeMap::new()); - self.longest = cmp::max(self.longest, str_width(sc.p.meta.name.as_str())); + let btm = ord_m.entry(sc.disp_ord).or_insert(BTreeMap::new()); + self.longest = cmp::max(self.longest, str_width(sc.name.as_str())); //self.longest = cmp::max(self.longest, sc.p.meta.name.len()); - btm.insert(sc.p.meta.name.clone(), sc.clone()); + btm.insert(sc.name.clone(), sc.clone()); } let mut first = true; @@ -655,7 +747,7 @@ impl<'a> Help<'a> { } else { self.writer.write_all(b"\n")?; } - self.write_arg(sc)?; + self.write_subcommand(sc)?; } } Ok(()) @@ -664,7 +756,7 @@ impl<'a> Help<'a> { /// Writes version of a Parser Object to the wrapped stream. fn write_version(&mut self, parser: &Parser) -> io::Result<()> { debugln!("Help::write_version;"); - write!(self.writer, "{}", parser.meta.version.unwrap_or(""))?; + write!(self.writer, "{}", parser.app.version.unwrap_or(""))?; Ok(()) } @@ -673,12 +765,12 @@ impl<'a> Help<'a> { debugln!("Help::write_bin_name;"); macro_rules! write_name { () => {{ - let mut name = parser.meta.name.clone(); + let mut name = parser.app.name.clone(); name = name.replace("{n}", "\n"); color!(self, wrap_help(&name, self.term_w), good)?; }}; } - if let Some(bn) = parser.meta.bin_name.as_ref() { + if let Some(bn) = parser.app.bin_name.as_ref() { if bn.contains(' ') { // Incase we're dealing with subcommands i.e. git mv is translated to git-mv color!(self, bn.replace(" ", "-"), good)? @@ -694,7 +786,7 @@ impl<'a> Help<'a> { /// Writes default help for a Parser Object to the wrapped stream. pub fn write_default_help(&mut self, parser: &Parser) -> ClapResult<()> { debugln!("Help::write_default_help;"); - if let Some(h) = parser.meta.pre_help { + if let Some(h) = parser.app.pre_help { self.write_before_after_help(h)?; self.writer.write_all(b"\n\n")?; } @@ -712,21 +804,21 @@ impl<'a> Help<'a> { self.writer.write_all(b" ")?; self.write_version(parser)?; self.writer.write_all(b"\n")?; - if let Some(author) = parser.meta.author { + if let Some(author) = parser.app.author { write_thing!(author) } if self.use_long { - if let Some(about) = parser.meta.long_about { + if let Some(about) = parser.app.long_about { debugln!("Help::write_default_help: writing long about"); write_thing!(about) - } else if let Some(about) = parser.meta.about { + } else if let Some(about) = parser.app.about { debugln!("Help::write_default_help: writing about"); write_thing!(about) } - } else if let Some(about) = parser.meta.about { + } else if let Some(about) = parser.app.about { debugln!("Help::write_default_help: writing about"); write_thing!(about) - } else if let Some(about) = parser.meta.long_about { + } else if let Some(about) = parser.app.long_about { debugln!("Help::write_default_help: writing long about"); write_thing!(about) } @@ -736,7 +828,7 @@ impl<'a> Help<'a> { self.writer, "\n{}{}\n\n", TAB, - usage::create_usage_no_title(parser, &[]) + Usage::new(parser).create_usage_no_title(&[]) )?; let flags = parser.has_flags(); @@ -748,7 +840,7 @@ impl<'a> Help<'a> { self.write_all_args(parser)?; } - if let Some(h) = parser.meta.more_help { + if let Some(h) = parser.app.more_help { if flags || opts || pos || subcmds { self.writer.write_all(b"\n\n")?; } @@ -858,7 +950,7 @@ fn copy_and_capture( // Methods to write Parser help using templates. -impl<'a> Help<'a> { +impl<'w> Help<'w> { /// Write help to stream for the parser in the format defined by the template. /// /// Tags arg given inside curly brackets: @@ -917,67 +1009,64 @@ impl<'a> Help<'a> { write!( self.writer, "{}", - parser.meta.version.unwrap_or("unknown version") + parser.app.version.unwrap_or("unknown version") )?; } b"author" => { write!( self.writer, "{}", - parser.meta.author.unwrap_or("unknown author") + parser.app.author.unwrap_or("unknown author") )?; } b"about" => { write!( self.writer, "{}", - parser.meta.about.unwrap_or("unknown about") + parser.app.about.unwrap_or("unknown about") )?; } b"long-about" => { write!( self.writer, "{}", - parser.meta.long_about.unwrap_or("unknown about") + parser.app.long_about.unwrap_or("unknown about") )?; } b"usage" => { - write!(self.writer, "{}", usage::create_usage_no_title(parser, &[]))?; + write!(self.writer, "{}", Usage::new(parser).create_usage_no_title(&[]))?; } b"all-args" => { self.write_all_args(parser)?; } b"unified" => { - let opts_flags = parser - .flags() - .map(as_arg_trait) - .chain(parser.opts().map(as_arg_trait)); + let opts_flags = parser.app.args.iter().filter(|a| a.has_switch()); self.write_args(opts_flags)?; } b"flags" => { - self.write_args(parser.flags().map(as_arg_trait))?; + self.write_args(flags!(parser.app))?; } b"options" => { - self.write_args(parser.opts().map(as_arg_trait))?; + self.write_args(opts!(parser.app))?; } b"positionals" => { - self.write_args(parser.positionals().map(as_arg_trait))?; + self.write_args(positionals!(parser.app))?; } b"subcommands" => { - self.write_subcommands(parser)?; + self.write_subcommands(parser.app)?; } b"after-help" => { write!( self.writer, "{}", - parser.meta.more_help.unwrap_or("unknown after-help") + parser.app.more_help.unwrap_or("unknown after-help") )?; } b"before-help" => { write!( self.writer, "{}", - parser.meta.pre_help.unwrap_or("unknown before-help") + parser.app.pre_help.unwrap_or("unknown before-help") )?; } // Unknown tag, write it back. diff --git a/src/app/meta.rs b/src/app/meta.rs deleted file mode 100644 index c7f128fe537..00000000000 --- a/src/app/meta.rs +++ /dev/null @@ -1,33 +0,0 @@ -#[doc(hidden)] -#[allow(missing_debug_implementations)] -#[derive(Default, Clone)] -pub struct AppMeta<'b> { - pub name: String, - pub bin_name: Option, - pub author: Option<&'b str>, - pub version: Option<&'b str>, - pub long_version: Option<&'b str>, - pub about: Option<&'b str>, - pub long_about: Option<&'b str>, - pub more_help: Option<&'b str>, - pub pre_help: Option<&'b str>, - pub aliases: Option>, // (name, visible) - pub usage_str: Option<&'b str>, - pub usage: Option, - pub help_str: Option<&'b str>, - pub disp_ord: usize, - pub term_w: Option, - pub max_w: Option, - pub template: Option<&'b str>, -} - -impl<'b> AppMeta<'b> { - pub fn new() -> Self { Default::default() } - pub fn with_name(s: String) -> Self { - AppMeta { - name: s, - disp_ord: 999, - ..Default::default() - } - } -} diff --git a/src/app/mod.rs b/src/app/mod.rs index 0a27cb36678..b079c30fe7c 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,32 +1,32 @@ mod settings; pub mod parser; -mod meta; mod help; mod validator; mod usage; // Std use std::env; -use std::ffi::{OsStr, OsString}; +use std::ffi::OsString; use std::fmt; use std::io::{self, BufRead, BufWriter, Write}; -use std::path::Path; +use std::path::{PathBuf, Path}; use std::process; -use std::rc::Rc; -use std::result::Result as StdResult; +use std::fs::File; +use std::iter::Peekable; // Third Party #[cfg(feature = "yaml")] use yaml_rust::Yaml; // Internal -use app::help::Help; use app::parser::Parser; -use args::{AnyArg, Arg, ArgGroup, ArgMatcher, ArgMatches, ArgSettings}; +use app::help::Help; +use args::{DispOrder, Arg, ArgGroup, ArgMatcher, ArgMatches}; +use args::settings::ArgSettings; use errors::Result as ClapResult; -pub use self::settings::AppSettings; -use completions::Shell; -use map::{self, VecMap}; +pub use self::settings::{AppFlags, AppSettings}; +use completions::{ComplGen, Shell}; +use fmt::ColorWhen; /// Used to create a representation of a command line program and all possible command line /// arguments. Application settings are set using the "builder pattern" with the @@ -56,12 +56,37 @@ use map::{self, VecMap}; /// // Your program logic starts here... /// ``` /// [`App::get_matches`]: ./struct.App.html#method.get_matches -#[allow(missing_debug_implementations)] +#[derive(Default, Debug, Clone)] pub struct App<'a, 'b> where 'a: 'b, { - #[doc(hidden)] pub p: Parser<'a, 'b>, + pub name: String, + pub bin_name: Option, + pub author: Option<&'b str>, + pub version: Option<&'b str>, + pub long_version: Option<&'b str>, + pub about: Option<&'b str>, + pub long_about: Option<&'b str>, + pub more_help: Option<&'b str>, + pub pre_help: Option<&'b str>, + pub aliases: Option>, // (name, visible) + pub usage_str: Option<&'b str>, + pub usage: Option, + pub help_str: Option<&'b str>, + pub disp_ord: usize, + pub term_w: Option, + pub max_w: Option, + pub template: Option<&'b str>, + settings: AppFlags, + pub g_settings: AppFlags, + pub args: Vec>, + pub subcommands: Vec>, + pub groups: Vec>, + help_short: Option, + version_short: Option, + pub help_message: Option<&'a str>, + pub version_message: Option<&'a str>, } @@ -79,15 +104,16 @@ impl<'a, 'b> App<'a, 'b> { /// ``` pub fn new>(n: S) -> Self { App { - p: Parser::with_name(n.into()), + name: n.into(), + ..Default::default() } } /// Get the name of the app - pub fn get_name(&self) -> &str { &self.p.meta.name } + pub fn get_name(&self) -> &str { &self.name } /// Get the name of the binary - pub fn get_bin_name(&self) -> Option<&str> { self.p.meta.bin_name.as_ref().map(|s| s.as_str()) } + pub fn get_bin_name(&self) -> Option<&str> { self.bin_name.as_ref().map(|s| s.as_str()) } /// Creates a new instance of an application requiring a name, but uses the [`crate_authors!`] /// and [`crate_version!`] macros to fill in the [`App::author`] and [`App::version`] fields. @@ -105,12 +131,12 @@ impl<'a, 'b> App<'a, 'b> { /// [`App::version`]: ./struct.App.html#method.author #[deprecated(since="2.14.1", note="Can never work; use explicit App::author() and App::version() calls instead")] pub fn with_defaults>(n: S) -> Self { - let mut a = App { - p: Parser::with_name(n.into()), - }; - a.p.meta.author = Some("Kevin K. "); - a.p.meta.version = Some("2.19.2"); - a + App { + name: n.into(), + author: Some("Kevin K. "), + version: Some("2.19.2"), + ..Default::default() + } } /// Creates a new instance of [`App`] from a .yml (YAML) file. A full example of supported YAML @@ -176,7 +202,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`crate_authors!`]: ./macro.crate_authors!.html /// [`examples/`]: https://github.com/kbknapp/clap-rs/tree/master/examples pub fn author>(mut self, author: S) -> Self { - self.p.meta.author = Some(author.into()); + self.author = Some(author.into()); self } @@ -199,7 +225,7 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [`SubCommand`]: ./struct.SubCommand.html pub fn bin_name>(mut self, name: S) -> Self { - self.p.meta.bin_name = Some(name.into()); + self.bin_name = Some(name.into()); self } @@ -222,7 +248,7 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [`App::long_about`]: ./struct.App.html#method.long_about pub fn about>(mut self, about: S) -> Self { - self.p.meta.about = Some(about.into()); + self.about = Some(about.into()); self } @@ -248,7 +274,7 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [`App::about`]: ./struct.App.html#method.about pub fn long_about>(mut self, about: S) -> Self { - self.p.meta.long_about = Some(about.into()); + self.long_about = Some(about.into()); self } @@ -275,7 +301,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`App::from_yaml`]: ./struct.App.html#method.from_yaml /// [`crate_name!`]: ./macro.crate_name.html pub fn name>(mut self, name: S) -> Self { - self.p.meta.name = name.into(); + self.name = name.into(); self } @@ -292,7 +318,7 @@ impl<'a, 'b> App<'a, 'b> { /// # ; /// ``` pub fn after_help>(mut self, help: S) -> Self { - self.p.meta.more_help = Some(help.into()); + self.more_help = Some(help.into()); self } @@ -309,7 +335,7 @@ impl<'a, 'b> App<'a, 'b> { /// # ; /// ``` pub fn before_help>(mut self, help: S) -> Self { - self.p.meta.pre_help = Some(help.into()); + self.pre_help = Some(help.into()); self } @@ -335,7 +361,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`examples/`]: https://github.com/kbknapp/clap-rs/tree/master/examples /// [`App::long_version`]: ./struct.App.html#method.long_version pub fn version>(mut self, ver: S) -> Self { - self.p.meta.version = Some(ver.into()); + self.version = Some(ver.into()); self } @@ -366,7 +392,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`examples/`]: https://github.com/kbknapp/clap-rs/tree/master/examples /// [`App::version`]: ./struct.App.html#method.version pub fn long_version>(mut self, ver: S) -> Self { - self.p.meta.long_version = Some(ver.into()); + self.long_version = Some(ver.into()); self } @@ -395,7 +421,7 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [`ArgMatches::usage`]: ./struct.ArgMatches.html#method.usage pub fn usage>(mut self, usage: S) -> Self { - self.p.meta.usage_str = Some(usage.into()); + self.usage_str = Some(usage.into()); self } @@ -434,7 +460,7 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [`Arg::help`]: ./struct.Arg.html#method.help pub fn help>(mut self, help: S) -> Self { - self.p.meta.help_str = Some(help.into()); + self.help_str = Some(help.into()); self } @@ -460,7 +486,11 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [`short`]: ./struct.Arg.html#method.short pub fn help_short + 'b>(mut self, s: S) -> Self { - self.p.help_short(s.as_ref()); + let c = s.as_ref().trim_left_matches(|c| c == '-') + .chars() + .nth(0) + .unwrap_or('h'); + self.help_short = Some(c); self } @@ -486,7 +516,11 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [`short`]: ./struct.Arg.html#method.short pub fn version_short>(mut self, s: S) -> Self { - self.p.version_short(s.as_ref()); + let c = s.as_ref().trim_left_matches(|c| c == '-') + .chars() + .nth(0) + .unwrap_or('V'); + self.version_short = Some(c); self } @@ -506,7 +540,7 @@ impl<'a, 'b> App<'a, 'b> { /// # ; /// ``` pub fn help_message>(mut self, s: S) -> Self { - self.p.help_message = Some(s.into()); + self.help_message = Some(s.into()); self } @@ -524,7 +558,7 @@ impl<'a, 'b> App<'a, 'b> { /// # ; /// ``` pub fn version_message>(mut self, s: S) -> Self { - self.p.version_message = Some(s.into()); + self.version_message = Some(s.into()); self } @@ -567,7 +601,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`App::before_help`]: ./struct.App.html#method.before_help /// [`AppSettings::UnifiedHelpMessage`]: ./enum.AppSettings.html#variant.UnifiedHelpMessage pub fn template>(mut self, s: S) -> Self { - self.p.meta.template = Some(s.into()); + self.template = Some(s.into()); self } @@ -587,7 +621,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`SubCommand`]: ./struct.SubCommand.html /// [`AppSettings`]: ./enum.AppSettings.html pub fn setting(mut self, setting: AppSettings) -> Self { - self.p.set(setting); + self.settings.set(setting); self } @@ -608,7 +642,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`AppSettings`]: ./enum.AppSettings.html pub fn settings(mut self, settings: &[AppSettings]) -> Self { for s in settings { - self.p.set(*s); + self.settings.set(*s); } self } @@ -630,8 +664,8 @@ impl<'a, 'b> App<'a, 'b> { /// [`SubCommand`]: ./struct.SubCommand.html /// [`AppSettings`]: ./enum.AppSettings.html pub fn global_setting(mut self, setting: AppSettings) -> Self { - self.p.set(setting); - self.p.g_settings.set(setting); + self.settings.set(setting); + self.g_settings.set(setting); self } @@ -654,8 +688,8 @@ impl<'a, 'b> App<'a, 'b> { /// [`AppSettings`]: ./enum.AppSettings.html pub fn global_settings(mut self, settings: &[AppSettings]) -> Self { for s in settings { - self.p.set(*s); - self.p.g_settings.set(*s) + self.settings.set(*s); + self.g_settings.set(*s) } self } @@ -675,7 +709,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`SubCommand`]: ./struct.SubCommand.html /// [`AppSettings`]: ./enum.AppSettings.html pub fn unset_setting(mut self, setting: AppSettings) -> Self { - self.p.unset(setting); + self.settings.unset(setting); self } @@ -696,7 +730,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`AppSettings`]: ./enum.AppSettings.html pub fn unset_settings(mut self, settings: &[AppSettings]) -> Self { for s in settings { - self.p.unset(*s); + self.settings.unset(*s); } self } @@ -727,7 +761,7 @@ impl<'a, 'b> App<'a, 'b> { /// # ; /// ``` pub fn set_term_width(mut self, width: usize) -> Self { - self.p.meta.term_w = Some(width); + self.term_w = Some(width); self } @@ -755,7 +789,7 @@ impl<'a, 'b> App<'a, 'b> { /// # ; /// ``` pub fn max_term_width(mut self, w: usize) -> Self { - self.p.meta.max_w = Some(w); + self.max_w = Some(w); self } @@ -781,7 +815,7 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [argument]: ./struct.Arg.html pub fn arg>>(mut self, a: A) -> Self { - self.p.add_arg(a.into()); + self.args.push(a.into()); self } @@ -801,7 +835,7 @@ impl<'a, 'b> App<'a, 'b> { /// [arguments]: ./struct.Arg.html pub fn args(mut self, args: &[Arg<'a, 'b>]) -> Self { for arg in args { - self.p.add_arg_ref(arg); + self.args.push(arg.clone()); } self } @@ -824,7 +858,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`Arg`]: ./struct.Arg.html /// [`Arg::from_usage`]: ./struct.Arg.html#method.from_usage pub fn arg_from_usage(mut self, usage: &'a str) -> Self { - self.p.add_arg(Arg::from_usage(usage)); + self.args.push(Arg::from_usage(usage)); self } @@ -856,7 +890,7 @@ impl<'a, 'b> App<'a, 'b> { if l.is_empty() { continue; } - self.p.add_arg(Arg::from_usage(l)); + self.args.push(Arg::from_usage(l)); } self } @@ -878,10 +912,10 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [`SubCommand`]: ./struct.SubCommand.html pub fn alias>(mut self, name: S) -> Self { - if let Some(ref mut als) = self.p.meta.aliases { + if let Some(ref mut als) = self.aliases { als.push((name.into(), false)); } else { - self.p.meta.aliases = Some(vec![(name.into(), false)]); + self.aliases = Some(vec![(name.into(), false)]); } self } @@ -907,12 +941,12 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [`SubCommand`]: ./struct.SubCommand.html pub fn aliases(mut self, names: &[&'b str]) -> Self { - if let Some(ref mut als) = self.p.meta.aliases { + if let Some(ref mut als) = self.aliases { for n in names { als.push((n, false)); } } else { - self.p.meta.aliases = Some(names.iter().map(|n| (*n, false)).collect::>()); + self.aliases = Some(names.iter().map(|n| (*n, false)).collect::>()); } self } @@ -933,10 +967,10 @@ impl<'a, 'b> App<'a, 'b> { /// [`SubCommand`]: ./struct.SubCommand.html /// [`App::alias`]: ./struct.App.html#method.alias pub fn visible_alias>(mut self, name: S) -> Self { - if let Some(ref mut als) = self.p.meta.aliases { + if let Some(ref mut als) = self.aliases { als.push((name.into(), true)); } else { - self.p.meta.aliases = Some(vec![(name.into(), true)]); + self.aliases = Some(vec![(name.into(), true)]); } self } @@ -957,12 +991,12 @@ impl<'a, 'b> App<'a, 'b> { /// [`SubCommand`]: ./struct.SubCommand.html /// [`App::aliases`]: ./struct.App.html#method.aliases pub fn visible_aliases(mut self, names: &[&'b str]) -> Self { - if let Some(ref mut als) = self.p.meta.aliases { + if let Some(ref mut als) = self.aliases { for n in names { als.push((n, true)); } } else { - self.p.meta.aliases = Some(names.iter().map(|n| (*n, true)).collect::>()); + self.aliases = Some(names.iter().map(|n| (*n, true)).collect::>()); } self } @@ -1002,7 +1036,7 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [`ArgGroup`]: ./struct.ArgGroup.html pub fn group(mut self, group: ArgGroup<'a>) -> Self { - self.p.add_group(group); + self.groups.push(group); self } @@ -1056,7 +1090,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`SubCommand`]: ./struct.SubCommand.html /// [`App`]: ./struct.App.html pub fn subcommand(mut self, subcmd: App<'a, 'b>) -> Self { - self.p.add_subcommand(subcmd); + self.subcommands.push(subcmd); self } @@ -1081,7 +1115,7 @@ impl<'a, 'b> App<'a, 'b> { I: IntoIterator>, { for subcmd in subcmds { - self.p.add_subcommand(subcmd); + self.subcommands.push(subcmd); } self } @@ -1134,7 +1168,7 @@ impl<'a, 'b> App<'a, 'b> { /// ``` /// [`SubCommand`]: ./struct.SubCommand.html pub fn display_order(mut self, ord: usize) -> Self { - self.p.meta.disp_ord = ord; + self.disp_ord = ord; self } @@ -1158,11 +1192,8 @@ impl<'a, 'b> App<'a, 'b> { pub fn print_help(&mut self) -> ClapResult<()> { // If there are global arguments, or settings we need to propgate them down to subcommands // before parsing incase we run into a subcommand - self.p.propagate_globals(); - self.p.propagate_settings(); - self.p.derive_display_order(); + self._build(); - self.p.create_help_and_version(); let out = io::stdout(); let mut buf_w = BufWriter::new(out.lock()); self.write_help(&mut buf_w) @@ -1188,11 +1219,8 @@ impl<'a, 'b> App<'a, 'b> { pub fn print_long_help(&mut self) -> ClapResult<()> { // If there are global arguments, or settings we need to propgate them down to subcommands // before parsing incase we run into a subcommand - self.p.propagate_globals(); - self.p.propagate_settings(); - self.p.derive_display_order(); + self._build(); - self.p.create_help_and_version(); let out = io::stdout(); let mut buf_w = BufWriter::new(out.lock()); self.write_long_help(&mut buf_w) @@ -1252,12 +1280,10 @@ impl<'a, 'b> App<'a, 'b> { /// [`-h` (short)]: ./struct.Arg.html#method.help /// [`--help` (long)]: ./struct.Arg.html#method.long_help pub fn write_long_help(&mut self, w: &mut W) -> ClapResult<()> { - self.p.propagate_globals(); - self.p.propagate_settings(); - self.p.derive_display_order(); - self.p.create_help_and_version(); + self._build(); - Help::write_app_help(w, self, true) + let p = Parser::new(self); + Help::write_parser_help(w, &p, true) } /// Writes the version message to the user to a [`io::Write`] object as if the user ran `-V`. @@ -1278,7 +1304,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`-V` (short)]: ./struct.App.html#method.version /// [`--version` (long)]: ./struct.App.html#method.long_version pub fn write_version(&self, w: &mut W) -> ClapResult<()> { - self.p.write_version(w, false).map_err(From::from) + self._write_version(w, false).map_err(From::from) } /// Writes the version message to the user to a [`io::Write`] object @@ -1299,7 +1325,7 @@ impl<'a, 'b> App<'a, 'b> { /// [`-V` (short)]: ./struct.App.html#method.version /// [`--version` (long)]: ./struct.App.html#method.long_version pub fn write_long_version(&self, w: &mut W) -> ClapResult<()> { - self.p.write_version(w, true).map_err(From::from) + self._write_version(w, true).map_err(From::from) } /// Generate a completions file for a specified shell at compile time. @@ -1391,8 +1417,22 @@ impl<'a, 'b> App<'a, 'b> { for_shell: Shell, out_dir: T, ) { - self.p.meta.bin_name = Some(bin_name.into()); - self.p.gen_completions(for_shell, out_dir.into()); + use std::error::Error; + + let out_dir = PathBuf::from(out_dir.into()); + let name = &*self.bin_name.as_ref().unwrap().clone(); + let file_name = match for_shell { + Shell::Bash => format!("{}.bash", name), + Shell::Fish => format!("{}.fish", name), + Shell::Zsh => format!("_{}", name), + Shell::PowerShell => format!("_{}.ps1", name), + }; + + let mut file = match File::create(out_dir.join(file_name)) { + Err(why) => panic!("couldn't create completion file: {}", why.description()), + Ok(file) => file, + }; + self.gen_completions_to(bin_name.into(), for_shell, &mut file) } @@ -1434,8 +1474,14 @@ impl<'a, 'b> App<'a, 'b> { for_shell: Shell, buf: &mut W, ) { - self.p.meta.bin_name = Some(bin_name.into()); - self.p.gen_completions_to(for_shell, buf); + self.bin_name = Some(bin_name.into()); + if !self.is_set(AppSettings::Propagated) { + self.propagate(); + self.build_bin_names(); + self.set(AppSettings::Propagated); + } + + ComplGen::new(self).generate(for_shell, buf) } /// Starts the parsing process, upon a failed parse an error will be displayed to the user and @@ -1514,7 +1560,7 @@ impl<'a, 'b> App<'a, 'b> { // Otherwise, write to stderr and exit if e.use_stderr() { wlnerr!("{}", e.message); - if self.p.is_set(AppSettings::WaitOnError) { + if self.settings.is_set(AppSettings::WaitOnError) { wlnerr!("\nPress [ENTER] / [RETURN] to continue..."); let mut s = String::new(); let i = io::stdin(); @@ -1596,17 +1642,6 @@ impl<'a, 'b> App<'a, 'b> { I: IntoIterator, T: Into + Clone, { - // If there are global arguments, or settings we need to propgate them down to subcommands - // before parsing incase we run into a subcommand - if !self.p.is_set(AppSettings::Propagated) { - self.p.propagate_globals(); - self.p.propagate_settings(); - self.p.derive_display_order(); - self.p.set(AppSettings::Propagated); - } - - let mut matcher = ArgMatcher::new(); - let mut it = itr.into_iter(); // Get the name of the program (argument 1 of env::args()) and determine the // actual file @@ -1615,30 +1650,350 @@ impl<'a, 'b> App<'a, 'b> { // will have two arguments, './target/release/my_prog', '-a' but we don't want // to display // the full path when displaying help messages and such - if !self.p.is_set(AppSettings::NoBinaryName) { + if !self.settings.is_set(AppSettings::NoBinaryName) { if let Some(name) = it.next() { let bn_os = name.into(); let p = Path::new(&*bn_os); if let Some(f) = p.file_name() { if let Some(s) = f.to_os_string().to_str() { - if self.p.meta.bin_name.is_none() { - self.p.meta.bin_name = Some(s.to_owned()); + if self.bin_name.is_none() { + self.bin_name = Some(s.to_owned()); } } } } } + self._do_parse(&mut it.peekable()) + } +} + +// Internally used only +#[doc(hidden)] +impl<'a, 'b> App<'a, 'b> { + #[doc(hidden)] + fn _do_parse(&mut self, it: &mut Peekable) -> ClapResult> + where + I: Iterator, + T: Into + Clone, + { + let mut matcher = ArgMatcher::new(); + + // If there are global arguments, or settings we need to propgate them down to subcommands + // before parsing incase we run into a subcommand + if !self.settings.is_set(AppSettings::Propagated) { + self._build(); + } + + let mut parser = Parser::new(self); + // do the real parsing - if let Err(e) = self.p.get_matches_with(&mut matcher, &mut it.peekable()) { + if let Err(e) = parser.get_matches_with(&mut matcher, it) { return Err(e); } - let global_arg_vec: Vec<&str> = (&self).p.global_args.iter().map(|ga| ga.b.name).collect(); + let global_arg_vec: Vec<&str> = (&self).args.iter().filter(|a| a.is_set(ArgSettings::Global)).map(|ga| ga.name).collect(); matcher.propagate_globals(&global_arg_vec); Ok(matcher.into()) } + + fn _build(&mut self) { + self.create_help_and_version(); + self.propagate(); + self.derive_display_order(); + for a in &mut self.args { + self.fill_in_arg_groups(a); + self.implied_settings(a); + a._build(); + } + } + + fn implied_settings(&mut self, a: &Arg<'a, 'b>) { + if a.is_set(ArgSettings::Last) { + // if an arg has `Last` set, we need to imply DontCollapseArgsInUsage so that args + // in the usage string don't get confused or left out. + self.set(AppSettings::DontCollapseArgsInUsage); + self.set(AppSettings::ContainsLast); + } + if let Some(l) = a.long { + if l == "version" { + self.unset(AppSettings::NeedsLongVersion); + } else if l == "help" { + self.unset(AppSettings::NeedsLongHelp); + } + } + } + + // @TODO @v3-alpha @perf: should only propagate globals to subcmd we find, or for help + pub fn propagate(&mut self) { + debugln!( + "Parser::propagate: self={}, g_settings={:#?}", + self.name, + self.g_settings + ); + for sc in &mut self.subcommands { + // We have to create a new scope in order to tell rustc the borrow of `sc` is + // done and to recursively call this method + debugln!( + "Parser::propagate: sc={}, settings={:#?}, g_settings={:#?}", + sc.name, + sc.settings, + sc.g_settings + ); + // We have to create a new scope in order to tell rustc the borrow of `sc` is + // done and to recursively call this method + { + let vsc = self.settings.is_set(AppSettings::VersionlessSubcommands); + let gv = self.settings.is_set(AppSettings::GlobalVersion); + + if vsc { + sc.set(AppSettings::DisableVersion); + } + if gv && sc.version.is_none() && self.version.is_some() { + sc.set(AppSettings::GlobalVersion); + sc.version = Some(self.version.unwrap()); + } + sc.settings = sc.settings | self.g_settings; + sc.g_settings = sc.g_settings | self.g_settings; + sc.term_w = self.term_w; + sc.max_w = self.max_w; + } + { + for a in self.args.iter().filter(|a| a.is_set(ArgSettings::Global)) { + sc.args.push(a.clone()); + } + } + sc.create_help_and_version(); + sc.propagate(); + } + } + + pub fn create_help_and_version(&mut self) { + debugln!("App::create_help_and_version;"); + // name is "hclap_help" because flags are sorted by name + if !self.contains_long("help") { + debugln!("App::create_help_and_version: Building --help"); + if self.help_short.is_none() && !self.contains_short('h') { + self.help_short = Some('h'); + } + let arg = Arg { + name: "hclap_help", + help: self.help_message.or(Some("Prints help information")), + short: self.help_short, + long: Some("help"), + ..Default::default() + }; + self.args.push(arg); + } + if !self.is_set(AppSettings::DisableVersion) && !self.contains_long("version") { + debugln!("App::create_help_and_version: Building --version"); + if self.version_short.is_none() && !self.contains_short('V') { + self.version_short = Some('V'); + } + // name is "vclap_version" because flags are sorted by name + let arg = Arg { + name: "vclap_version", + help: self.version_message.or(Some("Prints version information")), + short: self.version_short, + long: Some("version"), + ..Default::default() + }; + self.args.push(arg); + } + if self.has_subcommands() && !self.is_set(AppSettings::DisableHelpSubcommand) + && subcommands!(self).any(|s| s.name == "help") + { + debugln!("App::create_help_and_version: Building help"); + self.subcommands.push( + App::new("help") + .about("Prints this message or the help of the given subcommand(s)"), + ); + } + } + + #[cfg_attr(feature = "lints", allow(needless_borrow))] + pub fn derive_display_order(&mut self) { + if self.is_set(AppSettings::DeriveDisplayOrder) { + let unified = self.is_set(AppSettings::UnifiedHelpMessage); + for (i, o) in opts_mut!(self) + .enumerate() + .filter(|&(_, ref o)| o.disp_ord == 999) + { + o.disp_ord = if unified { o.unified_ord } else { i }; + } + for (i, f) in flags_mut!(self) + .enumerate() + .filter(|&(_, ref f)| f.disp_ord == 999) + { + f.disp_ord = if unified { f.unified_ord } else { i }; + } + for (i, sc) in &mut subcommands_mut!(self) + .enumerate() + .filter(|&(_, ref sc)| sc.disp_ord == 999) + { + sc.disp_ord = i; + } + } + for sc in subcommands_mut!(self) { + sc.derive_display_order(); + } + } + + fn fill_in_arg_groups(&mut self, a: &Arg<'a, 'b>) { + if let Some(ref grps) = a.groups { + for g in grps { + let mut found = false; + if let Some(ref mut ag) = groups_mut!(self).find(|grp| &grp.name == g) { + ag.args.push(a.name); + found = true; + } + if !found { + let mut ag = ArgGroup::with_name(g); + ag.args.push(a.name); + self.groups.push(ag); + } + } + } + } + + fn build_bin_names(&mut self) { + debugln!("Parser::build_bin_names;"); + for sc in subcommands_mut!(self) { + debug!("Parser::build_bin_names:iter: bin_name set..."); + if sc.bin_name.is_none() { + sdebugln!("No"); + let bin_name = format!( + "{}{}{}", + self + .bin_name + .as_ref() + .unwrap_or(&self.name.clone()), + if self.bin_name.is_some() { + " " + } else { + "" + }, + &*sc.name + ); + debugln!( + "Parser::build_bin_names:iter: Setting bin_name of {} to {}", + self.name, + bin_name + ); + sc.bin_name = Some(bin_name); + } else { + sdebugln!("yes ({:?})", sc.bin_name); + } + debugln!( + "Parser::build_bin_names:iter: Calling build_bin_names from...{}", + sc.name + ); + sc.build_bin_names(); + } + } + + + pub fn _write_version(&self, w: &mut W, use_long: bool) -> io::Result<()> { + let ver = if use_long { + self + .long_version + .unwrap_or_else(|| self.version.unwrap_or("")) + } else { + self + .version + .unwrap_or_else(|| self.long_version.unwrap_or("")) + }; + if let Some(bn) = self.bin_name.as_ref() { + if bn.contains(' ') { + // Incase we're dealing with subcommands i.e. git mv is translated to git-mv + write!(w, "{} {}", bn.replace(" ", "-"), ver) + } else { + write!(w, "{} {}", &self.name[..], ver) + } + } else { + write!(w, "{} {}", &self.name[..], ver) + } + } + + // Should we color the output? None=determined by output location, true=yes, false=no + #[doc(hidden)] + pub fn color(&self) -> ColorWhen { + debugln!("App::color;"); + debug!("App::color: Color setting..."); + if self.is_set(AppSettings::ColorNever) { + sdebugln!("Never"); + ColorWhen::Never + } else if self.is_set(AppSettings::ColorAlways) { + sdebugln!("Always"); + ColorWhen::Always + } else { + sdebugln!("Auto"); + ColorWhen::Auto + } + } +} + +// Internal Query Methods +#[doc(hidden)] +impl<'a, 'b> App<'a, 'b> { + fn contains_long(&self, l: &str) -> bool { longs!(self).any(|al| al == l) } + + fn contains_short(&self, s: char) -> bool { shorts!(self).any(|arg_s| arg_s == s) } + + pub fn is_set(&self, s: AppSettings) -> bool { self.settings.is_set(s) || self.g_settings.is_set(s)} + + pub fn set(&mut self, s: AppSettings) { self.settings.set(s) } + + pub fn set_global(&mut self, s: AppSettings) { self.g_settings.set(s) } + + pub fn unset_global(&mut self, s: AppSettings) { self.g_settings.unset(s) } + + pub fn unset(&mut self, s: AppSettings) { self.settings.unset(s) } + + pub fn has_subcommands(&self) -> bool { + !self.subcommands.is_empty() + } + + pub fn has_args(&self) -> bool { + !self.args.is_empty() + } + + pub fn has_opts(&self) -> bool { + opts!(self).count() > 0 + } + + pub fn has_flags(&self) -> bool { + flags!(self).count() > 0 + } + + pub fn has_positionals(&self) -> bool { + positionals!(self).count() > 0 + } + + pub fn has_visible_opts(&self) -> bool { + opts!(self).any(|o| !o.is_set(ArgSettings::Hidden)) + } + + pub fn has_visible_flags(&self) -> bool { + flags!(self).any(|o| !o.is_set(ArgSettings::Hidden)) + } + + pub fn has_visible_positionals(&self) -> bool { + positionals!(self).any(|o| !o.is_set(ArgSettings::Hidden)) + } + + pub fn has_visible_subcommands(&self) -> bool { + subcommands!(self) + .filter(|sc| sc.name != "help") + .any(|sc| !sc.is_set(AppSettings::Hidden)) + } + + fn use_long_help(&self) -> bool { + self.long_about.is_some() || self.args.iter().any(|f| f.long_help.is_some()) + || subcommands!(self) + .any(|s| s.long_about.is_some()) + } } #[cfg(feature = "yaml")] @@ -1782,60 +2137,11 @@ impl<'a> From<&'a Yaml> for App<'a, 'a> { } } -impl<'a, 'b> Clone for App<'a, 'b> { - fn clone(&self) -> Self { App { p: self.p.clone() } } +impl<'n, 'e> fmt::Display for App<'n, 'e> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.name) } } -impl<'n, 'e> AnyArg<'n, 'e> for App<'n, 'e> { - fn name(&self) -> &'n str { - unreachable!("App struct does not support AnyArg::name, this is a bug!") - } - fn overrides(&self) -> Option<&[&'e str]> { None } - fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { None } - fn blacklist(&self) -> Option<&[&'e str]> { None } - fn required_unless(&self) -> Option<&[&'e str]> { None } - fn val_names(&self) -> Option<&VecMap<&'e str>> { None } - fn is_set(&self, _: ArgSettings) -> bool { false } - fn val_terminator(&self) -> Option<&'e str> { None } - fn set(&mut self, _: ArgSettings) { - unreachable!("App struct does not support AnyArg::set, this is a bug!") - } - fn has_switch(&self) -> bool { false } - fn max_vals(&self) -> Option { None } - fn num_vals(&self) -> Option { None } - fn possible_vals(&self) -> Option<&[&'e str]> { None } - fn validator(&self) -> Option<&Rc StdResult<(), String>>> { None } - fn validator_os(&self) -> Option<&Rc StdResult<(), OsString>>> { None } - fn min_vals(&self) -> Option { None } - fn short(&self) -> Option { None } - fn long(&self) -> Option<&'e str> { None } - fn val_delim(&self) -> Option { None } - fn takes_value(&self) -> bool { true } - fn help(&self) -> Option<&'e str> { self.p.meta.about } - fn long_help(&self) -> Option<&'e str> { self.p.meta.long_about } - fn default_val(&self) -> Option<&'e OsStr> { None } - fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { - None - } - fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> { None } - fn longest_filter(&self) -> bool { true } - fn aliases(&self) -> Option> { - if let Some(ref aliases) = self.p.meta.aliases { - let vis_aliases: Vec<_> = aliases - .iter() - .filter_map(|&(n, v)| if v { Some(n) } else { None }) - .collect(); - if vis_aliases.is_empty() { - None - } else { - Some(vis_aliases) - } - } else { - None - } - } +impl<'b, 'c> DispOrder for App<'b, 'c> { + fn disp_ord(&self) -> usize { 999 } } -impl<'n, 'e> fmt::Display for App<'n, 'e> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.p.meta.name) } -} diff --git a/src/app/parser.rs b/src/app/parser.rs index 01e0e1f3561..73366aacc84 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -1,35 +1,30 @@ // Std use std::ffi::{OsStr, OsString}; -use std::fmt::Display; -use std::fs::File; use std::io::{self, BufWriter, Write}; #[cfg(feature = "debug")] use std::os::unix::ffi::OsStrExt; -use std::path::PathBuf; use std::slice::Iter; use std::iter::Peekable; +// Third party +use vec_map::VecMap; + // Internal use INTERNAL_ERROR_MSG; use INVALID_UTF8; use SubCommand; use app::App; use app::help::Help; -use app::meta::AppMeta; -use app::settings::AppFlags; -use args::{AnyArg, Arg, ArgGroup, ArgMatcher, Base, FlagBuilder, OptBuilder, PosBuilder, Switched}; +use args::{Arg, ArgGroup, ArgMatcher}; use args::settings::ArgSettings; -use completions::ComplGen; -use errors::{Error, ErrorKind}; +use errors::ErrorKind; +use errors::Error as ClapError; use errors::Result as ClapResult; -use fmt::ColorWhen; use osstringext::OsStrExt2; -use completions::Shell; use suggestions; use app::settings::AppSettings as AS; use app::validator::Validator; -use app::usage; -use map::{self, VecMap}; +use app::usage::Usage; #[derive(Debug, PartialEq, Copy, Clone)] #[doc(hidden)] @@ -43,215 +38,134 @@ pub enum ParseResult<'a> { ValuesDone, } -#[allow(missing_debug_implementations)] #[doc(hidden)] -#[derive(Clone, Default)] -pub struct Parser<'a, 'b> +pub struct Parser<'a, 'b, 'c> where 'a: 'b, + 'b: 'c { - pub meta: AppMeta<'b>, - settings: AppFlags, - pub g_settings: AppFlags, - pub flags: Vec>, - pub opts: Vec>, - pub positionals: VecMap>, - pub subcommands: Vec>, - pub groups: Vec>, - pub global_args: Vec>, + pub app: &'c mut App<'a, 'b>, pub required: Vec<&'a str>, pub r_ifs: Vec<(&'a str, &'b str, &'a str)>, pub overrides: Vec<(&'b str, &'a str)>, - help_short: Option, - version_short: Option, cache: Option<&'a str>, - pub help_message: Option<&'a str>, - pub version_message: Option<&'a str>, + num_opts: usize, + num_flags: usize, + pub positionals: VecMap<&'a str>, } -impl<'a, 'b> Parser<'a, 'b> +// Initializing Methods +impl<'a, 'b, 'c> Parser<'a, 'b, 'c> where 'a: 'b, + 'b: 'c, { - pub fn with_name(n: String) -> Self { - Parser { - meta: AppMeta::with_name(n), - g_settings: AppFlags::zeroed(), - ..Default::default() - } - } - - pub fn help_short(&mut self, s: &str) { - let c = s.trim_left_matches(|c| c == '-') - .chars() - .nth(0) - .unwrap_or('h'); - self.help_short = Some(c); - } - - pub fn version_short(&mut self, s: &str) { - let c = s.trim_left_matches(|c| c == '-') - .chars() - .nth(0) - .unwrap_or('V'); - self.version_short = Some(c); - } + pub fn new(app: &'c mut App<'a, 'b>) -> Self { + app._build(); + let reqs = app.args.iter().filter(|a| + a.settings.is_set(ArgSettings::Required)).map(|a| a.name).collect(); - pub fn gen_completions_to(&mut self, for_shell: Shell, buf: &mut W) { - if !self.is_set(AS::Propagated) { - self.propagate_help_version(); - self.build_bin_names(); - self.propagate_globals(); - self.propagate_settings(); - self.set(AS::Propagated); + Parser { + app: app, + required: reqs, + r_ifs: Vec::new(), + overrides: Vec::new(), + cache: None, + num_opts: 0, + num_flags: 0, + positionals: VecMap::new(), } - - ComplGen::new(self).generate(for_shell, buf) - } - - pub fn gen_completions(&mut self, for_shell: Shell, od: OsString) { - use std::error::Error; - - let out_dir = PathBuf::from(od); - let name = &*self.meta.bin_name.as_ref().unwrap().clone(); - let file_name = match for_shell { - Shell::Bash => format!("{}.bash", name), - Shell::Fish => format!("{}.fish", name), - Shell::Zsh => format!("_{}", name), - Shell::PowerShell => format!("_{}.ps1", name), - }; - - let mut file = match File::create(out_dir.join(file_name)) { - Err(why) => panic!("couldn't create completion file: {}", why.description()), - Ok(file) => file, - }; - self.gen_completions_to(for_shell, &mut file) - } - - #[inline] +} + // Perform some expensive assertions on the Parser itself fn app_debug_asserts(&mut self) -> bool { assert!(self.verify_positionals()); - let should_err = self.groups.iter().all(|g| { - g.args.iter().all(|arg| { - (self.flags.iter().any(|f| &f.b.name == arg) - || self.opts.iter().any(|o| &o.b.name == arg) - || self.positionals.values().any(|p| &p.b.name == arg) - || self.groups.iter().any(|g| &g.name == arg)) - }) - }); - let g = self.groups.iter().find(|g| { + + // * Args listed inside groups should exist + // * Groups should not have naming conflicts with Args + let g = groups!(self.app).find(|g| { g.args.iter().any(|arg| { - !(self.flags.iter().any(|f| &f.b.name == arg) - || self.opts.iter().any(|o| &o.b.name == arg) - || self.positionals.values().any(|p| &p.b.name == arg) - || self.groups.iter().any(|g| &g.name == arg)) + !(find!(self.app, *arg).is_some() + || groups!(self.app).any(|g| &g.name == arg)) }) }); assert!( - should_err, - "The group '{}' contains the arg '{}' that doesn't actually exist.", - g.unwrap().name, - g.unwrap() - .args - .iter() - .find(|arg| { - !(self.flags.iter().any(|f| &&f.b.name == arg) - || self.opts.iter().any(|o| &&o.b.name == arg) - || self.positionals.values().any(|p| &&p.b.name == arg) - || self.groups.iter().any(|g| &&g.name == arg)) - }) - .unwrap() + g.is_none(), + "The group '{}' contains an arg that doesn't exist or has a naming conflict with a group.", + g.unwrap().name ); true } - #[inline] - fn debug_asserts(&self, a: &Arg) -> bool { + // Perform expensive assertions on the Arg instance + fn arg_debug_asserts(&self, a: &Arg) -> bool { + // No naming conflicts assert!( - !arg_names!(self).any(|name| name == a.b.name), - format!("Non-unique argument name: {} is already in use", a.b.name) + !arg_names!(self.app).any(|name| name == a.name), + format!("Non-unique argument name: {} is already in use", a.name) ); - if let Some(l) = a.s.long { + + // Long conflicts + if let Some(l) = a.long { assert!( !self.contains_long(l), "Argument long must be unique\n\n\t--{} is already in use", l ); } - if let Some(s) = a.s.short { + + // Short conflicts + if let Some(s) = a.short { assert!( !self.contains_short(s), "Argument short must be unique\n\n\t-{} is already in use", s ); } - let i = if a.index.is_none() { - (self.positionals.len() + 1) - } else { - a.index.unwrap() as usize - }; - assert!( - !self.positionals.contains_key(i), - "Argument \"{}\" has the same index as another positional \ - argument\n\n\tPerhaps try .multiple(true) to allow one positional argument \ - to take multiple values", - a.b.name - ); + + if let Some(idx) = a.index { + // No index conflicts + assert!( + positionals!(self.app).find(|p| p.index == Some(idx as u64)).is_none(), + "Argument '{}' has the same index as another positional \ + argument\n\n\tUse Arg::multiple(true) to allow one positional argument \ + to take multiple values", + a.name + ); + } assert!( !(a.is_set(ArgSettings::Required) && a.is_set(ArgSettings::Global)), "Global arguments cannot be required.\n\n\t'{}' is marked as \ global and required", - a.b.name + a.name ); - if a.b.is_set(ArgSettings::Last) { + if a.is_set(ArgSettings::Last) { assert!( - !self.positionals - .values() - .any(|p| p.b.is_set(ArgSettings::Last)), + !positionals!(self.app) + .any(|p| p.is_set(ArgSettings::Last)), "Only one positional argument may have last(true) set. Found two." ); - assert!(a.s.long.is_none(), + assert!(a.long.is_none(), "Flags or Options may not have last(true) set. {} has both a long and last(true) set.", - a.b.name); - assert!(a.s.short.is_none(), + a.name); + assert!(a.short.is_none(), "Flags or Options may not have last(true) set. {} has both a short and last(true) set.", - a.b.name); + a.name); } true } - #[inline] fn add_conditional_reqs(&mut self, a: &Arg<'a, 'b>) { if let Some(ref r_ifs) = a.r_ifs { for &(arg, val) in r_ifs { - self.r_ifs.push((arg, val, a.b.name)); - } - } - } - - #[inline] - fn add_arg_groups(&mut self, a: &Arg<'a, 'b>) { - if let Some(ref grps) = a.b.groups { - for g in grps { - let mut found = false; - if let Some(ref mut ag) = self.groups.iter_mut().find(|grp| &grp.name == g) { - ag.args.push(a.b.name); - found = true; - } - if !found { - let mut ag = ArgGroup::with_name(g); - ag.args.push(a.b.name); - self.groups.push(ag); - } + self.r_ifs.push((arg, val, a.name)); } } } - #[inline] fn add_reqs(&mut self, a: &Arg<'a, 'b>) { if a.is_set(ArgSettings::Required) { // If the arg is required, add all it's requirements to master required list - if let Some(ref areqs) = a.b.requires { + if let Some(ref areqs) = a.requires { for name in areqs .iter() .filter(|&&(val, _)| val.is_none()) @@ -260,253 +174,56 @@ where self.required.push(name); } } - self.required.push(a.b.name); - } - } - - #[inline] - fn implied_settings(&mut self, a: &Arg<'a, 'b>) { - if a.is_set(ArgSettings::Last) { - // if an arg has `Last` set, we need to imply DontCollapseArgsInUsage so that args - // in the usage string don't get confused or left out. - self.set(AS::DontCollapseArgsInUsage); - self.set(AS::ContainsLast); - } - if let Some(l) = a.s.long { - if l == "version" { - self.unset(AS::NeedsLongVersion); - } else if l == "help" { - self.unset(AS::NeedsLongHelp); - } + self.required.push(a.name); } } // actually adds the arguments - pub fn add_arg(&mut self, a: Arg<'a, 'b>) { - // if it's global we have to clone anyways - if a.is_set(ArgSettings::Global) { - return self.add_arg_ref(&a); - } - debug_assert!(self.debug_asserts(&a)); + pub fn build_arg(&mut self, a: &mut Arg<'a, 'b>) { + // Run arg specific debug assertions if in debug mode + debug_assert!(self.arg_debug_asserts(a)); + + // Add requirements self.add_conditional_reqs(&a); - self.add_arg_groups(&a); self.add_reqs(&a); - self.implied_settings(&a); - if a.index.is_some() || (a.s.short.is_none() && a.s.long.is_none()) { - let i = if a.index.is_none() { - (self.positionals.len() + 1) - } else { - a.index.unwrap() as usize - }; - self.positionals - .insert(i, PosBuilder::from_arg(a, i as u64)); - } else if a.is_set(ArgSettings::TakesValue) { - let mut ob = OptBuilder::from(a); - ob.s.unified_ord = self.flags.len() + self.opts.len(); - self.opts.push(ob); - } else { - let mut fb = FlagBuilder::from(a); - fb.s.unified_ord = self.flags.len() + self.opts.len(); - self.flags.push(fb); - } - } - // actually adds the arguments but from a borrow (which means we have to do some clonine) - pub fn add_arg_ref(&mut self, a: &Arg<'a, 'b>) { - debug_assert!(self.debug_asserts(a)); - self.add_conditional_reqs(a); - self.add_arg_groups(a); - self.add_reqs(a); - self.implied_settings(a); - if a.index.is_some() || (a.s.short.is_none() && a.s.long.is_none()) { + + // Count types + if a.index.is_some() || (a.short.is_none() && a.long.is_none()) { let i = if a.index.is_none() { (self.positionals.len() + 1) } else { a.index.unwrap() as usize }; - let pb = PosBuilder::from_arg_ref(a, i as u64); - self.positionals.insert(i, pb); + a.index = Some(i as u64); + self.positionals.insert(i, a.name); } else if a.is_set(ArgSettings::TakesValue) { - let mut ob = OptBuilder::from(a); - ob.s.unified_ord = self.flags.len() + self.opts.len(); - self.opts.push(ob); + self.num_opts += 1; + a.unified_ord = self.num_flags + self.num_opts; } else { - let mut fb = FlagBuilder::from(a); - fb.s.unified_ord = self.flags.len() + self.opts.len(); - self.flags.push(fb); - } - if a.is_set(ArgSettings::Global) { - self.global_args.push(a.into()); + self.num_flags += 1; + a.unified_ord = self.num_flags + self.num_opts; } } - pub fn add_group(&mut self, group: ArgGroup<'a>) { + pub fn build_group(&mut self, group: &ArgGroup<'a>) { if group.required { self.required.push(group.name); if let Some(ref reqs) = group.requires { self.required.extend_from_slice(reqs); } -// if let Some(ref bl) = group.conflicts { -// self.blacklist.extend_from_slice(bl); -// } - } - if self.groups.iter().any(|g| g.name == group.name) { - let grp = self.groups - .iter_mut() - .find(|g| g.name == group.name) - .expect(INTERNAL_ERROR_MSG); - grp.args.extend_from_slice(&group.args); - grp.requires = group.requires.clone(); - grp.conflicts = group.conflicts.clone(); - grp.required = group.required; - } else { - self.groups.push(group); - } - } - - pub fn add_subcommand(&mut self, mut subcmd: App<'a, 'b>) { - debugln!( - "Parser::add_subcommand: term_w={:?}, name={}", - self.meta.term_w, - subcmd.p.meta.name - ); - subcmd.p.meta.term_w = self.meta.term_w; - if subcmd.p.meta.name == "help" { - self.unset(AS::NeedsSubcommandHelp); - } - - self.subcommands.push(subcmd); - } - - pub fn propagate_settings(&mut self) { - debugln!( - "Parser::propagate_settings: self={}, g_settings={:#?}", - self.meta.name, - self.g_settings - ); - for sc in &mut self.subcommands { - debugln!( - "Parser::propagate_settings: sc={}, settings={:#?}, g_settings={:#?}", - sc.p.meta.name, - sc.p.settings, - sc.p.g_settings - ); - // We have to create a new scope in order to tell rustc the borrow of `sc` is - // done and to recursively call this method - { - let vsc = self.settings.is_set(AS::VersionlessSubcommands); - let gv = self.settings.is_set(AS::GlobalVersion); - - if vsc { - sc.p.set(AS::DisableVersion); - } - if gv && sc.p.meta.version.is_none() && self.meta.version.is_some() { - sc.p.set(AS::GlobalVersion); - sc.p.meta.version = Some(self.meta.version.unwrap()); - } - sc.p.settings = sc.p.settings | self.g_settings; - sc.p.g_settings = sc.p.g_settings | self.g_settings; - sc.p.meta.term_w = self.meta.term_w; - sc.p.meta.max_w = self.meta.max_w; - } - sc.p.propagate_settings(); - } - } - - #[cfg_attr(feature = "lints", allow(needless_borrow))] - pub fn derive_display_order(&mut self) { - if self.is_set(AS::DeriveDisplayOrder) { - let unified = self.is_set(AS::UnifiedHelpMessage); - for (i, o) in self.opts - .iter_mut() - .enumerate() - .filter(|&(_, ref o)| o.s.disp_ord == 999) - { - o.s.disp_ord = if unified { o.s.unified_ord } else { i }; - } - for (i, f) in self.flags - .iter_mut() - .enumerate() - .filter(|&(_, ref f)| f.s.disp_ord == 999) - { - f.s.disp_ord = if unified { f.s.unified_ord } else { i }; - } - for (i, sc) in &mut self.subcommands - .iter_mut() - .enumerate() - .filter(|&(_, ref sc)| sc.p.meta.disp_ord == 999) - { - sc.p.meta.disp_ord = i; - } - } - for sc in &mut self.subcommands { - sc.p.derive_display_order(); - } - } - - pub fn required(&self) -> Iter<&str> { self.required.iter() } - - #[cfg_attr(feature = "lints", allow(needless_borrow))] - #[inline] - pub fn has_args(&self) -> bool { - !(self.flags.is_empty() && self.opts.is_empty() && self.positionals.is_empty()) - } - - #[inline] - pub fn has_opts(&self) -> bool { !self.opts.is_empty() } - - #[inline] - pub fn has_flags(&self) -> bool { !self.flags.is_empty() } - - #[inline] - pub fn has_positionals(&self) -> bool { !self.positionals.is_empty() } - - #[inline] - pub fn has_subcommands(&self) -> bool { !self.subcommands.is_empty() } - - #[inline] - pub fn has_visible_opts(&self) -> bool { - if self.opts.is_empty() { - return false; - } - self.opts.iter().any(|o| !o.is_set(ArgSettings::Hidden)) - } - - #[inline] - pub fn has_visible_flags(&self) -> bool { - if self.flags.is_empty() { - return false; - } - self.flags.iter().any(|f| !f.is_set(ArgSettings::Hidden)) - } - - #[inline] - pub fn has_visible_positionals(&self) -> bool { - if self.positionals.is_empty() { - return false; } - self.positionals - .values() - .any(|p| !p.is_set(ArgSettings::Hidden)) + // @TODO @dead-code: remove if tests pass + // if groups!(self.app).any(|g| g.name == group.name) { + // let grp = groups_mut!(self.app) + // .find(|g| g.name == group.name) + // .expect(INTERNAL_ERROR_MSG); + // grp.args.extend_from_slice(&group.args); + // grp.requires = group.requires.clone(); + // grp.conflicts = group.conflicts.clone(); + // grp.required = group.required; + // } } - #[inline] - pub fn has_visible_subcommands(&self) -> bool { - self.has_subcommands() - && self.subcommands - .iter() - .filter(|sc| sc.p.meta.name != "help") - .any(|sc| !sc.p.is_set(AS::Hidden)) - } - - #[inline] - pub fn is_set(&self, s: AS) -> bool { self.settings.is_set(s) } - - #[inline] - pub fn set(&mut self, s: AS) { self.settings.set(s) } - - #[inline] - pub fn unset(&mut self, s: AS) { self.settings.unset(s) } - #[cfg_attr(feature = "lints", allow(block_in_if_condition_stmt))] pub fn verify_positionals(&mut self) -> bool { // Because you must wait until all arguments have been supplied, this is the first chance @@ -515,29 +232,33 @@ where // Firt we verify that the index highest supplied index, is equal to the number of // positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3 // but no 2) - if let Some((idx, p)) = self.positionals.iter().rev().next() { - assert!( - !(idx != self.positionals.len()), - "Found positional argument \"{}\" whose index is {} but there \ + let highest_idx = self.positionals.keys().last().unwrap_or(0); + let num_p = self.positionals.len(); + assert!( + highest_idx != num_p, + "Found positional argument whose index is {} but there \ are only {} positional arguments defined", - p.b.name, - idx, - self.positionals.len() - ); - } + highest_idx, + num_p + ); // Next we verify that only the highest index has a .multiple(true) (if any) - if self.positionals.values().any(|a| { - a.b.is_set(ArgSettings::Multiple) && (a.index as usize != self.positionals.len()) + if positionals!(self.app).any(|a| { + a.is_set(ArgSettings::Multiple) && (a.index.unwrap_or(0) != highest_idx as u64) }) { + // First we make sure if there is a positional that allows multiple values + // the one before it (second to last) has one of these: + // * a value terminator + // * ArgSettings::Last + // * The last arg is Required let mut it = self.positionals.values().rev(); - let last = it.next().unwrap(); - let second_to_last = it.next().unwrap(); + let last = find!(self.app, *it.next().expect(INTERNAL_ERROR_MSG)).expect(INTERNAL_ERROR_MSG); + let second_to_last = find!(self.app, *it.next().expect(INTERNAL_ERROR_MSG)).expect(INTERNAL_ERROR_MSG); // Either the final positional is required // Or the second to last has a terminator or .last(true) set let ok = last.is_set(ArgSettings::Required) - || (second_to_last.v.terminator.is_some() - || second_to_last.b.is_set(ArgSettings::Last)) + || (second_to_last.terminator.is_some() + || second_to_last.is_set(ArgSettings::Last)) || last.is_set(ArgSettings::Last); assert!( ok, @@ -545,6 +266,8 @@ where last* positional argument, the last positional argument (i.e the one \ with the highest index) *must* have .required(true) or .last(true) set." ); + + // We make sure if the second to last is Multiple the last is ArgSettings::Last let ok = second_to_last.is_set(ArgSettings::Multiple) || last.is_set(ArgSettings::Last); assert!( ok, @@ -552,10 +275,10 @@ where argument may be set to .multiple(true)" ); - let count = self.positionals - .values() + // Next we check how many have both Multiple and not a specific number of values set + let count = positionals!(self.app) .filter(|p| { - p.b.settings.is_set(ArgSettings::Multiple) && p.v.num_vals.is_none() + p.settings.is_set(ArgSettings::Multiple) && p.num_vals.is_none() }) .count(); let ok = count <= 1 @@ -569,23 +292,22 @@ where ); } - if self.is_set(AS::AllowMissingPositional) { // Check that if a required positional argument is found, all positions with a lower // index are also required. let mut found = false; let mut foundx2 = false; - for p in self.positionals.values().rev() { - if foundx2 && !p.b.settings.is_set(ArgSettings::Required) { + for p in self.positionals.values().rev().map(|p_name| find!(self.app, *p_name).expect(INTERNAL_ERROR_MSG)) { + if foundx2 && !p.is_set(ArgSettings::Required) { assert!( - p.b.is_set(ArgSettings::Required), + p.is_set(ArgSettings::Required), "Found positional argument which is not required with a lower \ index than a required positional argument by two or more: {:?} \ - index {}", - p.b.name, + index {:?}", + p.name, p.index ); - } else if p.b.is_set(ArgSettings::Required) && !p.b.is_set(ArgSettings::Last) { + } else if p.is_set(ArgSettings::Required) && !p.is_set(ArgSettings::Last) { // Args that .last(true) don't count since they can be required and have // positionals with a lower index that aren't required // Imagine: prog [opt1] -- @@ -606,16 +328,16 @@ where // Check that if a required positional argument is found, all positions with a lower // index are also required let mut found = false; - for p in self.positionals.values().rev() { + for p in self.positionals.values().rev().map(|p_name| find!(self.app, *p_name).expect(INTERNAL_ERROR_MSG)) { if found { assert!( - p.b.is_set(ArgSettings::Required), + p.is_set(ArgSettings::Required), "Found positional argument which is not required with a lower \ - index than a required positional argument: {:?} index {}", - p.b.name, + index than a required positional argument: {:?} index {:?}", + p.name, p.index ); - } else if p.b.is_set(ArgSettings::Required) && !p.b.is_set(ArgSettings::Last) { + } else if p.is_set(ArgSettings::Required) && !p.is_set(ArgSettings::Last) { // Args that .last(true) don't count since they can be required and have // positionals with a lower index that aren't required // Imagine: prog [opt1] -- @@ -627,8 +349,8 @@ where } } } - if self.positionals.values().any(|p| { - p.b.is_set(ArgSettings::Last) && p.b.is_set(ArgSettings::Required) + if positionals!(self.app).any(|p| { + p.is_set(ArgSettings::Last) && p.is_set(ArgSettings::Required) }) && self.has_subcommands() && !self.is_set(AS::SubcommandsNegateReqs) { panic!( @@ -640,197 +362,39 @@ where true } - pub fn propagate_globals(&mut self) { - for sc in &mut self.subcommands { - // We have to create a new scope in order to tell rustc the borrow of `sc` is - // done and to recursively call this method - { - for a in &self.global_args { - sc.p.add_arg_ref(a); - } - } - sc.p.propagate_globals(); - } - } - - // Checks if the arg matches a subcommand name, or any of it's aliases (if defined) - fn possible_subcommand(&self, arg_os: &OsStr) -> (bool, Option<&str>) { - debugln!("Parser::possible_subcommand: arg={:?}", arg_os); - fn starts(h: &str, n: &OsStr) -> bool { - #[cfg(not(target_os = "windows"))] - use std::os::unix::ffi::OsStrExt; - #[cfg(target_os = "windows")] - use osstringext::OsStrExt3; - - let n_bytes = n.as_bytes(); - let h_bytes = OsStr::new(h).as_bytes(); + // Does all the initializing and prepares the parser + fn build(&mut self) { + // Run debug assertions if in debug mode + debug_assert!(self.app_debug_asserts()); - h_bytes.starts_with(n_bytes) + // Set the LowIndexMultiple flag if required + if positionals!(self.app).any(|a| { + a.is_set(ArgSettings::Multiple) && (a.index.unwrap_or(0) as usize != self.positionals.len()) + }) + && self.positionals.values() + .last() + .map_or(false, |p_name| + !find!(self.app, *p_name).expect(INTERNAL_ERROR_MSG).is_set(ArgSettings::Last)) + { + self.app.settings.set(AS::LowIndexMultiplePositional); } - if self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound) { - return (false, None); + for a in &mut self.app.args { + self.build_arg(a); } - if !self.is_set(AS::InferSubcommands) { - if let Some(sc) = find_subcmd!(self, arg_os) { - return (true, Some(&sc.p.meta.name)); - } - } else { - let v = self.subcommands - .iter() - .filter(|s| { - starts(&s.p.meta.name[..], &*arg_os) - || (s.p.meta.aliases.is_some() - && s.p - .meta - .aliases - .as_ref() - .unwrap() - .iter() - .filter(|&&(a, _)| starts(a, &*arg_os)) - .count() == 1) - }) - .map(|sc| &sc.p.meta.name) - .collect::>(); - if v.len() == 1 { - return (true, Some(v[0])); - } - } - (false, None) - } - - fn parse_help_subcommand(&self, it: &mut I) -> ClapResult> - where - I: Iterator, - T: Into, - { - debugln!("Parser::parse_help_subcommand;"); - let cmds: Vec = it.map(|c| c.into()).collect(); - let mut help_help = false; - let mut bin_name = self.meta - .bin_name - .as_ref() - .unwrap_or(&self.meta.name) - .clone(); - let mut sc = { - let mut sc: &Parser = self; - for (i, cmd) in cmds.iter().enumerate() { - if &*cmd.to_string_lossy() == "help" { - // cmd help help - help_help = true; - } - if let Some(c) = sc.subcommands - .iter() - .find(|s| &*s.p.meta.name == cmd) - .map(|sc| &sc.p) - { - sc = c; - if i == cmds.len() - 1 { - break; - } - } else if let Some(c) = sc.subcommands - .iter() - .find(|s| { - if let Some(ref als) = s.p.meta.aliases { - als.iter().any(|&(a, _)| a == &*cmd.to_string_lossy()) - } else { - false - } - }) - .map(|sc| &sc.p) - { - sc = c; - if i == cmds.len() - 1 { - break; - } - } else { - return Err(Error::unrecognized_subcommand( - cmd.to_string_lossy().into_owned(), - self.meta.bin_name.as_ref().unwrap_or(&self.meta.name), - self.color(), - )); - } - bin_name = format!("{} {}", bin_name, &*sc.meta.name); - } - sc.clone() - }; - if help_help { - let mut pb = PosBuilder::new("subcommand", 1); - pb.b.help = Some("The subcommand whose help message to display"); - pb.set(ArgSettings::Multiple); - sc.positionals.insert(1, pb); - sc.settings = sc.settings | self.g_settings; - } else { - sc.create_help_and_version(); + for g in &self.app.groups { + self.build_group(g); } - if sc.meta.bin_name != self.meta.bin_name { - sc.meta.bin_name = Some(format!("{} {}", bin_name, sc.meta.name)); - } - Err(sc._help(false)) - } - - // allow wrong self convention due to self.valid_neg_num = true and it's a private method - #[cfg_attr(feature = "lints", allow(wrong_self_convention))] - fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: ParseResult) -> bool { - debugln!( "Parser::is_new_arg:{:?}:{:?}", arg_os, needs_val_of); - let app_wide_settings = if self.is_set(AS::AllowLeadingHyphen) { - true - } else if self.is_set(AS::AllowNegativeNumbers) { - let a = arg_os.to_string_lossy(); - if a.parse::().is_ok() || a.parse::().is_ok() { - self.set(AS::ValidNegNumFound); - true - } else { - false - } - } else { - false - }; - let arg_allows_tac = match needs_val_of { - ParseResult::Opt(name) => { - let o = self.opts - .iter() - .find(|o| o.b.name == name) - .expect(INTERNAL_ERROR_MSG); - (o.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings) - } - ParseResult::Pos(name) => { - let p = self.positionals - .values() - .find(|p| p.b.name == name) - .expect(INTERNAL_ERROR_MSG); - (p.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings) - } - ParseResult::ValuesDone => return true, - _ => false, - }; - debugln!( "Parser::is_new_arg: arg_allows_tac={:?}", arg_allows_tac ); - - // Is this a new argument, or values from a previous option? - let mut ret = if arg_os.starts_with(b"--") { - debugln!("Parser::is_new_arg: -- found"); - if arg_os.len_() == 2 && !arg_allows_tac { - return true; // We have to return true so override everything else - } else if arg_allows_tac { - return false; - } - true - } else if arg_os.starts_with(b"-") { - debugln!("Parser::is_new_arg: - found"); - // a singe '-' by itself is a value and typically means "stdin" on unix systems - !(arg_os.len_() == 1) - } else { - debugln!("Parser::is_new_arg: probably value"); - false - }; - - ret = ret && !arg_allows_tac; - - debugln!("Parser::is_new_arg: starts_new_arg={:?}", ret); - ret } +} +// Parsing Methods +impl<'a, 'b, 'c> Parser<'a, 'b, 'c> +where + 'a: 'b, + 'b: 'c, +{ // The actual parsing function #[cfg_attr(feature = "lints", allow(while_let_on_iterator, collapsible_if))] pub fn get_matches_with( @@ -844,22 +408,9 @@ where { debugln!("Parser::get_matches_with;"); // Verify all positional assertions pass - debug_assert!(self.app_debug_asserts()); - if self.positionals.values().any(|a| { - a.b.is_set(ArgSettings::Multiple) && (a.index as usize != self.positionals.len()) - }) - && self.positionals - .values() - .last() - .map_or(false, |p| !p.is_set(ArgSettings::Last)) - { - self.settings.set(AS::LowIndexMultiplePositional); - } - let has_args = self.has_args(); + self.build(); - // Next we create the `--help` and `--version` arguments and add them if - // necessary - self.create_help_and_version(); + let has_args = self.has_args(); let mut subcmd_name: Option = None; let mut needs_val_of: ParseResult<'a> = ParseResult::NotFound; @@ -908,7 +459,7 @@ where if starts_new_arg { { - let any_arg = find_any_by_name!(self, self.cache.unwrap_or("")); + let any_arg = find!(self.app, self.cache.unwrap_or("")); matcher.process_arg_overrides(any_arg, &mut self.overrides, &mut self.required); } @@ -936,11 +487,11 @@ where if !(arg_os.to_string_lossy().parse::().is_ok() || arg_os.to_string_lossy().parse::().is_ok()) { - return Err(Error::unknown_argument( + return Err(ClapError::unknown_argument( &*arg_os.to_string_lossy(), "", - &*usage::create_error_usage(self, matcher, None), - self.color(), + &*Usage::new(self).create_error_usage(matcher, None), + self.app.color(), )); } }, @@ -954,11 +505,9 @@ where } else { if let ParseResult::Opt(name) = needs_val_of { // Check to see if parsing a value from a previous arg - let arg = self.opts - .iter() - .find(|o| o.b.name == name) + let arg = find!(self.app, name) .expect(INTERNAL_ERROR_MSG); - // get the OptBuilder so we can check the settings + // get the option so we can check the settings needs_val_of = self.add_val_to_arg(arg, &arg_os, matcher)?; // get the next value from the iterator continue; @@ -969,13 +518,13 @@ where if !(self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound)) && !self.is_set(AS::InferSubcommands) { - if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self)) { - return Err(Error::invalid_subcommand( + if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self.app)) { + return Err(ClapError::invalid_subcommand( arg_os.to_string_lossy().into_owned(), cdate, - self.meta.bin_name.as_ref().unwrap_or(&self.meta.name), - &*usage::create_error_usage(self, matcher, None), - self.color(), + self.app.bin_name.as_ref().unwrap_or(&self.app.name), + &*Usage::new(self).create_error_usage(matcher, None), + self.app.color(), )); } } @@ -996,8 +545,8 @@ where if let Some(na) = it.peek() { let n = (*na).clone().into(); needs_val_of = if needs_val_of != ParseResult::ValuesDone { - if let Some(p) = self.positionals.get(pos_counter) { - ParseResult::Pos(p.b.name) + if let Some(p) = positionals!(self.app).find(|p| p.index == Some(pos_counter as u64)) { + ParseResult::Pos(p.name) } else { ParseResult::ValuesDone } @@ -1006,7 +555,7 @@ where }; let sc_match = { self.possible_subcommand(&n).0 }; if self.is_new_arg(&n, needs_val_of) || sc_match - || suggestions::did_you_mean(&n.to_string_lossy(), sc_names!(self)) + || suggestions::did_you_mean(&n.to_string_lossy(), sc_names!(self.app)) .is_some() { debugln!("Parser::get_matches_with: Bumping the positional counter..."); @@ -1022,48 +571,48 @@ where debugln!("Parser::get_matches_with: .last(true) and --, setting last pos"); pos_counter = self.positionals.len(); } - if let Some(p) = self.positionals.get(pos_counter) { + if let Some(p) = positionals!(self.app).find(|p| p.index == Some(pos_counter as u64)) { if p.is_set(ArgSettings::Last) && !self.is_set(AS::TrailingValues) { - return Err(Error::unknown_argument( + return Err(ClapError::unknown_argument( &*arg_os.to_string_lossy(), "", - &*usage::create_error_usage(self, matcher, None), - self.color(), + &*Usage::new(self).create_error_usage(matcher, None), + self.app.color(), )); } if !self.is_set(AS::TrailingValues) && (self.is_set(AS::TrailingVarArg) && pos_counter == self.positionals.len()) { - self.settings.set(AS::TrailingValues); + self.app.settings.set(AS::TrailingValues); } - if self.cache.map_or(true, |name| name != p.b.name) { + if self.cache.map_or(true, |name| name != p.name) { { - let any_arg = find_any_by_name!(self, self.cache.unwrap_or("")); + let any_arg = find!(self.app, self.cache.unwrap_or("")); matcher.process_arg_overrides(any_arg, &mut self.overrides, &mut self.required); } - self.cache = Some(p.b.name); + self.cache = Some(p.name); } let _ = self.add_val_to_arg(p, &arg_os, matcher)?; - matcher.inc_occurrence_of(p.b.name); - let _ = self.groups_for_arg(p.b.name) + matcher.inc_occurrence_of(p.name); + let _ = self.groups_for_arg(p.name) .and_then(|vec| Some(matcher.inc_occurrences_of(&*vec))); - self.settings.set(AS::ValidArgFound); + self.app.settings.set(AS::ValidArgFound); // Only increment the positional counter if it doesn't allow multiples - if !p.b.settings.is_set(ArgSettings::Multiple) { + if !p.settings.is_set(ArgSettings::Multiple) { pos_counter += 1; } - self.settings.set(AS::ValidArgFound); + self.app.settings.set(AS::ValidArgFound); } else if self.is_set(AS::AllowExternalSubcommands) { // Get external subcommand name let sc_name = match arg_os.to_str() { Some(s) => s.to_string(), None => { if !self.is_set(AS::StrictUtf8) { - return Err(Error::invalid_utf8( - &*usage::create_error_usage(self, matcher, None), - self.color(), + return Err(ClapError::invalid_utf8( + &*Usage::new(self).create_error_usage(matcher, None), + self.app.color(), )); } arg_os.to_string_lossy().into_owned() @@ -1075,9 +624,9 @@ where while let Some(v) = it.next() { let a = v.into(); if a.to_str().is_none() && !self.is_set(AS::StrictUtf8) { - return Err(Error::invalid_utf8( - &*usage::create_error_usage(self, matcher, None), - self.color(), + return Err(ClapError::invalid_utf8( + &*Usage::new(self).create_error_usage(matcher, None), + self.app.color(), )); } sc_m.add_val_to("", &a); @@ -1092,62 +641,60 @@ where && arg_os.starts_with(b"-")) && !self.is_set(AS::InferSubcommands) { - return Err(Error::unknown_argument( + return Err(ClapError::unknown_argument( &*arg_os.to_string_lossy(), "", - &*usage::create_error_usage(self, matcher, None), - self.color(), + &*Usage::new(self).create_error_usage(matcher, None), + self.app.color(), )); } else if !has_args || self.is_set(AS::InferSubcommands) && self.has_subcommands() { if let Some(cdate) = - suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self)) + suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self.app)) { - return Err(Error::invalid_subcommand( + return Err(ClapError::invalid_subcommand( arg_os.to_string_lossy().into_owned(), cdate, - self.meta.bin_name.as_ref().unwrap_or(&self.meta.name), - &*usage::create_error_usage(self, matcher, None), - self.color(), + self.app.bin_name.as_ref().unwrap_or(&self.app.name), + &*Usage::new(self).create_error_usage(matcher, None), + self.app.color(), )); } else { - return Err(Error::unrecognized_subcommand( + return Err(ClapError::unrecognized_subcommand( arg_os.to_string_lossy().into_owned(), - self.meta.bin_name.as_ref().unwrap_or(&self.meta.name), - self.color(), + self.app.bin_name.as_ref().unwrap_or(&self.app.name), + self.app.color(), )); } } else { - return Err(Error::unknown_argument( + return Err(ClapError::unknown_argument( &*arg_os.to_string_lossy(), "", - &*usage::create_error_usage(self, matcher, None), - self.color(), + &*Usage::new(self).create_error_usage(matcher, None), + self.app.color(), )); } } if let Some(ref pos_sc_name) = subcmd_name { let sc_name = { - find_subcmd!(self, pos_sc_name) + find_subcmd!(self.app, pos_sc_name) .expect(INTERNAL_ERROR_MSG) - .p - .meta .name .clone() }; self.parse_subcommand(&*sc_name, matcher, it)?; } else if self.is_set(AS::SubcommandRequired) { - let bn = self.meta.bin_name.as_ref().unwrap_or(&self.meta.name); - return Err(Error::missing_subcommand( + let bn = self.app.bin_name.as_ref().unwrap_or(&self.app.name); + return Err(ClapError::missing_subcommand( bn, - &usage::create_error_usage(self, matcher, None), - self.color(), + &Usage::new(self).create_error_usage(matcher, None), + self.app.color(), )); } else if self.is_set(AS::SubcommandRequiredElseHelp) { debugln!("Parser::get_matches_with: SubcommandRequiredElseHelp=true"); let mut out = vec![]; self.write_help_err(&mut out)?; - return Err(Error { + return Err(ClapError { message: String::from_utf8_lossy(&*out).into_owned(), kind: ErrorKind::MissingArgumentOrSubcommand, info: None, @@ -1156,7 +703,7 @@ where // In case the last arg was new, we need to process it's overrides { - let any_arg = find_any_by_name!(self, self.cache.unwrap_or("")); + let any_arg = find!(self.app, self.cache.unwrap_or("")); matcher.process_arg_overrides(any_arg, &mut self.overrides, &mut self.required); } @@ -1165,6 +712,156 @@ where Validator::new(self).validate(needs_val_of, subcmd_name, matcher) } + // Checks if the arg matches a subcommand name, or any of it's aliases (if defined) + fn possible_subcommand(&self, arg_os: &OsStr) -> (bool, Option<&str>) { + debugln!("Parser::possible_subcommand: arg={:?}", arg_os); + fn starts(h: &str, n: &OsStr) -> bool { + #[cfg(not(target_os = "windows"))] + use std::os::unix::ffi::OsStrExt; + #[cfg(target_os = "windows")] + use osstringext::OsStrExt3; + + let n_bytes = n.as_bytes(); + let h_bytes = OsStr::new(h).as_bytes(); + + h_bytes.starts_with(n_bytes) + } + + if self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound) { + return (false, None); + } + if !self.is_set(AS::InferSubcommands) { + if let Some(sc) = find_subcmd!(self.app, arg_os) { + return (true, Some(&sc.name)); + } + } else { + let v = sc_names!(self.app) + .filter(|s| starts(s, &*arg_os)) + .collect::>(); + + if v.len() == 1 { + return (true, Some(v[0])); + } + } + (false, None) + } + + fn parse_help_subcommand(&self, it: &mut I) -> ClapResult> + where + I: Iterator, + T: Into, + { + debugln!("Parser::parse_help_subcommand;"); + let cmds: Vec = it.map(|c| c.into()).collect(); + let mut help_help = false; + let mut bin_name = self.app.bin_name.as_ref().unwrap_or(&self.app.name).clone(); + let mut sc = { + // @TODO @perf: cloning all these Apps ins't great, but since it's just displaying the help + // message there are bigger fish to fry + let mut sc = self.app.clone(); + for (i, cmd) in cmds.iter().enumerate() { + if &*cmd.to_string_lossy() == "help" { + // cmd help help + help_help = true; + break; // Maybe? + } + if let Some(c) = find_subcmd!(sc, cmd) + { + sc = c; + if i == cmds.len() - 1 { + break; + } + } else if let Some(c) = find_subcmd!(sc, &*cmd.to_string_lossy()) { + sc = c; + if i == cmds.len() - 1 { + break; + } + } else { + return Err(ClapError::unrecognized_subcommand( + cmd.to_string_lossy().into_owned(), + self.app.bin_name.as_ref().unwrap_or(&self.app.name), + self.app.color(), + )); + } + bin_name = format!("{} {}", bin_name, &*sc.name); + } + sc + }; + let mut parser = Parser::new(&mut sc); + if help_help { + let mut pb = Arg::with_name("subcommand") + .index(1) + .set(ArgSettings::Multiple) + .help("The subcommand whose help message to display"); + pb._build(); + parser.positionals.insert(1, pb.name); + parser.app.settings = parser.app.settings | self.app.g_settings; + parser.app.g_settings = self.app.g_settings; + } + if parser.app.bin_name != self.app.bin_name { + parser.app.bin_name = Some(format!("{} {}", bin_name, parser.app.name)); + } + Err(parser.help_err(false)) + } + + // allow wrong self convention due to self.valid_neg_num = true and it's a private method + #[cfg_attr(feature = "lints", allow(wrong_self_convention))] + fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: ParseResult) -> bool { + debugln!( "Parser::is_new_arg:{:?}:{:?}", arg_os, needs_val_of); + let app_wide_settings = if self.is_set(AS::AllowLeadingHyphen) { + true + } else if self.is_set(AS::AllowNegativeNumbers) { + let a = arg_os.to_string_lossy(); + if a.parse::().is_ok() || a.parse::().is_ok() { + self.set(AS::ValidNegNumFound); + true + } else { + false + } + } else { + false + }; + let arg_allows_tac = match needs_val_of { + ParseResult::Opt(name) => { + let o = find!(self.app, name) + .expect(INTERNAL_ERROR_MSG); + (o.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings) + } + ParseResult::Pos(name) => { + let p = find!(self.app, name) + .expect(INTERNAL_ERROR_MSG); + (p.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings) + } + ParseResult::ValuesDone => return true, + _ => false, + }; + debugln!( "Parser::is_new_arg: arg_allows_tac={:?}", arg_allows_tac ); + + // Is this a new argument, or values from a previous option? + let mut ret = if arg_os.starts_with(b"--") { + debugln!("Parser::is_new_arg: -- found"); + if arg_os.len_() == 2 && !arg_allows_tac { + return true; // We have to return true so override everything else + } else if arg_allows_tac { + return false; + } + true + } else if arg_os.starts_with(b"-") { + debugln!("Parser::is_new_arg: - found"); + // a singe '-' by itself is a value and typically means "stdin" on unix systems + !(arg_os.len_() == 1) + } else { + debugln!("Parser::is_new_arg: probably value"); + false + }; + + ret = ret && !arg_allows_tac; + + debugln!("Parser::is_new_arg: starts_new_arg={:?}", ret); + ret + } + + fn remove_overrides(&mut self, matcher: &mut ArgMatcher) { debugln!("Parser::remove_overrides:{:?};", self.overrides); for &(overr, name) in &self.overrides { @@ -1183,50 +880,6 @@ where } } - fn propagate_help_version(&mut self) { - debugln!("Parser::propagate_help_version;"); - self.create_help_and_version(); - for sc in &mut self.subcommands { - sc.p.propagate_help_version(); - } - } - - fn build_bin_names(&mut self) { - debugln!("Parser::build_bin_names;"); - for sc in &mut self.subcommands { - debug!("Parser::build_bin_names:iter: bin_name set..."); - if sc.p.meta.bin_name.is_none() { - sdebugln!("No"); - let bin_name = format!( - "{}{}{}", - self.meta - .bin_name - .as_ref() - .unwrap_or(&self.meta.name.clone()), - if self.meta.bin_name.is_some() { - " " - } else { - "" - }, - &*sc.p.meta.name - ); - debugln!( - "Parser::build_bin_names:iter: Setting bin_name of {} to {}", - self.meta.name, - bin_name - ); - sc.p.meta.bin_name = Some(bin_name); - } else { - sdebugln!("yes ({:?})", sc.p.meta.bin_name); - } - debugln!( - "Parser::build_bin_names:iter: Calling build_bin_names from...{}", - sc.p.meta.name - ); - sc.p.build_bin_names(); - } - } - fn parse_subcommand( &mut self, sc_name: &str, @@ -1245,178 +898,51 @@ where for k in matcher.arg_names() { hs.push(k); } - let reqs = usage::get_required_usage_from(self, &hs, Some(matcher), None, false); - - for s in &reqs { - write!(&mut mid_string, " {}", s).expect(INTERNAL_ERROR_MSG); - } - } - mid_string.push_str(" "); - if let Some(ref mut sc) = self.subcommands - .iter_mut() - .find(|s| s.p.meta.name == sc_name) - { - let mut sc_matcher = ArgMatcher::new(); - // bin_name should be parent's bin_name + [] + the sc's name separated by - // a space - sc.p.meta.usage = Some(format!( - "{}{}{}", - self.meta.bin_name.as_ref().unwrap_or(&String::new()), - if self.meta.bin_name.is_some() { - &*mid_string - } else { - "" - }, - &*sc.p.meta.name - )); - sc.p.meta.bin_name = Some(format!( - "{}{}{}", - self.meta.bin_name.as_ref().unwrap_or(&String::new()), - if self.meta.bin_name.is_some() { - " " - } else { - "" - }, - &*sc.p.meta.name - )); - debugln!( - "Parser::parse_subcommand: About to parse sc={}", - sc.p.meta.name - ); - debugln!("Parser::parse_subcommand: sc settings={:#?}", sc.p.settings); - sc.p.get_matches_with(&mut sc_matcher, it)?; - matcher.subcommand(SubCommand { - name: sc.p.meta.name.clone(), - matches: sc_matcher.into(), - }); - } - Ok(()) - } - - pub fn groups_for_arg(&self, name: &str) -> Option> { - debugln!("Parser::groups_for_arg: name={}", name); - - if self.groups.is_empty() { - debugln!("Parser::groups_for_arg: No groups defined"); - return None; - } - let mut res = vec![]; - debugln!("Parser::groups_for_arg: Searching through groups..."); - for grp in &self.groups { - for a in &grp.args { - if a == &name { - sdebugln!("\tFound '{}'", grp.name); - res.push(&*grp.name); - } - } - } - if res.is_empty() { - return None; - } - - Some(res) - } - - pub fn args_in_group(&self, group: &str) -> Vec { - let mut g_vec = vec![]; - let mut args = vec![]; - - for n in &self.groups - .iter() - .find(|g| g.name == group) - .expect(INTERNAL_ERROR_MSG) - .args - { - if let Some(f) = self.flags.iter().find(|f| &f.b.name == n) { - args.push(f.to_string()); - } else if let Some(f) = self.opts.iter().find(|o| &o.b.name == n) { - args.push(f.to_string()); - } else if let Some(p) = self.positionals.values().find(|p| &p.b.name == n) { - args.push(p.b.name.to_owned()); - } else { - g_vec.push(*n); - } - } - - for av in g_vec.iter().map(|g| self.args_in_group(g)) { - args.extend(av); - } - args.dedup(); - args.iter().map(ToOwned::to_owned).collect() - } - - pub fn arg_names_in_group(&self, group: &str) -> Vec<&'a str> { - let mut g_vec = vec![]; - let mut args = vec![]; - - for n in &self.groups - .iter() - .find(|g| g.name == group) - .expect(INTERNAL_ERROR_MSG) - .args - { - if self.groups.iter().any(|g| g.name == *n) { - args.extend(self.arg_names_in_group(n)); - g_vec.push(*n); - } else if !args.contains(n) { - args.push(*n); - } - } - - args.iter().map(|s| *s).collect() - } + let reqs = Usage::new(self).get_required_usage_from(&hs, Some(matcher), None, false); - pub fn create_help_and_version(&mut self) { - debugln!("Parser::create_help_and_version;"); - // name is "hclap_help" because flags are sorted by name - if !self.contains_long("help") { - debugln!("Parser::create_help_and_version: Building --help"); - if self.help_short.is_none() && !self.contains_short('h') { - self.help_short = Some('h'); + for s in &reqs { + write!(&mut mid_string, " {}", s).expect(INTERNAL_ERROR_MSG); } - let arg = FlagBuilder { - b: Base { - name: "hclap_help", - help: self.help_message.or(Some("Prints help information")), - ..Default::default() - }, - s: Switched { - short: self.help_short, - long: Some("help"), - ..Default::default() - }, - }; - self.flags.push(arg); } - if !self.is_set(AS::DisableVersion) && !self.contains_long("version") { - debugln!("Parser::create_help_and_version: Building --version"); - if self.version_short.is_none() && !self.contains_short('V') { - self.version_short = Some('V'); - } - // name is "vclap_version" because flags are sorted by name - let arg = FlagBuilder { - b: Base { - name: "vclap_version", - help: self.version_message.or(Some("Prints version information")), - ..Default::default() + mid_string.push_str(" "); + if let Some(ref mut sc) = subcommands_mut!(self.app) + .find(|s| s.name == sc_name) + { + let mut sc_matcher = ArgMatcher::new(); + // bin_name should be parent's bin_name + [] + the sc's name separated by + // a space + sc.usage = Some(format!( + "{}{}{}", + self.app.bin_name.as_ref().unwrap_or(&String::new()), + if self.app.bin_name.is_some() { + &*mid_string + } else { + "" }, - s: Switched { - short: self.version_short, - long: Some("version"), - ..Default::default() + &*sc.name + )); + sc.bin_name = Some(format!( + "{}{}{}", + self.app.bin_name.as_ref().unwrap_or(&String::new()), + if self.app.bin_name.is_some() { + " " + } else { + "" }, - }; - self.flags.push(arg); - } - if !self.subcommands.is_empty() && !self.is_set(AS::DisableHelpSubcommand) - && self.is_set(AS::NeedsSubcommandHelp) - { - debugln!("Parser::create_help_and_version: Building help"); - self.subcommands.push( - App::new("help") - .about("Prints this message or the help of the given subcommand(s)"), + &*sc.name + )); + debugln!( + "Parser::parse_subcommand: About to parse sc={}", + sc.name ); + debugln!("Parser::parse_subcommand: sc settings={:#?}", sc.settings); + sc.get_matches_with(&mut sc_matcher, it)?; + matcher.subcommand(SubCommand { + name: sc.name.clone(), + matches: sc_matcher.into(), + }); } + Ok(()) } // Retrieves the names of all args the user has supplied thus far, except required ones @@ -1429,11 +955,11 @@ where ); if arg == "help" && self.is_set(AS::NeedsLongHelp) { sdebugln!("Help"); - return Err(self._help(true)); + return Err(self.help_err(true)); } if arg == "version" && self.is_set(AS::NeedsLongVersion) { sdebugln!("Version"); - return Err(self._version(true)); + return Err(self.version_err(true)); } sdebugln!("Neither"); @@ -1446,59 +972,22 @@ where "Parser::check_for_help_and_version_char: Checking if -{} is help or version...", arg ); - if let Some(h) = self.help_short { + if let Some(h) = self.app.help_short { if arg == h && self.is_set(AS::NeedsLongHelp) { sdebugln!("Help"); - return Err(self._help(false)); + return Err(self.help_err(false)); } } - if let Some(v) = self.version_short { + if let Some(v) = self.app.version_short { if arg == v && self.is_set(AS::NeedsLongVersion) { sdebugln!("Version"); - return Err(self._version(false)); + return Err(self.version_err(false)); } } sdebugln!("Neither"); Ok(()) } - fn use_long_help(&self) -> bool { - self.meta.long_about.is_some() || self.flags.iter().any(|f| f.b.long_help.is_some()) - || self.opts.iter().any(|o| o.b.long_help.is_some()) - || self.positionals.values().any(|p| p.b.long_help.is_some()) - || self.subcommands - .iter() - .any(|s| s.p.meta.long_about.is_some()) - } - - fn _help(&self, mut use_long: bool) -> Error { - debugln!("Parser::_help: use_long={:?}", use_long); - use_long = use_long && self.use_long_help(); - let mut buf = vec![]; - match Help::write_parser_help(&mut buf, self, use_long) { - Err(e) => e, - _ => Error { - message: String::from_utf8(buf).unwrap_or_default(), - kind: ErrorKind::HelpDisplayed, - info: None, - }, - } - } - - fn _version(&self, use_long: bool) -> Error { - debugln!("Parser::_version: "); - let out = io::stdout(); - let mut buf_w = BufWriter::new(out.lock()); - match self.print_version(&mut buf_w, use_long) { - Err(e) => e, - _ => Error { - message: String::new(), - kind: ErrorKind::VersionDisplayed, - info: None, - }, - } - } - fn parse_long_arg( &mut self, matcher: &mut ArgMatcher<'a>, @@ -1518,24 +1007,24 @@ where full_arg.trim_left_matches(b'-') }; - if let Some(opt) = find_opt_by_long!(@os self, arg) { + if let Some(opt) = find_by_long!(self.app, arg) { debugln!( "Parser::parse_long_arg: Found valid opt '{}'", opt.to_string() ); - self.settings.set(AS::ValidArgFound); + self.app.settings.set(AS::ValidArgFound); let ret = self.parse_opt(val, opt, val.is_some(), matcher)?; - if self.cache.map_or(true, |name| name != opt.b.name) { - self.cache = Some(opt.b.name); + if self.cache.map_or(true, |name| name != opt.name) { + self.cache = Some(opt.name); } return Ok(ret); - } else if let Some(flag) = find_flag_by_long!(@os self, arg) { + } else if let Some(flag) = find_by_long!(self.app, arg) { debugln!( "Parser::parse_long_arg: Found valid flag '{}'", flag.to_string() ); - self.settings.set(AS::ValidArgFound); + self.app.settings.set(AS::ValidArgFound); // Only flags could be help or version, and we need to check the raw long // so this is the first point to check self.check_for_help_and_version_str(arg)?; @@ -1543,8 +1032,8 @@ where self.parse_flag(flag, matcher)?; // Handle conflicts, requirements, etc. - if self.cache.map_or(true, |name| name != flag.b.name) { - self.cache = Some(flag.b.name); + if self.cache.map_or(true, |name| name != flag.name) { + self.cache = Some(flag.name); } return Ok(ParseResult::Flag); @@ -1593,9 +1082,9 @@ where // concatenated value: -oval // Option: -o // Value: val - if let Some(opt) = find_opt_by_short!(self, c) { + if let Some(opt) = find_by_short!(self.app, c) { debugln!("Parser::parse_short_arg:iter:{}: Found valid opt", c); - self.settings.set(AS::ValidArgFound); + self.app.settings.set(AS::ValidArgFound); // Check for trailing concatenated value let p: Vec<_> = arg.splitn(2, c).collect(); debugln!( @@ -1620,30 +1109,30 @@ where // Default to "we're expecting a value later" let ret = self.parse_opt(val, opt, false, matcher)?; - if self.cache.map_or(true, |name| name != opt.b.name) { - self.cache = Some(opt.b.name); + if self.cache.map_or(true, |name| name != opt.name) { + self.cache = Some(opt.name); } return Ok(ret); - } else if let Some(flag) = find_flag_by_short!(self, c) { + } else if let Some(flag) = find_by_short!(self.app, c) { debugln!("Parser::parse_short_arg:iter:{}: Found valid flag", c); - self.settings.set(AS::ValidArgFound); + self.app.settings.set(AS::ValidArgFound); // Only flags can be help or version self.check_for_help_and_version_char(c)?; ret = self.parse_flag(flag, matcher)?; // Handle conflicts, requirements, overrides, etc. // Must be called here due to mutablilty - if self.cache.map_or(true, |name| name != flag.b.name) { - self.cache = Some(flag.b.name); + if self.cache.map_or(true, |name| name != flag.name) { + self.cache = Some(flag.name); } } else { let arg = format!("-{}", c); - return Err(Error::unknown_argument( + return Err(ClapError::unknown_argument( &*arg, "", - &*usage::create_error_usage(self, matcher, None), - self.color(), + &*Usage::new(self).create_error_usage(matcher, None), + self.app.color(), )); } } @@ -1653,16 +1142,16 @@ where fn parse_opt( &self, val: Option<&OsStr>, - opt: &OptBuilder<'a, 'b>, + opt: &Arg<'a, 'b>, had_eq: bool, matcher: &mut ArgMatcher<'a>, ) -> ClapResult> { - debugln!("Parser::parse_opt; opt={}, val={:?}", opt.b.name, val); - debugln!("Parser::parse_opt; opt.settings={:?}", opt.b.settings); + debugln!("Parser::parse_opt; opt={}, val={:?}", opt.name, val); + debugln!("Parser::parse_opt; opt.settings={:?}", opt.settings); let mut has_eq = false; let no_val = val.is_none(); let empty_vals = opt.is_set(ArgSettings::EmptyValues); - let min_vals_zero = opt.v.min_vals.unwrap_or(1) == 0; + let min_vals_zero = opt.min_vals.unwrap_or(1) == 0; let needs_eq = opt.is_set(ArgSettings::RequireEquals); debug!("Parser::parse_opt; Checking for val..."); @@ -1671,10 +1160,10 @@ where let v = fv.trim_left_matches(b'='); if !empty_vals && (v.len_() == 0 || (needs_eq && !has_eq)) { sdebugln!("Found Empty - Error"); - return Err(Error::empty_value( + return Err(ClapError::empty_value( opt, - &*usage::create_error_usage(self, matcher, None), - self.color(), + &*Usage::new(self).create_error_usage(matcher, None), + self.app.color(), )); } sdebugln!("Found - {:?}, len: {}", v, v.len_()); @@ -1686,18 +1175,18 @@ where self.add_val_to_arg(opt, v, matcher)?; } else if needs_eq && !(empty_vals || min_vals_zero) { sdebugln!("None, but requires equals...Error"); - return Err(Error::empty_value( + return Err(ClapError::empty_value( opt, - &*usage::create_error_usage(self, matcher, None), - self.color(), + &*Usage::new(self).create_error_usage(matcher, None), + self.app.color(), )); } else { sdebugln!("None"); } - matcher.inc_occurrence_of(opt.b.name); + matcher.inc_occurrence_of(opt.name); // Increment or create the group "args" - self.groups_for_arg(opt.b.name) + self.groups_for_arg(opt.name) .and_then(|vec| Some(matcher.inc_occurrences_of(&*vec))); let needs_delim = opt.is_set(ArgSettings::RequireDelimiter); @@ -1707,29 +1196,27 @@ where return Ok(ParseResult::ValuesDone); } else if no_val || (mult && !needs_delim) && !has_eq && matcher.needs_more_vals(opt) { debugln!("Parser::parse_opt: More arg vals required..."); - return Ok(ParseResult::Opt(opt.b.name)); + return Ok(ParseResult::Opt(opt.name)); } debugln!("Parser::parse_opt: More arg vals not required..."); Ok(ParseResult::ValuesDone) } - fn add_val_to_arg( + fn add_val_to_arg( &self, - arg: &A, + arg: &Arg<'a, 'b>, val: &OsStr, matcher: &mut ArgMatcher<'a>, ) -> ClapResult> - where - A: AnyArg<'a, 'b> + Display, { - debugln!("Parser::add_val_to_arg; arg={}, val={:?}", arg.name(), val); + debugln!("Parser::add_val_to_arg; arg={}, val={:?}", arg.name, val); debugln!( "Parser::add_val_to_arg; trailing_vals={:?}, DontDelimTrailingVals={:?}", self.is_set(AS::TrailingValues), self.is_set(AS::DontDelimitTrailingValues) ); if !(self.is_set(AS::TrailingValues) && self.is_set(AS::DontDelimitTrailingValues)) { - if let Some(delim) = arg.val_delim() { + if let Some(delim) = arg.val_delim { if val.is_empty_() { Ok(self.add_single_val_to_arg(arg, val, matcher)?) } else { @@ -1753,158 +1240,83 @@ where } } - fn add_single_val_to_arg( + fn add_single_val_to_arg( &self, - arg: &A, + arg: &Arg<'a, 'b>, v: &OsStr, matcher: &mut ArgMatcher<'a>, ) -> ClapResult> - where - A: AnyArg<'a, 'b> + Display, { debugln!("Parser::add_single_val_to_arg;"); debugln!("Parser::add_single_val_to_arg: adding val...{:?}", v); - if let Some(t) = arg.val_terminator() { + if let Some(t) = arg.terminator { if t == v { return Ok(ParseResult::ValuesDone); } } - matcher.add_val_to(arg.name(), v); + matcher.add_val_to(arg.name, v); // Increment or create the group "args" - if let Some(grps) = self.groups_for_arg(arg.name()) { + if let Some(grps) = self.groups_for_arg(arg.name) { for grp in grps { matcher.add_val_to(&*grp, v); } } if matcher.needs_more_vals(arg) { - return Ok(ParseResult::Opt(arg.name())); + return Ok(ParseResult::Opt(arg.name)); } Ok(ParseResult::ValuesDone) } - fn parse_flag( &self, - flag: &FlagBuilder<'a, 'b>, + flag: &Arg<'a, 'b>, matcher: &mut ArgMatcher<'a>, ) -> ClapResult> { debugln!("Parser::parse_flag;"); - matcher.inc_occurrence_of(flag.b.name); + matcher.inc_occurrence_of(flag.name); // Increment or create the group "args" - self.groups_for_arg(flag.b.name) + self.groups_for_arg(flag.name) .and_then(|vec| Some(matcher.inc_occurrences_of(&*vec))); Ok(ParseResult::Flag) } - fn did_you_mean_error(&self, arg: &str, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { - // Didn't match a flag or option - let suffix = suggestions::did_you_mean_flag_suffix(arg, longs!(self), &self.subcommands); - - // Add the arg to the matches to build a proper usage string - if let Some(name) = suffix.1 { - if let Some(opt) = find_opt_by_long!(self, name) { - self.groups_for_arg(&*opt.b.name) - .and_then(|grps| Some(matcher.inc_occurrences_of(&*grps))); - matcher.insert(&*opt.b.name); - } else if let Some(flg) = find_flag_by_long!(self, name) { - self.groups_for_arg(&*flg.b.name) - .and_then(|grps| Some(matcher.inc_occurrences_of(&*grps))); - matcher.insert(&*flg.b.name); - } - } - - let used_arg = format!("--{}", arg); - Err(Error::unknown_argument( - &*used_arg, - &*suffix.0, - &*usage::create_error_usage(self, matcher, None), - self.color(), - )) - } - - // Prints the version to the user and exits if quit=true - fn print_version(&self, w: &mut W, use_long: bool) -> ClapResult<()> { - self.write_version(w, use_long)?; - w.flush().map_err(Error::from) - } - - pub fn write_version(&self, w: &mut W, use_long: bool) -> io::Result<()> { - let ver = if use_long { - self.meta - .long_version - .unwrap_or_else(|| self.meta.version.unwrap_or("")) - } else { - self.meta - .version - .unwrap_or_else(|| self.meta.long_version.unwrap_or("")) - }; - if let Some(bn) = self.meta.bin_name.as_ref() { - if bn.contains(' ') { - // Incase we're dealing with subcommands i.e. git mv is translated to git-mv - write!(w, "{} {}", bn.replace(" ", "-"), ver) - } else { - write!(w, "{} {}", &self.meta.name[..], ver) - } - } else { - write!(w, "{} {}", &self.meta.name[..], ver) - } - } - - pub fn print_help(&self) -> ClapResult<()> { - let out = io::stdout(); - let mut buf_w = BufWriter::new(out.lock()); - self.write_help(&mut buf_w) - } - - pub fn write_help(&self, w: &mut W) -> ClapResult<()> { - Help::write_parser_help(w, self, false) - } - - pub fn write_long_help(&self, w: &mut W) -> ClapResult<()> { - Help::write_parser_help(w, self, true) - } - - pub fn write_help_err(&self, w: &mut W) -> ClapResult<()> { - Help::write_parser_help_to_stderr(w, self) - } - pub fn add_defaults(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { debugln!("Parser::add_defaults;"); macro_rules! add_val { (@default $_self:ident, $a:ident, $m:ident) => { - if let Some(ref val) = $a.v.default_val { - debugln!("Parser::add_defaults:iter:{}: has default vals", $a.b.name); - if $m.get($a.b.name).map(|ma| ma.vals.len()).map(|len| len == 0).unwrap_or(false) { - debugln!("Parser::add_defaults:iter:{}: has no user defined vals", $a.b.name); + if let Some(ref val) = $a.default_val { + debugln!("Parser::add_defaults:iter:{}: has default vals", $a.name); + if $m.get($a.name).map(|ma| ma.vals.len()).map(|len| len == 0).unwrap_or(false) { + debugln!("Parser::add_defaults:iter:{}: has no user defined vals", $a.name); $_self.add_val_to_arg($a, OsStr::new(val), $m)?; - if $_self.cache.map_or(true, |name| name != $a.name()) { - $_self.cache = Some($a.name()); + if $_self.cache.map_or(true, |name| name != $a.name) { + $_self.cache = Some($a.name); } - } else if $m.get($a.b.name).is_some() { - debugln!("Parser::add_defaults:iter:{}: has user defined vals", $a.b.name); + } else if $m.get($a.name).is_some() { + debugln!("Parser::add_defaults:iter:{}: has user defined vals", $a.name); } else { - debugln!("Parser::add_defaults:iter:{}: wasn't used", $a.b.name); + debugln!("Parser::add_defaults:iter:{}: wasn't used", $a.name); $_self.add_val_to_arg($a, OsStr::new(val), $m)?; - if $_self.cache.map_or(true, |name| name != $a.name()) { - $_self.cache = Some($a.name()); + if $_self.cache.map_or(true, |name| name != $a.name) { + $_self.cache = Some($a.name); } } } else { - debugln!("Parser::add_defaults:iter:{}: doesn't have default vals", $a.b.name); + debugln!("Parser::add_defaults:iter:{}: doesn't have default vals", $a.name); } }; ($_self:ident, $a:ident, $m:ident) => { - if let Some(ref vm) = $a.v.default_vals_ifs { + if let Some(ref vm) = $a.default_vals_ifs { sdebugln!(" has conditional defaults"); let mut done = false; - if $m.get($a.b.name).is_none() { + if $m.get($a.name).is_none() { for &(arg, val, default) in vm.values() { let add = if let Some(a) = $m.get(arg) { if let Some(v) = val { @@ -1917,8 +1329,8 @@ where }; if add { $_self.add_val_to_arg($a, OsStr::new(default), $m)?; - if $_self.cache.map_or(true, |name| name != $a.name()) { - $_self.cache = Some($a.name()); + if $_self.cache.map_or(true, |name| name != $a.name) { + $_self.cache = Some($a.name); } done = true; break; @@ -1936,92 +1348,201 @@ where }; } - for o in &self.opts { - debug!("Parser::add_defaults:iter:{}:", o.b.name); + for o in opts!(self.app) { + debug!("Parser::add_defaults:iter:{}:", o.name); add_val!(self, o, matcher); } - for p in self.positionals.values() { - debug!("Parser::add_defaults:iter:{}:", p.b.name); + for p in positionals!(self.app) { + debug!("Parser::add_defaults:iter:{}:", p.name); add_val!(self, p, matcher); } Ok(()) } pub fn add_env(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { - macro_rules! add_val { - ($_self:ident, $a:ident, $m:ident) => { - if let Some(ref val) = $a.v.env { - if $m.get($a.b.name).map(|ma| ma.vals.len()).map(|len| len == 0).unwrap_or(false) { - if let Some(ref val) = val.1 { - $_self.add_val_to_arg($a, OsStr::new(val), $m)?; - - if $_self.cache.map_or(true, |name| name != $a.name()) { - $_self.cache = Some($a.name()); - } + for a in &self.app.args { + if let Some(ref val) = a.env { + if matcher.get(a.name).map(|ma| ma.vals.len()).map(|len| len == 0).unwrap_or(false) { + if let Some(ref val) = val.1 { + self.add_val_to_arg(a, OsStr::new(val), matcher)?; + + if self.cache.map_or(true, |name| name != a.name) { + self.cache = Some(a.name); } - } else { - if let Some(ref val) = val.1 { - $_self.add_val_to_arg($a, OsStr::new(val), $m)?; + } + } else { + if let Some(ref val) = val.1 { + self.add_val_to_arg(a, OsStr::new(val), matcher)?; - if $_self.cache.map_or(true, |name| name != $a.name()) { - $_self.cache = Some($a.name()); - } + if self.cache.map_or(true, |name| name != a.name) { + self.cache = Some(a.name); } } } - }; + } } + Ok(()) + } +} - for o in &self.opts { - add_val!(self, o, matcher); +// Error, Help, and Version Methods +impl<'a, 'b, 'c> Parser<'a, 'b, 'c> +where + 'a: 'b, + 'b: 'c, +{ + fn did_you_mean_error(&self, arg: &str, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { + // Didn't match a flag or option + let suffix = suggestions::did_you_mean_flag_suffix(arg, longs!(self.app), &*self.app.subcommands); + + // Add the arg to the matches to build a proper usage string + if let Some(name) = suffix.1 { + if let Some(opt) = find_by_long!(self.app, name) { + self.groups_for_arg(&*opt.name) + .and_then(|grps| Some(matcher.inc_occurrences_of(&*grps))); + matcher.insert(&*opt.name); + } else if let Some(flg) = find_by_long!(self.app, name) { + self.groups_for_arg(&*flg.name) + .and_then(|grps| Some(matcher.inc_occurrences_of(&*grps))); + matcher.insert(&*flg.name); + } } - for p in self.positionals.values() { - add_val!(self, p, matcher); + + let used_arg = format!("--{}", arg); + Err(ClapError::unknown_argument( + &*used_arg, + &*suffix.0, + &*Usage::new(self).create_error_usage(matcher, None), + self.app.color(), + )) + } + + // Prints the version to the user and exits if quit=true + fn print_version(&self, w: &mut W, use_long: bool) -> ClapResult<()> { + self.app._write_version(w, use_long)?; + w.flush().map_err(ClapError::from) + } + + pub fn print_help(&self) -> ClapResult<()> { + let out = io::stdout(); + let mut buf_w = BufWriter::new(out.lock()); + self.write_help(&mut buf_w) + } + + pub fn write_help(&self, w: &mut W) -> ClapResult<()> { + Help::write_parser_help(w, &self, false) + } + + pub fn write_long_help(&self, w: &mut W) -> ClapResult<()> { + Help::write_parser_help(w, &self, true) + } + + pub fn write_help_err(&self, w: &mut W) -> ClapResult<()> { + Help::write_parser_help_to_stderr(w, &self) + } + + fn help_err(&self, mut use_long: bool) -> ClapError { + debugln!("Parser::_help: use_long={:?}", use_long); + use_long = use_long && self.app.use_long_help(); + let mut buf = vec![]; + match Help::write_parser_help(&mut buf, self, use_long) { + Err(e) => e, + _ => ClapError { + message: String::from_utf8(buf).unwrap_or_default(), + kind: ErrorKind::HelpDisplayed, + info: None, + }, } - Ok(()) } - pub fn flags(&self) -> Iter> { self.flags.iter() } + fn version_err(&self, use_long: bool) -> ClapError { + debugln!("Parser::_version: "); + let out = io::stdout(); + let mut buf_w = BufWriter::new(out.lock()); + match self.print_version(&mut buf_w, use_long) { + Err(e) => e, + _ => ClapError { + message: String::new(), + kind: ErrorKind::VersionDisplayed, + info: None, + }, + } + } - pub fn opts(&self) -> Iter> { self.opts.iter() } - pub fn positionals(&self) -> map::Values> { self.positionals.values() } +} - pub fn subcommands(&self) -> Iter { self.subcommands.iter() } +// Query Methods +impl<'a, 'b, 'c> Parser<'a, 'b, 'c> +where + 'a: 'b, + 'b: 'c, +{ + pub fn groups_for_arg(&self, name: &str) -> Option> { + debugln!("Parser::groups_for_arg: name={}", name); - // Should we color the output? None=determined by output location, true=yes, false=no - #[doc(hidden)] - pub fn color(&self) -> ColorWhen { - debugln!("Parser::color;"); - debug!("Parser::color: Color setting..."); - if self.is_set(AS::ColorNever) { - sdebugln!("Never"); - ColorWhen::Never - } else if self.is_set(AS::ColorAlways) { - sdebugln!("Always"); - ColorWhen::Always - } else { - sdebugln!("Auto"); - ColorWhen::Auto + if self.app.groups.is_empty() { + debugln!("Parser::groups_for_arg: No groups defined"); + return None; + } + let mut res = vec![]; + debugln!("Parser::groups_for_arg: Searching through groups..."); + for grp in groups!(self.app) { + for a in &grp.args { + if a == &name { + sdebugln!("\tFound '{}'", grp.name); + res.push(&*grp.name); + } + } + } + if res.is_empty() { + return None; } + + Some(res) } - pub fn find_any_arg(&self, name: &str) -> Option<&AnyArg<'a, 'b>> { - if let Some(f) = find_by_name!(self, name, flags, iter) { - return Some(f); + pub fn args_in_group(&self, group: &str) -> Vec { + let mut g_vec = vec![]; + let mut args = vec![]; + + for n in find!(self.app, group, groups).expect(INTERNAL_ERROR_MSG).args { + if let Some(f) = find!(self.app, n) { + args.push(f.to_string()); + } else { + g_vec.push(n); + } } - if let Some(o) = find_by_name!(self, name, opts, iter) { - return Some(o); + + for av in g_vec.iter().map(|g| self.args_in_group(g)) { + args.extend(av); } - if let Some(p) = find_by_name!(self, name, positionals, values) { - return Some(p); + args.dedup(); + args.iter().map(ToOwned::to_owned).collect() + } + + pub fn arg_names_in_group(&self, group: &str) -> Vec<&'a str> { + let mut g_vec = vec![]; + let mut args = vec![]; + + for n in find!(self.app, group, groups) + .expect(INTERNAL_ERROR_MSG) + .args + { + if groups!(self.app).any(|g| g.name == n) { + args.extend(self.arg_names_in_group(n)); + g_vec.push(n); + } else if !args.contains(&&n) { + args.push(n); + } } - None + + args.iter().map(|s| *s).collect() } /// Check is a given string matches the binary name for this parser fn is_bin_name(&self, value: &str) -> bool { - self.meta + self.app .bin_name .as_ref() .and_then(|name| Some(value == name)) @@ -2030,7 +1551,7 @@ where /// Check is a given string is an alias for this parser fn is_alias(&self, value: &str) -> bool { - self.meta + self.app .aliases .as_ref() .and_then(|aliases| { @@ -2044,35 +1565,56 @@ where .unwrap_or(false) } - // Only used for completion scripts due to bin_name messiness - #[cfg_attr(feature = "lints", allow(block_in_if_condition_stmt))] - pub fn find_subcommand(&'b self, sc: &str) -> Option<&'b App<'a, 'b>> { - debugln!("Parser::find_subcommand: sc={}", sc); - debugln!( - "Parser::find_subcommand: Currently in Parser...{}", - self.meta.bin_name.as_ref().unwrap() - ); - for s in &self.subcommands { - if s.p.is_bin_name(sc) { - return Some(s); - } - // XXX: why do we split here? - // isn't `sc` supposed to be single word already? - let last = sc.split(' ').rev().next().expect(INTERNAL_ERROR_MSG); - if s.p.is_alias(last) { - return Some(s); - } + fn contains_long(&self, l: &str) -> bool { self.app.contains_long(l) } - if let Some(app) = s.p.find_subcommand(sc) { - return Some(app); - } - } - None + fn contains_short(&self, s: char) -> bool { self.app.contains_short(s) } + + pub fn required(&self) -> Iter<&str> { self.required.iter() } + + #[cfg_attr(feature = "lints", allow(needless_borrow))] + pub fn has_args(&self) -> bool { + self.app.has_args() + } + + pub fn has_opts(&self) -> bool { + self.app.has_opts() + } + + pub fn has_flags(&self) -> bool { + self.app.has_flags() + } + + pub fn has_positionals(&self) -> bool { + !self.positionals.is_empty() + } + + pub fn has_subcommands(&self) -> bool { + self.app.has_subcommands() + } + + pub fn has_visible_opts(&self) -> bool { + self.app.has_visible_opts() + } + + pub fn has_visible_flags(&self) -> bool { + self.app.has_visible_flags() + } + + pub fn has_visible_positionals(&self) -> bool { + self.app.has_visible_positionals() + } + + #[inline] + pub fn has_visible_subcommands(&self) -> bool { + self.app.has_visible_subcommands() } #[inline] - fn contains_long(&self, l: &str) -> bool { longs!(self).any(|al| al == &l) } + pub fn is_set(&self, s: AS) -> bool { self.app.is_set(s) } + + #[inline] + pub fn set(&mut self, s: AS) { self.app.set(s) } #[inline] - fn contains_short(&self, s: char) -> bool { shorts!(self).any(|arg_s| arg_s == &s) } + pub fn unset(&mut self, s: AS) { self.app.unset(s) } } diff --git a/src/app/usage.rs b/src/app/usage.rs index 609058843c8..9b165ec1de5 100644 --- a/src/app/usage.rs +++ b/src/app/usage.rs @@ -3,477 +3,469 @@ use std::collections::{BTreeMap, VecDeque}; // Internal use INTERNAL_ERROR_MSG; -use args::{AnyArg, ArgMatcher, PosBuilder}; +use args::{Arg, ArgMatcher}; use args::settings::ArgSettings; use app::settings::AppSettings as AS; use app::parser::Parser; -// Creates a usage string for display. This happens just after all arguments were parsed, but before -// any subcommands have been parsed (so as to give subcommands their own usage recursively) -pub fn create_usage_with_title(p: &Parser, used: &[&str]) -> String { - debugln!("usage::create_usage_with_title;"); - let mut usage = String::with_capacity(75); - usage.push_str("USAGE:\n "); - usage.push_str(&*create_usage_no_title(p, used)); - usage -} +pub struct Usage<'a, 'b, 'c,'z>(&'z Parser<'a, 'b, 'c>) +where + 'a: 'b, + 'b: 'c, + 'c: 'z; -// Creates a usage string to be used in error message (i.e. one with currently used args) -pub fn create_error_usage<'a, 'b>( - p: &Parser<'a, 'b>, - matcher: &'b ArgMatcher<'a>, - extra: Option<&str>, -) -> String { - let mut args: Vec<_> = matcher - .arg_names() - .iter() - .filter(|n| { - if let Some(o) = find_by_name!(p, **n, opts, iter) { - !o.b.is_set(ArgSettings::Required) && !o.b.is_set(ArgSettings::Hidden) - } else if let Some(p) = find_by_name!(p, **n, positionals, values) { - !p.b.is_set(ArgSettings::Required) && p.b.is_set(ArgSettings::Hidden) - } else { - true // flags can't be required, so they're always true - } - }) - .map(|&n| n) - .collect(); - if let Some(r) = extra { - args.push(r); - } - create_usage_with_title(p, &*args) -} +impl<'a, 'b, 'c,'z> Usage<'a, 'b, 'c, 'z> { + pub fn new(p: &'z Parser<'a, 'b, 'c>) -> Self { Usage(p) } -// Creates a usage string (*without title*) if one was not provided by the user manually. -pub fn create_usage_no_title(p: &Parser, used: &[&str]) -> String { - debugln!("usage::create_usage_no_title;"); - if let Some(u) = p.meta.usage_str { - String::from(&*u) - } else if used.is_empty() { - create_help_usage(p, true) - } else { - create_smart_usage(p, used) + // Creates a usage string for display. This happens just after all arguments were parsed, but before + // any subcommands have been parsed (so as to give subcommands their own usage recursively) + pub fn create_usage_with_title(&self, used: &[&str]) -> String { + debugln!("usage::create_usage_with_title;"); + let mut usage = String::with_capacity(75); + usage.push_str("USAGE:\n "); + usage.push_str(&*self.create_usage_no_title(used)); + usage } -} -// Creates a usage string for display in help messages (i.e. not for errors) -pub fn create_help_usage(p: &Parser, incl_reqs: bool) -> String { - let mut usage = String::with_capacity(75); - let name = p.meta - .usage - .as_ref() - .unwrap_or_else(|| p.meta.bin_name.as_ref().unwrap_or(&p.meta.name)); - usage.push_str(&*name); - let req_string = if incl_reqs { - let mut reqs: Vec<&str> = p.required().map(|r| &**r).collect(); - reqs.sort(); - reqs.dedup(); - get_required_usage_from(p, &reqs, None, None, false) + // Creates a usage string to be used in error message (i.e. one with currently used args) + pub fn create_error_usage(&self, + matcher: &ArgMatcher<'a>, + extra: Option<&str>, + ) -> String { + let mut args: Vec<_> = matcher + .arg_names() .iter() - .fold(String::new(), |a, s| a + &format!(" {}", s)[..]) - } else { - String::new() - }; - - let flags = needs_flags_tag(p); - if flags && !p.is_set(AS::UnifiedHelpMessage) { - usage.push_str(" [FLAGS]"); - } else if flags { - usage.push_str(" [OPTIONS]"); - } - if !p.is_set(AS::UnifiedHelpMessage) && p.opts.iter().any(|o| { - !o.is_set(ArgSettings::Required) && !o.is_set(ArgSettings::Hidden) - }) { - usage.push_str(" [OPTIONS]"); + .filter(|n| { + if let Some(a) = find!(self.0.app, **n) { + !a.is_set(ArgSettings::Required) && !a.is_set(ArgSettings::Hidden) + } else { + true // flags can't be required, so they're always true + } + }) + .map(|&n| n) + .collect(); + if let Some(r) = extra { + args.push(r); + } + self.create_usage_with_title(&*args) } - usage.push_str(&req_string[..]); - - let has_last = p.positionals.values().any(|p| p.is_set(ArgSettings::Last)); - // places a '--' in the usage string if there are args and options - // supporting multiple values - if p.opts.iter().any(|o| o.is_set(ArgSettings::Multiple)) - && p.positionals - .values() - .any(|p| !p.is_set(ArgSettings::Required)) - && !(p.has_visible_subcommands() || p.is_set(AS::AllowExternalSubcommands)) - && !has_last - { - usage.push_str(" [--]"); + // Creates a usage string (*without title*) if one was not provided by the user manually. + pub fn create_usage_no_title(&self, used: &[&str]) -> String { + debugln!("usage::create_usage_no_title;"); + if let Some(u) = self.0.app.usage_str { + String::from(&*u) + } else if used.is_empty() { + self.create_help_usage(true) + } else { + self.create_smart_usage(used) + } } - let not_req_or_hidden = |p: &PosBuilder| { - (!p.is_set(ArgSettings::Required) || p.is_set(ArgSettings::Last)) - && !p.is_set(ArgSettings::Hidden) - }; - if p.has_positionals() && p.positionals.values().any(not_req_or_hidden) { - if let Some(args_tag) = get_args_tag(p, incl_reqs) { - usage.push_str(&*args_tag); + + // Creates a usage string for display in help messages (i.e. not for errors) + pub fn create_help_usage(&self, incl_reqs: bool) -> String { + let mut usage = String::with_capacity(75); + let name = self.0.app + .usage + .as_ref() + .unwrap_or_else(|| self.0.app.bin_name.as_ref().unwrap_or(&self.0.app.name)); + usage.push_str(&*name); + let req_string = if incl_reqs { + let mut reqs: Vec<&str> = self.0.required().map(|r| &**r).collect(); + reqs.sort(); + reqs.dedup(); + self.get_required_usage_from(&reqs, None, None, false) + .iter() + .fold(String::new(), |a, s| a + &format!(" {}", s)[..]) } else { - usage.push_str(" [ARGS]"); + String::new() + }; + + let flags = self.needs_flags_tag(); + if flags && !self.0.is_set(AS::UnifiedHelpMessage) { + usage.push_str(" [FLAGS]"); + } else if flags { + usage.push_str(" [OPTIONS]"); } - if has_last && incl_reqs { - let pos = p.positionals - .values() - .find(|p| p.b.is_set(ArgSettings::Last)) - .expect(INTERNAL_ERROR_MSG); - debugln!("usage::create_help_usage: '{}' has .last(true)", pos.name()); - let req = pos.is_set(ArgSettings::Required); - if req - && p.positionals - .values() - .any(|p| !p.is_set(ArgSettings::Required)) - { - usage.push_str(" -- <"); - } else if req { - usage.push_str(" [--] <"); + if !self.0.is_set(AS::UnifiedHelpMessage) && opts!(self.0.app).any(|o| { + !o.is_set(ArgSettings::Required) && !o.is_set(ArgSettings::Hidden) + }) { + usage.push_str(" [OPTIONS]"); + } + + usage.push_str(&req_string[..]); + + let has_last = positionals!(self.0.app).any(|p| p.is_set(ArgSettings::Last)); + // places a '--' in the usage string if there are args and options + // supporting multiple values + if opts!(self.0.app).any(|o| o.is_set(ArgSettings::Multiple)) + && positionals!(self.0.app) + .any(|p| !p.is_set(ArgSettings::Required)) + && !(self.0.app.has_visible_subcommands() || self.0.is_set(AS::AllowExternalSubcommands)) + && !has_last + { + usage.push_str(" [--]"); + } + let not_req_or_hidden = |p: &Arg| { + (!p.is_set(ArgSettings::Required) || p.is_set(ArgSettings::Last)) + && !p.is_set(ArgSettings::Hidden) + }; + if positionals!(self.0.app).any(not_req_or_hidden) { + if let Some(args_tag) = self.get_args_tag(incl_reqs) { + usage.push_str(&*args_tag); } else { - usage.push_str(" [-- <"); + usage.push_str(" [ARGS]"); } - usage.push_str(&*pos.name_no_brackets()); - usage.push_str(">"); - usage.push_str(pos.multiple_str()); - if !req { - usage.push_str("]"); + if has_last && incl_reqs { + let pos = positionals!(self.0.app) + .find(|p| p.is_set(ArgSettings::Last)) + .expect(INTERNAL_ERROR_MSG); + debugln!("usage::create_help_usage: '{}' has .last(true)", pos.name); + let req = pos.is_set(ArgSettings::Required); + if req + && positionals!(self.0.app) + .any(|p| !p.is_set(ArgSettings::Required)) + { + usage.push_str(" -- <"); + } else if req { + usage.push_str(" [--] <"); + } else { + usage.push_str(" [-- <"); + } + usage.push_str(&*pos.name_no_brackets()); + usage.push_str(">"); + usage.push_str(pos.multiple_str()); + if !req { + usage.push_str("]"); + } } } - } - // incl_reqs is only false when this function is called recursively - if p.has_visible_subcommands() && incl_reqs || p.is_set(AS::AllowExternalSubcommands) { - if p.is_set(AS::SubcommandsNegateReqs) || p.is_set(AS::ArgsNegateSubcommands) { - if !p.is_set(AS::ArgsNegateSubcommands) { - usage.push_str("\n "); - usage.push_str(&*create_help_usage(p, false)); + // incl_reqs is only false when this function is called recursively + if self.0.app.has_visible_subcommands() && incl_reqs || self.0.is_set(AS::AllowExternalSubcommands) { + if self.0.is_set(AS::SubcommandsNegateReqs) || self.0.is_set(AS::ArgsNegateSubcommands) { + if !self.0.is_set(AS::ArgsNegateSubcommands) { + usage.push_str("\n "); + usage.push_str(&*self.create_help_usage(false)); + usage.push_str(" "); + } else { + usage.push_str("\n "); + usage.push_str(&*name); + usage.push_str(" "); + } + } else if self.0.is_set(AS::SubcommandRequired) || self.0.is_set(AS::SubcommandRequiredElseHelp) { usage.push_str(" "); } else { - usage.push_str("\n "); - usage.push_str(&*name); - usage.push_str(" "); + usage.push_str(" [SUBCOMMAND]"); } - } else if p.is_set(AS::SubcommandRequired) || p.is_set(AS::SubcommandRequiredElseHelp) { - usage.push_str(" "); - } else { - usage.push_str(" [SUBCOMMAND]"); } + usage.shrink_to_fit(); + debugln!("usage::create_help_usage: usage={}", usage); + usage } - usage.shrink_to_fit(); - debugln!("usage::create_help_usage: usage={}", usage); - usage -} -// Creates a context aware usage string, or "smart usage" from currently used -// args, and requirements -fn create_smart_usage(p: &Parser, used: &[&str]) -> String { - debugln!("usage::smart_usage;"); - let mut usage = String::with_capacity(75); - let mut hs: Vec<&str> = p.required().map(|s| &**s).collect(); - hs.extend_from_slice(used); + // Creates a context aware usage string, or "smart usage" from currently used + // args, and requirements + fn create_smart_usage(&self, used: &[&str]) -> String { + debugln!("usage::smart_usage;"); + let mut usage = String::with_capacity(75); + let mut hs: Vec<&str> = self.0.required().map(|s| &**s).collect(); + hs.extend_from_slice(used); - let r_string = get_required_usage_from(p, &hs, None, None, false) - .iter() - .fold(String::new(), |acc, s| acc + &format!(" {}", s)[..]); + let r_string = self.get_required_usage_from(&hs, None, None, false) + .iter() + .fold(String::new(), |acc, s| acc + &format!(" {}", s)[..]); - usage.push_str( - &p.meta - .usage - .as_ref() - .unwrap_or_else(|| p.meta.bin_name.as_ref().unwrap_or(&p.meta.name))[..], - ); - usage.push_str(&*r_string); - if p.is_set(AS::SubcommandRequired) { - usage.push_str(" "); + usage.push_str( + &self.0.app + .usage + .as_ref() + .unwrap_or_else(|| self.0.app.bin_name.as_ref().unwrap_or(&self.0.app.name))[..], + ); + usage.push_str(&*r_string); + if self.0.is_set(AS::SubcommandRequired) { + usage.push_str(" "); + } + usage.shrink_to_fit(); + usage } - usage.shrink_to_fit(); - usage -} -// Gets the `[ARGS]` tag for the usage string -fn get_args_tag(p: &Parser, incl_reqs: bool) -> Option { - debugln!("usage::get_args_tag;"); - let mut count = 0; - 'outer: for pos in p.positionals - .values() - .filter(|pos| !pos.is_set(ArgSettings::Required)) - .filter(|pos| !pos.is_set(ArgSettings::Hidden)) - .filter(|pos| !pos.is_set(ArgSettings::Last)) - { - debugln!("usage::get_args_tag:iter:{}:", pos.b.name); - if let Some(g_vec) = p.groups_for_arg(pos.b.name) { - for grp_s in &g_vec { - debugln!("usage::get_args_tag:iter:{}:iter:{};", pos.b.name, grp_s); - // if it's part of a required group we don't want to count it - if p.groups.iter().any(|g| g.required && (&g.name == grp_s)) { - continue 'outer; + // Gets the `[ARGS]` tag for the usage string + fn get_args_tag(&self, incl_reqs: bool) -> Option { + debugln!("usage::get_args_tag;"); + let mut count = 0; + 'outer: for pos in positionals!(self.0.app) + .filter(|pos| !pos.is_set(ArgSettings::Required)) + .filter(|pos| !pos.is_set(ArgSettings::Hidden)) + .filter(|pos| !pos.is_set(ArgSettings::Last)) + { + debugln!("usage::get_args_tag:iter:{}:", pos.name); + if let Some(g_vec) = self.0.groups_for_arg(pos.name) { + for grp_s in &g_vec { + debugln!("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 groups!(self.0.app).any(|g| g.required && (&g.name == grp_s)) { + continue 'outer; + } } } + count += 1; + debugln!( + "usage::get_args_tag:iter: {} Args not required or hidden", + count + ); } - count += 1; - debugln!( - "usage::get_args_tag:iter: {} Args not required or hidden", - count - ); - } - if !p.is_set(AS::DontCollapseArgsInUsage) && count > 1 { - debugln!("usage::get_args_tag:iter: More than one, returning [ARGS]"); - return None; // [ARGS] - } else if count == 1 && incl_reqs { - let pos = p.positionals - .values() - .find(|pos| { - !pos.is_set(ArgSettings::Required) && !pos.is_set(ArgSettings::Hidden) - && !pos.is_set(ArgSettings::Last) - }) - .expect(INTERNAL_ERROR_MSG); - debugln!( - "usage::get_args_tag:iter: Exactly one, returning '{}'", - pos.name() - ); - return Some(format!( - " [{}]{}", - pos.name_no_brackets(), - pos.multiple_str() - )); - } else if p.is_set(AS::DontCollapseArgsInUsage) && !p.positionals.is_empty() && incl_reqs { - debugln!("usage::get_args_tag:iter: Don't collapse returning all"); - return Some( - p.positionals - .values() - .filter(|pos| !pos.is_set(ArgSettings::Required)) - .filter(|pos| !pos.is_set(ArgSettings::Hidden)) - .filter(|pos| !pos.is_set(ArgSettings::Last)) - .map(|pos| { - format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()) + if !self.0.is_set(AS::DontCollapseArgsInUsage) && count > 1 { + debugln!("usage::get_args_tag:iter: More than one, returning [ARGS]"); + return None; // [ARGS] + } else if count == 1 && incl_reqs { + let pos = positionals!(self.0.app) + .find(|pos| { + !pos.is_set(ArgSettings::Required) && !pos.is_set(ArgSettings::Hidden) + && !pos.is_set(ArgSettings::Last) }) - .collect::>() - .join(""), - ); - } else if !incl_reqs { - debugln!("usage::get_args_tag:iter: incl_reqs=false, building secondary usage string"); - let highest_req_pos = p.positionals - .iter() - .filter_map(|(idx, pos)| { - if pos.b.is_set(ArgSettings::Required) && !pos.b.is_set(ArgSettings::Last) { - Some(idx) - } else { - None - } - }) - .max() - .unwrap_or_else(|| p.positionals.len()); - return Some( - p.positionals - .iter() - .filter_map(|(idx, pos)| { - if idx <= highest_req_pos { - Some(pos) + .expect(INTERNAL_ERROR_MSG); + debugln!( + "usage::get_args_tag:iter: Exactly one, returning '{}'", + pos.name + ); + return Some(format!( + " [{}]{}", + pos.name_no_brackets(), + pos.multiple_str() + )); + } else if self.0.is_set(AS::DontCollapseArgsInUsage) && self.0.has_positionals() && incl_reqs { + debugln!("usage::get_args_tag:iter: Don't collapse returning all"); + return Some( + positionals!(self.0.app) + .filter(|pos| !pos.is_set(ArgSettings::Required)) + .filter(|pos| !pos.is_set(ArgSettings::Hidden)) + .filter(|pos| !pos.is_set(ArgSettings::Last)) + .map(|pos| { + format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()) + }) + .collect::>() + .join(""), + ); + } else if !incl_reqs { + debugln!("usage::get_args_tag:iter: incl_reqs=false, building secondary usage string"); + let highest_req_pos = positionals!(self.0.app) + .filter_map(|pos| { + if pos.is_set(ArgSettings::Required) && !pos.is_set(ArgSettings::Last) { + Some(pos.index) } else { None } }) - .filter(|pos| !pos.is_set(ArgSettings::Required)) - .filter(|pos| !pos.is_set(ArgSettings::Hidden)) - .filter(|pos| !pos.is_set(ArgSettings::Last)) - .map(|pos| { - format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()) - }) - .collect::>() - .join(""), - ); + .max() + .unwrap_or_else(|| Some(positionals!(self.0.app).count() as u64)); + return Some( + positionals!(self.0.app) + .filter_map(|pos| { + if pos.index <= highest_req_pos { + Some(pos) + } else { + None + } + }) + .filter(|pos| !pos.is_set(ArgSettings::Required)) + .filter(|pos| !pos.is_set(ArgSettings::Hidden)) + .filter(|pos| !pos.is_set(ArgSettings::Last)) + .map(|pos| { + format!(" [{}]{}", pos.name_no_brackets(), pos.multiple_str()) + }) + .collect::>() + .join(""), + ); + } + Some("".into()) } - Some("".into()) -} -// Determines if we need the `[FLAGS]` tag in the usage string -fn needs_flags_tag(p: &Parser) -> bool { - debugln!("usage::needs_flags_tag;"); - 'outer: for f in &p.flags { - debugln!("usage::needs_flags_tag:iter: f={};", f.b.name); - if let Some(l) = f.s.long { - if l == "help" || l == "version" { - // Don't print `[FLAGS]` just for help or version - continue; + // Determines if we need the `[FLAGS]` tag in the usage string + fn needs_flags_tag(&self) -> bool { + debugln!("usage::needs_flags_tag;"); + 'outer: for f in flags!(self.0.app) { + debugln!("usage::needs_flags_tag:iter: f={};", f.name); + if let Some(l) = f.long { + if l == "help" || l == "version" { + // Don't print `[FLAGS]` just for help or version + continue; + } } - } - if let Some(g_vec) = p.groups_for_arg(f.b.name) { - for grp_s in &g_vec { - debugln!("usage::needs_flags_tag:iter:iter: grp_s={};", grp_s); - if p.groups.iter().any(|g| &g.name == grp_s && g.required) { - debugln!("usage::needs_flags_tag:iter:iter: Group is required"); - continue 'outer; + if let Some(g_vec) = self.0.groups_for_arg(f.name) { + for grp_s in &g_vec { + debugln!("usage::needs_flags_tag:iter:iter: grp_s={};", grp_s); + if groups!(self.0.app).any(|g| &g.name == grp_s && g.required) { + debugln!("usage::needs_flags_tag:iter:iter: Group is required"); + continue 'outer; + } } } + if f.is_set(ArgSettings::Hidden) { + continue; + } + debugln!("usage::needs_flags_tag:iter: [FLAGS] required"); + return true; } - if f.is_set(ArgSettings::Hidden) { - continue; - } - debugln!("usage::needs_flags_tag:iter: [FLAGS] required"); - return true; - } - debugln!("usage::needs_flags_tag: [FLAGS] not required"); - false -} + debugln!("usage::needs_flags_tag: [FLAGS] not required"); + false + } -// Returns the required args in usage string form by fully unrolling all groups -pub fn get_required_usage_from<'a, 'b>( - p: &Parser<'a, 'b>, - reqs: &[&'a str], - matcher: Option<&ArgMatcher<'a>>, - extra: Option<&str>, - incl_last: bool, -) -> VecDeque { - debugln!( - "usage::get_required_usage_from: reqs={:?}, extra={:?}", - reqs, - extra - ); - let mut desc_reqs: Vec<&str> = vec![]; - desc_reqs.extend(extra); - let mut new_reqs: Vec<&str> = vec![]; - macro_rules! get_requires { - (@group $a: ident, $v:ident, $p:ident) => {{ - if let Some(rl) = p.groups.iter() - .filter(|g| g.requires.is_some()) - .find(|g| &g.name == $a) - .map(|g| g.requires.as_ref().unwrap()) { - for r in rl { - if !$p.contains(&r) { - debugln!("usage::get_required_usage_from:iter:{}: adding group req={:?}", - $a, r); - $v.push(r); + // Returns the required args in usage string form by fully unrolling all groups + pub fn get_required_usage_from( + &self, + reqs: &[&str], + matcher: Option<&ArgMatcher<'a>>, + extra: Option<&str>, + incl_last: bool, + ) -> VecDeque { + debugln!( + "usage::get_required_usage_from: reqs={:?}, extra={:?}", + reqs, + extra + ); + let mut desc_reqs: Vec<&str> = vec![]; + desc_reqs.extend(extra); + let mut new_reqs: Vec<&str> = vec![]; + macro_rules! get_requires { + (@group $a: ident, $v:ident, $p:ident) => {{ + if let Some(rl) = groups!(self.0.app) + .filter(|g| g.requires.is_some()) + .find(|g| &g.name == $a) + .map(|g| g.requires.as_ref().unwrap()) { + for r in rl { + if !$p.contains(&r) { + debugln!("usage::get_required_usage_from:iter:{}: adding group req={:?}", + $a, r); + $v.push(r); + } } } - } - }}; - ($a:ident, $what:ident, $how:ident, $v:ident, $p:ident) => {{ - if let Some(rl) = p.$what.$how() - .filter(|a| a.b.requires.is_some()) - .find(|arg| &arg.b.name == $a) - .map(|a| a.b.requires.as_ref().unwrap()) { - for &(_, r) in rl.iter() { - if !$p.contains(&r) { - debugln!("usage::get_required_usage_from:iter:{}: adding arg req={:?}", - $a, r); - $v.push(r); + }}; + ($a:ident, $what:ident, $how:ident, $v:ident, $p:ident) => {{ + if let Some(rl) = $what!(self.0.app) + .filter(|a| a.requires.is_some()) + .find(|arg| &arg.name == $a) + .map(|a| a.requires.as_ref().unwrap()) { + for &(_, r) in rl.iter() { + if !$p.contains(&r) { + debugln!("usage::get_required_usage_from:iter:{}: adding arg req={:?}", + $a, r); + $v.push(r); + } } } + }}; + } + // initialize new_reqs + for a in reqs { + get_requires!(a, flags, iter, new_reqs, reqs); + get_requires!(a, opts, iter, new_reqs, reqs); + get_requires!(a, positionals, values, new_reqs, reqs); + get_requires!(@group a, new_reqs, reqs); + } + desc_reqs.extend_from_slice(&*new_reqs); + debugln!( + "usage::get_required_usage_from: after init desc_reqs={:?}", + desc_reqs + ); + loop { + let mut tmp = vec![]; + for a in &new_reqs { + get_requires!(a, flags, iter, tmp, desc_reqs); + get_requires!(a, opts, iter, tmp, desc_reqs); + get_requires!(a, positionals, values, tmp, desc_reqs); + get_requires!(@group a, tmp, desc_reqs); + } + if tmp.is_empty() { + debugln!("usage::get_required_usage_from: no more children"); + break; + } else { + debugln!("usage::get_required_usage_from: after iter tmp={:?}", tmp); + debugln!( + "usage::get_required_usage_from: after iter new_reqs={:?}", + new_reqs + ); + desc_reqs.extend_from_slice(&*new_reqs); + new_reqs.clear(); + new_reqs.extend_from_slice(&*tmp); + debugln!( + "usage::get_required_usage_from: after iter desc_reqs={:?}", + desc_reqs + ); } - }}; - } - // initialize new_reqs - for a in reqs { - get_requires!(a, flags, iter, new_reqs, reqs); - get_requires!(a, opts, iter, new_reqs, reqs); - get_requires!(a, positionals, values, new_reqs, reqs); - get_requires!(@group a, new_reqs, reqs); - } - desc_reqs.extend_from_slice(&*new_reqs); - debugln!( - "usage::get_required_usage_from: after init desc_reqs={:?}", - desc_reqs - ); - loop { - let mut tmp = vec![]; - for a in &new_reqs { - get_requires!(a, flags, iter, tmp, desc_reqs); - get_requires!(a, opts, iter, tmp, desc_reqs); - get_requires!(a, positionals, values, tmp, desc_reqs); - get_requires!(@group a, tmp, desc_reqs); } - if tmp.is_empty() { - debugln!("usage::get_required_usage_from: no more children"); - break; + desc_reqs.extend_from_slice(reqs); + desc_reqs.sort(); + desc_reqs.dedup(); + debugln!( + "usage::get_required_usage_from: final desc_reqs={:?}", + desc_reqs + ); + let mut ret_val = VecDeque::new(); + let args_in_groups = groups!(self.0.app) + .filter(|gn| desc_reqs.contains(&gn.name)) + .flat_map(|g| self.0.arg_names_in_group(g.name)) + .collect::>(); + + let pmap = if let Some(m) = matcher { + desc_reqs + .iter() + .filter(|a| self.0.positionals.values().any(|p| &p == a)) + .filter(|&pos| !m.contains(pos)) + .filter_map(|pos| find!(self.0.app, *pos)) + .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last)) + .filter(|pos| !args_in_groups.contains(&pos.name)) + .map(|pos| (pos.index.unwrap(), pos)) + .collect::>() // sort by index } else { - debugln!("usage::get_required_usage_from: after iter tmp={:?}", tmp); - debugln!( - "usage::get_required_usage_from: after iter new_reqs={:?}", - new_reqs - ); - desc_reqs.extend_from_slice(&*new_reqs); - new_reqs.clear(); - new_reqs.extend_from_slice(&*tmp); - debugln!( - "usage::get_required_usage_from: after iter desc_reqs={:?}", - desc_reqs - ); + desc_reqs + .iter() + .filter(|a| self.0.positionals.values().any(|p| &p == a)) + .filter_map(|pos| find!(self.0.app, *pos)) + .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last)) + .filter(|pos| !args_in_groups.contains(&pos.name)) + .map(|pos| (pos.index.unwrap(), pos)) + .collect::>() // sort by index + }; + debugln!( + "usage::get_required_usage_from: args_in_groups={:?}", + args_in_groups + ); + for &p in pmap.values() { + let s = p.to_string(); + if args_in_groups.is_empty() || !args_in_groups.contains(&&*s) { + ret_val.push_back(s); + } } - } - desc_reqs.extend_from_slice(reqs); - desc_reqs.sort(); - desc_reqs.dedup(); - debugln!( - "usage::get_required_usage_from: final desc_reqs={:?}", - desc_reqs - ); - let mut ret_val = VecDeque::new(); - let args_in_groups = p.groups - .iter() - .filter(|gn| desc_reqs.contains(&gn.name)) - .flat_map(|g| p.arg_names_in_group(g.name)) - .collect::>(); - - let pmap = if let Some(m) = matcher { - desc_reqs + for a in desc_reqs .iter() - .filter(|a| p.positionals.values().any(|p| &&p.b.name == a)) - .filter(|&pos| !m.contains(pos)) - .filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos)) - .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last)) - .filter(|pos| !args_in_groups.contains(&pos.b.name)) - .map(|pos| (pos.index, pos)) - .collect::>() // sort by index - } else { - desc_reqs + .filter(|name| !positionals!(self.0.app).any(|p| &&p.name == name)) + .filter(|name| !groups!(self.0.app).any(|g| &&g.name == name)) + .filter(|name| !args_in_groups.contains(name)) + .filter(|name| { + !(matcher.is_some() && matcher.as_ref().unwrap().contains(name)) + }) { + debugln!("usage::get_required_usage_from:iter:{}:", a); + let arg = find!(self.0.app, *a).map(|f| f.to_string()).expect(INTERNAL_ERROR_MSG); + ret_val.push_back(arg); + } + let mut g_vec: Vec = vec![]; + for g in desc_reqs .iter() - .filter(|a| p.positionals.values().any(|pos| &&pos.b.name == a)) - .filter_map(|pos| p.positionals.values().find(|x| &x.b.name == pos)) - .filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last)) - .filter(|pos| !args_in_groups.contains(&pos.b.name)) - .map(|pos| (pos.index, pos)) - .collect::>() // sort by index - }; - debugln!( - "usage::get_required_usage_from: args_in_groups={:?}", - args_in_groups - ); - for &p in pmap.values() { - let s = p.to_string(); - if args_in_groups.is_empty() || !args_in_groups.contains(&&*s) { - ret_val.push_back(s); + .filter(|n| groups!(self.0.app).any(|g| &&g.name == n)) + { + let g_string = self.0.args_in_group(g).join("|"); + let elem = format!("<{}>", &g_string[..g_string.len()]); + if !g_vec.contains(&elem) { + g_vec.push(elem); + } } - } - for a in desc_reqs - .iter() - .filter(|name| !p.positionals.values().any(|p| &&p.b.name == name)) - .filter(|name| !p.groups.iter().any(|g| &&g.name == name)) - .filter(|name| !args_in_groups.contains(name)) - .filter(|name| { - !(matcher.is_some() && matcher.as_ref().unwrap().contains(name)) - }) { - debugln!("usage::get_required_usage_from:iter:{}:", a); - let arg = find_by_name!(p, *a, flags, iter) - .map(|f| f.to_string()) - .unwrap_or_else(|| { - find_by_name!(p, *a, opts, iter) - .map(|o| o.to_string()) - .expect(INTERNAL_ERROR_MSG) - }); - ret_val.push_back(arg); - } - let mut g_vec: Vec = vec![]; - for g in desc_reqs - .iter() - .filter(|n| p.groups.iter().any(|g| &&g.name == n)) - { - let g_string = p.args_in_group(g).join("|"); - let elem = format!("<{}>", &g_string[..g_string.len()]); - if !g_vec.contains(&elem) { - g_vec.push(elem); + for g in g_vec { + ret_val.push_back(g); } - } - for g in g_vec { - ret_val.push_back(g); - } - ret_val + ret_val + } } diff --git a/src/app/validator.rs b/src/app/validator.rs index 9751321a149..3a1503b08a5 100644 --- a/src/app/validator.rs +++ b/src/app/validator.rs @@ -1,12 +1,11 @@ // std -use std::fmt::Display; #[allow(unused_imports)] use std::ascii::AsciiExt; // Internal use INTERNAL_ERROR_MSG; use INVALID_UTF8; -use args::{AnyArg, ArgMatcher, MatchedArg}; +use args::{ArgMatcher, MatchedArg, Arg}; use args::settings::ArgSettings; use errors::{Error, ErrorKind}; use errors::Result as ClapResult; @@ -14,15 +13,17 @@ use osstringext::OsStrExt2; use app::settings::AppSettings as AS; use app::parser::{ParseResult, Parser}; use fmt::{Colorizer, ColorizerOption}; -use app::usage; +use app::usage::Usage; -pub struct Validator<'a, 'b, 'z>(&'z mut Parser<'a, 'b>) +pub struct Validator<'a, 'b, 'c,'z>(&'z mut Parser<'a, 'b, 'c>) where 'a: 'b, - 'b: 'z; + 'b: 'c, + 'c: 'z; -impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { - pub fn new(p: &'z mut Parser<'a, 'b>) -> Self { Validator(p) } + +impl<'a, 'b, 'c,'z> Validator<'a, 'b, 'c, 'z> { + pub fn new(p: &'z mut Parser<'a, 'b, 'c>) -> Self { Validator(p) } pub fn validate( &mut self, @@ -36,23 +37,19 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { self.0.add_defaults(matcher)?; if let ParseResult::Opt(a) = needs_val_of { debugln!("Validator::validate: needs_val_of={:?}", a); - let o = self.0 - .opts - .iter() - .find(|o| o.b.name == a) - .expect(INTERNAL_ERROR_MSG); + let o = find!(self.0.app, a).expect(INTERNAL_ERROR_MSG); self.validate_required(matcher)?; reqs_validated = true; - let should_err = if let Some(v) = matcher.0.args.get(&*o.b.name) { - v.vals.is_empty() && !(o.v.min_vals.is_some() && o.v.min_vals.unwrap() == 0) + let should_err = if let Some(v) = matcher.0.args.get(&*o.name) { + v.vals.is_empty() && !(o.min_vals.is_some() && o.min_vals.unwrap() == 0) } else { true }; if should_err { return Err(Error::empty_value( o, - &*usage::create_error_usage(self.0, matcher, None), - self.0.color(), + &*Usage::new(self.0).create_error_usage(matcher, None), + self.0.app.color(), )); } } @@ -73,21 +70,19 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { self.validate_required(matcher)?; } self.validate_matched_args(matcher)?; - matcher.usage(usage::create_usage_with_title(self.0, &[])); + matcher.usage(Usage::new(self.0).create_usage_with_title(&[])); Ok(()) } - fn validate_arg_values( + fn validate_arg_values( &self, - arg: &A, + arg: &Arg, ma: &MatchedArg, matcher: &ArgMatcher<'a>, ) -> ClapResult<()> - where - A: AnyArg<'a, 'b> + Display, { - debugln!("Validator::validate_arg_values: arg={:?}", arg.name()); + debugln!("Validator::validate_arg_values: arg={:?}", arg.name); for val in &ma.vals { if self.0.is_set(AS::StrictUtf8) && val.to_str().is_none() { debugln!( @@ -95,11 +90,11 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { val ); return Err(Error::invalid_utf8( - &*usage::create_error_usage(self.0, matcher, None), - self.0.color(), + &*Usage::new(self.0).create_error_usage(matcher, None), + self.0.app.color(), )); } - if let Some(p_vals) = arg.possible_vals() { + if let Some(ref p_vals) = arg.possible_vals { debugln!("Validator::validate_arg_values: possible_vals={:?}", p_vals); let val_str = val.to_string_lossy(); let ok = if arg.is_set(ArgSettings::CaseInsensitive) { @@ -112,38 +107,38 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { val_str, p_vals, arg, - &*usage::create_error_usage(self.0, matcher, None), - self.0.color(), + &*Usage::new(self.0).create_error_usage(matcher, None), + self.0.app.color(), )); } } if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty_() - && matcher.contains(&*arg.name()) + && matcher.contains(&*arg.name) { debugln!("Validator::validate_arg_values: illegal empty val found"); return Err(Error::empty_value( arg, - &*usage::create_error_usage(self.0, matcher, None), - self.0.color(), + &*Usage::new(self.0).create_error_usage(matcher, None), + self.0.app.color(), )); } - if let Some(vtor) = arg.validator() { + if let Some(ref vtor) = arg.validator { debug!("Validator::validate_arg_values: checking validator..."); if let Err(e) = vtor(val.to_string_lossy().into_owned()) { sdebugln!("error"); - return Err(Error::value_validation(Some(arg), e, self.0.color())); + return Err(Error::value_validation(Some(arg), e, self.0.app.color())); } else { sdebugln!("good"); } } - if let Some(vtor) = arg.validator_os() { + if let Some(ref vtor) = arg.validator_os { debug!("Validator::validate_arg_values: checking validator_os..."); if let Err(e) = vtor(val) { sdebugln!("error"); return Err(Error::value_validation( Some(arg), (*e).to_string_lossy().to_string(), - self.0.color(), + self.0.app.color(), )); } else { sdebugln!("good"); @@ -153,44 +148,35 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { Ok(()) } - fn build_err(&self, name: &str, matcher: &ArgMatcher) -> ClapResult<()> { + fn build_err(&self, name: &str, matcher: &ArgMatcher<'a>) -> ClapResult<()> { debugln!("build_err!: name={}", name); - let mut c_with = find_from!(self.0, &name, blacklist, &matcher); + let mut c_with = find_from!(self.0.app, &name, blacklist, &matcher); c_with = c_with.or( - self.0.find_any_arg(&name).map_or(None, |aa| aa.blacklist()) + find!(self.0.app, name).map_or(None, |aa| aa.blacklist) .map_or(None, |bl| bl.iter().find(|arg| matcher.contains(arg))) - .map_or(None, |an| self.0.find_any_arg(an)) + .map_or(None, |an| find!(self.0.app, *an)) .map_or(None, |aa| Some(format!("{}", aa))) ); debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, &name); // matcher.remove(&name); - let usg = usage::create_error_usage(self.0, matcher, None); - if let Some(f) = find_by_name!(self.0, name, flags, iter) { + let usg = Usage::new(self.0).create_error_usage(matcher, None); + if let Some(f) = find!(self.0.app, name) { debugln!("build_err!: It was a flag..."); - Err(Error::argument_conflict(f, c_with, &*usg, self.0.color())) - } else if let Some(o) = find_by_name!(self.0, name, opts, iter) { - debugln!("build_err!: It was an option..."); - Err(Error::argument_conflict(o, c_with, &*usg, self.0.color())) + Err(Error::argument_conflict(f, c_with, &*usg, self.0.app.color())) } else { - match find_by_name!(self.0, name, positionals, values) { - Some(p) => { - debugln!("build_err!: It was a positional..."); - Err(Error::argument_conflict(p, c_with, &*usg, self.0.color())) - }, - None => panic!(INTERNAL_ERROR_MSG) - } + panic!(INTERNAL_ERROR_MSG); } } - fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { + fn validate_blacklist(&self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { debugln!("Validator::validate_blacklist;"); let mut conflicts: Vec<&str> = vec![]; for (&name, _) in matcher.iter() { debugln!("Validator::validate_blacklist:iter:{};", name); if let Some(grps) = self.0.groups_for_arg(name) { for grp in &grps { - if let Some(g) = self.0.groups.iter().find(|g| &g.name == grp) { + if let Some(g) = self.0.app.groups.iter().find(|g| &g.name == grp) { if !g.multiple { for arg in &g.args { if arg == &name { @@ -205,8 +191,8 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { } } } - if let Some(arg) = find_any_by_name!(self.0, name) { - if let Some(bl) = arg.blacklist() { + if let Some(arg) = find!(self.0.app, name) { + if let Some(bl) = arg.blacklist { for conf in bl { if matcher.get(conf).is_some() { conflicts.push(conf); @@ -218,7 +204,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { let args = self.0.arg_names_in_group(name); for arg in &args { debugln!("Validator::validate_blacklist:iter:{}:group:iter:{};", name, arg); - if let Some(bl) = find_any_by_name!(self.0, *arg).unwrap().blacklist() { + if let Some(bl) = find!(self.0.app, *arg).unwrap().blacklist { for conf in bl { if matcher.get(conf).is_some() { conflicts.push(conf); @@ -235,7 +221,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { name ); let mut should_err = false; - if self.0.groups.iter().any(|g| &g.name == name) { + if groups!(self.0.app).any(|g| &g.name == name) { debugln!( "Validator::validate_blacklist:iter:{}: groups contains it...", name @@ -277,25 +263,13 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { name, ma.vals ); - if let Some(opt) = find_by_name!(self.0, *name, opts, iter) { - self.validate_arg_num_vals(opt, ma, matcher)?; - self.validate_arg_values(opt, ma, matcher)?; - self.validate_arg_requires(opt, ma, matcher)?; - self.validate_arg_num_occurs(opt, ma, matcher)?; - } else if let Some(flag) = find_by_name!(self.0, *name, flags, iter) { - self.validate_arg_requires(flag, ma, matcher)?; - self.validate_arg_num_occurs(flag, ma, matcher)?; - } else if let Some(pos) = find_by_name!(self.0, *name, positionals, values) { - self.validate_arg_num_vals(pos, ma, matcher)?; - self.validate_arg_num_occurs(pos, ma, matcher)?; - self.validate_arg_values(pos, ma, matcher)?; - self.validate_arg_requires(pos, ma, matcher)?; + if let Some(arg) = find!(self.0.app, *name) { + self.validate_arg_num_vals(arg, ma, matcher)?; + self.validate_arg_values(arg, ma, matcher)?; + self.validate_arg_requires(arg, ma, matcher)?; + self.validate_arg_num_occurs(arg, ma, matcher)?; } else { - let grp = self.0 - .groups - .iter() - .find(|g| &g.name == name) - .expect(INTERNAL_ERROR_MSG); + let grp = find!(self.0.app, *name, groups).expect(INTERNAL_ERROR_MSG); if let Some(ref g_reqs) = grp.requires { if g_reqs.iter().any(|&n| !matcher.contains(n)) { return self.missing_required_error(matcher, None); @@ -306,38 +280,34 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { Ok(()) } - fn validate_arg_num_occurs( + fn validate_arg_num_occurs( &self, - a: &A, + a: &Arg, ma: &MatchedArg, - matcher: &ArgMatcher, + matcher: &ArgMatcher<'a>, ) -> ClapResult<()> - where - A: AnyArg<'a, 'b> + Display, { - debugln!("Validator::validate_arg_num_occurs: a={};", a.name()); + debugln!("Validator::validate_arg_num_occurs: a={};", a.name); if ma.occurs > 1 && !a.is_set(ArgSettings::Multiple) { // Not the first time, and we don't allow multiples return Err(Error::unexpected_multiple_usage( a, - &*usage::create_error_usage(self.0, matcher, None), - self.0.color(), + &*Usage::new(self.0).create_error_usage(matcher, None), + self.0.app.color(), )); } Ok(()) } - fn validate_arg_num_vals( + fn validate_arg_num_vals( &self, - a: &A, + a: &Arg, ma: &MatchedArg, - matcher: &ArgMatcher, + matcher: &ArgMatcher<'a>, ) -> ClapResult<()> - where - A: AnyArg<'a, 'b> + Display, { debugln!("Validator::validate_arg_num_vals;"); - if let Some(num) = a.num_vals() { + if let Some(num) = a.num_vals { debugln!("Validator::validate_arg_num_vals: num_vals set...{}", num); let should_err = if a.is_set(ArgSettings::Multiple) { ((ma.vals.len() as u64) % num) != 0 @@ -361,12 +331,12 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { } else { "ere" }, - &*usage::create_error_usage(self.0, matcher, None), - self.0.color(), + &*Usage::new(self.0).create_error_usage(matcher, None), + self.0.app.color(), )); } } - if let Some(num) = a.max_vals() { + if let Some(num) = a.max_vals { debugln!("Validator::validate_arg_num_vals: max_vals set...{}", num); if (ma.vals.len() as u64) > num { debugln!("Validator::validate_arg_num_vals: Sending error TooManyValues"); @@ -378,12 +348,12 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { .to_str() .expect(INVALID_UTF8), a, - &*usage::create_error_usage(self.0, matcher, None), - self.0.color(), + &*Usage::new(self.0).create_error_usage(matcher, None), + self.0.app.color(), )); } } - let min_vals_zero = if let Some(num) = a.min_vals() { + let min_vals_zero = if let Some(num) = a.min_vals { debugln!("Validator::validate_arg_num_vals: min_vals set: {}", num); if (ma.vals.len() as u64) < num && num != 0 { debugln!("Validator::validate_arg_num_vals: Sending error TooFewValues"); @@ -391,8 +361,8 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { a, num, ma.vals.len(), - &*usage::create_error_usage(self.0, matcher, None), - self.0.color(), + &*Usage::new(self.0).create_error_usage(matcher, None), + self.0.app.color(), )); } num == 0 @@ -401,27 +371,25 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { }; // Issue 665 (https://github.com/kbknapp/clap-rs/issues/665) // Issue 1105 (https://github.com/kbknapp/clap-rs/issues/1105) - if a.takes_value() && !min_vals_zero && ma.vals.is_empty() { + if a.is_set(ArgSettings::TakesValue) && !min_vals_zero && ma.vals.is_empty() { return Err(Error::empty_value( a, - &*usage::create_error_usage(self.0, matcher, None), - self.0.color(), + &*Usage::new(self.0).create_error_usage(matcher, None), + self.0.app.color(), )); } Ok(()) } - fn validate_arg_requires( + fn validate_arg_requires( &self, - a: &A, + a: &Arg, ma: &MatchedArg, - matcher: &ArgMatcher, + matcher: &ArgMatcher<'a>, ) -> ClapResult<()> - where - A: AnyArg<'a, 'b> + Display, { - debugln!("Validator::validate_arg_requires:{};", a.name()); - if let Some(a_reqs) = a.requires() { + debugln!("Validator::validate_arg_requires:{};", a.name); + if let Some(ref a_reqs) = a.requires { for &(val, name) in a_reqs.iter().filter(|&&(val, _)| val.is_some()) { let missing_req = |v| v == val.expect(INTERNAL_ERROR_MSG) && !matcher.contains(name); @@ -438,7 +406,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { Ok(()) } - fn validate_required(&self, matcher: &ArgMatcher) -> ClapResult<()> { + fn validate_required(&self, matcher: &ArgMatcher<'a>) -> ClapResult<()> { debugln!( "Validator::validate_required: required={:?};", self.0.required @@ -449,7 +417,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { if matcher.contains(name) { continue 'outer; } - if let Some(a) = find_any_by_name!(self.0, *name) { + if let Some(a) = find!(self.0.app, *name) { if self.is_missing_required_ok(a, matcher) { continue 'outer; } @@ -468,28 +436,25 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { Ok(()) } - fn validate_arg_conflicts(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option { - debugln!("Validator::validate_arg_conflicts: a={:?};", a.name()); - a.blacklist().map(|bl| { + fn validate_arg_conflicts(&self, a: &Arg, matcher: &ArgMatcher) -> Option { + debugln!("Validator::validate_arg_conflicts: a={:?};", a.name); + a.blacklist.map(|bl| { bl.iter().any(|conf| { matcher.contains(conf) - || self.0 - .groups - .iter() - .find(|g| &g.name == conf) + || find!(self.0.app, *conf, groups) .map_or(false, |g| g.args.iter().any(|arg| matcher.contains(arg))) }) }) } - fn validate_required_unless(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option { - debugln!("Validator::validate_required_unless: a={:?};", a.name()); + fn validate_required_unless(&self, a: &Arg, matcher: &ArgMatcher) -> Option { + debugln!("Validator::validate_required_unless: a={:?};", a.name); macro_rules! check { ($how:ident, $_self:expr, $a:ident, $m:ident) => {{ - $a.required_unless().map(|ru| { + $a.r_unless.map(|ru| { ru.iter().$how(|n| { $m.contains(n) || { - if let Some(grp) = $_self.groups.iter().find(|g| &g.name == n) { + if let Some(grp) = find!($_self.app, *n, groups) { grp.args.iter().any(|arg| $m.contains(arg)) } else { false @@ -506,11 +471,11 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { } } - fn missing_required_error(&self, matcher: &ArgMatcher, extra: Option<&str>) -> ClapResult<()> { + fn missing_required_error(&self, matcher: &ArgMatcher<'a>, extra: Option<&str>) -> ClapResult<()> { debugln!("Validator::missing_required_error: extra={:?}", extra); let c = Colorizer::new(ColorizerOption { use_stderr: true, - when: self.0.color(), + when: self.0.app.color(), }); let mut reqs = self.0.required.iter().map(|&r| &*r).collect::>(); if let Some(r) = extra { @@ -520,7 +485,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { reqs.dedup(); debugln!("Validator::missing_required_error: reqs={:#?}", reqs); let req_args = - usage::get_required_usage_from(self.0, &reqs[..], Some(matcher), extra, true) + Usage::new(self.0).get_required_usage_from(&reqs[..], Some(matcher), extra, true) .iter() .fold(String::new(), |acc, s| { acc + &format!("\n {}", c.error(s))[..] @@ -531,14 +496,14 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { ); Err(Error::missing_required_argument( &*req_args, - &*usage::create_error_usage(self.0, matcher, extra), - self.0.color(), + &*Usage::new(self.0).create_error_usage(matcher, extra), + self.0.app.color(), )) } #[inline] - fn is_missing_required_ok(&self, a: &AnyArg, matcher: &ArgMatcher) -> bool { - debugln!("Validator::is_missing_required_ok: a={}", a.name()); + fn is_missing_required_ok(&self, a: &Arg, matcher: &ArgMatcher) -> bool { + debugln!("Validator::is_missing_required_ok: a={}", a.name); self.validate_arg_conflicts(a, matcher).unwrap_or(false) || self.validate_required_unless(a, matcher).unwrap_or(false) } diff --git a/src/args/any_arg.rs b/src/args/any_arg.rs deleted file mode 100644 index eee52283328..00000000000 --- a/src/args/any_arg.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Std -use std::rc::Rc; -use std::fmt as std_fmt; -use std::ffi::{OsStr, OsString}; - -// Internal -use args::settings::ArgSettings; -use map::{self, VecMap}; -use INTERNAL_ERROR_MSG; - -#[doc(hidden)] -pub trait AnyArg<'n, 'e>: std_fmt::Display { - fn name(&self) -> &'n str; - fn overrides(&self) -> Option<&[&'e str]>; - fn aliases(&self) -> Option>; - fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]>; - fn blacklist(&self) -> Option<&[&'e str]>; - fn required_unless(&self) -> Option<&[&'e str]>; - fn is_set(&self, ArgSettings) -> bool; - fn set(&mut self, ArgSettings); - fn has_switch(&self) -> bool; - fn max_vals(&self) -> Option; - fn min_vals(&self) -> Option; - fn num_vals(&self) -> Option; - fn possible_vals(&self) -> Option<&[&'e str]>; - fn validator(&self) -> Option<&Rc Result<(), String>>>; - fn validator_os(&self) -> Option<&Rc Result<(), OsString>>>; - fn short(&self) -> Option; - fn long(&self) -> Option<&'e str>; - fn val_delim(&self) -> Option; - fn takes_value(&self) -> bool; - fn val_names(&self) -> Option<&VecMap<&'e str>>; - fn help(&self) -> Option<&'e str>; - fn long_help(&self) -> Option<&'e str>; - fn default_val(&self) -> Option<&'e OsStr>; - fn default_vals_ifs(&self) -> Option, &'e OsStr)>>; - fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)>; - fn longest_filter(&self) -> bool; - fn val_terminator(&self) -> Option<&'e str>; -} - -pub trait DispOrder { - fn disp_ord(&self) -> usize; -} - -impl<'n, 'e, 'z, T: ?Sized> AnyArg<'n, 'e> for &'z T where T: AnyArg<'n, 'e> + 'z { - fn name(&self) -> &'n str { (*self).name() } - fn overrides(&self) -> Option<&[&'e str]> { (*self).overrides() } - fn aliases(&self) -> Option> { (*self).aliases() } - fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { (*self).requires() } - fn blacklist(&self) -> Option<&[&'e str]> { (*self).blacklist() } - fn required_unless(&self) -> Option<&[&'e str]> { (*self).required_unless() } - fn is_set(&self, a: ArgSettings) -> bool { (*self).is_set(a) } - fn set(&mut self, _: ArgSettings) { panic!(INTERNAL_ERROR_MSG) } - fn has_switch(&self) -> bool { (*self).has_switch() } - fn max_vals(&self) -> Option { (*self).max_vals() } - fn min_vals(&self) -> Option { (*self).min_vals() } - fn num_vals(&self) -> Option { (*self).num_vals() } - fn possible_vals(&self) -> Option<&[&'e str]> { (*self).possible_vals() } - fn validator(&self) -> Option<&Rc Result<(), String>>> { (*self).validator() } - fn validator_os(&self) -> Option<&Rc Result<(), OsString>>> { (*self).validator_os() } - fn short(&self) -> Option { (*self).short() } - fn long(&self) -> Option<&'e str> { (*self).long() } - fn val_delim(&self) -> Option { (*self).val_delim() } - fn takes_value(&self) -> bool { (*self).takes_value() } - fn val_names(&self) -> Option<&VecMap<&'e str>> { (*self).val_names() } - fn help(&self) -> Option<&'e str> { (*self).help() } - fn long_help(&self) -> Option<&'e str> { (*self).long_help() } - fn default_val(&self) -> Option<&'e OsStr> { (*self).default_val() } - fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { (*self).default_vals_ifs() } - fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> { (*self).env() } - fn longest_filter(&self) -> bool { (*self).longest_filter() } - fn val_terminator(&self) -> Option<&'e str> { (*self).val_terminator() } -} diff --git a/src/args/arg.rs b/src/args/arg.rs index bdcd63a5ec4..520875e7115 100644 --- a/src/args/arg.rs +++ b/src/args/arg.rs @@ -1,20 +1,24 @@ #[cfg(feature = "yaml")] use std::collections::BTreeMap; use std::rc::Rc; +use std::borrow::Cow; +use std::fmt::{self, Display, Formatter}; use std::ffi::{OsStr, OsString}; #[cfg(target_os = "windows")] use osstringext::OsStrExt3; #[cfg(not(target_os = "windows"))] use std::os::unix::ffi::OsStrExt; use std::env; +use std::cmp::{Ord, Ordering}; #[cfg(feature = "yaml")] use yaml_rust::Yaml; use map::VecMap; use usage_parser::UsageParser; -use args::settings::ArgSettings; -use args::arg_builder::{Base, Switched, Valued}; +use args::settings::{ArgSettings, ArgFlags}; +use args::DispOrder; +use INTERNAL_ERROR_MSG; /// The abstract representation of a command line argument. Used to set all the options and /// relationships that define a valid argument for the program. @@ -44,9 +48,32 @@ pub struct Arg<'a, 'b> where 'a: 'b, { - #[doc(hidden)] pub b: Base<'a, 'b>, - #[doc(hidden)] pub s: Switched<'b>, - #[doc(hidden)] pub v: Valued<'a, 'b>, + pub name: &'a str, + pub help: Option<&'b str>, + pub long_help: Option<&'b str>, + pub blacklist: Option>, + pub settings: ArgFlags, + pub r_unless: Option>, + pub overrides: Option>, + pub groups: Option>, + pub requires: Option, &'a str)>>, + pub short: Option, + pub long: Option<&'b str>, + pub aliases: Option>, // (name, visible) + pub disp_ord: usize, + pub unified_ord: usize, + pub possible_vals: Option>, + pub val_names: Option>, + pub num_vals: Option, + pub max_vals: Option, + pub min_vals: Option, + pub validator: Option Result<(), String>>>, + pub validator_os: Option Result<(), OsString>>>, + pub val_delim: Option, + pub default_val: Option<&'b OsStr>, + pub default_vals_ifs: Option, &'b OsStr)>>, + pub env: Option<(&'a OsStr, Option)>, + pub terminator: Option<&'b str>, #[doc(hidden)] pub index: Option, #[doc(hidden)] pub r_ifs: Option>, } @@ -71,7 +98,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// [`Arg`]: ./struct.Arg.html pub fn with_name(n: &'a str) -> Self { Arg { - b: Base::new(n), + name: n, ..Default::default() } } @@ -328,7 +355,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// ``` /// [`short`]: ./struct.Arg.html#method.short pub fn short>(mut self, s: S) -> Self { - self.s.short = s.as_ref().trim_left_matches(|c| c == '-').chars().nth(0); + self.short = s.as_ref().trim_left_matches(|c| c == '-').chars().nth(0); self } @@ -368,7 +395,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// assert!(m.is_present("cfg")); /// ``` pub fn long(mut self, l: &'b str) -> Self { - self.s.long = Some(l.trim_left_matches(|c| c == '-')); + self.long = Some(l.trim_left_matches(|c| c == '-')); self } @@ -394,10 +421,10 @@ impl<'a, 'b> Arg<'a, 'b> { /// ``` /// [`Arg`]: ./struct.Arg.html pub fn alias>(mut self, name: S) -> Self { - if let Some(ref mut als) = self.s.aliases { + if let Some(ref mut als) = self.aliases { als.push((name.into(), false)); } else { - self.s.aliases = Some(vec![(name.into(), false)]); + self.aliases = Some(vec![(name.into(), false)]); } self } @@ -424,12 +451,12 @@ impl<'a, 'b> Arg<'a, 'b> { /// ``` /// [`Arg`]: ./struct.Arg.html pub fn aliases(mut self, names: &[&'b str]) -> Self { - if let Some(ref mut als) = self.s.aliases { + if let Some(ref mut als) = self.aliases { for n in names { als.push((n, false)); } } else { - self.s.aliases = Some(names.iter().map(|n| (*n, false)).collect::>()); + self.aliases = Some(names.iter().map(|n| (*n, false)).collect::>()); } self } @@ -455,10 +482,10 @@ impl<'a, 'b> Arg<'a, 'b> { /// [`Arg`]: ./struct.Arg.html /// [`App::alias`]: ./struct.Arg.html#method.alias pub fn visible_alias>(mut self, name: S) -> Self { - if let Some(ref mut als) = self.s.aliases { + if let Some(ref mut als) = self.aliases { als.push((name.into(), true)); } else { - self.s.aliases = Some(vec![(name.into(), true)]); + self.aliases = Some(vec![(name.into(), true)]); } self } @@ -482,12 +509,12 @@ impl<'a, 'b> Arg<'a, 'b> { /// [`Arg`]: ./struct.Arg.html /// [`App::aliases`]: ./struct.Arg.html#method.aliases pub fn visible_aliases(mut self, names: &[&'b str]) -> Self { - if let Some(ref mut als) = self.s.aliases { + if let Some(ref mut als) = self.aliases { for n in names { als.push((n, true)); } } else { - self.s.aliases = Some(names.iter().map(|n| (*n, true)).collect::>()); + self.aliases = Some(names.iter().map(|n| (*n, true)).collect::>()); } self } @@ -543,7 +570,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// ``` /// [`Arg::long_help`]: ./struct.Arg.html#method.long_help pub fn help(mut self, h: &'b str) -> Self { - self.b.help = Some(h); + self.help = Some(h); self } @@ -614,7 +641,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// ``` /// [`Arg::help`]: ./struct.Arg.html#method.help pub fn long_help(mut self, h: &'b str) -> Self { - self.b.long_help = Some(h); + self.long_help = Some(h); self } @@ -940,10 +967,10 @@ impl<'a, 'b> Arg<'a, 'b> { /// [`Arg::required`]: ./struct.Arg.html#method.required /// [`Arg::required_unless(name)`]: ./struct.Arg.html#method.required_unless pub fn required_unless(mut self, name: &'a str) -> Self { - if let Some(ref mut vec) = self.b.r_unless { + if let Some(ref mut vec) = self.r_unless { vec.push(name); } else { - self.b.r_unless = Some(vec![name]); + self.r_unless = Some(vec![name]); } self.required(true) } @@ -1012,12 +1039,12 @@ impl<'a, 'b> Arg<'a, 'b> { /// [`Arg::required_unless_one`]: ./struct.Arg.html#method.required_unless_one /// [`Arg::required_unless_all(names)`]: ./struct.Arg.html#method.required_unless_all pub fn required_unless_all(mut self, names: &[&'a str]) -> Self { - if let Some(ref mut vec) = self.b.r_unless { + if let Some(ref mut vec) = self.r_unless { for s in names { vec.push(s); } } else { - self.b.r_unless = Some(names.iter().map(|s| *s).collect::>()); + self.r_unless = Some(names.iter().map(|s| *s).collect::>()); } self.setb(ArgSettings::RequiredUnlessAll); self.required(true) @@ -1088,12 +1115,12 @@ impl<'a, 'b> Arg<'a, 'b> { /// [`Arg::required_unless_one(names)`]: ./struct.Arg.html#method.required_unless_one /// [`Arg::required_unless_all`]: ./struct.Arg.html#method.required_unless_all pub fn required_unless_one(mut self, names: &[&'a str]) -> Self { - if let Some(ref mut vec) = self.b.r_unless { + if let Some(ref mut vec) = self.r_unless { for s in names { vec.push(s); } } else { - self.b.r_unless = Some(names.iter().map(|s| *s).collect::>()); + self.r_unless = Some(names.iter().map(|s| *s).collect::>()); } self.required(true) } @@ -1136,10 +1163,10 @@ impl<'a, 'b> Arg<'a, 'b> { /// assert_eq!(res.unwrap_err().kind, ErrorKind::ArgumentConflict); /// ``` pub fn conflicts_with(mut self, name: &'a str) -> Self { - if let Some(ref mut vec) = self.b.blacklist { + if let Some(ref mut vec) = self.blacklist { vec.push(name); } else { - self.b.blacklist = Some(vec![name]); + self.blacklist = Some(vec![name]); } self } @@ -1186,12 +1213,12 @@ impl<'a, 'b> Arg<'a, 'b> { /// ``` /// [`Arg::conflicts_with`]: ./struct.Arg.html#method.conflicts_with pub fn conflicts_with_all(mut self, names: &[&'a str]) -> Self { - if let Some(ref mut vec) = self.b.blacklist { + if let Some(ref mut vec) = self.blacklist { for s in names { vec.push(s); } } else { - self.b.blacklist = Some(names.iter().map(|s| *s).collect::>()); + self.blacklist = Some(names.iter().map(|s| *s).collect::>()); } self } @@ -1223,10 +1250,10 @@ impl<'a, 'b> Arg<'a, 'b> { /// assert!(!m.is_present("flag")); /// ``` pub fn overrides_with(mut self, name: &'a str) -> Self { - if let Some(ref mut vec) = self.b.overrides { + if let Some(ref mut vec) = self.overrides { vec.push(name.as_ref()); } else { - self.b.overrides = Some(vec![name.as_ref()]); + self.overrides = Some(vec![name.as_ref()]); } self } @@ -1259,12 +1286,12 @@ impl<'a, 'b> Arg<'a, 'b> { /// assert!(!m.is_present("flag")); /// ``` pub fn overrides_with_all(mut self, names: &[&'a str]) -> Self { - if let Some(ref mut vec) = self.b.overrides { + if let Some(ref mut vec) = self.overrides { for s in names { vec.push(s); } } else { - self.b.overrides = Some(names.iter().map(|s| *s).collect::>()); + self.overrides = Some(names.iter().map(|s| *s).collect::>()); } self } @@ -1325,12 +1352,12 @@ impl<'a, 'b> Arg<'a, 'b> { /// [Conflicting]: ./struct.Arg.html#method.conflicts_with /// [override]: ./struct.Arg.html#method.overrides_with pub fn requires(mut self, name: &'a str) -> Self { - if let Some(ref mut vec) = self.b.requires { + if let Some(ref mut vec) = self.requires { vec.push((None, name)); } else { let mut vec = vec![]; vec.push((None, name)); - self.b.requires = Some(vec); + self.requires = Some(vec); } self } @@ -1395,10 +1422,10 @@ impl<'a, 'b> Arg<'a, 'b> { /// [Conflicting]: ./struct.Arg.html#method.conflicts_with /// [override]: ./struct.Arg.html#method.overrides_with pub fn requires_if(mut self, val: &'b str, arg: &'a str) -> Self { - if let Some(ref mut vec) = self.b.requires { + if let Some(ref mut vec) = self.requires { vec.push((Some(val), arg)); } else { - self.b.requires = Some(vec![(Some(val), arg)]); + self.requires = Some(vec![(Some(val), arg)]); } self } @@ -1455,7 +1482,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// [Conflicting]: ./struct.Arg.html#method.conflicts_with /// [override]: ./struct.Arg.html#method.overrides_with pub fn requires_ifs(mut self, ifs: &[(&'b str, &'a str)]) -> Self { - if let Some(ref mut vec) = self.b.requires { + if let Some(ref mut vec) = self.requires { for &(val, arg) in ifs { vec.push((Some(val), arg)); } @@ -1464,7 +1491,7 @@ impl<'a, 'b> Arg<'a, 'b> { for &(val, arg) in ifs { vec.push((Some(val), arg)); } - self.b.requires = Some(vec); + self.requires = Some(vec); } self } @@ -1699,7 +1726,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// [override]: ./struct.Arg.html#method.overrides_with /// [`Arg::requires_all(&[arg, arg2])`]: ./struct.Arg.html#method.requires_all pub fn requires_all(mut self, names: &[&'a str]) -> Self { - if let Some(ref mut vec) = self.b.requires { + if let Some(ref mut vec) = self.requires { for s in names { vec.push((None, s)); } @@ -1708,7 +1735,7 @@ impl<'a, 'b> Arg<'a, 'b> { for s in names { vec.push((None, *s)); } - self.b.requires = Some(vec); + self.requires = Some(vec); } self } @@ -2127,7 +2154,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// [`max_values`]: ./struct.Arg.html#method.max_values pub fn value_terminator(mut self, term: &'b str) -> Self { self.setb(ArgSettings::TakesValue); - self.v.terminator = Some(term); + self.terminator = Some(term); self } @@ -2319,12 +2346,12 @@ impl<'a, 'b> Arg<'a, 'b> { /// [options]: ./struct.Arg.html#method.takes_value /// [positional arguments]: ./struct.Arg.html#method.index pub fn possible_values(mut self, names: &[&'b str]) -> Self { - if let Some(ref mut vec) = self.v.possible_vals { + if let Some(ref mut vec) = self.possible_vals { for s in names { vec.push(s); } } else { - self.v.possible_vals = Some(names.iter().map(|s| *s).collect::>()); + self.possible_vals = Some(names.iter().map(|s| *s).collect::>()); } self } @@ -2383,10 +2410,10 @@ impl<'a, 'b> Arg<'a, 'b> { /// [options]: ./struct.Arg.html#method.takes_value /// [positional arguments]: ./struct.Arg.html#method.index pub fn possible_value(mut self, name: &'b str) -> Self { - if let Some(ref mut vec) = self.v.possible_vals { + if let Some(ref mut vec) = self.possible_vals { vec.push(name); } else { - self.v.possible_vals = Some(vec![name]); + self.possible_vals = Some(vec![name]); } self } @@ -2475,10 +2502,10 @@ impl<'a, 'b> Arg<'a, 'b> { /// ``` /// [`ArgGroup`]: ./struct.ArgGroup.html pub fn group(mut self, name: &'a str) -> Self { - if let Some(ref mut vec) = self.b.groups { + if let Some(ref mut vec) = self.groups { vec.push(name); } else { - self.b.groups = Some(vec![name]); + self.groups = Some(vec![name]); } self } @@ -2515,12 +2542,12 @@ impl<'a, 'b> Arg<'a, 'b> { /// ``` /// [`ArgGroup`]: ./struct.ArgGroup.html pub fn groups(mut self, names: &[&'a str]) -> Self { - if let Some(ref mut vec) = self.b.groups { + if let Some(ref mut vec) = self.groups { for s in names { vec.push(s); } } else { - self.b.groups = Some(names.into_iter().map(|s| *s).collect::>()); + self.groups = Some(names.into_iter().map(|s| *s).collect::>()); } self } @@ -2563,7 +2590,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple pub fn number_of_values(mut self, qty: u64) -> Self { self.setb(ArgSettings::TakesValue); - self.v.num_vals = Some(qty); + self.num_vals = Some(qty); self } @@ -2607,7 +2634,7 @@ impl<'a, 'b> Arg<'a, 'b> { where F: Fn(String) -> Result<(), String> + 'static, { - self.v.validator = Some(Rc::new(f)); + self.validator = Some(Rc::new(f)); self } @@ -2645,7 +2672,7 @@ impl<'a, 'b> Arg<'a, 'b> { where F: Fn(&OsStr) -> Result<(), OsString> + 'static, { - self.v.validator_os = Some(Rc::new(f)); + self.validator_os = Some(Rc::new(f)); self } @@ -2707,7 +2734,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple pub fn max_values(mut self, qty: u64) -> Self { self.setb(ArgSettings::TakesValue); - self.v.max_vals = Some(qty); + self.max_vals = Some(qty); self } @@ -2769,7 +2796,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// ``` /// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple pub fn min_values(mut self, qty: u64) -> Self { - self.v.min_vals = Some(qty); + self.min_vals = Some(qty); self.set(ArgSettings::TakesValue) } @@ -2821,14 +2848,14 @@ impl<'a, 'b> Arg<'a, 'b> { /// [`Arg::value_delimiter`]: ./struct.Arg.html#method.value_delimiter pub fn use_delimiter(mut self, d: bool) -> Self { if d { - if self.v.val_delim.is_none() { - self.v.val_delim = Some(','); + if self.val_delim.is_none() { + self.val_delim = Some(','); } self.setb(ArgSettings::TakesValue); self.setb(ArgSettings::UseValueDelimiter); self.unset(ArgSettings::ValueDelimiterNotSet) } else { - self.v.val_delim = None; + self.val_delim = None; self.unsetb(ArgSettings::UseValueDelimiter); self.unset(ArgSettings::ValueDelimiterNotSet) } @@ -2946,7 +2973,7 @@ impl<'a, 'b> Arg<'a, 'b> { self.unsetb(ArgSettings::ValueDelimiterNotSet); self.setb(ArgSettings::TakesValue); self.setb(ArgSettings::UseValueDelimiter); - self.v.val_delim = Some( + self.val_delim = Some( d.chars() .nth(0) .expect("Failed to get value_delimiter from arg"), @@ -3019,7 +3046,7 @@ impl<'a, 'b> Arg<'a, 'b> { self.unsetb(ArgSettings::ValueDelimiterNotSet); self.setb(ArgSettings::UseValueDelimiter); } - if let Some(ref mut vals) = self.v.val_names { + if let Some(ref mut vals) = self.val_names { let mut l = vals.len(); for s in names { vals.insert(l, s); @@ -3030,7 +3057,7 @@ impl<'a, 'b> Arg<'a, 'b> { for (i, n) in names.iter().enumerate() { vm.insert(i, *n); } - self.v.val_names = Some(vm); + self.val_names = Some(vm); } self } @@ -3083,13 +3110,13 @@ impl<'a, 'b> Arg<'a, 'b> { /// [`Arg::takes_value(true)`]: ./struct.Arg.html#method.takes_value pub fn value_name(mut self, name: &'b str) -> Self { self.setb(ArgSettings::TakesValue); - if let Some(ref mut vals) = self.v.val_names { + if let Some(ref mut vals) = self.val_names { let l = vals.len(); vals.insert(l, name); } else { let mut vm = VecMap::new(); vm.insert(0, name); - self.v.val_names = Some(vm); + self.val_names = Some(vm); } self } @@ -3167,7 +3194,7 @@ impl<'a, 'b> Arg<'a, 'b> { /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html pub fn default_value_os(mut self, val: &'a OsStr) -> Self { self.setb(ArgSettings::TakesValue); - self.v.default_val = Some(val); + self.default_val = Some(val); self } @@ -3286,13 +3313,13 @@ impl<'a, 'b> Arg<'a, 'b> { default: &'b OsStr, ) -> Self { self.setb(ArgSettings::TakesValue); - if let Some(ref mut vm) = self.v.default_vals_ifs { + if let Some(ref mut vm) = self.default_vals_ifs { let l = vm.len(); vm.insert(l, (arg, val, default)); } else { let mut vm = VecMap::new(); vm.insert(0, (arg, val, default)); - self.v.default_vals_ifs = Some(vm); + self.default_vals_ifs = Some(vm); } self } @@ -3511,12 +3538,12 @@ impl<'a, 'b> Arg<'a, 'b> { pub fn env_os(mut self, name: &'a OsStr) -> Self { self.setb(ArgSettings::TakesValue); - self.v.env = Some((name, env::var_os(name))); + self.env = Some((name, env::var_os(name))); self } /// @TODO @p2 @docs @release: write docs - pub fn hide_env_values(self, hide: bool) -> Self { + pub fn hide_env_values(self, hide: bool) -> Self { if hide { self.set(ArgSettings::HideEnvValues) } else { @@ -3634,13 +3661,13 @@ impl<'a, 'b> Arg<'a, 'b> { /// [positional arguments]: ./struct.Arg.html#method.index /// [index]: ./struct.Arg.html#method.index pub fn display_order(mut self, ord: usize) -> Self { - self.s.disp_ord = ord; + self.disp_ord = ord; self } /// Checks if one of the [`ArgSettings`] settings is set for the argument /// [`ArgSettings`]: ./enum.ArgSettings.html - pub fn is_set(&self, s: ArgSettings) -> bool { self.b.is_set(s) } + pub fn is_set(&self, s: ArgSettings) -> bool { self.is_set(s) } /// Sets one of the [`ArgSettings`] settings for the argument /// [`ArgSettings`]: ./enum.ArgSettings.html @@ -3657,24 +3684,378 @@ impl<'a, 'b> Arg<'a, 'b> { } #[doc(hidden)] - pub fn setb(&mut self, s: ArgSettings) { self.b.set(s); } + pub fn _build(&mut self) { + if self.index.is_some() || (self.short.is_none() && self.long.is_none()) { + if self.max_vals.is_some() || self.min_vals.is_some() + || (self.num_vals.is_some() && self.num_vals.unwrap() > 1) + { + self.setb(ArgSettings::Multiple); + } + } else if self.is_set(ArgSettings::TakesValue) { + if let Some(ref vec) = self.val_names { + if vec.len() > 1 { + self.num_vals = Some(vec.len() as u64); + } + } + } + } + + #[doc(hidden)] + pub fn setb(&mut self, s: ArgSettings) { self.set(s); } #[doc(hidden)] - pub fn unsetb(&mut self, s: ArgSettings) { self.b.unset(s); } + pub fn unsetb(&mut self, s: ArgSettings) { self.unset(s); } + + #[doc(hidden)] + pub fn has_switch(&self) -> bool { self.short.is_some() || self.long.is_some() } + + #[doc(hidden)] + pub fn longest_filter(&self) -> bool { + self.is_set(ArgSettings::TakesValue) || self.index.is_some() || self.long.is_some() + } + + // Used for positionals when printing + #[doc(hidden)] + pub fn multiple_str(&self) -> &str { + let mult_vals = self.val_names + .as_ref() + .map_or(true, |names| names.len() < 2); + if self.is_set(ArgSettings::Multiple) && mult_vals { + "..." + } else { + "" + } + } + + // Used for positionals when printing + #[doc(hidden)] + pub fn name_no_brackets(&self) -> Cow { + debugln!("PosBuilder::name_no_brackets;"); + let mut delim = String::new(); + delim.push(if self.is_set(ArgSettings::RequireDelimiter) { + self.val_delim.expect(INTERNAL_ERROR_MSG) + } else { + ' ' + }); + if let Some(ref names) = self.val_names { + debugln!("PosBuilder:name_no_brackets: val_names={:#?}", names); + if names.len() > 1 { + Cow::Owned( + names + .values() + .map(|n| format!("<{}>", n)) + .collect::>() + .join(&*delim), + ) + } else { + Cow::Borrowed(names.values().next().expect(INTERNAL_ERROR_MSG)) + } + } else { + debugln!("PosBuilder:name_no_brackets: just name"); + Cow::Borrowed(self.name) + } + } } impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for Arg<'a, 'b> { fn from(a: &'z Arg<'a, 'b>) -> Self { - Arg { - b: a.b.clone(), - v: a.v.clone(), - s: a.s.clone(), - index: a.index, - r_ifs: a.r_ifs.clone(), - } + a.clone() } } impl<'n, 'e> PartialEq for Arg<'n, 'e> { - fn eq(&self, other: &Arg<'n, 'e>) -> bool { self.b == other.b } + fn eq(&self, other: &Arg<'n, 'e>) -> bool { self.name == other.name } } + +impl<'n, 'e> Display for Arg<'n, 'e> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + if self.short.is_none() && self.long.is_none() { + // Positional + let mut delim = String::new(); + delim.push(if self.is_set(ArgSettings::RequireDelimiter) { + self.val_delim.expect(INTERNAL_ERROR_MSG) + } else { + ' ' + }); + if let Some(ref names) = self.val_names { + write!( + f, + "{}", + names + .values() + .map(|n| format!("<{}>", n)) + .collect::>() + .join(&*delim) + )?; + } else { + write!(f, "<{}>", self.name)?; + } + if self.settings.is_set(ArgSettings::Multiple) + && (self.val_names.is_none() || self.val_names.as_ref().unwrap().len() == 1) + { + write!(f, "...")?; + } + return Ok(()); + } + let sep = if self.is_set(ArgSettings::RequireEquals) { + "=" + } else { + " " + }; + // Write the name such --long or -l + if let Some(l) = self.long { + write!(f, "--{}{}", l, sep)?; + } else { + write!(f, "-{}{}", self.short.unwrap(), sep)?; + } + let delim = if self.is_set(ArgSettings::RequireDelimiter) { + self.val_delim.expect(INTERNAL_ERROR_MSG) + } else { + ' ' + }; + + // Write the values such as + if let Some(ref vec) = self.val_names { + let mut it = vec.iter().peekable(); + while let Some((_, val)) = it.next() { + write!(f, "<{}>", val)?; + if it.peek().is_some() { + write!(f, "{}", delim)?; + } + } + let num = vec.len(); + if self.is_set(ArgSettings::Multiple) && num == 1 { + write!(f, "...")?; + } + } else if let Some(num) = self.num_vals { + let mut it = (0..num).peekable(); + while let Some(_) = it.next() { + write!(f, "<{}>", self.name)?; + if it.peek().is_some() { + write!(f, "{}", delim)?; + } + } + if self.is_set(ArgSettings::Multiple) && num == 1 { + write!(f, "...")?; + } + } else { + write!( + f, + "<{}>{}", + self.name, + if self.is_set(ArgSettings::Multiple) { + "..." + } else { + "" + } + )?; + } + + Ok(()) + } +} + +impl<'n, 'e> PartialOrd for Arg<'n , 'e> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl<'n, 'e> Ord for Arg<'n, 'e> { + fn cmp(&self, other: &Arg) -> Ordering { + self.name.cmp(&other.name) + } +} + +impl<'n, 'e> Eq for Arg<'n, 'e> { } + +impl<'n, 'e> fmt::Debug for Arg<'n, 'e> { + fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + write!(f, "Arg {{ name: {:?}, help: {:?}, long_help: {:?}, conflicts_with: {:?}, \ + settings: {:?}, required_unless: {:?}, overrides_with: {:?}, groups: {:?}, \ + requires: {:?}, requires_ifs: {:?}, short: {:?}, index: {:?}, long: {:?}, \ + aliases: {:?}, possible_values: {:?}, value_names: {:?}, number_of_values: {:?}, \ + max_values: {:?}, min_values: {:?}, value_delimiter: {:?}, default_value_ifs: {:?}, \ + value_terminator: {:?}, display_order: {:?}, env: {:?}, unified_ord: {:?}, \ + default_value: {:?}, validator: {}, validator_os: {} \ + }}", + self.name, + self.help, + self.long_help, + self.blacklist, + self.settings, + self.r_unless, + self.overrides, + self.groups, + self.requires, + self.r_ifs, + self.short, + self.index, + self.long, + self.aliases, + self.possible_vals, + self.val_names, + self.num_vals, + self.max_vals, + self.min_vals, + self.val_delim, + self.default_vals_ifs, + self.terminator, + self.disp_ord, + self.env, + self.unified_ord, + self.default_val, + self.validator.as_ref().map_or("None", |_| "Some(Fn)"), + self.validator_os.as_ref().map_or("None", |_| "Some(Fn)") + ) + } +} + +impl<'n, 'e> DispOrder for Arg<'n, 'e> { + fn disp_ord(&self) -> usize { self.disp_ord } +} + +// Flags +#[cfg(test)] +mod test { + use map::VecMap; + use args::settings::ArgSettings; + use super::Arg; + + #[test] + fn flag_display() { + let mut f = Arg::with_name("flg"); + f.settings.set(ArgSettings::Multiple); + f.long = Some("flag"); + + assert_eq!(&*format!("{}", f), "--flag"); + + let mut f2 = Arg::new("flg"); + f2.short = Some('f'); + + assert_eq!(&*format!("{}", f2), "-f"); + } + + #[test] + fn flag_display_single_alias() { + let mut f = Arg::with_name("flg"); + f.long = Some("flag"); + f.aliases = Some(vec![("als", true)]); + + assert_eq!(&*format!("{}", f), "--flag"); + } + + #[test] + fn flag_display_multiple_aliases() { + let mut f = Arg::with_name("flg"); + f.short = Some('f'); + f.aliases = Some(vec![ + ("alias_not_visible", false), + ("f2", true), + ("f3", true), + ("f4", true), + ]); + assert_eq!(&*format!("{}", f), "-f"); + } + + // Options + + #[test] + fn option_display1() { + let mut o = Arg::with_name("opt"); + o.long = Some("option"); + o.settings.set(ArgSettings::Multiple); + + assert_eq!(&*format!("{}", o), "--option ..."); + } + + #[test] + fn option_display2() { + let mut v_names = VecMap::new(); + v_names.insert(0, "file"); + v_names.insert(1, "name"); + + let mut o2 = Arg::new("opt"); + o2.short = Some('o'); + o2.val_names = Some(v_names); + + assert_eq!(&*format!("{}", o2), "-o "); + } + + #[test] + fn option_display3() { + let mut v_names = VecMap::new(); + v_names.insert(0, "file"); + v_names.insert(1, "name"); + + let mut o2 = Arg::new("opt"); + o2.short = Some('o'); + o2.val_names = Some(v_names); + o2.settings.set(ArgSettings::Multiple); + + assert_eq!(&*format!("{}", o2), "-o "); + } + + #[test] + fn option_display_single_alias() { + let mut o = Arg::with_name("opt"); + o.long = Some("option"); + o.aliases = Some(vec![("als", true)]); + + assert_eq!(&*format!("{}", o), "--option "); + } + + #[test] + fn option_display_multiple_aliases() { + let mut o = Arg::with_name("opt"); + o.long = Some("option"); + o.aliases = Some(vec![ + ("als_not_visible", false), + ("als2", true), + ("als3", true), + ("als4", true), + ]); + assert_eq!(&*format!("{}", o), "--option "); + } + + // Positionals + + #[test] + fn positiona_display_mult() { + let mut p = Arg::with_name("pos", 1); + p.settings.set(ArgSettings::Multiple); + + assert_eq!(&*format!("{}", p), "..."); + } + + #[test] + fn positional_display_required() { + let mut p2 = Arg::with_name("pos", 1); + p2.settings.set(ArgSettings::Required); + + assert_eq!(&*format!("{}", p2), ""); + } + + #[test] + fn positional_display_val_names() { + let mut p2 = Arg::with_name("pos", 1); + let mut vm = VecMap::new(); + vm.insert(0, "file1"); + vm.insert(1, "file2"); + p2.val_names = Some(vm); + + assert_eq!(&*format!("{}", p2), " "); + } + + #[test] + fn positional_display_val_names_req() { + let mut p2 = Arg::with_name("pos", 1); + p2.settings.set(ArgSettings::Required); + let mut vm = VecMap::new(); + vm.insert(0, "file1"); + vm.insert(1, "file2"); + p2.val_names = Some(vm); + + assert_eq!(&*format!("{}", p2), " "); + } +} \ No newline at end of file diff --git a/src/args/arg_builder/base.rs b/src/args/arg_builder/base.rs deleted file mode 100644 index fef9d8ab979..00000000000 --- a/src/args/arg_builder/base.rs +++ /dev/null @@ -1,38 +0,0 @@ -use args::{Arg, ArgFlags, ArgSettings}; - -#[derive(Debug, Clone, Default)] -pub struct Base<'a, 'b> -where - 'a: 'b, -{ - pub name: &'a str, - pub help: Option<&'b str>, - pub long_help: Option<&'b str>, - pub blacklist: Option>, - pub settings: ArgFlags, - pub r_unless: Option>, - pub overrides: Option>, - pub groups: Option>, - pub requires: Option, &'a str)>>, -} - -impl<'n, 'e> Base<'n, 'e> { - pub fn new(name: &'n str) -> Self { - Base { - name: name, - ..Default::default() - } - } - - pub fn set(&mut self, s: ArgSettings) { self.settings.set(s); } - pub fn unset(&mut self, s: ArgSettings) { self.settings.unset(s); } - pub fn is_set(&self, s: ArgSettings) -> bool { self.settings.is_set(s) } -} - -impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Base<'n, 'e> { - fn from(a: &'z Arg<'n, 'e>) -> Self { a.b.clone() } -} - -impl<'n, 'e> PartialEq for Base<'n, 'e> { - fn eq(&self, other: &Base<'n, 'e>) -> bool { self.name == other.name } -} diff --git a/src/args/arg_builder/flag.rs b/src/args/arg_builder/flag.rs deleted file mode 100644 index 641e7777e00..00000000000 --- a/src/args/arg_builder/flag.rs +++ /dev/null @@ -1,159 +0,0 @@ -// Std -use std::convert::From; -use std::fmt::{Display, Formatter, Result}; -use std::rc::Rc; -use std::result::Result as StdResult; -use std::ffi::{OsStr, OsString}; -use std::mem; - -// Internal -use Arg; -use args::{AnyArg, ArgSettings, Base, DispOrder, Switched}; -use map::{self, VecMap}; - -#[derive(Default, Clone, Debug)] -#[doc(hidden)] -pub struct FlagBuilder<'n, 'e> -where - 'n: 'e, -{ - pub b: Base<'n, 'e>, - pub s: Switched<'e>, -} - -impl<'n, 'e> FlagBuilder<'n, 'e> { - pub fn new(name: &'n str) -> Self { - FlagBuilder { - b: Base::new(name), - ..Default::default() - } - } -} - -impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for FlagBuilder<'a, 'b> { - fn from(a: &'z Arg<'a, 'b>) -> Self { - FlagBuilder { - b: Base::from(a), - s: Switched::from(a), - } - } -} - -impl<'a, 'b> From> for FlagBuilder<'a, 'b> { - fn from(mut a: Arg<'a, 'b>) -> Self { - FlagBuilder { - b: mem::replace(&mut a.b, Base::default()), - s: mem::replace(&mut a.s, Switched::default()), - } - } -} - -impl<'n, 'e> Display for FlagBuilder<'n, 'e> { - fn fmt(&self, f: &mut Formatter) -> Result { - if let Some(l) = self.s.long { - write!(f, "--{}", l)?; - } else { - write!(f, "-{}", self.s.short.unwrap())?; - } - - Ok(()) - } -} - -impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> { - fn name(&self) -> &'n str { self.b.name } - fn overrides(&self) -> Option<&[&'e str]> { self.b.overrides.as_ref().map(|o| &o[..]) } - fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { - self.b.requires.as_ref().map(|o| &o[..]) - } - fn blacklist(&self) -> Option<&[&'e str]> { self.b.blacklist.as_ref().map(|o| &o[..]) } - fn required_unless(&self) -> Option<&[&'e str]> { self.b.r_unless.as_ref().map(|o| &o[..]) } - fn is_set(&self, s: ArgSettings) -> bool { self.b.settings.is_set(s) } - fn has_switch(&self) -> bool { true } - fn takes_value(&self) -> bool { false } - fn set(&mut self, s: ArgSettings) { self.b.settings.set(s) } - fn max_vals(&self) -> Option { None } - fn val_names(&self) -> Option<&VecMap<&'e str>> { None } - fn num_vals(&self) -> Option { None } - fn possible_vals(&self) -> Option<&[&'e str]> { None } - fn validator(&self) -> Option<&Rc StdResult<(), String>>> { None } - fn validator_os(&self) -> Option<&Rc StdResult<(), OsString>>> { None } - fn min_vals(&self) -> Option { None } - fn short(&self) -> Option { self.s.short } - fn long(&self) -> Option<&'e str> { self.s.long } - fn val_delim(&self) -> Option { None } - fn help(&self) -> Option<&'e str> { self.b.help } - fn long_help(&self) -> Option<&'e str> { self.b.long_help } - fn val_terminator(&self) -> Option<&'e str> { None } - fn default_val(&self) -> Option<&'e OsStr> { None } - fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { - None - } - fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> { None } - fn longest_filter(&self) -> bool { self.s.long.is_some() } - fn aliases(&self) -> Option> { - if let Some(ref aliases) = self.s.aliases { - let vis_aliases: Vec<_> = aliases - .iter() - .filter_map(|&(n, v)| if v { Some(n) } else { None }) - .collect(); - if vis_aliases.is_empty() { - None - } else { - Some(vis_aliases) - } - } else { - None - } - } -} - -impl<'n, 'e> DispOrder for FlagBuilder<'n, 'e> { - fn disp_ord(&self) -> usize { self.s.disp_ord } -} - -impl<'n, 'e> PartialEq for FlagBuilder<'n, 'e> { - fn eq(&self, other: &FlagBuilder<'n, 'e>) -> bool { self.b == other.b } -} - -#[cfg(test)] -mod test { - use args::settings::ArgSettings; - use super::FlagBuilder; - - #[test] - fn flagbuilder_display() { - let mut f = FlagBuilder::new("flg"); - f.b.settings.set(ArgSettings::Multiple); - f.s.long = Some("flag"); - - assert_eq!(&*format!("{}", f), "--flag"); - - let mut f2 = FlagBuilder::new("flg"); - f2.s.short = Some('f'); - - assert_eq!(&*format!("{}", f2), "-f"); - } - - #[test] - fn flagbuilder_display_single_alias() { - let mut f = FlagBuilder::new("flg"); - f.s.long = Some("flag"); - f.s.aliases = Some(vec![("als", true)]); - - assert_eq!(&*format!("{}", f), "--flag"); - } - - #[test] - fn flagbuilder_display_multiple_aliases() { - let mut f = FlagBuilder::new("flg"); - f.s.short = Some('f'); - f.s.aliases = Some(vec![ - ("alias_not_visible", false), - ("f2", true), - ("f3", true), - ("f4", true), - ]); - assert_eq!(&*format!("{}", f), "-f"); - } -} diff --git a/src/args/arg_builder/mod.rs b/src/args/arg_builder/mod.rs deleted file mode 100644 index d1a7a660866..00000000000 --- a/src/args/arg_builder/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub use self::flag::FlagBuilder; -pub use self::option::OptBuilder; -pub use self::positional::PosBuilder; -pub use self::base::Base; -pub use self::switched::Switched; -pub use self::valued::Valued; - -mod flag; -mod positional; -mod option; -mod base; -mod valued; -mod switched; diff --git a/src/args/arg_builder/option.rs b/src/args/arg_builder/option.rs deleted file mode 100644 index 4bb147a7d2d..00000000000 --- a/src/args/arg_builder/option.rs +++ /dev/null @@ -1,244 +0,0 @@ -// Std -use std::fmt::{Display, Formatter, Result}; -use std::rc::Rc; -use std::result::Result as StdResult; -use std::ffi::{OsStr, OsString}; -use std::mem; - -// Internal -use args::{AnyArg, Arg, ArgSettings, Base, DispOrder, Switched, Valued}; -use map::{self, VecMap}; -use INTERNAL_ERROR_MSG; - -#[allow(missing_debug_implementations)] -#[doc(hidden)] -#[derive(Default, Clone)] -pub struct OptBuilder<'n, 'e> -where - 'n: 'e, -{ - pub b: Base<'n, 'e>, - pub s: Switched<'e>, - pub v: Valued<'n, 'e>, -} - -impl<'n, 'e> OptBuilder<'n, 'e> { - pub fn new(name: &'n str) -> Self { - OptBuilder { - b: Base::new(name), - ..Default::default() - } - } -} - -impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for OptBuilder<'n, 'e> { - fn from(a: &'z Arg<'n, 'e>) -> Self { - OptBuilder { - b: Base::from(a), - s: Switched::from(a), - v: Valued::from(a), - } - } -} - -impl<'n, 'e> From> for OptBuilder<'n, 'e> { - fn from(mut a: Arg<'n, 'e>) -> Self { - a.v.fill_in(); - OptBuilder { - b: mem::replace(&mut a.b, Base::default()), - s: mem::replace(&mut a.s, Switched::default()), - v: mem::replace(&mut a.v, Valued::default()), - } - } -} - -impl<'n, 'e> Display for OptBuilder<'n, 'e> { - fn fmt(&self, f: &mut Formatter) -> Result { - debugln!("OptBuilder::fmt:{}", self.b.name); - let sep = if self.b.is_set(ArgSettings::RequireEquals) { - "=" - } else { - " " - }; - // Write the name such --long or -l - if let Some(l) = self.s.long { - write!(f, "--{}{}", l, sep)?; - } else { - write!(f, "-{}{}", self.s.short.unwrap(), sep)?; - } - let delim = if self.is_set(ArgSettings::RequireDelimiter) { - self.v.val_delim.expect(INTERNAL_ERROR_MSG) - } else { - ' ' - }; - - // Write the values such as - if let Some(ref vec) = self.v.val_names { - let mut it = vec.iter().peekable(); - while let Some((_, val)) = it.next() { - write!(f, "<{}>", val)?; - if it.peek().is_some() { - write!(f, "{}", delim)?; - } - } - let num = vec.len(); - if self.is_set(ArgSettings::Multiple) && num == 1 { - write!(f, "...")?; - } - } else if let Some(num) = self.v.num_vals { - let mut it = (0..num).peekable(); - while let Some(_) = it.next() { - write!(f, "<{}>", self.b.name)?; - if it.peek().is_some() { - write!(f, "{}", delim)?; - } - } - if self.is_set(ArgSettings::Multiple) && num == 1 { - write!(f, "...")?; - } - } else { - write!( - f, - "<{}>{}", - self.b.name, - if self.is_set(ArgSettings::Multiple) { - "..." - } else { - "" - } - )?; - } - - Ok(()) - } -} - -impl<'n, 'e> AnyArg<'n, 'e> for OptBuilder<'n, 'e> { - fn name(&self) -> &'n str { self.b.name } - fn overrides(&self) -> Option<&[&'e str]> { self.b.overrides.as_ref().map(|o| &o[..]) } - fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { - self.b.requires.as_ref().map(|o| &o[..]) - } - fn blacklist(&self) -> Option<&[&'e str]> { self.b.blacklist.as_ref().map(|o| &o[..]) } - fn required_unless(&self) -> Option<&[&'e str]> { self.b.r_unless.as_ref().map(|o| &o[..]) } - fn val_names(&self) -> Option<&VecMap<&'e str>> { self.v.val_names.as_ref() } - fn is_set(&self, s: ArgSettings) -> bool { self.b.settings.is_set(s) } - fn has_switch(&self) -> bool { true } - fn set(&mut self, s: ArgSettings) { self.b.settings.set(s) } - fn max_vals(&self) -> Option { self.v.max_vals } - fn val_terminator(&self) -> Option<&'e str> { self.v.terminator } - fn num_vals(&self) -> Option { self.v.num_vals } - fn possible_vals(&self) -> Option<&[&'e str]> { self.v.possible_vals.as_ref().map(|o| &o[..]) } - fn validator(&self) -> Option<&Rc StdResult<(), String>>> { - self.v.validator.as_ref() - } - fn validator_os(&self) -> Option<&Rc StdResult<(), OsString>>> { - self.v.validator_os.as_ref() - } - fn min_vals(&self) -> Option { self.v.min_vals } - fn short(&self) -> Option { self.s.short } - fn long(&self) -> Option<&'e str> { self.s.long } - fn val_delim(&self) -> Option { self.v.val_delim } - fn takes_value(&self) -> bool { true } - fn help(&self) -> Option<&'e str> { self.b.help } - fn long_help(&self) -> Option<&'e str> { self.b.long_help } - fn default_val(&self) -> Option<&'e OsStr> { self.v.default_val } - fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { - self.v.default_vals_ifs.as_ref().map(|vm| vm.values()) - } - fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> { - self.v - .env - .as_ref() - .map(|&(key, ref value)| (key, value.as_ref())) - } - fn longest_filter(&self) -> bool { true } - fn aliases(&self) -> Option> { - if let Some(ref aliases) = self.s.aliases { - let vis_aliases: Vec<_> = aliases - .iter() - .filter_map(|&(n, v)| if v { Some(n) } else { None }) - .collect(); - if vis_aliases.is_empty() { - None - } else { - Some(vis_aliases) - } - } else { - None - } - } -} - -impl<'n, 'e> DispOrder for OptBuilder<'n, 'e> { - fn disp_ord(&self) -> usize { self.s.disp_ord } -} - -impl<'n, 'e> PartialEq for OptBuilder<'n, 'e> { - fn eq(&self, other: &OptBuilder<'n, 'e>) -> bool { self.b == other.b } -} - -#[cfg(test)] -mod test { - use args::settings::ArgSettings; - use super::OptBuilder; - use map::VecMap; - - #[test] - fn optbuilder_display1() { - let mut o = OptBuilder::new("opt"); - o.s.long = Some("option"); - o.b.settings.set(ArgSettings::Multiple); - - assert_eq!(&*format!("{}", o), "--option ..."); - } - - #[test] - fn optbuilder_display2() { - let mut v_names = VecMap::new(); - v_names.insert(0, "file"); - v_names.insert(1, "name"); - - let mut o2 = OptBuilder::new("opt"); - o2.s.short = Some('o'); - o2.v.val_names = Some(v_names); - - assert_eq!(&*format!("{}", o2), "-o "); - } - - #[test] - fn optbuilder_display3() { - let mut v_names = VecMap::new(); - v_names.insert(0, "file"); - v_names.insert(1, "name"); - - let mut o2 = OptBuilder::new("opt"); - o2.s.short = Some('o'); - o2.v.val_names = Some(v_names); - o2.b.settings.set(ArgSettings::Multiple); - - assert_eq!(&*format!("{}", o2), "-o "); - } - - #[test] - fn optbuilder_display_single_alias() { - let mut o = OptBuilder::new("opt"); - o.s.long = Some("option"); - o.s.aliases = Some(vec![("als", true)]); - - assert_eq!(&*format!("{}", o), "--option "); - } - - #[test] - fn optbuilder_display_multiple_aliases() { - let mut o = OptBuilder::new("opt"); - o.s.long = Some("option"); - o.s.aliases = Some(vec![ - ("als_not_visible", false), - ("als2", true), - ("als3", true), - ("als4", true), - ]); - assert_eq!(&*format!("{}", o), "--option "); - } -} diff --git a/src/args/arg_builder/positional.rs b/src/args/arg_builder/positional.rs deleted file mode 100644 index 43fdca4c580..00000000000 --- a/src/args/arg_builder/positional.rs +++ /dev/null @@ -1,229 +0,0 @@ -// Std -use std::borrow::Cow; -use std::fmt::{Display, Formatter, Result}; -use std::rc::Rc; -use std::result::Result as StdResult; -use std::ffi::{OsStr, OsString}; -use std::mem; - -// Internal -use Arg; -use args::{AnyArg, ArgSettings, Base, DispOrder, Valued}; -use INTERNAL_ERROR_MSG; -use map::{self, VecMap}; - -#[allow(missing_debug_implementations)] -#[doc(hidden)] -#[derive(Clone, Default)] -pub struct PosBuilder<'n, 'e> -where - 'n: 'e, -{ - pub b: Base<'n, 'e>, - pub v: Valued<'n, 'e>, - pub index: u64, -} - -impl<'n, 'e> PosBuilder<'n, 'e> { - pub fn new(name: &'n str, idx: u64) -> Self { - PosBuilder { - b: Base::new(name), - index: idx, - ..Default::default() - } - } - - pub fn from_arg_ref(a: &Arg<'n, 'e>, idx: u64) -> Self { - let mut pb = PosBuilder { - b: Base::from(a), - v: Valued::from(a), - index: idx, - }; - if a.v.max_vals.is_some() || a.v.min_vals.is_some() - || (a.v.num_vals.is_some() && a.v.num_vals.unwrap() > 1) - { - pb.b.settings.set(ArgSettings::Multiple); - } - pb - } - - pub fn from_arg(mut a: Arg<'n, 'e>, idx: u64) -> Self { - if a.v.max_vals.is_some() || a.v.min_vals.is_some() - || (a.v.num_vals.is_some() && a.v.num_vals.unwrap() > 1) - { - a.b.settings.set(ArgSettings::Multiple); - } - PosBuilder { - b: mem::replace(&mut a.b, Base::default()), - v: mem::replace(&mut a.v, Valued::default()), - index: idx, - } - } - - pub fn multiple_str(&self) -> &str { - let mult_vals = self.v - .val_names - .as_ref() - .map_or(true, |names| names.len() < 2); - if self.is_set(ArgSettings::Multiple) && mult_vals { - "..." - } else { - "" - } - } - - pub fn name_no_brackets(&self) -> Cow { - debugln!("PosBuilder::name_no_brackets;"); - let mut delim = String::new(); - delim.push(if self.is_set(ArgSettings::RequireDelimiter) { - self.v.val_delim.expect(INTERNAL_ERROR_MSG) - } else { - ' ' - }); - if let Some(ref names) = self.v.val_names { - debugln!("PosBuilder:name_no_brackets: val_names={:#?}", names); - if names.len() > 1 { - Cow::Owned( - names - .values() - .map(|n| format!("<{}>", n)) - .collect::>() - .join(&*delim), - ) - } else { - Cow::Borrowed(names.values().next().expect(INTERNAL_ERROR_MSG)) - } - } else { - debugln!("PosBuilder:name_no_brackets: just name"); - Cow::Borrowed(self.b.name) - } - } -} - -impl<'n, 'e> Display for PosBuilder<'n, 'e> { - fn fmt(&self, f: &mut Formatter) -> Result { - let mut delim = String::new(); - delim.push(if self.is_set(ArgSettings::RequireDelimiter) { - self.v.val_delim.expect(INTERNAL_ERROR_MSG) - } else { - ' ' - }); - if let Some(ref names) = self.v.val_names { - write!( - f, - "{}", - names - .values() - .map(|n| format!("<{}>", n)) - .collect::>() - .join(&*delim) - )?; - } else { - write!(f, "<{}>", self.b.name)?; - } - if self.b.settings.is_set(ArgSettings::Multiple) - && (self.v.val_names.is_none() || self.v.val_names.as_ref().unwrap().len() == 1) - { - write!(f, "...")?; - } - - Ok(()) - } -} - -impl<'n, 'e> AnyArg<'n, 'e> for PosBuilder<'n, 'e> { - fn name(&self) -> &'n str { self.b.name } - fn overrides(&self) -> Option<&[&'e str]> { self.b.overrides.as_ref().map(|o| &o[..]) } - fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { - self.b.requires.as_ref().map(|o| &o[..]) - } - fn blacklist(&self) -> Option<&[&'e str]> { self.b.blacklist.as_ref().map(|o| &o[..]) } - fn required_unless(&self) -> Option<&[&'e str]> { self.b.r_unless.as_ref().map(|o| &o[..]) } - fn val_names(&self) -> Option<&VecMap<&'e str>> { self.v.val_names.as_ref() } - fn is_set(&self, s: ArgSettings) -> bool { self.b.settings.is_set(s) } - fn set(&mut self, s: ArgSettings) { self.b.settings.set(s) } - fn has_switch(&self) -> bool { false } - fn max_vals(&self) -> Option { self.v.max_vals } - fn val_terminator(&self) -> Option<&'e str> { self.v.terminator } - fn num_vals(&self) -> Option { self.v.num_vals } - fn possible_vals(&self) -> Option<&[&'e str]> { self.v.possible_vals.as_ref().map(|o| &o[..]) } - fn validator(&self) -> Option<&Rc StdResult<(), String>>> { - self.v.validator.as_ref() - } - fn validator_os(&self) -> Option<&Rc StdResult<(), OsString>>> { - self.v.validator_os.as_ref() - } - fn min_vals(&self) -> Option { self.v.min_vals } - fn short(&self) -> Option { None } - fn long(&self) -> Option<&'e str> { None } - fn val_delim(&self) -> Option { self.v.val_delim } - fn takes_value(&self) -> bool { true } - fn help(&self) -> Option<&'e str> { self.b.help } - fn long_help(&self) -> Option<&'e str> { self.b.long_help } - fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { - self.v.default_vals_ifs.as_ref().map(|vm| vm.values()) - } - fn default_val(&self) -> Option<&'e OsStr> { self.v.default_val } - fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> { - self.v - .env - .as_ref() - .map(|&(key, ref value)| (key, value.as_ref())) - } - fn longest_filter(&self) -> bool { true } - fn aliases(&self) -> Option> { None } -} - -impl<'n, 'e> DispOrder for PosBuilder<'n, 'e> { - fn disp_ord(&self) -> usize { self.index as usize } -} - -impl<'n, 'e> PartialEq for PosBuilder<'n, 'e> { - fn eq(&self, other: &PosBuilder<'n, 'e>) -> bool { self.b == other.b } -} - -#[cfg(test)] -mod test { - use args::settings::ArgSettings; - use super::PosBuilder; - use map::VecMap; - - #[test] - fn display_mult() { - let mut p = PosBuilder::new("pos", 1); - p.b.settings.set(ArgSettings::Multiple); - - assert_eq!(&*format!("{}", p), "..."); - } - - #[test] - fn display_required() { - let mut p2 = PosBuilder::new("pos", 1); - p2.b.settings.set(ArgSettings::Required); - - assert_eq!(&*format!("{}", p2), ""); - } - - #[test] - fn display_val_names() { - let mut p2 = PosBuilder::new("pos", 1); - let mut vm = VecMap::new(); - vm.insert(0, "file1"); - vm.insert(1, "file2"); - p2.v.val_names = Some(vm); - - assert_eq!(&*format!("{}", p2), " "); - } - - #[test] - fn display_val_names_req() { - let mut p2 = PosBuilder::new("pos", 1); - p2.b.settings.set(ArgSettings::Required); - let mut vm = VecMap::new(); - vm.insert(0, "file1"); - vm.insert(1, "file2"); - p2.v.val_names = Some(vm); - - assert_eq!(&*format!("{}", p2), " "); - } -} diff --git a/src/args/arg_builder/switched.rs b/src/args/arg_builder/switched.rs deleted file mode 100644 index 224b2f2b24b..00000000000 --- a/src/args/arg_builder/switched.rs +++ /dev/null @@ -1,38 +0,0 @@ -use Arg; - -#[derive(Debug)] -pub struct Switched<'b> { - pub short: Option, - pub long: Option<&'b str>, - pub aliases: Option>, // (name, visible) - pub disp_ord: usize, - pub unified_ord: usize, -} - -impl<'e> Default for Switched<'e> { - fn default() -> Self { - Switched { - short: None, - long: None, - aliases: None, - disp_ord: 999, - unified_ord: 999, - } - } -} - -impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Switched<'e> { - fn from(a: &'z Arg<'n, 'e>) -> Self { a.s.clone() } -} - -impl<'e> Clone for Switched<'e> { - fn clone(&self) -> Self { - Switched { - short: self.short, - long: self.long, - aliases: self.aliases.clone(), - disp_ord: self.disp_ord, - unified_ord: self.unified_ord, - } - } -} diff --git a/src/args/arg_builder/valued.rs b/src/args/arg_builder/valued.rs deleted file mode 100644 index d70854dc89a..00000000000 --- a/src/args/arg_builder/valued.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::rc::Rc; -use std::ffi::{OsStr, OsString}; - -use map::VecMap; - -use Arg; - -#[allow(missing_debug_implementations)] -#[derive(Clone)] -pub struct Valued<'a, 'b> -where - 'a: 'b, -{ - pub possible_vals: Option>, - pub val_names: Option>, - pub num_vals: Option, - pub max_vals: Option, - pub min_vals: Option, - pub validator: Option Result<(), String>>>, - pub validator_os: Option Result<(), OsString>>>, - pub val_delim: Option, - pub default_val: Option<&'b OsStr>, - pub default_vals_ifs: Option, &'b OsStr)>>, - pub env: Option<(&'a OsStr, Option)>, - pub terminator: Option<&'b str>, -} - -impl<'n, 'e> Default for Valued<'n, 'e> { - fn default() -> Self { - Valued { - possible_vals: None, - num_vals: None, - min_vals: None, - max_vals: None, - val_names: None, - validator: None, - validator_os: None, - val_delim: None, - default_val: None, - default_vals_ifs: None, - env: None, - terminator: None, - } - } -} - -impl<'n, 'e> Valued<'n, 'e> { - pub fn fill_in(&mut self) { - if let Some(ref vec) = self.val_names { - if vec.len() > 1 { - self.num_vals = Some(vec.len() as u64); - } - } - } -} - -impl<'n, 'e, 'z> From<&'z Arg<'n, 'e>> for Valued<'n, 'e> { - fn from(a: &'z Arg<'n, 'e>) -> Self { - let mut v = a.v.clone(); - if let Some(ref vec) = a.v.val_names { - if vec.len() > 1 { - v.num_vals = Some(vec.len() as u64); - } - } - v - } -} diff --git a/src/args/arg_matcher.rs b/src/args/arg_matcher.rs index 25f3fc5d749..3c9d5b02917 100644 --- a/src/args/arg_matcher.rs +++ b/src/args/arg_matcher.rs @@ -6,8 +6,7 @@ use std::ops::Deref; use std::mem; // Internal -use args::{ArgMatches, MatchedArg, SubCommand}; -use args::AnyArg; +use args::{Arg, ArgMatches, MatchedArg, SubCommand}; use args::settings::ArgSettings; #[doc(hidden)] @@ -21,10 +20,10 @@ impl<'a> Default for ArgMatcher<'a> { impl<'a> ArgMatcher<'a> { pub fn new() -> Self { ArgMatcher::default() } - pub fn process_arg_overrides<'b>(&mut self, a: Option<&AnyArg<'a, 'b>>, overrides: &mut Vec<(&'b str, &'a str)>, required: &mut Vec<&'a str>) { - debugln!("ArgMatcher::process_arg_overrides:{:?};", a.map_or(None, |a| Some(a.name()))); + pub fn process_arg_overrides<'b>(&mut self, a: Option<&Arg<'a, 'b>>, overrides: &mut Vec<(&'b str, &'a str)>, required: &mut Vec<&'a str>) { + debugln!("ArgMatcher::process_arg_overrides:{:?};", a.map_or(None, |a| Some(a.name))); if let Some(aa) = a { - if let Some(a_overrides) = aa.overrides() { + if let Some(ref a_overrides) = aa.overrides { for overr in a_overrides { debugln!("ArgMatcher::process_arg_overrides:iter:{};", overr); if self.is_present(overr) { @@ -38,7 +37,7 @@ impl<'a> ArgMatcher<'a> { } } } else { - overrides.push((overr, aa.name())); + overrides.push((overr, aa.name)); } } } @@ -147,23 +146,21 @@ impl<'a> ArgMatcher<'a> { ma.vals.push(val.to_owned()); } - pub fn needs_more_vals<'b, A>(&self, o: &A) -> bool - where - A: AnyArg<'a, 'b>, + pub fn needs_more_vals<'b>(&self, o: &Arg) -> bool { - debugln!("ArgMatcher::needs_more_vals: o={}", o.name()); - if let Some(ma) = self.get(o.name()) { - if let Some(num) = o.num_vals() { + debugln!("ArgMatcher::needs_more_vals: o={}", o.name); + if let Some(ma) = self.get(o.name) { + if let Some(num) = o.num_vals { debugln!("ArgMatcher::needs_more_vals: num_vals...{}", num); return if o.is_set(ArgSettings::Multiple) { ((ma.vals.len() as u64) % num) != 0 } else { num != (ma.vals.len() as u64) }; - } else if let Some(num) = o.max_vals() { + } else if let Some(num) = o.max_vals { debugln!("ArgMatcher::needs_more_vals: max_vals...{}", num); return !((ma.vals.len() as u64) > num); - } else if o.min_vals().is_some() { + } else if o.min_vals.is_some() { debugln!("ArgMatcher::needs_more_vals: min_vals...true"); return true; } diff --git a/src/args/mod.rs b/src/args/mod.rs index 21f9b850d5f..1ab4b856239 100644 --- a/src/args/mod.rs +++ b/src/args/mod.rs @@ -1,6 +1,4 @@ -pub use self::any_arg::{AnyArg, DispOrder}; pub use self::arg::Arg; -pub use self::arg_builder::{Base, FlagBuilder, OptBuilder, PosBuilder, Switched, Valued}; pub use self::arg_matcher::ArgMatcher; pub use self::arg_matches::{ArgMatches, OsValues, Values}; pub use self::group::ArgGroup; @@ -11,11 +9,13 @@ pub use self::subcommand::SubCommand; #[macro_use] mod macros; mod arg; -pub mod any_arg; mod arg_matches; mod arg_matcher; mod subcommand; -mod arg_builder; mod matched_arg; mod group; pub mod settings; + +pub trait DispOrder { + fn disp_ord(&self) -> usize; +} diff --git a/src/completions/bash.rs b/src/completions/bash.rs index f563922d8e9..178fd3167ca 100644 --- a/src/completions/bash.rs +++ b/src/completions/bash.rs @@ -2,19 +2,14 @@ use std::io::Write; // Internal -use app::parser::Parser; -use args::{ArgSettings, OptBuilder}; +use app::App; +use args::{ArgSettings, Arg}; use completions; -pub struct BashGen<'a, 'b> -where - 'a: 'b, -{ - p: &'b Parser<'a, 'b>, -} +pub struct BashGen<'a, 'b> (&'b App<'a, 'b> ) where 'a: 'b; impl<'a, 'b> BashGen<'a, 'b> { - pub fn new(p: &'b Parser<'a, 'b>) -> Self { BashGen { p: p } } + pub fn new(app: &'b App<'a, 'b>) -> Self { BashGen ( app ) } pub fn generate_to(&self, buf: &mut W) { w!( @@ -62,10 +57,10 @@ impl<'a, 'b> BashGen<'a, 'b> { complete -F _{name} -o bashdefault -o default {name} ", - name = self.p.meta.bin_name.as_ref().unwrap(), - name_opts = self.all_options_for_path(self.p.meta.bin_name.as_ref().unwrap()), + name = self.0.bin_name.as_ref().unwrap(), + name_opts = self.all_options_for_path(self.0.bin_name.as_ref().unwrap()), name_opts_details = - self.option_details_for_path(self.p.meta.bin_name.as_ref().unwrap()), + self.option_details_for_path(self.0.bin_name.as_ref().unwrap()), subcmds = self.all_subcommands(), subcmd_details = self.subcommand_details() ).as_bytes() @@ -75,7 +70,7 @@ complete -F _{name} -o bashdefault -o default {name} fn all_subcommands(&self) -> String { debugln!("BashGen::all_subcommands;"); let mut subcmds = String::new(); - let scs = completions::all_subcommand_names(self.p); + let scs = completions::all_subcommand_names(self.0); for sc in &scs { subcmds = format!( @@ -95,7 +90,7 @@ complete -F _{name} -o bashdefault -o default {name} fn subcommand_details(&self) -> String { debugln!("BashGen::subcommand_details;"); let mut subcmd_dets = String::new(); - let mut scs = completions::get_all_subcommand_paths(self.p, true); + let mut scs = completions::get_all_subcommand_paths(self.0, true); scs.sort(); scs.dedup(); @@ -130,14 +125,14 @@ complete -F _{name} -o bashdefault -o default {name} fn option_details_for_path(&self, path: &str) -> String { debugln!("BashGen::option_details_for_path: path={}", path); - let mut p = self.p; + let mut p = self.0; for sc in path.split("__").skip(1) { debugln!("BashGen::option_details_for_path:iter: sc={}", sc); - p = &find_subcmd!(p, sc).unwrap().p; + p = &find_subcmd!(p, sc).unwrap(); } let mut opts = String::new(); - for o in p.opts() { - if let Some(l) = o.s.long { + for o in opts!(p) { + if let Some(l) = o.long { opts = format!( "{} --{}) @@ -149,7 +144,7 @@ complete -F _{name} -o bashdefault -o default {name} self.vals_for(o) ); } - if let Some(s) = o.s.short { + if let Some(s) = o.short { opts = format!( "{} -{}) @@ -165,15 +160,14 @@ complete -F _{name} -o bashdefault -o default {name} opts } - fn vals_for(&self, o: &OptBuilder) -> String { - debugln!("BashGen::vals_for: o={}", o.b.name); - use args::AnyArg; + fn vals_for(&self, o: &Arg) -> String { + debugln!("BashGen::vals_for: o={}", o.name); let mut ret = String::new(); let mut needs_quotes = true; - if let Some(vals) = o.possible_vals() { + if let Some(ref vals) = o.possible_vals { needs_quotes = false; ret = format!("$(compgen -W \"{}\" -- ${{cur}})", vals.join(" ")); - } else if let Some(vec) = o.val_names() { + } else if let Some(ref vec) = o.val_names { let mut it = vec.iter().peekable(); while let Some((_, val)) = it.next() { ret = format!( @@ -187,13 +181,13 @@ complete -F _{name} -o bashdefault -o default {name} if o.is_set(ArgSettings::Multiple) && num == 1 { ret = format!("{}...", ret); } - } else if let Some(num) = o.num_vals() { + } else if let Some(num) = o.num_vals { let mut it = (0..num).peekable(); while let Some(_) = it.next() { ret = format!( "{}<{}>{}", ret, - o.name(), + o.name, if it.peek().is_some() { " " } else { "" } ); } @@ -201,7 +195,7 @@ complete -F _{name} -o bashdefault -o default {name} ret = format!("{}...", ret); } } else { - ret = format!("<{}>", o.name()); + ret = format!("<{}>", o.name); if o.is_set(ArgSettings::Multiple) { ret = format!("{}...", ret); } @@ -213,10 +207,10 @@ complete -F _{name} -o bashdefault -o default {name} } fn all_options_for_path(&self, path: &str) -> String { debugln!("BashGen::all_options_for_path: path={}", path); - let mut p = self.p; + let mut p = self.0; for sc in path.split("__").skip(1) { debugln!("BashGen::all_options_for_path:iter: sc={}", sc); - p = &find_subcmd!(p, sc).unwrap().p; + p = &find_subcmd!(p, sc).unwrap(); } let mut opts = shorts!(p).fold(String::new(), |acc, s| format!("{} -{}", acc, s)); opts = format!( @@ -227,19 +221,17 @@ complete -F _{name} -o bashdefault -o default {name} opts = format!( "{} {}", opts, - p.positionals - .values() + positionals!(p) .fold(String::new(), |acc, p| format!("{} {}", acc, p)) ); opts = format!( "{} {}", opts, - p.subcommands - .iter() - .fold(String::new(), |acc, s| format!("{} {}", acc, s.p.meta.name)) + subcommands!(p) + .fold(String::new(), |acc, s| format!("{} {}", acc, s.name)) ); - for sc in &p.subcommands { - if let Some(ref aliases) = sc.p.meta.aliases { + for sc in subcommands!(p) { + if let Some(ref aliases) = sc.aliases { opts = format!( "{} {}", opts, diff --git a/src/completions/fish.rs b/src/completions/fish.rs index 841c39b3a39..19c5c0f95ab 100644 --- a/src/completions/fish.rs +++ b/src/completions/fish.rs @@ -2,20 +2,15 @@ use std::io::Write; // Internal -use app::parser::Parser; +use app::App; -pub struct FishGen<'a, 'b> -where - 'a: 'b, -{ - p: &'b Parser<'a, 'b>, -} +pub struct FishGen<'a, 'b> (&'b App<'a, 'b> ) where 'a: 'b; impl<'a, 'b> FishGen<'a, 'b> { - pub fn new(p: &'b Parser<'a, 'b>) -> Self { FishGen { p: p } } + pub fn new(app: &'b App<'a, 'b>) -> Self { FishGen ( app ) } pub fn generate_to(&self, buf: &mut W) { - let command = self.p.meta.bin_name.as_ref().unwrap(); + let command = self.0.bin_name.as_ref().unwrap(); // function to detect subcommand let detect_subcommand_function = r#"function __fish_using_command @@ -62,44 +57,44 @@ fn gen_fish_inner(root_command: &str, comp_gen: &FishGen, parent_cmds: &str, buf parent_cmds ); - for option in comp_gen.p.opts() { + for option in opts!(comp_gen.0) { let mut template = basic_template.clone(); - if let Some(data) = option.s.short { + if let Some(data) = option.short { template.push_str(format!(" -s {}", data).as_str()); } - if let Some(data) = option.s.long { + if let Some(data) = option.long { template.push_str(format!(" -l {}", data).as_str()); } - if let Some(data) = option.b.help { + if let Some(data) = option.help { template.push_str(format!(" -d '{}'", escape_string(data)).as_str()); } - if let Some(ref data) = option.v.possible_vals { + if let Some(ref data) = option.possible_vals { template.push_str(format!(" -r -f -a \"{}\"", data.join(" ")).as_str()); } buffer.push_str(template.as_str()); buffer.push_str("\n"); } - for flag in comp_gen.p.flags() { + for flag in flags!(comp_gen.0) { let mut template = basic_template.clone(); - if let Some(data) = flag.s.short { + if let Some(data) = flag.short { template.push_str(format!(" -s {}", data).as_str()); } - if let Some(data) = flag.s.long { + if let Some(data) = flag.long { template.push_str(format!(" -l {}", data).as_str()); } - if let Some(data) = flag.b.help { + if let Some(data) = flag.help { template.push_str(format!(" -d '{}'", escape_string(data)).as_str()); } buffer.push_str(template.as_str()); buffer.push_str("\n"); } - for subcommand in &comp_gen.p.subcommands { + for subcommand in subcommands!(comp_gen.0) { let mut template = basic_template.clone(); template.push_str(" -f"); - template.push_str(format!(" -a \"{}\"", &subcommand.p.meta.name).as_str()); - if let Some(data) = subcommand.p.meta.about { + template.push_str(format!(" -a \"{}\"", &subcommand.name).as_str()); + if let Some(data) = subcommand.about { template.push_str(format!(" -d '{}'", escape_string(data)).as_str()) } buffer.push_str(template.as_str()); @@ -107,14 +102,14 @@ fn gen_fish_inner(root_command: &str, comp_gen: &FishGen, parent_cmds: &str, buf } // generate options of subcommands - for subcommand in &comp_gen.p.subcommands { - let sub_comp_gen = FishGen::new(&subcommand.p); + for subcommand in subcommands!(comp_gen.0) { + let sub_comp_gen = FishGen::new(&subcommand); // make new "parent_cmds" for different subcommands let mut sub_parent_cmds = parent_cmds.to_string(); if !sub_parent_cmds.is_empty() { sub_parent_cmds.push_str(" "); } - sub_parent_cmds.push_str(&subcommand.p.meta.name); + sub_parent_cmds.push_str(&subcommand.name); gen_fish_inner(root_command, &sub_comp_gen, &sub_parent_cmds, buffer); } } diff --git a/src/completions/macros.rs b/src/completions/macros.rs index 653c72c4863..243861785d1 100644 --- a/src/completions/macros.rs +++ b/src/completions/macros.rs @@ -8,15 +8,15 @@ macro_rules! w { } macro_rules! get_zsh_arg_conflicts { - ($p:ident, $arg:ident, $msg:ident) => { - if let Some(conf_vec) = $arg.blacklist() { + ($app:expr, $arg:ident, $msg:ident) => { + if let Some(conf_vec) = $arg.blacklist { let mut v = vec![]; for arg_name in conf_vec { - let arg = $p.find_any_arg(arg_name).expect($msg); - if let Some(s) = arg.short() { + let arg = find!($app, arg_name).expect($msg); + if let Some(s) = arg.short { v.push(format!("-{}", s)); } - if let Some(l) = arg.long() { + if let Some(l) = arg.long { v.push(format!("--{}", l)); } } diff --git a/src/completions/mod.rs b/src/completions/mod.rs index 742c6161956..986585bd66b 100644 --- a/src/completions/mod.rs +++ b/src/completions/mod.rs @@ -10,29 +10,24 @@ mod shell; use std::io::Write; // Internal -use app::parser::Parser; +use app::App; use self::bash::BashGen; use self::fish::FishGen; use self::zsh::ZshGen; use self::powershell::PowerShellGen; pub use self::shell::Shell; -pub struct ComplGen<'a, 'b> -where - 'a: 'b, -{ - p: &'b Parser<'a, 'b>, -} +pub struct ComplGen<'a, 'b> (&'b App<'a, 'b>, ) where 'a: 'b; impl<'a, 'b> ComplGen<'a, 'b> { - pub fn new(p: &'b Parser<'a, 'b>) -> Self { ComplGen { p: p } } + pub fn new(app: &'b App<'a, 'b>) -> Self { ComplGen ( app ) } pub fn generate(&self, for_shell: Shell, buf: &mut W) { match for_shell { - Shell::Bash => BashGen::new(self.p).generate_to(buf), - Shell::Fish => FishGen::new(self.p).generate_to(buf), - Shell::Zsh => ZshGen::new(self.p).generate_to(buf), - Shell::PowerShell => PowerShellGen::new(self.p).generate_to(buf), + Shell::Bash => BashGen::new(self.0).generate_to(buf), + Shell::Fish => FishGen::new(self.0).generate_to(buf), + Shell::Zsh => ZshGen::new(self.0).generate_to(buf), + Shell::PowerShell => PowerShellGen::new(self.0).generate_to(buf), } } } @@ -43,13 +38,13 @@ impl<'a, 'b> ComplGen<'a, 'b> { // // Also note, aliases are treated as their own subcommands but duplicates of whatever they're // aliasing. -pub fn all_subcommand_names(p: &Parser) -> Vec { +pub fn all_subcommand_names(p: &App) -> Vec { debugln!("all_subcommand_names;"); let mut subcmds: Vec<_> = subcommands_of(p) .iter() .map(|&(ref n, _)| n.clone()) .collect(); - for sc_v in p.subcommands.iter().map(|s| all_subcommand_names(&s.p)) { + for sc_v in subcommands!(p).map(|s| all_subcommand_names(&s)) { subcmds.extend(sc_v); } subcmds.sort(); @@ -63,10 +58,10 @@ pub fn all_subcommand_names(p: &Parser) -> Vec { // // Also note, aliases are treated as their own subcommands but duplicates of whatever they're // aliasing. -pub fn all_subcommands(p: &Parser) -> Vec<(String, String)> { +pub fn all_subcommands(p: &App) -> Vec<(String, String)> { debugln!("all_subcommands;"); let mut subcmds: Vec<_> = subcommands_of(p); - for sc_v in p.subcommands.iter().map(|s| all_subcommands(&s.p)) { + for sc_v in subcommands!(p).map(|s| all_subcommands(&s)) { subcmds.extend(sc_v); } subcmds @@ -78,11 +73,11 @@ pub fn all_subcommands(p: &Parser) -> Vec<(String, String)> { // // Also note, aliases are treated as their own subcommands but duplicates of whatever they're // aliasing. -pub fn subcommands_of(p: &Parser) -> Vec<(String, String)> { +pub fn subcommands_of(p: &App) -> Vec<(String, String)> { debugln!( "subcommands_of: name={}, bin_name={}", - p.meta.name, - p.meta.bin_name.as_ref().unwrap() + p.name, + p.bin_name.as_ref().unwrap() ); let mut subcmds = vec![]; @@ -93,11 +88,11 @@ pub fn subcommands_of(p: &Parser) -> Vec<(String, String)> { if !p.has_subcommands() { let mut ret = vec![]; debugln!("subcommands_of: Looking for aliases..."); - if let Some(ref aliases) = p.meta.aliases { + if let Some(ref aliases) = p.aliases { for &(n, _) in aliases { debugln!("subcommands_of:iter:iter: Found alias...{}", n); let mut als_bin_name: Vec<_> = - p.meta.bin_name.as_ref().unwrap().split(' ').collect(); + p.bin_name.as_ref().unwrap().split(' ').collect(); als_bin_name.push(n); let old = als_bin_name.len() - 2; als_bin_name.swap_remove(old); @@ -106,19 +101,19 @@ pub fn subcommands_of(p: &Parser) -> Vec<(String, String)> { } return ret; } - for sc in &p.subcommands { + for sc in subcommands!(p) { debugln!( "subcommands_of:iter: name={}, bin_name={}", - sc.p.meta.name, - sc.p.meta.bin_name.as_ref().unwrap() + sc.name, + sc.bin_name.as_ref().unwrap() ); debugln!("subcommands_of:iter: Looking for aliases..."); - if let Some(ref aliases) = sc.p.meta.aliases { + if let Some(ref aliases) = sc.aliases { for &(n, _) in aliases { debugln!("subcommands_of:iter:iter: Found alias...{}", n); let mut als_bin_name: Vec<_> = - p.meta.bin_name.as_ref().unwrap().split(' ').collect(); + p.bin_name.as_ref().unwrap().split(' ').collect(); als_bin_name.push(n); let old = als_bin_name.len() - 2; als_bin_name.swap_remove(old); @@ -126,22 +121,22 @@ pub fn subcommands_of(p: &Parser) -> Vec<(String, String)> { } } subcmds.push(( - sc.p.meta.name.clone(), - sc.p.meta.bin_name.as_ref().unwrap().clone(), + sc.name.clone(), + sc.bin_name.as_ref().unwrap().clone(), )); } subcmds } -pub fn get_all_subcommand_paths(p: &Parser, first: bool) -> Vec { +pub fn get_all_subcommand_paths(p: &App, first: bool) -> Vec { debugln!("get_all_subcommand_paths;"); let mut subcmds = vec![]; if !p.has_subcommands() { if !first { - let name = &*p.meta.name; - let path = p.meta.bin_name.as_ref().unwrap().clone().replace(" ", "__"); + let name = &*p.name; + let path = p.bin_name.as_ref().unwrap().clone().replace(" ", "__"); let mut ret = vec![path.clone()]; - if let Some(ref aliases) = p.meta.aliases { + if let Some(ref aliases) = p.aliases { for &(n, _) in aliases { ret.push(path.replace(name, n)); } @@ -150,25 +145,22 @@ pub fn get_all_subcommand_paths(p: &Parser, first: bool) -> Vec { } return vec![]; } - for sc in &p.subcommands { - let name = &*sc.p.meta.name; - let path = sc.p - .meta - .bin_name + for sc in subcommands!(p) { + let name = &*sc.name; + let path = sc.bin_name .as_ref() .unwrap() .clone() .replace(" ", "__"); subcmds.push(path.clone()); - if let Some(ref aliases) = sc.p.meta.aliases { + if let Some(ref aliases) = sc.aliases { for &(n, _) in aliases { subcmds.push(path.replace(name, n)); } } } - for sc_v in p.subcommands - .iter() - .map(|s| get_all_subcommand_paths(&s.p, false)) + for sc_v in subcommands!(p) + .map(|s| get_all_subcommand_paths(&s, false)) { subcmds.extend(sc_v); } diff --git a/src/completions/powershell.rs b/src/completions/powershell.rs index 19b149692e4..5f8d726f043 100644 --- a/src/completions/powershell.rs +++ b/src/completions/powershell.rs @@ -2,25 +2,20 @@ use std::io::Write; // Internal -use app::parser::Parser; +use app::App; use INTERNAL_ERROR_MSG; -pub struct PowerShellGen<'a, 'b> -where - 'a: 'b, -{ - p: &'b Parser<'a, 'b>, -} +pub struct PowerShellGen<'a, 'b> (&'b App<'a, 'b>) where 'a: 'b; impl<'a, 'b> PowerShellGen<'a, 'b> { - pub fn new(p: &'b Parser<'a, 'b>) -> Self { PowerShellGen { p: p } } + pub fn new(app: &'b App<'a, 'b>) -> Self { PowerShellGen ( app ) } pub fn generate_to(&self, buf: &mut W) { - let bin_name = self.p.meta.bin_name.as_ref().unwrap(); + let bin_name = self.0.bin_name.as_ref().unwrap(); let mut names = vec![]; let (subcommands_detection_cases, subcommands_cases) = - generate_inner(self.p, "", &mut names); + generate_inner(self.0, "", &mut names); let mut bin_names = vec![bin_name.to_string(), format!("./{0}", bin_name)]; if cfg!(windows) { @@ -46,7 +41,7 @@ impl<'a, 'b> PowerShellGen<'a, 'b> { %{{ switch ($_.ToString()) {{ {subcommands_detection_cases} - default {{ + default {{ break }} }} @@ -76,7 +71,7 @@ impl<'a, 'b> PowerShellGen<'a, 'b> { } fn generate_inner<'a, 'b, 'p>( - p: &'p Parser<'a, 'b>, + p: &'p App<'a, 'b>, previous_command_name: &str, names: &mut Vec<&'p str>, ) -> (String, String) { @@ -85,14 +80,14 @@ fn generate_inner<'a, 'b, 'p>( format!( "{}_{}", previous_command_name, - &p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG) + &p.bin_name.as_ref().expect(INTERNAL_ERROR_MSG) ) } else { - format!("{}_{}", previous_command_name, &p.meta.name) + format!("{}_{}", previous_command_name, &p.name) }; - let mut subcommands_detection_cases = if !names.contains(&&*p.meta.name) { - names.push(&*p.meta.name); + let mut subcommands_detection_cases = if !names.contains(&&*p.name) { + names.push(&*p.name); format!( r" '{0}' {{ @@ -100,7 +95,7 @@ fn generate_inner<'a, 'b, 'p>( break }} ", - &p.meta.name + &p.name ) } else { String::new() @@ -108,7 +103,7 @@ fn generate_inner<'a, 'b, 'p>( let mut completions = String::new(); for subcommand in &p.subcommands { - completions.push_str(&format!("'{}', ", &subcommand.p.meta.name)); + completions.push_str(&format!("'{}', ", &subcommand.name)); } for short in shorts!(p) { completions.push_str(&format!("'-{}', ", short)); @@ -127,9 +122,9 @@ fn generate_inner<'a, 'b, 'p>( completions.trim_right_matches(", ") ); - for subcommand in &p.subcommands { + for subcommand in subcommands!(p) { let (subcommand_subcommands_detection_cases, subcommand_subcommands_cases) = - generate_inner(&subcommand.p, &command_name, names); + generate_inner(&subcommand, &command_name, names); subcommands_detection_cases.push_str(&subcommand_subcommands_detection_cases); subcommands_cases.push_str(&subcommand_subcommands_cases); } diff --git a/src/completions/zsh.rs b/src/completions/zsh.rs index 11dd868eea6..59cd4270ed0 100644 --- a/src/completions/zsh.rs +++ b/src/completions/zsh.rs @@ -5,22 +5,16 @@ use std::ascii::AsciiExt; // Internal use app::App; -use app::parser::Parser; -use args::{AnyArg, ArgSettings}; +use args::ArgSettings; use completions; use INTERNAL_ERROR_MSG; -pub struct ZshGen<'a, 'b> -where - 'a: 'b, -{ - p: &'b Parser<'a, 'b>, -} +pub struct ZshGen<'a, 'b> (&'b App<'a, 'b>) where 'a: 'b; impl<'a, 'b> ZshGen<'a, 'b> { - pub fn new(p: &'b Parser<'a, 'b>) -> Self { + pub fn new(app: &'b App<'a, 'b>) -> Self { debugln!("ZshGen::new;"); - ZshGen { p: p } + ZshGen ( app ) } pub fn generate_to(&self, buf: &mut W) { @@ -52,10 +46,10 @@ _{name}() {{ {subcommand_details} _{name} \"$@\"", - name = self.p.meta.bin_name.as_ref().unwrap(), - initial_args = get_args_of(self.p), - subcommands = get_subcommands_of(self.p), - subcommand_details = subcommand_details(self.p) + name = self.0.bin_name.as_ref().unwrap(), + initial_args = get_args_of(self.0), + subcommands = get_subcommands_of(self.0), + subcommand_details = subcommand_details(self.0) ).as_bytes() ); } @@ -88,7 +82,7 @@ _{name} \"$@\"", // ) // _describe -t commands 'rustup commands' commands "$@" // -fn subcommand_details(p: &Parser) -> String { +fn subcommand_details(p: &App) -> String { debugln!("ZshGen::subcommand_details;"); // First we do ourself let mut ret = vec![ @@ -101,8 +95,8 @@ _{bin_name_underscore}_commands() {{ ) _describe -t commands '{bin_name} commands' commands \"$@\" }}", - bin_name_underscore = p.meta.bin_name.as_ref().unwrap().replace(" ", "__"), - bin_name = p.meta.bin_name.as_ref().unwrap(), + bin_name_underscore = p.bin_name.as_ref().unwrap().replace(" ", "__"), + bin_name = p.bin_name.as_ref().unwrap(), subcommands_and_args = subcommands_of(p) ), ]; @@ -142,7 +136,7 @@ _{bin_name_underscore}_commands() {{ // A snippet from rustup: // 'show:Show the active and installed toolchains' // 'update:Update Rust toolchains' -fn subcommands_of(p: &Parser) -> String { +fn subcommands_of(p: &App) -> String { debugln!("ZshGen::subcommands_of;"); let mut ret = vec![]; fn add_sc(sc: &App, n: &str, ret: &mut Vec) { @@ -150,8 +144,7 @@ fn subcommands_of(p: &Parser) -> String { let s = format!( "\"{name}:{help}\" \\", name = n, - help = sc.p - .meta + help = sc .about .unwrap_or("") .replace("[", "\\[") @@ -163,13 +156,13 @@ fn subcommands_of(p: &Parser) -> String { } // The subcommands - for sc in p.subcommands() { + for sc in subcommands!(p) { debugln!( "ZshGen::subcommands_of:iter: subcommand={}", - sc.p.meta.name + sc.name ); - add_sc(sc, &sc.p.meta.name, &mut ret); - if let Some(ref v) = sc.p.meta.aliases { + add_sc(sc, &sc.name, &mut ret); + if let Some(ref v) = sc.aliases { for alias in v.iter().filter(|&&(_, vis)| vis).map(|&(n, _)| n) { add_sc(sc, alias, &mut ret); } @@ -208,7 +201,7 @@ fn subcommands_of(p: &Parser) -> String { // [name_hyphen] = The full space deliniated bin_name, but replace spaces with hyphens // [repeat] = From the same recursive calls, but for all subcommands // [subcommand_args] = The same as zsh::get_args_of -fn get_subcommands_of(p: &Parser) -> String { +fn get_subcommands_of(p: &App) -> String { debugln!("get_subcommands_of;"); debugln!( @@ -247,19 +240,19 @@ fn get_subcommands_of(p: &Parser) -> String { esac ;; esac", - name = p.meta.name, - name_hyphen = p.meta.bin_name.as_ref().unwrap().replace(" ", "-"), + name = p.name, + name_hyphen = p.bin_name.as_ref().unwrap().replace(" ", "-"), subcommands = subcmds.join("\n"), - pos = p.positionals().len() + 1 + pos = positionals!(p).count() + 1 ) } -fn parser_of<'a, 'b>(p: &'b Parser<'a, 'b>, sc: &str) -> &'b Parser<'a, 'b> { +fn parser_of<'a, 'b>(p: &'b App<'a, 'b>, sc: &str) -> &'b App<'a, 'b> { debugln!("parser_of: sc={}", sc); - if sc == p.meta.bin_name.as_ref().unwrap_or(&String::new()) { + if sc == p.bin_name.as_ref().unwrap_or(&String::new()) { return p; } - &p.find_subcommand(sc).expect(INTERNAL_ERROR_MSG).p + find_subcmd!(p, sc).expect(INTERNAL_ERROR_MSG) } // Writes out the args section, which ends up being the flags, opts and postionals, and a jump to @@ -282,7 +275,7 @@ fn parser_of<'a, 'b>(p: &'b Parser<'a, 'b>, sc: &str) -> &'b Parser<'a, 'b> { // -C: modify the $context internal variable // -s: Allow stacking of short args (i.e. -a -b -c => -abc) // -S: Do not complete anything after '--' and treat those as argument values -fn get_args_of(p: &Parser) -> String { +fn get_args_of(p: &App) -> String { debugln!("get_args_of;"); let mut ret = vec![String::from("_arguments \"${_arguments_options[@]}\" \\")]; let opts = write_opts_of(p); @@ -291,13 +284,13 @@ fn get_args_of(p: &Parser) -> String { let sc_or_a = if p.has_subcommands() { format!( "\":: :_{name}_commands\" \\", - name = p.meta.bin_name.as_ref().unwrap().replace(" ", "__") + name = p.bin_name.as_ref().unwrap().replace(" ", "__") ) } else { String::new() }; let sc = if p.has_subcommands() { - format!("\"*::: :->{name}\" \\", name = p.meta.name) + format!("\"*::: :->{name}\" \\", name = p.name) } else { String::new() }; @@ -341,12 +334,12 @@ fn escape_value(string: &str) -> String { .replace(" ", "\\ ") } -fn write_opts_of(p: &Parser) -> String { +fn write_opts_of(p: &App) -> String { debugln!("write_opts_of;"); let mut ret = vec![]; - for o in p.opts() { - debugln!("write_opts_of:iter: o={}", o.name()); - let help = o.help().map_or(String::new(), escape_help); + for o in opts!(p) { + debugln!("write_opts_of:iter: o={}", o.name); + let help = o.help.map_or(String::new(), escape_help); let mut conflicts = get_zsh_arg_conflicts!(p, o, INTERNAL_ERROR_MSG); conflicts = if conflicts.is_empty() { String::new() @@ -359,13 +352,13 @@ fn write_opts_of(p: &Parser) -> String { } else { "" }; - let pv = if let Some(pv_vec) = o.possible_vals() { + let pv = if let Some(ref pv_vec) = o.possible_vals { format!(": :({})", pv_vec.iter().map( |v| escape_value(*v)).collect::>().join(" ")) } else { String::new() }; - if let Some(short) = o.short() { + if let Some(short) = o.short { let s = format!( "'{conflicts}{multiple}-{arg}+[{help}]{possible_values}' \\", conflicts = conflicts, @@ -378,7 +371,7 @@ fn write_opts_of(p: &Parser) -> String { debugln!("write_opts_of:iter: Wrote...{}", &*s); ret.push(s); } - if let Some(long) = o.long() { + if let Some(long) = o.long { let l = format!( "'{conflicts}{multiple}--{arg}=[{help}]{possible_values}' \\", conflicts = conflicts, @@ -396,12 +389,12 @@ fn write_opts_of(p: &Parser) -> String { ret.join("\n") } -fn write_flags_of(p: &Parser) -> String { +fn write_flags_of(p: &App) -> String { debugln!("write_flags_of;"); let mut ret = vec![]; - for f in p.flags() { - debugln!("write_flags_of:iter: f={}", f.name()); - let help = f.help().map_or(String::new(), escape_help); + for f in flags!(p) { + debugln!("write_flags_of:iter: f={}", f.name); + let help = f.help.map_or(String::new(), escape_help); let mut conflicts = get_zsh_arg_conflicts!(p, f, INTERNAL_ERROR_MSG); conflicts = if conflicts.is_empty() { String::new() @@ -414,7 +407,7 @@ fn write_flags_of(p: &Parser) -> String { } else { "" }; - if let Some(short) = f.short() { + if let Some(short) = f.short { let s = format!( "'{conflicts}{multiple}-{arg}[{help}]' \\", multiple = multiple, @@ -427,7 +420,7 @@ fn write_flags_of(p: &Parser) -> String { ret.push(s); } - if let Some(long) = f.long() { + if let Some(long) = f.long { let l = format!( "'{conflicts}{multiple}--{arg}[{help}]' \\", conflicts = conflicts, @@ -444,21 +437,20 @@ fn write_flags_of(p: &Parser) -> String { ret.join("\n") } -fn write_positionals_of(p: &Parser) -> String { +fn write_positionals_of(p: &App) -> String { debugln!("write_positionals_of;"); let mut ret = vec![]; - for arg in p.positionals() { + for arg in positionals!(p) { debugln!("write_positionals_of:iter: arg={}", arg.b.name); let a = format!( "'{optional}:{name}{help}:{action}' \\", - optional = if !arg.b.is_set(ArgSettings::Required) { ":" } else { "" }, - name = arg.b.name, - help = arg.b - .help + optional = if !arg.is_set(ArgSettings::Required) { ":" } else { "" }, + name = arg.name, + help = arg.help .map_or("".to_owned(), |v| " -- ".to_owned() + v) .replace("[", "\\[") .replace("]", "\\]"), - action = arg.possible_vals().map_or("_files".to_owned(), |values| { + action = arg.possible_vals.map_or("_files".to_owned(), |values| { format!("({})", values.iter().map(|v| escape_value(*v)).collect::>().join(" ")) }) diff --git a/src/errors.rs b/src/errors.rs index 664819d6c90..f38073e108d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -8,7 +8,7 @@ use std::process; use std::result::Result as StdResult; // Internal -use args::AnyArg; +use args::Arg; use fmt::{ColorWhen, Colorizer, ColorizerOption}; use suggestions; @@ -405,7 +405,7 @@ impl Error { #[doc(hidden)] pub fn argument_conflict<'a, 'b, O, U>( - arg: &AnyArg, + arg: &Arg, other: Option, usage: U, color: ColorWhen, @@ -414,7 +414,7 @@ impl Error { O: Into, U: Display, { - let mut v = vec![arg.name().to_owned()]; + let mut v = vec![arg.name.to_owned()]; let c = Colorizer::new(ColorizerOption { use_stderr: true, when: color, @@ -443,7 +443,7 @@ impl Error { } #[doc(hidden)] - pub fn empty_value<'a, 'b, U>(arg: &AnyArg, usage: U, color: ColorWhen) -> Self + pub fn empty_value<'a, 'b, U>(arg: &Arg, usage: U, color: ColorWhen) -> Self where U: Display, { @@ -463,7 +463,7 @@ impl Error { c.good("--help") ), kind: ErrorKind::EmptyValue, - info: Some(vec![arg.name().to_owned()]), + info: Some(vec![arg.name.to_owned()]), } } @@ -471,7 +471,7 @@ impl Error { pub fn invalid_value<'a, 'b, B, G, U>( bad_val: B, good_vals: &[G], - arg: &AnyArg, + arg: &Arg, usage: U, color: ColorWhen, ) -> Self @@ -509,7 +509,7 @@ impl Error { c.good("--help") ), kind: ErrorKind::InvalidValue, - info: Some(vec![arg.name().to_owned(), bad_val.as_ref().to_owned()]), + info: Some(vec![arg.name.to_owned(), bad_val.as_ref().to_owned()]), } } @@ -657,7 +657,7 @@ impl Error { } #[doc(hidden)] - pub fn too_many_values<'a, 'b, V, U>(val: V, arg: &AnyArg, usage: U, color: ColorWhen) -> Self + pub fn too_many_values<'a, 'b, V, U>(val: V, arg: &Arg, usage: U, color: ColorWhen) -> Self where V: AsRef + Display + ToOwned, U: Display, @@ -680,13 +680,13 @@ impl Error { c.good("--help") ), kind: ErrorKind::TooManyValues, - info: Some(vec![arg.name().to_owned(), v.to_owned()]), + info: Some(vec![arg.name.to_owned(), v.to_owned()]), } } #[doc(hidden)] pub fn too_few_values<'a, 'b, U>( - arg: &AnyArg, + arg: &Arg, min_vals: u64, curr_vals: usize, usage: U, @@ -714,12 +714,12 @@ impl Error { c.good("--help") ), kind: ErrorKind::TooFewValues, - info: Some(vec![arg.name().to_owned()]), + info: Some(vec![arg.name.to_owned()]), } } #[doc(hidden)] - pub fn value_validation<'a, 'b>(arg: Option<&AnyArg>, err: String, color: ColorWhen) -> Self + pub fn value_validation<'a, 'b>(arg: Option<&Arg>, err: String, color: ColorWhen) -> Self { let c = Colorizer::new(ColorizerOption { use_stderr: true, @@ -743,13 +743,13 @@ impl Error { #[doc(hidden)] pub fn value_validation_auto(err: String) -> Self { - let n: Option<&AnyArg> = None; + let n: Option<&Arg> = None; Error::value_validation(n, err, ColorWhen::Auto) } #[doc(hidden)] pub fn wrong_number_of_values<'a, 'b, S, U>( - arg: &AnyArg, + arg: &Arg, num_vals: u64, curr_vals: usize, suffix: S, @@ -779,12 +779,12 @@ impl Error { c.good("--help") ), kind: ErrorKind::WrongNumberOfValues, - info: Some(vec![arg.name().to_owned()]), + info: Some(vec![arg.name.to_owned()]), } } #[doc(hidden)] - pub fn unexpected_multiple_usage<'a, 'b, U>(arg: &AnyArg, usage: U, color: ColorWhen) -> Self + pub fn unexpected_multiple_usage<'a, 'b, U>(arg: &Arg, usage: U, color: ColorWhen) -> Self where U: Display, { @@ -804,7 +804,7 @@ impl Error { c.good("--help") ), kind: ErrorKind::UnexpectedMultipleUsage, - info: Some(vec![arg.name().to_owned()]), + info: Some(vec![arg.name.to_owned()]), } } diff --git a/src/macros.rs b/src/macros.rs index 52fb35a4de9..b0cf2337c90 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -852,38 +852,91 @@ macro_rules! write_nspaces { }) } -// convenience macro for remove an item from a vec -//macro_rules! vec_remove_all { -// ($vec:expr, $to_rem:expr) => { -// debugln!("vec_remove_all! to_rem={:?}", $to_rem); -// for i in (0 .. $vec.len()).rev() { -// let should_remove = $to_rem.any(|name| name == &$vec[i]); -// if should_remove { $vec.swap_remove(i); } -// } -// }; -//} +macro_rules! flags { + ($app:expr, $how:ident) => { + $app.args.$how().filter(|a| !a.settings.is_set(::args::settings::ArgSettings::TakesValue) && (a.short.is_some() || a.long.is_some())) + }; + ($app:expr) => { + flags!($app, iter) + } +} + +macro_rules! flags_mut { + ($app:expr) => { + flags!($app, iter_mut) + } +} + +macro_rules! opts { + ($app:expr, $how:ident) => { + $app.args.$how().filter(|a| a.settings.is_set(::args::settings::ArgSettings::TakesValue) && (a.short.is_some() || a.long.is_some())) + }; + ($app:expr) => { + opts!($app, iter) + } +} + +#[allow(unused_macros)] +macro_rules! opts_mut { + ($app:expr) => { + opts!($app, iter) + } +} + +macro_rules! positionals { + ($app:expr, $how:ident) => { + $app.args.$how().filter(|a| !(a.short.is_some() || a.long.is_some())) + }; + ($app:expr) => { + positionals!($app, iter) + } +} + +#[allow(unused_macros)] +macro_rules! positionals_mut { + ($app:expr) => { + positionals!($app, iter_mut) + } +} + +macro_rules! subcommands { + ($app:expr, $how:ident) => { + $app.subcommands.$how() + }; + ($app:expr) => { + subcommands!($app, iter) + } +} + +macro_rules! subcommands_mut { + ($app:expr) => { + subcommands!($app, iter_mut) + } +} + +macro_rules! groups { + ($app:expr, $how:ident) => { + $app.groups.$how() + }; + ($app:expr) => { + groups!($app, iter) + } +} + +macro_rules! groups_mut { + ($app:expr) => { + groups!($app, iter_mut) + } +} + macro_rules! find_from { - ($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{ + ($app:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{ let mut ret = None; for k in $matcher.arg_names() { - if let Some(f) = find_by_name!($_self, k, flags, iter) { - if let Some(ref v) = f.$from() { + if let Some(a) = find!($app, k) { + if let Some(ref v) = a.$from { if v.contains($arg_name) { - ret = Some(f.to_string()); - } - } - } - if let Some(o) = find_by_name!($_self, k, opts, iter) { - if let Some(ref v) = o.$from() { - if v.contains(&$arg_name) { - ret = Some(o.to_string()); - } - } - } - if let Some(pos) = find_by_name!($_self, k, positionals, values) { - if let Some(ref v) = pos.$from() { - if v.contains($arg_name) { - ret = Some(pos.b.name.to_owned()); + ret = Some(a.to_string()); } } } @@ -892,83 +945,23 @@ macro_rules! find_from { }}; } -//macro_rules! find_name_from { -// ($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{ -// let mut ret = None; -// for k in $matcher.arg_names() { -// if let Some(f) = find_by_name!($_self, k, flags, iter) { -// if let Some(ref v) = f.$from() { -// if v.contains($arg_name) { -// ret = Some(f.b.name); -// } -// } -// } -// if let Some(o) = find_by_name!($_self, k, opts, iter) { -// if let Some(ref v) = o.$from() { -// if v.contains(&$arg_name) { -// ret = Some(o.b.name); -// } -// } -// } -// if let Some(pos) = find_by_name!($_self, k, positionals, values) { -// if let Some(ref v) = pos.$from() { -// if v.contains($arg_name) { -// ret = Some(pos.b.name); -// } -// } -// } -// } -// ret -// }}; -//} - - -macro_rules! find_any_by_name { - ($p:expr, $name:expr) => { - { - fn as_trait_obj<'a, 'b, T: AnyArg<'a, 'b>>(x: &T) -> &AnyArg<'a, 'b> { x } - find_by_name!($p, $name, flags, iter).map(as_trait_obj).or( - find_by_name!($p, $name, opts, iter).map(as_trait_obj).or( - find_by_name!($p, $name, positionals, values).map(as_trait_obj) - ) - ) - } - } -} // Finds an arg by name -macro_rules! find_by_name { - ($p:expr, $name:expr, $what:ident, $how:ident) => { - $p.$what.$how().find(|o| o.b.name == $name) +macro_rules! find { + ($app:expr, $name:expr, $what:ident) => { + $what!($app).find(|a| a.name == $name) + }; + ($app:expr, $name:expr) => { + $app.args.iter().find(|a| a.name == $name) } } -// Finds an option including if it's aliasesed -macro_rules! find_opt_by_long { - (@os $_self:ident, $long:expr) => {{ - _find_by_long!($_self, $long, opts) - }}; - ($_self:ident, $long:expr) => {{ - _find_by_long!($_self, $long, opts) - }}; -} - -macro_rules! find_flag_by_long { - (@os $_self:ident, $long:expr) => {{ - _find_by_long!($_self, $long, flags) - }}; - ($_self:ident, $long:expr) => {{ - _find_by_long!($_self, $long, flags) - }}; -} - -macro_rules! _find_by_long { - ($_self:ident, $long:expr, $what:ident) => {{ - $_self.$what - .iter() - .filter(|a| a.s.long.is_some()) +macro_rules! find_by_long { + ($app:expr, $long:expr, $what:ident) => {{ + $what!($app) + .filter(|a| a.long.is_some()) .find(|a| { - a.s.long.unwrap() == $long || - (a.s.aliases.is_some() && + a.long.unwrap() == $long || + (a.aliases.is_some() && a.s .aliases .as_ref() @@ -976,104 +969,93 @@ macro_rules! _find_by_long { .iter() .any(|&(alias, _)| alias == $long)) }) - }} -} - -// Finds an option -macro_rules! find_opt_by_short { - ($_self:ident, $short:expr) => {{ - _find_by_short!($_self, $short, opts) - }} -} - -macro_rules! find_flag_by_short { - ($_self:ident, $short:expr) => {{ - _find_by_short!($_self, $short, flags) - }} + }}; + ($app:expr, $long:expr) => {{ + $app.args.iter() + .filter(|a| a.long.is_some()) + .find(|a| { + a.long.unwrap() == $long || + (a.aliases.is_some() && + a.aliases + .as_ref() + .unwrap() + .iter() + .any(|&(alias, _)| alias == $long)) + }) + }}; } -macro_rules! _find_by_short { - ($_self:ident, $short:expr, $what:ident) => {{ - $_self.$what - .iter() - .filter(|a| a.s.short.is_some()) - .find(|a| a.s.short.unwrap() == $short) +macro_rules! find_by_short { + ($app:expr, $short:expr, $what:ident) => {{ + $what!($app) + .filter(|a| a.short.is_some()) + .find(|a| a.short.unwrap() == $short) + }}; + ($app:expr, $short:expr) => {{ + $app.args.iter() + .filter(|a| a.short.is_some()) + .find(|a| a.short.unwrap() == $short) }} } macro_rules! find_subcmd { ($_self:expr, $sc:expr) => {{ - $_self.subcommands - .iter() + subcommands!($_self) .find(|s| { - &*s.p.meta.name == $sc || - (s.p.meta.aliases.is_some() && - s.p - .meta - .aliases - .as_ref() - .unwrap() - .iter() - .any(|&(n, _)| n == $sc)) + &*s.name == $sc || + (s.aliases.is_some() && + s.aliases + .as_ref() + .unwrap() + .iter() + .any(|&(n, _)| n == $sc)) }) }}; } macro_rules! shorts { - ($_self:ident) => {{ - _shorts_longs!($_self, short) + ($app:expr) => {{ + _shorts_longs!($app, short) }}; } macro_rules! longs { - ($_self:ident) => {{ - _shorts_longs!($_self, long) + ($app:expr) => {{ + _shorts_longs!($app, long) }}; } macro_rules! _shorts_longs { - ($_self:ident, $what:ident) => {{ - $_self.flags - .iter() - .filter(|f| f.s.$what.is_some()) - .map(|f| f.s.$what.as_ref().unwrap()) - .chain($_self.opts.iter() - .filter(|o| o.s.$what.is_some()) - .map(|o| o.s.$what.as_ref().unwrap())) - }}; -} - -macro_rules! arg_names { - ($_self:ident) => {{ - _names!(@args $_self) - }}; -} - -macro_rules! sc_names { - ($_self:ident) => {{ - _names!(@sc $_self) + ($app:expr, $what:ident) => {{ + $app.args.iter().filter_map(|a| a.$what) }}; } macro_rules! _names { - (@args $_self:ident) => {{ - $_self.flags - .iter() - .map(|f| &*f.b.name) - .chain($_self.opts.iter() - .map(|o| &*o.b.name) - .chain($_self.positionals.values() - .map(|p| &*p.b.name))) + (@args $app:expr) => {{ + $app.args.iter().map(|a| &*a.name) }}; - (@sc $_self:ident) => {{ - $_self.subcommands + (@sc $app:expr) => {{ + $app.subcommands .iter() - .map(|s| &*s.p.meta.name) - .chain($_self.subcommands + .map(|s| &*s.name) + .chain($app.subcommands .iter() - .filter(|s| s.p.meta.aliases.is_some()) - .flat_map(|s| s.p.meta.aliases.as_ref().unwrap().iter().map(|&(n, _)| n))) + .filter(|s| s.aliases.is_some()) + .flat_map(|s| s.aliases.as_ref().unwrap().iter().map(|&(n, _)| n))) }} } + +macro_rules! arg_names { + ($app:expr) => {{ + _names!(@args $app) + }}; +} + +macro_rules! sc_names { + ($app:expr) => {{ + _names!(@sc $app) + }}; +} \ No newline at end of file diff --git a/src/suggestions.rs b/src/suggestions.rs index 23832cb3a02..acca15ab50c 100644 --- a/src/suggestions.rs +++ b/src/suggestions.rs @@ -1,10 +1,10 @@ -use app::App; // Third Party #[cfg(feature = "suggestions")] use strsim; // Internal use fmt::Format; +use app::App; /// Produces a string from a given list of possible values which is similar to /// the passed in value `v` with a certain confidence. @@ -42,14 +42,13 @@ where /// 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_flag_suffix<'z, T, I>( +pub fn did_you_mean_flag_suffix<'z, I>( arg: &str, longs: I, subcommands: &'z [App], ) -> (String, Option<&'z str>) where - T: AsRef + 'z, - I: IntoIterator, + I: IntoIterator, { match did_you_mean(arg, longs) { Some(candidate) => { @@ -61,14 +60,9 @@ where return (suffix, Some(candidate)); } None => for subcommand in subcommands { - let opts = subcommand - .p - .flags - .iter() - .filter_map(|f| f.s.long) - .chain(subcommand.p.opts.iter().filter_map(|o| o.s.long)); + let longs = longs!(subcommand); - if let Some(candidate) = did_you_mean(arg, opts) { + if let Some(candidate) = did_you_mean(arg, longs) { let suffix = format!( "\n\tDid you mean to put '{}{}' after the subcommand '{}'?", Format::Good("--"), diff --git a/src/usage_parser.rs b/src/usage_parser.rs index f6d5ac60696..f3d04deec5a 100644 --- a/src/usage_parser.rs +++ b/src/usage_parser.rs @@ -60,17 +60,17 @@ impl<'a> UsageParser<'a> { } } debug_assert!( - !arg.b.name.is_empty(), + !arg.name.is_empty(), format!( "No name found for Arg when parsing usage string: {}", self.usage ) ); - arg.v.num_vals = match arg.v.val_names { + arg.num_vals = match arg.val_names { Some(ref v) if v.len() >= 2 => Some(v.len() as u64), _ => None, }; - debugln!("UsageParser::parse: vals...{:?}", arg.v.val_names); + debugln!("UsageParser::parse: vals...{:?}", arg.val_names); arg } @@ -88,21 +88,21 @@ impl<'a> UsageParser<'a> { let name = &self.usage[self.start..self.pos]; if self.prev == UsageToken::Unknown { debugln!("UsageParser::name: setting name...{}", name); - arg.b.name = name; - if arg.s.long.is_none() && arg.s.short.is_none() { + arg.name = name; + if arg.long.is_none() && arg.short.is_none() { debugln!("UsageParser::name: explicit name set..."); self.explicit_name_set = true; self.prev = UsageToken::Name; } } else { debugln!("UsageParser::name: setting val name...{}", name); - if let Some(ref mut v) = arg.v.val_names { + if let Some(ref mut v) = arg.val_names { let len = v.len(); v.insert(len, name); } else { let mut v = VecMap::new(); v.insert(0, name); - arg.v.val_names = Some(v); + arg.val_names = Some(v); arg.setb(ArgSettings::TakesValue); } self.prev = UsageToken::ValName; @@ -142,10 +142,10 @@ impl<'a> UsageParser<'a> { let name = &self.usage[self.start..self.pos]; if !self.explicit_name_set { debugln!("UsageParser::long: setting name...{}", name); - arg.b.name = name; + arg.name = name; } debugln!("UsageParser::long: setting long...{}", name); - arg.s.long = Some(name); + arg.long = Some(name); self.prev = UsageToken::Long; } @@ -154,12 +154,12 @@ impl<'a> UsageParser<'a> { let start = &self.usage[self.pos..]; let short = start.chars().nth(0).expect(INTERNAL_ERROR_MSG); debugln!("UsageParser::short: setting short...{}", short); - arg.s.short = Some(short); - if arg.b.name.is_empty() { + arg.short = Some(short); + if arg.name.is_empty() { // --long takes precedence but doesn't set self.explicit_name_set let name = &start[..short.len_utf8()]; debugln!("UsageParser::short: setting name...{}", name); - arg.b.name = name; + arg.name = name; } self.prev = UsageToken::Short; } @@ -179,8 +179,8 @@ impl<'a> UsageParser<'a> { if arg.is_set(ArgSettings::TakesValue) { arg.setb(ArgSettings::UseValueDelimiter); arg.unsetb(ArgSettings::ValueDelimiterNotSet); - if arg.v.val_delim.is_none() { - arg.v.val_delim = Some(','); + if arg.val_delim.is_none() { + arg.val_delim = Some(','); } } self.prev = UsageToken::Multiple; @@ -199,7 +199,7 @@ impl<'a> UsageParser<'a> { "UsageParser::help: setting help...{}", &self.usage[self.start..self.pos] ); - arg.b.help = Some(&self.usage[self.start..self.pos]); + arg.help = Some(&self.usage[self.start..self.pos]); self.pos += 1; // Move to next byte to keep from thinking ending ' is a start self.prev = UsageToken::Help; } @@ -227,1034 +227,1034 @@ mod test { #[test] fn create_flag_usage() { let a = Arg::from_usage("[flag] -f 'some help info'"); - assert_eq!(a.b.name, "flag"); - assert_eq!(a.s.short.unwrap(), 'f'); - assert!(a.s.long.is_none()); - assert_eq!(a.b.help.unwrap(), "some help info"); + assert_eq!(a.name, "flag"); + assert_eq!(a.short.unwrap(), 'f'); + assert!(a.long.is_none()); + assert_eq!(a.help.unwrap(), "some help info"); assert!(!a.is_set(ArgSettings::Multiple)); - assert!(a.v.val_names.is_none()); - assert!(a.v.num_vals.is_none()); + assert!(a.val_names.is_none()); + assert!(a.num_vals.is_none()); let b = Arg::from_usage("[flag] --flag 'some help info'"); - assert_eq!(b.b.name, "flag"); - assert_eq!(b.s.long.unwrap(), "flag"); - assert!(b.s.short.is_none()); - assert_eq!(b.b.help.unwrap(), "some help info"); + assert_eq!(b.name, "flag"); + assert_eq!(b.long.unwrap(), "flag"); + assert!(b.short.is_none()); + assert_eq!(b.help.unwrap(), "some help info"); assert!(!b.is_set(ArgSettings::Multiple)); - assert!(a.v.val_names.is_none()); - assert!(a.v.num_vals.is_none()); + assert!(a.val_names.is_none()); + assert!(a.num_vals.is_none()); let b = Arg::from_usage("--flag 'some help info'"); - assert_eq!(b.b.name, "flag"); - assert_eq!(b.s.long.unwrap(), "flag"); - assert!(b.s.short.is_none()); - assert_eq!(b.b.help.unwrap(), "some help info"); + assert_eq!(b.name, "flag"); + assert_eq!(b.long.unwrap(), "flag"); + assert!(b.short.is_none()); + assert_eq!(b.help.unwrap(), "some help info"); assert!(!b.is_set(ArgSettings::Multiple)); - assert!(b.v.val_names.is_none()); - assert!(b.v.num_vals.is_none()); + assert!(b.val_names.is_none()); + assert!(b.num_vals.is_none()); let c = Arg::from_usage("[flag] -f --flag 'some help info'"); - assert_eq!(c.b.name, "flag"); - assert_eq!(c.s.short.unwrap(), 'f'); - assert_eq!(c.s.long.unwrap(), "flag"); - assert_eq!(c.b.help.unwrap(), "some help info"); + assert_eq!(c.name, "flag"); + assert_eq!(c.short.unwrap(), 'f'); + assert_eq!(c.long.unwrap(), "flag"); + assert_eq!(c.help.unwrap(), "some help info"); assert!(!c.is_set(ArgSettings::Multiple)); - assert!(c.v.val_names.is_none()); - assert!(c.v.num_vals.is_none()); + assert!(c.val_names.is_none()); + assert!(c.num_vals.is_none()); let d = Arg::from_usage("[flag] -f... 'some help info'"); - assert_eq!(d.b.name, "flag"); - assert_eq!(d.s.short.unwrap(), 'f'); - assert!(d.s.long.is_none()); - assert_eq!(d.b.help.unwrap(), "some help info"); + assert_eq!(d.name, "flag"); + assert_eq!(d.short.unwrap(), 'f'); + assert!(d.long.is_none()); + assert_eq!(d.help.unwrap(), "some help info"); assert!(d.is_set(ArgSettings::Multiple)); - assert!(d.v.val_names.is_none()); - assert!(d.v.num_vals.is_none()); + assert!(d.val_names.is_none()); + assert!(d.num_vals.is_none()); let e = Arg::from_usage("[flag] -f --flag... 'some help info'"); - assert_eq!(e.b.name, "flag"); - assert_eq!(e.s.long.unwrap(), "flag"); - assert_eq!(e.s.short.unwrap(), 'f'); - assert_eq!(e.b.help.unwrap(), "some help info"); + assert_eq!(e.name, "flag"); + assert_eq!(e.long.unwrap(), "flag"); + assert_eq!(e.short.unwrap(), 'f'); + assert_eq!(e.help.unwrap(), "some help info"); assert!(e.is_set(ArgSettings::Multiple)); - assert!(e.v.val_names.is_none()); - assert!(e.v.num_vals.is_none()); + assert!(e.val_names.is_none()); + assert!(e.num_vals.is_none()); let e = Arg::from_usage("-f --flag... 'some help info'"); - assert_eq!(e.b.name, "flag"); - assert_eq!(e.s.long.unwrap(), "flag"); - assert_eq!(e.s.short.unwrap(), 'f'); - assert_eq!(e.b.help.unwrap(), "some help info"); + assert_eq!(e.name, "flag"); + assert_eq!(e.long.unwrap(), "flag"); + assert_eq!(e.short.unwrap(), 'f'); + assert_eq!(e.help.unwrap(), "some help info"); assert!(e.is_set(ArgSettings::Multiple)); - assert!(e.v.val_names.is_none()); - assert!(e.v.num_vals.is_none()); + assert!(e.val_names.is_none()); + assert!(e.num_vals.is_none()); let e = Arg::from_usage("--flags"); - assert_eq!(e.b.name, "flags"); - assert_eq!(e.s.long.unwrap(), "flags"); - assert!(e.v.val_names.is_none()); - assert!(e.v.num_vals.is_none()); + assert_eq!(e.name, "flags"); + assert_eq!(e.long.unwrap(), "flags"); + assert!(e.val_names.is_none()); + assert!(e.num_vals.is_none()); let e = Arg::from_usage("--flags..."); - assert_eq!(e.b.name, "flags"); - assert_eq!(e.s.long.unwrap(), "flags"); + assert_eq!(e.name, "flags"); + assert_eq!(e.long.unwrap(), "flags"); assert!(e.is_set(ArgSettings::Multiple)); - assert!(e.v.val_names.is_none()); - assert!(e.v.num_vals.is_none()); + assert!(e.val_names.is_none()); + assert!(e.num_vals.is_none()); let e = Arg::from_usage("[flags] -f"); - assert_eq!(e.b.name, "flags"); - assert_eq!(e.s.short.unwrap(), 'f'); - assert!(e.v.val_names.is_none()); - assert!(e.v.num_vals.is_none()); + assert_eq!(e.name, "flags"); + assert_eq!(e.short.unwrap(), 'f'); + assert!(e.val_names.is_none()); + assert!(e.num_vals.is_none()); let e = Arg::from_usage("[flags] -f..."); - assert_eq!(e.b.name, "flags"); - assert_eq!(e.s.short.unwrap(), 'f'); + assert_eq!(e.name, "flags"); + assert_eq!(e.short.unwrap(), 'f'); assert!(e.is_set(ArgSettings::Multiple)); - assert!(e.v.val_names.is_none()); - assert!(e.v.num_vals.is_none()); + assert!(e.val_names.is_none()); + assert!(e.num_vals.is_none()); let a = Arg::from_usage("-f 'some help info'"); - assert_eq!(a.b.name, "f"); - assert_eq!(a.s.short.unwrap(), 'f'); - assert!(a.s.long.is_none()); - assert_eq!(a.b.help.unwrap(), "some help info"); + assert_eq!(a.name, "f"); + assert_eq!(a.short.unwrap(), 'f'); + assert!(a.long.is_none()); + assert_eq!(a.help.unwrap(), "some help info"); assert!(!a.is_set(ArgSettings::Multiple)); - assert!(a.v.val_names.is_none()); - assert!(a.v.num_vals.is_none()); + assert!(a.val_names.is_none()); + assert!(a.num_vals.is_none()); let e = Arg::from_usage("-f"); - assert_eq!(e.b.name, "f"); - assert_eq!(e.s.short.unwrap(), 'f'); - assert!(e.v.val_names.is_none()); - assert!(e.v.num_vals.is_none()); + assert_eq!(e.name, "f"); + assert_eq!(e.short.unwrap(), 'f'); + assert!(e.val_names.is_none()); + assert!(e.num_vals.is_none()); let e = Arg::from_usage("-f..."); - assert_eq!(e.b.name, "f"); - assert_eq!(e.s.short.unwrap(), 'f'); + assert_eq!(e.name, "f"); + assert_eq!(e.short.unwrap(), 'f'); assert!(e.is_set(ArgSettings::Multiple)); - assert!(e.v.val_names.is_none()); - assert!(e.v.num_vals.is_none()); + assert!(e.val_names.is_none()); + assert!(e.num_vals.is_none()); } #[test] fn create_option_usage0() { // Short only let a = Arg::from_usage("[option] -o [opt] 'some help info'"); - assert_eq!(a.b.name, "option"); - assert_eq!(a.s.short.unwrap(), 'o'); - assert!(a.s.long.is_none()); - assert_eq!(a.b.help.unwrap(), "some help info"); + assert_eq!(a.name, "option"); + assert_eq!(a.short.unwrap(), 'o'); + assert!(a.long.is_none()); + assert_eq!(a.help.unwrap(), "some help info"); assert!(!a.is_set(ArgSettings::Multiple)); assert!(a.is_set(ArgSettings::TakesValue)); assert!(!a.is_set(ArgSettings::Required)); assert_eq!( - a.v.val_names.unwrap().values().collect::>(), + a.val_names.unwrap().values().collect::>(), [&"opt"] ); - assert!(a.v.num_vals.is_none()); + assert!(a.num_vals.is_none()); } #[test] fn create_option_usage1() { let b = Arg::from_usage("-o [opt] 'some help info'"); - assert_eq!(b.b.name, "o"); - assert_eq!(b.s.short.unwrap(), 'o'); - assert!(b.s.long.is_none()); - assert_eq!(b.b.help.unwrap(), "some help info"); + assert_eq!(b.name, "o"); + assert_eq!(b.short.unwrap(), 'o'); + assert!(b.long.is_none()); + assert_eq!(b.help.unwrap(), "some help info"); assert!(!b.is_set(ArgSettings::Multiple)); assert!(b.is_set(ArgSettings::TakesValue)); assert!(!b.is_set(ArgSettings::Required)); assert_eq!( - b.v.val_names.unwrap().values().collect::>(), + b.val_names.unwrap().values().collect::>(), [&"opt"] ); - assert!(b.v.num_vals.is_none()); + assert!(b.num_vals.is_none()); } #[test] fn create_option_usage2() { let c = Arg::from_usage(" ### v2.29.2 (2018-01-16) diff --git a/README.md b/README.md index 92b23856995..5a972cfa5bb 100644 --- a/README.md +++ b/README.md @@ -537,9 +537,9 @@ This is inherently an unresolvable crate graph in Cargo right now. Cargo require #### Minimum Version of Rust -`clap` will officially support current stable Rust, minus two releases, but may work with prior releases as well. For example, current stable Rust at the time of this writing is 1.21.0, meaning `clap` is guaranteed to compile with 1.19.0 and beyond. +`clap` will officially support current stable Rust, minus two releases, but may work with prior releases as well. For example, current stable Rust at the time of this writing is 1.35.0, meaning `clap` is guaranteed to compile with 1.33.0 and beyond. -At the 1.22.0 stable release, `clap` will be guaranteed to compile with 1.20.0 and beyond, etc. +At the 1.36.0 stable release, `clap` will be guaranteed to compile with 1.34.0 and beyond, etc. Upon bumping the minimum version of Rust (assuming it's within the stable-2 range), it *must* be clearly annotated in the `CHANGELOG.md` From 2e2616b59eb8f28d0dacd73f2765da9ab2f112d7 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Wed, 19 Jun 2019 09:24:30 -0700 Subject: [PATCH 0370/3337] Fix compiling on windows This fixes compilation errors when building clap targetting windows with: ``` % rustup run nightly cargo check --target x86_64-pc-windows-msvc ``` --- src/build/arg/mod.rs | 4 ++-- src/parse/parser.rs | 6 +++--- src/util/osstringext.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/build/arg/mod.rs b/src/build/arg/mod.rs index 7e368c3eef2..5f28953cc91 100644 --- a/src/build/arg/mod.rs +++ b/src/build/arg/mod.rs @@ -2,8 +2,6 @@ mod settings; pub use self::settings::{ArgFlags, ArgSettings}; // Std -#[cfg(any(target_os = "windows", target_arch = "wasm32"))] -use osstringext::OsStrExt3; use std::borrow::Cow; use std::cmp::{Ord, Ordering}; use std::env; @@ -22,6 +20,8 @@ use yaml_rust; // Internal use crate::build::UsageParser; use crate::util::Key; +#[cfg(any(target_os = "windows", target_arch = "wasm32"))] +use crate::util::OsStrExt3; use crate::INTERNAL_ERROR_MSG; type Validator = Rc Result<(), String>>; diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 2d9aae9d671..314fe73a40a 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -1,6 +1,4 @@ // Std -#[cfg(all(feature = "debug", any(target_os = "windows", target_arch = "wasm32")))] -use osstringext::OsStrExt3; use std::cell::Cell; use std::ffi::{OsStr, OsString}; use std::io::{self, BufWriter, Write}; @@ -26,6 +24,8 @@ use crate::parse::features::suggestions; use crate::parse::Validator; use crate::parse::{ArgMatcher, SubCommand}; use crate::util::{self, ChildGraph, Key, OsStrExt2, EMPTY_HASH}; +#[cfg(all(feature = "debug", any(target_os = "windows", target_arch = "wasm32")))] +use crate::util::OsStrExt3; use crate::INTERNAL_ERROR_MSG; use crate::INVALID_UTF8; @@ -720,7 +720,7 @@ where debugln!("Parser::possible_subcommand: arg={:?}", arg_os); fn starts(h: &str, n: &OsStr) -> bool { #[cfg(target_os = "windows")] - use osstringext::OsStrExt3; + use crate::util::OsStrExt3; #[cfg(not(target_os = "windows"))] use std::os::unix::ffi::OsStrExt; diff --git a/src/util/osstringext.rs b/src/util/osstringext.rs index af4f00b3e18..29c1b933cd5 100644 --- a/src/util/osstringext.rs +++ b/src/util/osstringext.rs @@ -2,7 +2,7 @@ use std::ffi::OsStr; #[cfg(not(any(target_os = "windows", target_arch = "wasm32")))] use std::os::unix::ffi::OsStrExt; #[cfg(any(target_os = "windows", target_arch = "wasm32"))] -use INVALID_UTF8; +use crate::INVALID_UTF8; #[cfg(any(target_os = "windows", target_arch = "wasm32"))] pub trait OsStrExt3 { From 2664703587f8ca0c67a77c91db82d218327da67c Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Wed, 19 Jun 2019 08:53:15 -0700 Subject: [PATCH 0371/3337] Fix most warnings This patch: * Removes unused `App::contains_long` * Removes `std::ascii::AsciiExt` (deprecated in 1.26) * Replaces `trim_left_matches` with `trim_start_matches` (added in 1.30) * Adds `dyn` (added in 1.27) * Removes unused `mkeymap::KeyType::{is_short,is_long}` * Cleans up unused imports --- Cargo.toml | 1 - src/build/app/mod.rs | 7 ------- src/build/app/settings.rs | 1 - src/build/arg/mod.rs | 7 +++---- src/build/arg/settings.rs | 2 -- src/macros.rs | 2 -- src/mkeymap.rs | 12 ------------ src/output/help.rs | 4 ++-- src/parse/features/suggestions.rs | 2 +- tests/possible_values.rs | 3 --- 10 files changed, 6 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bc06bbe9fe0..9544e5f5e17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,4 @@ [package] - name = "clap" version = "3.0.0-beta.1" authors = ["Kevin K. "] diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index 5f4727e9d6d..6f212b9dd46 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -1769,13 +1769,6 @@ impl<'b> App<'b> { } } - pub(crate) fn contains_long(&self, l: &str) -> bool { - if !self.is_set(AppSettings::Built) { - panic!("If App::_build hasn't been called, manually search through Arg longs"); - } - self.args.contains_long(l) - } - pub(crate) fn contains_short(&self, s: char) -> bool { if !self.is_set(AppSettings::Built) { panic!("If App::_build hasn't been called, manually search through Arg shorts"); diff --git a/src/build/app/settings.rs b/src/build/app/settings.rs index 8c421d6eb15..5407144db1d 100644 --- a/src/build/app/settings.rs +++ b/src/build/app/settings.rs @@ -1,6 +1,5 @@ // Std #[allow(unused_imports)] -use std::ascii::AsciiExt; use std::ops::BitOr; use std::str::FromStr; diff --git a/src/build/arg/mod.rs b/src/build/arg/mod.rs index 5f28953cc91..b1655252e65 100644 --- a/src/build/arg/mod.rs +++ b/src/build/arg/mod.rs @@ -24,8 +24,8 @@ use crate::util::Key; use crate::util::OsStrExt3; use crate::INTERNAL_ERROR_MSG; -type Validator = Rc Result<(), String>>; -type ValidatorOs = Rc Result<(), String>>; +type Validator = Rc Result<(), String>>; +type ValidatorOs = Rc Result<(), String>>; type Id = u64; @@ -305,7 +305,7 @@ impl<'help> Arg<'help> { /// assert!(m.is_present("cfg")); /// ``` pub fn long(mut self, l: &'help str) -> Self { - self.long = Some(l.trim_left_matches(|c| c == '-')); + self.long = Some(l.trim_start_matches(|c| c == '-')); self } @@ -3281,7 +3281,6 @@ impl<'help> Arg<'help> { /// /// ```rust /// # use clap::{App, Arg, ArgSettings}; - /// # use std::ascii::AsciiExt; /// let m = App::new("pv") /// .arg(Arg::with_name("option") /// .long("--option") diff --git a/src/build/arg/settings.rs b/src/build/arg/settings.rs index b29f6ef74bd..89aa9f42c0f 100644 --- a/src/build/arg/settings.rs +++ b/src/build/arg/settings.rs @@ -1,6 +1,4 @@ // Std -#[allow(unused_imports)] -use std::ascii::AsciiExt; use std::str::FromStr; bitflags! { diff --git a/src/macros.rs b/src/macros.rs index 82493c3c291..41982643a82 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -324,8 +324,6 @@ macro_rules! arg_enum { type Err = String; fn from_str(s: &str) -> ::std::result::Result { - #[allow(unused_imports)] - use ::std::ascii::AsciiExt; match s { $(stringify!($v) | _ if s.eq_ignore_ascii_case(stringify!($v)) => Ok($e::$v)),+, diff --git a/src/mkeymap.rs b/src/mkeymap.rs index ca9af5d03b4..f1ef72daace 100644 --- a/src/mkeymap.rs +++ b/src/mkeymap.rs @@ -30,18 +30,6 @@ impl KeyType { _ => false, } } - pub(crate) fn is_short(&self) -> bool { - match *self { - KeyType::Short(_) => true, - _ => false, - } - } - pub(crate) fn is_long(&self) -> bool { - match *self { - KeyType::Long(_) => true, - _ => false, - } - } } impl PartialEq<&str> for KeyType { diff --git a/src/output/help.rs b/src/output/help.rs index 2f5f27d6bc0..5fd115ff03d 100644 --- a/src/output/help.rs +++ b/src/output/help.rs @@ -33,7 +33,7 @@ const TAB: &str = " "; /// /// Wraps a writer stream providing different methods to generate help for `clap` objects. pub struct Help<'b, 'c, 'd, 'w> { - writer: &'w mut Write, + writer: &'w mut dyn Write, parser: &'d Parser<'b, 'c>, next_line_help: bool, hide_pv: bool, @@ -48,7 +48,7 @@ pub struct Help<'b, 'c, 'd, 'w> { // Public Functions impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { /// Create a new `Help` instance. - pub fn new(w: &'w mut Write, parser: &'d Parser<'b, 'c>, use_long: bool, stderr: bool) -> Self { + pub fn new(w: &'w mut dyn Write, parser: &'d Parser<'b, 'c>, use_long: bool, stderr: bool) -> Self { debugln!("Help::new;"); let term_w = match parser.app.term_w { Some(0) => usize::MAX, diff --git a/src/parse/features/suggestions.rs b/src/parse/features/suggestions.rs index 23814d4db7a..a7b38ce24bb 100644 --- a/src/parse/features/suggestions.rs +++ b/src/parse/features/suggestions.rs @@ -3,7 +3,7 @@ use strsim; // Internal -use crate::build::{App, Propagation}; +use crate::build::App; use crate::output::fmt::Format; /// Produces a string from a given list of possible values which is similar to diff --git a/tests/possible_values.rs b/tests/possible_values.rs index 9248e5739f9..176ecb646dd 100644 --- a/tests/possible_values.rs +++ b/tests/possible_values.rs @@ -3,9 +3,6 @@ extern crate regex; include!("../clap-test.rs"); -#[allow(unused_imports)] -use std::ascii::AsciiExt; - use clap::{App, Arg, ErrorKind}; #[cfg(feature = "suggestions")] From fb138ab44c4e7194e357dd60bb5d05e98d163cdc Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Wed, 19 Jun 2019 15:49:01 -0700 Subject: [PATCH 0372/3337] Disable testing yaml for now v3-master's yaml feature has been broken for some time. Disable it for now to get the builds back to green. --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 53e566359c2..f4c335f5d6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,8 @@ before_script: script: - | travis-cargo test -- --verbose --no-default-features && - travis-cargo --skip nightly test -- --verbose --features "yaml unstable" && - travis-cargo --only nightly test -- --verbose --features "yaml unstable nightly" && + travis-cargo --skip nightly test -- --verbose --features "unstable" && + travis-cargo --only nightly test -- --verbose --features "unstable nightly" && travis-cargo --only nightly bench addons: apt: @@ -50,7 +50,7 @@ after_success: cd ../.. && rm -rf kcov-master && cargo clean && - cargo test --no-run --features "yaml unstable" && + cargo test --no-run --features "unstable" && for file in target/debug/*-*; do mkdir -p "target/cov/$(basename $file)"; kcov --exclude-pattern=/.cargo --verify "target/cov/$(basename $file)" "$file"; done && kcov --coveralls-id=$TRAVIS_JOB_ID --merge target/cov target/cov/* && echo "Uploaded code coverage" From 4a20c6aaefceb2f8eaec4d6e442d27d22deb1b2c Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Wed, 19 Jun 2019 15:49:09 -0700 Subject: [PATCH 0373/3337] Fix compiling with "--features yaml" --- .travis.yml | 6 +++--- examples/17_yaml.rs | 2 +- src/build/app/mod.rs | 7 +------ src/build/arg_group.rs | 6 +++--- src/parse/matches/subcommand.rs | 4 ---- tests/app.yml | 5 ++++- tests/app_2space.yml | 7 +++++-- tests/yaml.rs | 6 +++--- 8 files changed, 20 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index f4c335f5d6b..53e566359c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,8 @@ before_script: script: - | travis-cargo test -- --verbose --no-default-features && - travis-cargo --skip nightly test -- --verbose --features "unstable" && - travis-cargo --only nightly test -- --verbose --features "unstable nightly" && + travis-cargo --skip nightly test -- --verbose --features "yaml unstable" && + travis-cargo --only nightly test -- --verbose --features "yaml unstable nightly" && travis-cargo --only nightly bench addons: apt: @@ -50,7 +50,7 @@ after_success: cd ../.. && rm -rf kcov-master && cargo clean && - cargo test --no-run --features "unstable" && + cargo test --no-run --features "yaml unstable" && for file in target/debug/*-*; do mkdir -p "target/cov/$(basename $file)"; kcov --exclude-pattern=/.cargo --verify "target/cov/$(basename $file)" "$file"; done && kcov --coveralls-id=$TRAVIS_JOB_ID --merge target/cov target/cov/* && echo "Uploaded code coverage" diff --git a/examples/17_yaml.rs b/examples/17_yaml.rs index 81bdbaf97bd..c2d9a0d7f66 100644 --- a/examples/17_yaml.rs +++ b/examples/17_yaml.rs @@ -32,7 +32,7 @@ fn main() { // Finally we call get_matches() to start the parsing process. We use the matches just as we // normally would let yml = load_yaml!("17_yaml.yml"); - let m = App::from_yaml(yml).get_matches(); + let m = App::from(yml).get_matches(); // Because the example 17_yaml.yml is rather large we'll just look a single arg so you can // see that it works... diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index 6f212b9dd46..fa312690b13 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -1911,11 +1911,6 @@ impl<'a> From<&'a Yaml> for App<'a> { yaml_str!(a, yaml, about); yaml_str!(a, yaml, before_help); yaml_str!(a, yaml, after_help); - yaml_str!(a, yaml, template); - yaml_str!(a, yaml, usage); - yaml_str!(a, yaml, help); - yaml_str!(a, yaml, help_message); - yaml_str!(a, yaml, version_message); yaml_str!(a, yaml, alias); yaml_str!(a, yaml, visible_alias); @@ -2007,7 +2002,7 @@ impl<'a> From<&'a Yaml> for App<'a> { } if let Some(v) = yaml["subcommands"].as_vec() { for sc_yaml in v { - a = a.subcommand(::from_yaml(sc_yaml)); + a = a.subcommand(App::from(sc_yaml)); } } if let Some(v) = yaml["groups"].as_vec() { diff --git a/src/build/arg_group.rs b/src/build/arg_group.rs index 3689b6c96c1..c29080cc056 100644 --- a/src/build/arg_group.rs +++ b/src/build/arg_group.rs @@ -625,9 +625,9 @@ requires: - r4"; let yml = &YamlLoader::load_from_str(g_yaml).expect("failed to load YAML file")[0]; let g = ArgGroup::from_yaml(yml); - let args = vec!["a1", "a4", "a2", "a3"]; - let reqs = vec!["r1", "r2", "r3", "r4"]; - let confs = vec!["c1", "c2", "c3", "c4"]; + let args = vec!["a1".key(), "a4".key(), "a2".key(), "a3".key()]; + let reqs = vec!["r1".key(), "r2".key(), "r3".key(), "r4".key()]; + let confs = vec!["c1".key(), "c2".key(), "c3".key(), "c4".key()]; assert_eq!(g.args, args); assert_eq!(g.requires, Some(reqs)); assert_eq!(g.conflicts, Some(confs)); diff --git a/src/parse/matches/subcommand.rs b/src/parse/matches/subcommand.rs index 273192f06a7..06869bf95d0 100644 --- a/src/parse/matches/subcommand.rs +++ b/src/parse/matches/subcommand.rs @@ -1,7 +1,3 @@ -// Third Party -#[cfg(feature = "yaml")] -use yaml_rust::Yaml; - // Internal use crate::ArgMatches; diff --git a/tests/app.yml b/tests/app.yml index c34ca1bf5ff..e98b20ab63f 100644 --- a/tests/app.yml +++ b/tests/app.yml @@ -4,8 +4,11 @@ about: tests clap library author: Kevin K. settings: - ArgRequiredElseHelp -help_message: prints help with a nonstandard description args: + - help: + short: h + long: help + help: prints help with a nonstandard description - opt: short: o long: option diff --git a/tests/app_2space.yml b/tests/app_2space.yml index 3413935e549..76796378fed 100644 --- a/tests/app_2space.yml +++ b/tests/app_2space.yml @@ -4,10 +4,13 @@ about: tests clap library author: Kevin K. settings: - ArgRequiredElseHelp -help_message: prints help with a nonstandard description args: + - help: + short: h + long: help + help: prints help with a nonstandard description - opt: short: o long: option multiple: true - help: tests options \ No newline at end of file + help: tests options diff --git a/tests/yaml.rs b/tests/yaml.rs index 1cd516a5e3b..9b9ed64c6cd 100644 --- a/tests/yaml.rs +++ b/tests/yaml.rs @@ -8,7 +8,7 @@ use clap::App; #[test] fn create_app_from_yaml() { let yml = load_yaml!("app.yml"); - App::from_yaml(yml); + App::from(yml); } // TODO: Uncomment to test yaml with 2 spaces https://github.com/chyh1990/yaml-rust/issues/101 @@ -21,7 +21,7 @@ fn create_app_from_yaml() { #[test] fn help_message() { let yml = load_yaml!("app.yml"); - let mut app = App::from_yaml(yml); + let mut app = App::from(yml); // Generate the full help message! let _ = app.try_get_matches_from_mut(Vec::::new()); @@ -36,7 +36,7 @@ fn help_message() { #[test] fn author() { let yml = load_yaml!("app.yml"); - let mut app = App::from_yaml(yml); + let mut app = App::from(yml); // Generate the full help message! let _ = app.try_get_matches_from_mut(Vec::::new()); From cacc23473c64ec8a57471285b3978face05b4d86 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Wed, 19 Jun 2019 16:58:18 -0700 Subject: [PATCH 0374/3337] Remove v2 depecated features. This patch: * Removes the `ArgSettings::Global` variant, and replaces all users of it to `Arg::global(...)`. The variant itself is lifted up into a field on Arg. This was deprecated in clap 2.32.0. * Removes AppFlags::PropagateGlobalValuesDown. This was deprecated in clap 2.27.0. * Removes `Arg::empty_values`. This was deprecated in clap 2.30.0. * Removes `ArgMatches::usage`. This was deprecated in clap 2.32.0. --- benches/03_complex.rs | 5 ++-- src/build/app/mod.rs | 6 ++--- src/build/app/settings.rs | 41 -------------------------------- src/build/arg/mod.rs | 30 ++++++----------------- src/build/arg/settings.rs | 10 -------- src/parse/matches/arg_matches.rs | 8 ------- tests/propagate_globals.rs | 6 ++--- 7 files changed, 16 insertions(+), 90 deletions(-) diff --git a/benches/03_complex.rs b/benches/03_complex.rs index d4adb669159..f4025dcc431 100644 --- a/benches/03_complex.rs +++ b/benches/03_complex.rs @@ -19,7 +19,7 @@ macro_rules! create_app { .author("Kevin K. ") .arg("-o --option=[opt]... 'tests options'") .arg("[positional] 'tests positionals'") - .arg(Arg::from("-f --flag... 'tests flags'").setting(ArgSettings::Global)) + .arg(Arg::from("-f --flag... 'tests flags'").global(true)) .args(&[ Arg::from("[flag2] -F 'tests flags with exclusions'") .conflicts_with("flag") @@ -76,7 +76,8 @@ fn create_app_builder(b: &mut Bencher) { .short('f') .help("tests flags") .long("flag") - .settings(&[ArgSettings::MultipleOccurrences, ArgSettings::Global]), + .global(true) + .settings(&[ArgSettings::MultipleOccurrences]), ) .arg( Arg::with_name("flag2") diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index fa312690b13..b2537fc50c9 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -1398,7 +1398,7 @@ impl<'b> App<'b> { .args .args .iter() - .filter(|a| a.is_set(ArgSettings::Global)) + .filter(|a| a.global) .map(|ga| ga.id) .collect(); @@ -1515,7 +1515,7 @@ impl<'b> App<'b> { .args .args .iter() - .filter(|a| a.is_set(ArgSettings::Global)) + .filter(|a| a.global) { $sc.args.push(a.clone()); } @@ -1666,7 +1666,7 @@ impl<'b> App<'b> { ); } assert!( - !(a.is_set(ArgSettings::Required) && a.is_set(ArgSettings::Global)), + !(a.is_set(ArgSettings::Required) && a.global), "Global arguments cannot be required.\n\n\t'{}' is marked as \ global and required", a.name diff --git a/src/build/app/settings.rs b/src/build/app/settings.rs index 5407144db1d..cec618c1aa9 100644 --- a/src/build/app/settings.rs +++ b/src/build/app/settings.rs @@ -61,7 +61,6 @@ impl Default for AppFlags { fn default() -> Self { AppFlags(Flags::UTF8_NONE | Flags::COLOR_AUTO) } } -#[allow(deprecated)] impl AppFlags { pub fn new() -> Self { AppFlags::default() } pub fn zeroed() -> Self { AppFlags(Flags::empty()) } @@ -90,7 +89,6 @@ impl AppFlags { NoAutoHelp => Flags::NO_AUTO_HELP, NoAutoVersion => Flags::NO_AUTO_VERSION, NoBinaryName => Flags::NO_BIN_NAME, - PropagateGlobalValuesDown=> Flags::PROPAGATE_VALS_DOWN, StrictUtf8 => Flags::UTF8_STRICT, SubcommandsNegateReqs => Flags::SC_NEGATE_REQS, SubcommandRequired => Flags::SC_REQUIRED, @@ -669,45 +667,6 @@ pub enum AppSettings { /// ``` NextLineHelp, - /// **DEPRECATED**: This setting is no longer required in order to propagate values up or down - /// - /// Specifies that the parser should propagate global arg's values down or up through any *used* - /// child subcommands. Meaning, if a subcommand wasn't used, the values won't be propagated to - /// said subcommand. - /// - /// # Examples - /// - /// ```rust - /// # use clap::{App, Arg, AppSettings, }; - /// let m = App::new("myprog") - /// .arg(Arg::from("[cmd] 'command to run'") - /// .global(true)) - /// .subcommand(App::new("foo")) - /// .get_matches_from(vec!["myprog", "set", "foo"]); - /// - /// assert_eq!(m.value_of("cmd"), Some("set")); - /// - /// let sub_m = m.subcommand_matches("foo").unwrap(); - /// assert_eq!(sub_m.value_of("cmd"), Some("set")); - /// ``` - /// Now doing the same thing, but *not* using any subcommands will result in the value not being - /// propagated down. - /// - /// ```rust - /// # use clap::{App, Arg, AppSettings, }; - /// let m = App::new("myprog") - /// .arg(Arg::from("[cmd] 'command to run'") - /// .global(true)) - /// .subcommand(App::new("foo")) - /// .get_matches_from(vec!["myprog", "set"]); - /// - /// assert_eq!(m.value_of("cmd"), Some("set")); - /// - /// assert!(m.subcommand_matches("foo").is_none()); - /// ``` - #[deprecated(since = "2.27.0", note = "No longer required to propagate values")] - PropagateGlobalValuesDown, - /// Allows [``]s to override all requirements of the parent command. /// For example if you had a subcommand or top level application with a required argument /// that is only required as long as there is no subcommand present, diff --git a/src/build/arg/mod.rs b/src/build/arg/mod.rs index b1655252e65..b6fc4cfeaf4 100644 --- a/src/build/arg/mod.rs +++ b/src/build/arg/mod.rs @@ -114,6 +114,8 @@ pub struct Arg<'help> { pub r_ifs: Option>, #[doc(hidden)] pub help_heading: Option<&'help str>, + #[doc(hidden)] + pub global: bool, } impl<'help> Arg<'help> { @@ -191,7 +193,6 @@ impl<'help> Arg<'help> { "multiple" => yaml_to_bool!(a, v, multiple), "hidden" => yaml_to_bool!(a, v, hidden), "next_line_help" => yaml_to_bool!(a, v, next_line_help), - "empty_values" => yaml_to_bool!(a, v, empty_values), "group" => yaml_to_str!(a, v, group), "number_of_values" => yaml_to_u64!(a, v, number_of_values), "max_values" => yaml_to_u64!(a, v, max_values), @@ -3020,7 +3021,7 @@ impl<'help> Arg<'help> { /// # use clap::{App, Arg, ArgSettings}; /// Arg::with_name("debug") /// .short('d') - /// .setting(ArgSettings::Global) + /// .global(true) /// # ; /// ``` /// @@ -3034,7 +3035,7 @@ impl<'help> Arg<'help> { /// .arg(Arg::with_name("verb") /// .long("verbose") /// .short('v') - /// .setting(ArgSettings::Global)) + /// .global(true)) /// .subcommand(App::new("test")) /// .subcommand(App::new("do-stuff")) /// .get_matches_from(vec![ @@ -3050,12 +3051,9 @@ impl<'help> Arg<'help> { /// [`ArgMatches`]: ./struct.ArgMatches.html /// [`ArgMatches::is_present("flag")`]: ./struct.ArgMatches.html#method.is_present /// [`Arg`]: ./struct.Arg.html - pub fn global(self, g: bool) -> Self { - if g { - self.setting(ArgSettings::Global) - } else { - self.unset_setting(ArgSettings::Global) - } + pub fn global(mut self, g: bool) -> Self { + self.global = g; + self } /// Specifies that *multiple values* may only be set using the delimiter. This means if an @@ -3210,20 +3208,6 @@ impl<'help> Arg<'help> { } } - /// **Deprecated** - #[deprecated( - since = "2.30.0", - note = "Use `Arg::setting(ArgSettings::AllowEmptyValues)` instead. Will be removed in v3.0-beta" - )] - pub fn empty_values(mut self, ev: bool) -> Self { - if ev { - self.setting(ArgSettings::AllowEmptyValues) - } else { - self = self.setting(ArgSettings::TakesValue); - self.unset_setting(ArgSettings::AllowEmptyValues) - } - } - /// Hides an argument from help message output. /// /// **NOTE:** This does **not** hide the argument from usage strings on error diff --git a/src/build/arg/settings.rs b/src/build/arg/settings.rs index 89aa9f42c0f..0d431fd81f5 100644 --- a/src/build/arg/settings.rs +++ b/src/build/arg/settings.rs @@ -40,7 +40,6 @@ impl ArgFlags { MultipleOccurrences => Flags::MULTIPLE_OCC, MultipleValues => Flags::MULTIPLE_VALS, AllowEmptyValues => Flags::EMPTY_VALS, - Global => Flags::GLOBAL, Hidden => Flags::HIDDEN, TakesValue => Flags::TAKES_VAL, UseValueDelimiter => Flags::USE_DELIM, @@ -82,10 +81,6 @@ pub enum ArgSettings { MultipleOccurrences, /// Allows an arg accept empty values such as `""` AllowEmptyValues, - /// Sets an arg to be global (i.e. exist in all subcommands) - /// **DEPRECATED** - #[deprecated(since = "2.32.0", note = "Use `App::global_arg` instead")] - Global, /// Hides an arg from the help message Hidden, /// Allows an argument to take a value (such as `--option value`) @@ -128,7 +123,6 @@ impl FromStr for ArgSettings { fn from_str(s: &str) -> Result::Err> { match &*s.to_ascii_lowercase() { "required" => Ok(ArgSettings::Required), - "global" => Ok(ArgSettings::Global), "allowemptyvalues" => Ok(ArgSettings::AllowEmptyValues), "hidden" => Ok(ArgSettings::Hidden), "takesvalue" => Ok(ArgSettings::TakesValue), @@ -165,10 +159,6 @@ mod test { "allowemptyvalues".parse::().unwrap(), ArgSettings::AllowEmptyValues ); - assert_eq!( - "global".parse::().unwrap(), - ArgSettings::Global - ); assert_eq!( "hidepossiblevalues".parse::().unwrap(), ArgSettings::HidePossibleValues diff --git a/src/parse/matches/arg_matches.rs b/src/parse/matches/arg_matches.rs index 57e755942ea..7821bb60e57 100644 --- a/src/parse/matches/arg_matches.rs +++ b/src/parse/matches/arg_matches.rs @@ -744,14 +744,6 @@ impl ArgMatches { .as_ref() .map_or(("", None), |sc| (&sc.name[..], Some(&sc.matches))) } - - // @TODO @v3-beta: remove - /// **Deprecated** - #[deprecated( - since = "2.32.0", - note = "Use App::usage instead. Will be removed in v3-beta" - )] - pub fn usage(&self) -> &str { panic!("Use App::usage instead. Will be removed in v3-beta") } } // The following were taken and adapated from vec_map source diff --git a/tests/propagate_globals.rs b/tests/propagate_globals.rs index cfc28a0a40a..961f1f5d0c7 100644 --- a/tests/propagate_globals.rs +++ b/tests/propagate_globals.rs @@ -12,7 +12,7 @@ mod tests { Arg::with_name("GLOBAL_ARG") .long("global-arg") .help("Specifies something needed by the subcommands") - .setting(ArgSettings::Global) + .global(true) .setting(ArgSettings::TakesValue) .default_value("default_value"), ) @@ -20,8 +20,8 @@ mod tests { Arg::with_name("GLOBAL_FLAG") .long("global-flag") .help("Specifies something needed by the subcommands") - .setting(ArgSettings::MultipleOccurrences) - .setting(ArgSettings::Global), + .global(true) + .setting(ArgSettings::MultipleOccurrences), ) .subcommand(App::new("outer").subcommand(App::new("inner"))) } From 5ff2bb4dcf4680a4e3d0d74dda02c7a5989b4305 Mon Sep 17 00:00:00 2001 From: Jeremy Stucki Date: Fri, 21 Jun 2019 09:02:33 +0200 Subject: [PATCH 0375/3337] Use map instead of match on Option --- src/parse/features/suggestions.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/parse/features/suggestions.rs b/src/parse/features/suggestions.rs index a7b38ce24bb..73fb0420162 100644 --- a/src/parse/features/suggestions.rs +++ b/src/parse/features/suggestions.rs @@ -25,10 +25,8 @@ where candidate = Some((confidence, pv.as_ref().to_owned())); } } - match candidate { - None => None, - Some((_, candidate)) => Some(candidate.to_owned()), - } + + candidate.map(|(_, candidate)| candidate) } #[cfg(not(feature = "suggestions"))] From c5c64d48b7d96a0ffc2f8181ccf3a27b0d4f62b8 Mon Sep 17 00:00:00 2001 From: Jeremy Stucki Date: Fri, 21 Jun 2019 09:04:54 +0200 Subject: [PATCH 0376/3337] Use map_or instead of match --- src/util/strext.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/strext.rs b/src/util/strext.rs index 6f81367ab2c..5886d6cb880 100644 --- a/src/util/strext.rs +++ b/src/util/strext.rs @@ -8,9 +8,9 @@ impl _StrExt for str { if index == self.len() { return true; } - match self.as_bytes().get(index) { - None => false, - Some(&b) => b < 128 || b >= 192, - } + + self.as_bytes() + .get(index) + .map_or(false, |&b| b < 128 || b >= 192) } } From c6327bc22fd9ae770ceb5b900dcb1f31f1c50f49 Mon Sep 17 00:00:00 2001 From: Jeremy Stucki Date: Fri, 21 Jun 2019 09:06:06 +0200 Subject: [PATCH 0377/3337] Use initialization shorthand --- src/output/help.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/output/help.rs b/src/output/help.rs index 5fd115ff03d..0a6b533b056 100644 --- a/src/output/help.rs +++ b/src/output/help.rs @@ -74,10 +74,10 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { next_line_help: nlh, hide_pv, term_w, - color: color, + color, longest: 0, force_next_line: false, - cizer: cizer, + cizer, use_long, } } From 0f6ffe3612df9d783cea9d36374952d62d510fd1 Mon Sep 17 00:00:00 2001 From: Jeremy Stucki Date: Fri, 21 Jun 2019 09:06:58 +0200 Subject: [PATCH 0378/3337] Remove needless lifetimes --- tests/propagate_globals.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/propagate_globals.rs b/tests/propagate_globals.rs index 961f1f5d0c7..cdd232415e4 100644 --- a/tests/propagate_globals.rs +++ b/tests/propagate_globals.rs @@ -30,12 +30,12 @@ mod tests { app.get_matches_from(argv.split(" ").collect::>()) } - fn get_outer_matches<'a>(m: &'a ArgMatches) -> &'a ArgMatches { + fn get_outer_matches(m: &ArgMatches) -> &ArgMatches { m.subcommand_matches("outer") .expect("could not access outer subcommand") } - fn get_inner_matches<'a>(m: &'a ArgMatches) -> &'a ArgMatches { + fn get_inner_matches(m: &ArgMatches) -> &ArgMatches { get_outer_matches(m) .subcommand_matches("inner") .expect("could not access inner subcommand") From 9e10c455052f62acf17faea921ea724e9ff7e98d Mon Sep 17 00:00:00 2001 From: Jeremy Stucki Date: Fri, 21 Jun 2019 09:54:19 +0200 Subject: [PATCH 0379/3337] Use map instead of 'if let' --- src/parse/matches/arg_matches.rs | 44 +++++++++++++------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/src/parse/matches/arg_matches.rs b/src/parse/matches/arg_matches.rs index 7821bb60e57..26c1c237141 100644 --- a/src/parse/matches/arg_matches.rs +++ b/src/parse/matches/arg_matches.rs @@ -213,14 +213,14 @@ impl ArgMatches { /// [`Values`]: ./struct.Values.html /// [`Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html pub fn values_of(&self, id: T) -> Option> { - if let Some(arg) = self.args.get(&id.key()) { + self.args.get(&id.key()).map(|arg| { fn to_str_slice(o: &OsString) -> &str { o.to_str().expect(INVALID_UTF8) } let to_str_slice: fn(&OsString) -> &str = to_str_slice; // coerce to fn pointer - return Some(Values { + + Values { iter: arg.vals.iter().map(to_str_slice), - }); - } - None + } + }) } /// Gets the lossy values of a specific argument. If the option wasn't present at runtime @@ -248,15 +248,12 @@ impl ArgMatches { /// assert_eq!(itr.next(), None); /// ``` pub fn values_of_lossy(&self, id: T) -> Option> { - if let Some(arg) = self.args.get(&id.key()) { - return Some( - arg.vals - .iter() - .map(|v| v.to_string_lossy().into_owned()) - .collect(), - ); - } - None + self.args.get(&id.key()).map(|arg| { + arg.vals + .iter() + .map(|v| v.to_string_lossy().into_owned()) + .collect() + }) } /// Gets a [`OsValues`] struct which is implements [`Iterator`] for [`OsString`] values of a @@ -293,12 +290,10 @@ impl ArgMatches { pub fn values_of_os<'a, T: Key>(&'a self, id: T) -> Option> { fn to_str_slice(o: &OsString) -> &OsStr { &*o } let to_str_slice: fn(&'a OsString) -> &'a OsStr = to_str_slice; // coerce to fn pointer - if let Some(arg) = self.args.get(&id.key()) { - return Some(OsValues { - iter: arg.vals.iter().map(to_str_slice), - }); - } - None + + self.args.get(&id.key()).map(|arg| OsValues { + iter: arg.vals.iter().map(to_str_slice), + }) } /// Returns `true` if an argument was present at runtime, otherwise `false`. @@ -584,12 +579,9 @@ impl ArgMatches { /// [`ArgMatches::index_of`]: ./struct.ArgMatches.html#method.index_of /// [delimiter]: ./struct.Arg.html#method.value_delimiter pub fn indices_of(&self, id: T) -> Option> { - if let Some(arg) = self.args.get(&id.key()) { - return Some(Indices { - iter: arg.indices.iter().cloned(), - }); - } - None + self.args.get(&id.key()).map(|arg| Indices { + iter: arg.indices.iter().cloned(), + }) } /// Because [`Subcommand`]s are essentially "sub-[`App`]s" they have their own [`ArgMatches`] From 89f8cc6929b2f9edec750955014ec4cf89cfbb92 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Sun, 23 Jun 2019 11:36:35 -0500 Subject: [PATCH 0380/3337] fix: add 'require_equals' support to YAML parsing --- src/build/arg/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/build/arg/mod.rs b/src/build/arg/mod.rs index b6fc4cfeaf4..690cccb4a87 100644 --- a/src/build/arg/mod.rs +++ b/src/build/arg/mod.rs @@ -200,6 +200,7 @@ impl<'help> Arg<'help> { "value_name" => yaml_to_str!(a, v, value_name), "use_delimiter" => yaml_to_bool!(a, v, use_delimiter), "allow_hyphen_values" => yaml_to_bool!(a, v, allow_hyphen_values), + "require_equals" => yaml_to_bool!(a, v, require_equals), "require_delimiter" => yaml_to_bool!(a, v, require_delimiter), "value_delimiter" => yaml_to_str!(a, v, value_delimiter), "required_unless" => yaml_to_str!(a, v, required_unless), From 83b0ab506444f585f4e160d61363e069935adb69 Mon Sep 17 00:00:00 2001 From: Tyler Ruckinger Date: Tue, 25 Jun 2019 19:02:53 -0400 Subject: [PATCH 0381/3337] Fixup tests --- tests/groups.rs | 7 ++++++- tests/help.rs | 31 ++++++++++++++++++++++++++++++- tests/multiple_values.rs | 23 +++++++++++++++++------ tests/positionals.rs | 11 ++++++----- tests/unique_args.rs | 16 +++++++++++----- 5 files changed, 70 insertions(+), 18 deletions(-) diff --git a/tests/groups.rs b/tests/groups.rs index 4b10678a17a..9f3a6eda345 100644 --- a/tests/groups.rs +++ b/tests/groups.rs @@ -45,8 +45,13 @@ fn required_group_missing_arg() { assert_eq!(err.kind, ErrorKind::MissingRequiredArgument); } +// This tests a programmer error and will only succeed with debug_assertions +// #[cfg(debug_assertions)] #[test] -#[should_panic] +// This used to provide a nice, programmer-friendly error. +// Now the error directs the programmer to file a bug report with clap. +// #[should_panic(expected = "The group 'req' contains the arg 'flg' that doesn't actually exist.")] +#[should_panic(expected = "internal error")] fn non_existing_arg() { let _ = App::new("group") .arg("-f, --flag 'some flag'") diff --git a/tests/help.rs b/tests/help.rs index 6a1c650691e..93397d1ebc2 100644 --- a/tests/help.rs +++ b/tests/help.rs @@ -4,7 +4,7 @@ extern crate regex; include!("../clap-test.rs"); -use clap::{App, AppSettings, Arg, ArgSettings, ErrorKind}; +use clap::{App, AppSettings, Arg, ArgGroup, ArgSettings, ErrorKind}; static REQUIRE_DELIM_HELP: &'static str = "test 1.3 Kevin K. @@ -532,6 +532,22 @@ OPTIONS: NETWORKING: -n, --no-proxy Do not use system proxy settings"; +static ISSUE_1487: &'static str = "test + +USAGE: + ctest + +ARGS: + + + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information +"; + + + fn setup() -> App<'static> { App::new("test") .author("Kevin K.") @@ -1505,3 +1521,16 @@ fn show_short_about_issue_897() { false )); } + +#[test] +fn issue_1487() { + let app = App::new("test") + .arg(Arg::with_name("arg1") + .group("group1")) + .arg(Arg::with_name("arg2") + .group("group1")) + .group(ArgGroup::with_name("group1") + .args(&["arg1", "arg2"]) + .required(true)); + assert!(test::compare_output(app, "ctest -h", ISSUE_1487, false)); +} diff --git a/tests/multiple_values.rs b/tests/multiple_values.rs index aad11565b8a..001adafab1e 100644 --- a/tests/multiple_values.rs +++ b/tests/multiple_values.rs @@ -963,8 +963,13 @@ fn req_delimiter_complex() { ); } +// This tests a programmer error and will only succeed with debug_assertions +#[cfg(debug_assertions)] #[test] -#[should_panic] +#[should_panic(expected = "When using a positional argument with \ +.multiple(true) that is *not the last* positional argument, the last \ +positional argument (i.e the one with the highest index) *must* have \ +.required(true) or .last(true) set.")] fn low_index_positional_not_required() { let _ = App::new("lip") .arg( @@ -974,11 +979,14 @@ fn low_index_positional_not_required() { .multiple(true), ) .arg(Arg::with_name("target").index(2)) - .try_get_matches_from(vec!["lip", "file1", "file2", "file3", "target"]); + .try_get_matches_from(vec![""]); } +// This tests a programmer error and will only succeed with debug_assertions +#[cfg(debug_assertions)] #[test] -#[should_panic] +#[should_panic(expected = "Only one positional argument with .multiple(true) \ +set is allowed per command, unless the second one also has .last(true) set")] fn low_index_positional_last_multiple_too() { let _ = App::new("lip") .arg( @@ -993,11 +1001,14 @@ fn low_index_positional_last_multiple_too() { .required(true) .multiple(true), ) - .try_get_matches_from(vec!["lip", "file1", "file2", "file3", "target"]); + .try_get_matches_from(vec![""]); } +// This tests a programmer error and will only succeed with debug_assertions +#[cfg(debug_assertions)] #[test] -#[should_panic] +#[should_panic(expected = "Only the last positional argument, or second to \ +last positional argument may be set to .multiple(true)")] fn low_index_positional_too_far_back() { let _ = App::new("lip") .arg( @@ -1008,7 +1019,7 @@ fn low_index_positional_too_far_back() { ) .arg(Arg::with_name("target").required(true).index(2)) .arg(Arg::with_name("target2").required(true).index(3)) - .try_get_matches_from(vec!["lip", "file1", "file2", "file3", "target"]); + .try_get_matches_from(vec![""]); } #[test] diff --git a/tests/positionals.rs b/tests/positionals.rs index d3b0f2a83f8..65e95b47907 100644 --- a/tests/positionals.rs +++ b/tests/positionals.rs @@ -218,15 +218,16 @@ fn single_positional_required_usage_string() { assert_eq!(app.generate_usage(), "USAGE:\n test "); } +// This tests a programmer error and will only succeed with debug_assertions +#[cfg(debug_assertions)] #[test] -#[should_panic] +#[should_panic(expected = "Found positional argument which is not required \ +with a lower index than a required positional argument")] fn missing_required() { - let r = App::new("test") + let _ = App::new("test") .arg("[FILE1] 'some file'") .arg(" 'some file'") - .try_get_matches_from(vec!["test", "file"]); - assert!(r.is_err()); - assert_eq!(r.unwrap_err().kind, ErrorKind::MissingRequiredArgument); + .try_get_matches_from(vec![""]); } #[test] diff --git a/tests/unique_args.rs b/tests/unique_args.rs index b24f85fd9e6..6f3d41e8b2f 100644 --- a/tests/unique_args.rs +++ b/tests/unique_args.rs @@ -2,19 +2,23 @@ extern crate clap; use clap::{App, Arg}; +// This tests a programmer error and will only succeed with debug_assertions +#[cfg(debug_assertions)] #[test] -#[should_panic] +#[should_panic(expected = "Arg names must be unique")] fn unique_arg_names() { let _ = App::new("some") .args(&[ - Arg::with_name("arg").short('a'), - Arg::with_name("arg").short('b'), + Arg::with_name("arg1").short('a'), + Arg::with_name("arg1").short('b'), ]) .try_get_matches(); } +// This tests a programmer error and will only succeed with debug_assertions +#[cfg(debug_assertions)] #[test] -#[should_panic] +#[should_panic(expected = "Argument short must be unique")] fn unique_arg_shorts() { let _ = App::new("some") .args(&[ @@ -24,8 +28,10 @@ fn unique_arg_shorts() { .try_get_matches(); } +// This tests a programmer error and will only succeed with debug_assertions +#[cfg(debug_assertions)] #[test] -#[should_panic] +#[should_panic(expected = "Argument long must be unique")] fn unique_arg_longs() { let _ = App::new("some") .args(&[ From a5e3e62bef2c4de3ce0c9a882f028b23f4ac752c Mon Sep 17 00:00:00 2001 From: Tyler Ruckinger Date: Tue, 25 Jun 2019 19:51:12 -0400 Subject: [PATCH 0382/3337] test in mkeymap does not need #[should_panic] --- src/mkeymap.rs | 12 ++++++------ tests/help.rs | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/mkeymap.rs b/src/mkeymap.rs index f1ef72daace..2e7455c097f 100644 --- a/src/mkeymap.rs +++ b/src/mkeymap.rs @@ -292,7 +292,7 @@ mod tests { } #[test] - #[should_panic] + // #[should_panic(expected = "Len changed")] fn insert_duplicate_value() { let mut map: MKeyMap = MKeyMap::new(); @@ -302,11 +302,11 @@ mod tests { map.insert(Long(OsString::from("Two")), Arg::with_name("Value1")); - assert_eq!(map.args.len(), orig_len); - assert_eq!( - map.get(&Long(OsString::from("One"))), - map.get(&Long(OsString::from("Two"))) - ); + assert_eq!(map.args.len(), orig_len + 1/* , "Len changed" */); + // assert_eq!( + // map.get(&Long(OsString::from("One"))), + // map.get(&Long(OsString::from("Two"))) + // ); } // #[test] diff --git a/tests/help.rs b/tests/help.rs index 93397d1ebc2..13f2b787ac2 100644 --- a/tests/help.rs +++ b/tests/help.rs @@ -543,8 +543,7 @@ ARGS: FLAGS: -h, --help Prints help information - -V, --version Prints version information -"; + -V, --version Prints version information"; From 8d953cab08528bec0c977befaf2d26a5ab900f23 Mon Sep 17 00:00:00 2001 From: Ross Harrison Date: Mon, 1 Jul 2019 11:01:11 -0500 Subject: [PATCH 0383/3337] feat(Errors): Add Pattern suggestion to Unknown Arg Error Message --- src/parse/errors.rs | 19 +++++++++++++------ tests/opts.rs | 1 + tests/subcommands.rs | 1 + 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/parse/errors.rs b/src/parse/errors.rs index 8a7b94d4587..f9212ddcddf 100644 --- a/src/parse/errors.rs +++ b/src/parse/errors.rs @@ -852,19 +852,26 @@ impl Error { use_stderr: true, when: color, }); + + let suggest_pattern = format!("If you tried to supply `{}` as a PATTERN use `-- {}`", a, a); + + let did_you_mean_message = if did_you_mean.is_empty() { + "\n".to_owned() + } else { + format!("{}\n", did_you_mean) + }; + Error { message: format!( "{} Found argument '{}' which wasn't expected, or isn't valid in \ - this context{}\n\ + this context{}\ + {}\n\n\ {}\n\n\ For more information try {}", c.error("error:"), c.warning(&*a), - if did_you_mean.is_empty() { - "\n".to_owned() - } else { - format!("{}\n", did_you_mean) - }, + did_you_mean_message, + suggest_pattern, usage, c.good("--help") ), diff --git a/tests/opts.rs b/tests/opts.rs index 6016f9f8b57..6c38d3366e0 100644 --- a/tests/opts.rs +++ b/tests/opts.rs @@ -9,6 +9,7 @@ use clap::{App, Arg, ArgMatches, ArgSettings, ErrorKind}; static DYM: &'static str = "error: Found argument '--optio' which wasn't expected, or isn't valid in this context \tDid you mean --option? +If you tried to supply `--optio` as a PATTERN use `-- --optio` USAGE: clap-test --option ... diff --git a/tests/subcommands.rs b/tests/subcommands.rs index c2036a012eb..5959f282a53 100644 --- a/tests/subcommands.rs +++ b/tests/subcommands.rs @@ -46,6 +46,7 @@ For more information try --help"; static DYM_ARG: &'static str = "error: Found argument '--subcm' which wasn't expected, or isn't valid in this context \tDid you mean to put '--subcmdarg' after the subcommand 'subcmd'? +If you tried to supply `--subcm` as a PATTERN use `-- --subcm` USAGE: dym [SUBCOMMAND] From ebfe225af30b5193e38e893e4b158684b9e189c0 Mon Sep 17 00:00:00 2001 From: Ross Harrison Date: Mon, 1 Jul 2019 11:25:46 -0500 Subject: [PATCH 0384/3337] refactor(Parser,Errors): refactor(Parser,Errors): Update unknown_argument function to take option Update unknown_argument function to take option --- src/parse/errors.rs | 9 ++++----- src/parse/parser.rs | 19 +++++++++++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/parse/errors.rs b/src/parse/errors.rs index f9212ddcddf..45ce5708a78 100644 --- a/src/parse/errors.rs +++ b/src/parse/errors.rs @@ -842,7 +842,7 @@ impl Error { } #[doc(hidden)] - pub fn unknown_argument(arg: A, did_you_mean: &str, usage: U, color: ColorWhen) -> Self + pub fn unknown_argument(arg: A, did_you_mean: Option, usage: U, color: ColorWhen) -> Self where A: Into, U: Display, @@ -855,10 +855,9 @@ impl Error { let suggest_pattern = format!("If you tried to supply `{}` as a PATTERN use `-- {}`", a, a); - let did_you_mean_message = if did_you_mean.is_empty() { - "\n".to_owned() - } else { - format!("{}\n", did_you_mean) + let did_you_mean_message = match did_you_mean { + Some(s) => format!("{}\n", s), + _ => "\n".to_owned() }; Error { diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 314fe73a40a..64072b32de3 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -459,7 +459,7 @@ where { return Err(ClapError::unknown_argument( &*arg_os.to_string_lossy(), - "", + None, &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), )); @@ -578,7 +578,7 @@ where if p.is_set(ArgSettings::Last) && !self.is_set(AS::TrailingValues) { return Err(ClapError::unknown_argument( &*arg_os.to_string_lossy(), - "", + None, &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), )); @@ -652,7 +652,7 @@ where { return Err(ClapError::unknown_argument( &*arg_os.to_string_lossy(), - "", + None, &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), )); @@ -677,7 +677,7 @@ where } else { return Err(ClapError::unknown_argument( &*arg_os.to_string_lossy(), - "", + None, &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), )); @@ -1129,7 +1129,7 @@ where let arg = format!("-{}", c); return Err(ClapError::unknown_argument( &*arg, - "", + None, &*Usage::new(self).create_usage_with_title(&[]), self.app.color(), )); @@ -1499,9 +1499,16 @@ where }) .cloned() .collect(); + + let did_you_mean_msg = if suffix.0.is_empty() { + None + } else { + Some(suffix.0) + }; + Err(ClapError::unknown_argument( &*format!("--{}", arg), - &*suffix.0, + did_you_mean_msg, &*Usage::new(self).create_usage_with_title(&*used), self.app.color(), )) From a87284020f5c3bf4a29281594789ccab1db57da6 Mon Sep 17 00:00:00 2001 From: Nootan Ghimire Date: Wed, 17 Jul 2019 10:44:14 +1000 Subject: [PATCH 0385/3337] style(help): add newline after writing help --- src/output/help.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/output/help.rs b/src/output/help.rs index 5fd115ff03d..221aa4ca4dc 100644 --- a/src/output/help.rs +++ b/src/output/help.rs @@ -92,6 +92,9 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { } else { self.write_default_help()?; } + + write!(self.writer, "\n")?; + Ok(()) } } From 7f835ed24db408e664c6fac42ce5ddc88b38956b Mon Sep 17 00:00:00 2001 From: demoray Date: Tue, 23 Jul 2019 10:03:07 -0400 Subject: [PATCH 0386/3337] use std::sync::Once::new() rather than ONCE_INIT Updated to v3-master per discussion on #1521 --- src/macros.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 41982643a82..05881c6693a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -459,7 +459,7 @@ macro_rules! crate_version { macro_rules! crate_authors { ($sep:expr) => {{ use std::ops::Deref; - use std::sync::{Once, ONCE_INIT}; + use std::sync::Once; #[allow(missing_copy_implementations)] #[allow(dead_code)] @@ -472,7 +472,7 @@ macro_rules! crate_authors { #[allow(unsafe_code)] fn deref(&self) -> &'static str { - static ONCE: Once = ONCE_INIT; + static ONCE: Once = Once::new(); static mut VALUE: *const String = 0 as *const String; unsafe { From ee808f464f455f18662387dbb453fc4903e4648d Mon Sep 17 00:00:00 2001 From: Dylan DPC Date: Thu, 25 Jul 2019 12:14:05 +0200 Subject: [PATCH 0387/3337] Update 17_yaml.yml --- examples/17_yaml.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/17_yaml.yml b/examples/17_yaml.yml index b0d58b3933a..5f8eb89c6bb 100644 --- a/examples/17_yaml.yml +++ b/examples/17_yaml.yml @@ -82,7 +82,7 @@ subcommands: help: example subcommand positional index: 1 -# ArgGroups are supported as well, and must be sepcified in the 'groups:' +# ArgGroups are supported as well, and must be specified in the 'groups:' # object of this file groups: # the name of the ArgGoup is specified here From ece3f66e0ffd702db71e045f985384ab13b042f9 Mon Sep 17 00:00:00 2001 From: Bastien Orivel Date: Sun, 25 Aug 2019 13:02:47 +0200 Subject: [PATCH 0388/3337] Update strsim to 0.9 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9544e5f5e17..bcbc1331e82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ bitflags = "1.0" unicode-width = "0.1.4" textwrap = "0.11" indexmap = "1.0.1" -strsim = { version = "0.8.0", optional = true } +strsim = { version = "0.9.0", optional = true } yaml-rust = { version = "0.4", optional = true } atty = { version = "0.2.2", optional = true } vec_map = { version = "0.8", optional = true } From 727ab1b82c410e73a35824269c364c40d792c57f Mon Sep 17 00:00:00 2001 From: Pavan Kumar Sunkara Date: Tue, 17 Sep 2019 08:26:01 +0200 Subject: [PATCH 0389/3337] Fix for clap changes in lifetimes, subcommand and get_matches_* (#16) --- Cargo.toml | 14 +++++++------- README.md | 6 +++--- src/derives/clap.rs | 26 +++++++++++++------------- src/derives/from_argmatches.rs | 4 ++-- src/derives/into_app.rs | 12 ++++++------ tests/arg_enum_basic.rs | 4 ++-- tests/arg_enum_case_sensitive.rs | 4 ++-- tests/nested-subcommands.rs | 2 +- 8 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b913459d91d..50a93361bd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,13 @@ name = "clap_derive" version = "0.3.0" authors = [ - "Guillaume Pinot ", - "Kevin K. ", + "Guillaume Pinot ", + "Kevin K. ", "hoverbear " ] description = "Parse command line argument by defining a struct, derive crate." documentation = "https://docs.rs/clap_derive" -repository = "https://github.com/kbknapp/clap_derive" +repository = "https://github.com/clap-rs/clap_derive" keywords = ["clap", "cli", "derive", "proc_macro", "parse"] categories = ["command-line-interface", "development-tools::procedural-macro-helpers"] license = "Apache-2.0/MIT" @@ -18,17 +18,17 @@ readme = "README.md" proc-macro = true [badges] -travis-ci = { repository = "kbknapp/clap_derive" } -appveyor = { repository = "https://github.com/kbknapp/clap_derive", service = "github" } +travis-ci = { repository = "clap-rs/clap_derive" } +appveyor = { repository = "https://github.com/clap-rs/clap_derive", service = "github" } [dependencies] -syn = "0.14" +syn = "0.15.39" quote = "0.6" proc-macro2 = "0.4" clippy = {version = "0.0.174", optional = true } [dev-dependencies] -clap = {git = "https://github.com/kbknapp/clap-rs", branch = "v3-master"} # ONLY FOR INITIAL DEVELOPMENT...change to real crates.io ver for rlease! +clap = { git = "https://github.com/clap-rs/clap", branch = "v3-master"} # ONLY FOR INITIAL DEVELOPMENT...change to real crates.io ver for rlease! [features] default = [] diff --git a/README.md b/README.md index 89b8e6c9a84..b847361f4cd 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,14 @@ This crate is currently a work in progress and not meant to be used. Please use [`structopt`](https://github.com/TeXitoi/structopt) while this crate is being built. -# clap_derive[![Build status](https://travis-ci.org/kbknapp/clap_derive.svg?branch=master)](https://travis-ci.org/kbknapp/clap_derive) [![](https://img.shields.io/crates/v/clap_derive.svg)](https://crates.io/crates/clap_derive) [![](https://docs.rs/clap_derive/badge.svg)](https://docs.rs/clap_derive) +# clap_derive[![Build status](https://travis-ci.org/clap-rs/clap_derive.svg?branch=master)](https://travis-ci.org/clap-rs/clap_derive) [![](https://img.shields.io/crates/v/clap_derive.svg)](https://crates.io/crates/clap_derive) [![](https://docs.rs/clap_derive/badge.svg)](https://docs.rs/clap_derive) Parse command line argument by defining a struct. It combines [structopt](https://github.com/TeXitoi/structopt) and [clap](https://crates.io/crates/clap) into a single experience. This crate is used by clap, and not meant to be used directly by consumers. ## Documentation -Find it on [Docs.rs](https://docs.rs/clap_derive). You can also check the [examples](https://github.com/kbknapp/clap_derive/tree/master/examples) and the [changelog](https://github.com/kbknapp/clap_derive/blob/master/CHANGELOG.md). +Find it on [Docs.rs](https://docs.rs/clap_derive). You can also check the [examples](https://github.com/clap-rs/clap_derive/tree/master/examples) and the [changelog](https://github.com/clap-rs/clap_derive/blob/master/CHANGELOG.md). ## Example @@ -111,7 +111,7 @@ Opt { debug: true, verbose: 3, speed: 1337, output: "foo.txt", nb_cars: Some(4), ## clap_derive rustc version policy -- Minimum rustc version modification must be specified in the [changelog](https://github.com/kbknapp/clap_derive/blob/master/CHANGELOG.md) and in the [travis configuration](https://github.com/kbknapp/clap_derive/blob/master/.travis.yml). +- Minimum rustc version modification must be specified in the [changelog](https://github.com/clap-rs/clap_derive/blob/master/CHANGELOG.md) and in the [travis configuration](https://github.com/clap-rs/clap_derive/blob/master/.travis.yml). - Contributors can increment minimum rustc version without any justification if the new version is required by the latest version of one of clap_derive's depedencies (`cargo update` will not fail on clap_derive). - Contributors can increment minimum rustc version if the library user experience is improved. diff --git a/src/derives/clap.rs b/src/derives/clap.rs index 09cda77082e..be03b5f8c0f 100644 --- a/src/derives/clap.rs +++ b/src/derives/clap.rs @@ -46,7 +46,7 @@ fn gen_app_augmentation( } }; - Some(quote!{ + Some(quote! { let #app_var = <#subcmd_type>::augment_app( #app_var ); #required }) @@ -111,7 +111,7 @@ fn gen_app_augmentation( }; let methods = attrs.methods(); let name = attrs.name(); - Some(quote!{ + Some(quote! { let #app_var = #app_var.arg( ::clap::Arg::with_name(#name) #modifier @@ -135,9 +135,9 @@ fn gen_augment_app_fn( let app_var = syn::Ident::new("app", proc_macro2::Span::call_site()); let augmentation = gen_app_augmentation(fields, &app_var); quote! { - pub fn augment_app<'a, 'b>( - #app_var: ::clap::App<'a, 'b> - ) -> ::clap::App<'a, 'b> { + pub fn augment_app<'b>( + #app_var: ::clap::App<'b> + ) -> ::clap::App<'b> { #augmentation } } @@ -177,7 +177,7 @@ fn gen_augment_app_for_enum( let from_attrs = attrs.methods(); quote! { .subcommand({ - let #app_var = ::clap::SubCommand::with_name(#name); + let #app_var = ::clap::App::new(#name); let #app_var = #arg_block; #app_var#from_attrs }) @@ -185,9 +185,9 @@ fn gen_augment_app_for_enum( }); quote! { - pub fn augment_app<'a, 'b>( - app: ::clap::App<'a, 'b> - ) -> ::clap::App<'a, 'b> { + pub fn augment_app<'b>( + app: ::clap::App<'b> + ) -> ::clap::App<'b> { app #( #subcommands )* } } @@ -220,8 +220,8 @@ fn gen_from_subcommand( }); quote! { - pub fn from_subcommand<'a, 'b>( - sub: (&'b str, Option<&'b ::clap::ArgMatches<'a>>) + pub fn from_subcommand<'b>( + sub: (&'b str, Option<&'b ::clap::ArgMatches>) ) -> Option { match sub { #( #match_arms ),*, @@ -319,7 +319,7 @@ fn gen_parse_fns(name: &syn::Ident) -> proc_macro2::TokenStream { fn try_parse() -> ::std::result::Result<#name, ::clap::Error> { use ::clap::{FromArgMatches, IntoApp}; - Ok(#name::from_argmatches(&#name::into_app().get_matches_safe()?)) + Ok(#name::from_argmatches(&#name::into_app().try_get_matches()?)) } fn parse_from(itr: I) -> #name @@ -335,7 +335,7 @@ fn gen_parse_fns(name: &syn::Ident) -> proc_macro2::TokenStream { I: ::std::iter::IntoIterator, T: Into<::std::ffi::OsString> + Clone { use ::clap::{FromArgMatches, IntoApp}; - Ok(#name::from_argmatches(&#name::into_app().get_matches_from_safe(itr)?)) + Ok(#name::from_argmatches(&#name::into_app().try_get_matches_from(itr)?)) } } } diff --git a/src/derives/from_argmatches.rs b/src/derives/from_argmatches.rs index 8b39b8370b0..3015f15d014 100644 --- a/src/derives/from_argmatches.rs +++ b/src/derives/from_argmatches.rs @@ -46,7 +46,7 @@ pub fn gen_from_argmatches_impl_for_struct( #from_argmatches_fn } - impl<'a> From<::clap::ArgMatches<'a>> for #name { + impl From<::clap::ArgMatches> for #name { fn from(m: ::clap::ArgMatches) -> Self { use ::clap::FromArgMatches; ::from_argmatches(&m) @@ -152,7 +152,7 @@ pub fn gen_from_argmatches_impl_for_enum(name: &syn::Ident) -> proc_macro2::Toke } } - impl<'a> From<::clap::ArgMatches<'a>> for #name { + impl From<::clap::ArgMatches> for #name { fn from(m: ::clap::ArgMatches) -> Self { use ::clap::FromArgMatches; ::from_argmatches(&m) diff --git a/src/derives/into_app.rs b/src/derives/into_app.rs index 9c619998cae..c1433423775 100644 --- a/src/derives/into_app.rs +++ b/src/derives/into_app.rs @@ -43,8 +43,8 @@ pub fn gen_into_app_impl_for_struct( #into_app_fn } - impl<'a, 'b> Into<::clap::App<'a, 'b>> for #name { - fn into(self) -> ::clap::App<'a, 'b> { + impl<'b> Into<::clap::App<'b>> for #name { + fn into(self) -> ::clap::App<'b> { use ::clap::IntoApp; <#name as ::clap::IntoApp>::into_app() } @@ -55,7 +55,7 @@ pub fn gen_into_app_impl_for_struct( pub fn gen_into_app_fn_for_struct(struct_attrs: &[syn::Attribute]) -> proc_macro2::TokenStream { let app = gen_app_builder(struct_attrs); quote! { - fn into_app<'a, 'b>() -> ::clap::App<'a, 'b> { + fn into_app<'b>() -> ::clap::App<'b> { Self::augment_app(#app) } } @@ -82,8 +82,8 @@ pub fn gen_into_app_impl_for_enum( #into_app_fn } - impl<'a, 'b> Into<::clap::App<'a, 'b>> for #name { - fn into(self) -> ::clap::App<'a, 'b> { + impl<'b> Into<::clap::App<'b>> for #name { + fn into(self) -> ::clap::App<'b> { use ::clap::IntoApp; <#name as ::clap::IntoApp>::into_app() } @@ -94,7 +94,7 @@ pub fn gen_into_app_impl_for_enum( pub fn gen_into_app_fn_for_enum(enum_attrs: &[syn::Attribute]) -> proc_macro2::TokenStream { let gen = gen_app_builder(enum_attrs); quote! { - fn into_app<'a, 'b>() -> ::clap::App<'a, 'b> { + fn into_app<'b>() -> ::clap::App<'b> { let app = #gen .setting(::clap::AppSettings::SubcommandRequiredElseHelp); Self::augment_app(app) diff --git a/tests/arg_enum_basic.rs b/tests/arg_enum_basic.rs index 5d6ce1f12d6..703fe097978 100644 --- a/tests/arg_enum_basic.rs +++ b/tests/arg_enum_basic.rs @@ -30,7 +30,7 @@ // .takes_value(true) // .possible_values(&ArgChoice::variants()), // ) -// .get_matches_from_safe(vec!["", "foo"]) +// .try_get_matches_from(vec!["", "foo"]) // .unwrap(); // let t = value_t!(matches.value_of("arg"), ArgChoice); // assert!(t.is_ok()); @@ -46,7 +46,7 @@ // .takes_value(true) // .possible_values(&ArgChoice::variants()), // ) -// .get_matches_from_safe(vec!["", "Foo"]) +// .try_get_matches_from(vec!["", "Foo"]) // .unwrap(); // let t = value_t!(matches.value_of("arg"), ArgChoice); // assert!(t.is_ok()); diff --git a/tests/arg_enum_case_sensitive.rs b/tests/arg_enum_case_sensitive.rs index 160a1c45fac..2f82a0f9154 100644 --- a/tests/arg_enum_case_sensitive.rs +++ b/tests/arg_enum_case_sensitive.rs @@ -29,7 +29,7 @@ // .takes_value(true) // .possible_values(&ArgChoice::variants()), // ) -// .get_matches_from_safe(vec!["", "foo"]); // We expect this to fail. +// .try_get_matches_from(vec!["", "foo"]); // We expect this to fail. // assert!(matches.is_err()); // assert_eq!(matches.unwrap_err().kind, clap::ErrorKind::InvalidValue); // } @@ -43,7 +43,7 @@ // .takes_value(true) // .possible_values(&ArgChoice::variants()), // ) -// .get_matches_from_safe(vec!["", "Foo"]) +// .try_get_matches_from(vec!["", "Foo"]) // .unwrap(); // let t = value_t!(matches.value_of("arg"), ArgChoice); // assert!(t.is_ok()); diff --git a/tests/nested-subcommands.rs b/tests/nested-subcommands.rs index 74e158c8c96..37c0d237894 100644 --- a/tests/nested-subcommands.rs +++ b/tests/nested-subcommands.rs @@ -189,7 +189,7 @@ fn sub_sub_cmd_with_option() { use clap::{FromArgMatches, IntoApp}; SubSubCmdWithOption::into_app() - .get_matches_from_safe(args) + .try_get_matches_from(args) .ok() .map(|m| SubSubCmdWithOption::from_argmatches(&m)) } From a829ad08ac2cf3b067ddc88af53b245b3b2251fb Mon Sep 17 00:00:00 2001 From: Pavan Kumar Sunkara Date: Fri, 11 Oct 2019 17:04:24 +0200 Subject: [PATCH 0390/3337] test: Get 'cargo test' working with clap v3 (#18) --- Cargo.toml | 2 +- examples/no_version.rs | 2 +- examples/raw_attributes.rs | 3 ++- tests/author_version_about.rs | 1 + tests/raw_attributes.rs | 8 ++------ 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 50a93361bd1..3c9c7b59386 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ proc-macro2 = "0.4" clippy = {version = "0.0.174", optional = true } [dev-dependencies] -clap = { git = "https://github.com/clap-rs/clap", branch = "v3-master"} # ONLY FOR INITIAL DEVELOPMENT...change to real crates.io ver for rlease! +clap = { git = "https://github.com/clap-rs/clap", branch = "master"} # ONLY FOR INITIAL DEVELOPMENT...change to real crates.io ver for rlease! [features] default = [] diff --git a/examples/no_version.rs b/examples/no_version.rs index 7be7e0ae6bb..0102707f172 100644 --- a/examples/no_version.rs +++ b/examples/no_version.rs @@ -9,7 +9,7 @@ use clap::{AppSettings, Clap}; about = "", version = "", author = "", - raw(global_settings = "&[AppSettings::DisableVersion]") + raw(global_setting = "AppSettings::DisableVersion") )] struct Opt {} diff --git a/examples/raw_attributes.rs b/examples/raw_attributes.rs index 960efe6924c..78a601a86f4 100644 --- a/examples/raw_attributes.rs +++ b/examples/raw_attributes.rs @@ -19,7 +19,8 @@ use clap::{AppSettings, Clap}; /// An example of raw attributes #[derive(Clap, Debug)] -#[clap(raw(global_settings = "&[AppSettings::ColoredHelp, AppSettings::VersionlessSubcommands]"))] +#[clap(raw(global_setting = "AppSettings::ColoredHelp"))] +#[clap(raw(global_setting = "AppSettings::VersionlessSubcommands"))] struct Opt { /// Output file #[clap(short = "o", long = "output")] diff --git a/tests/author_version_about.rs b/tests/author_version_about.rs index 010f2147b71..9de7eaaf48b 100644 --- a/tests/author_version_about.rs +++ b/tests/author_version_about.rs @@ -45,6 +45,7 @@ FLAGS: -V, --version Prints version information + "; #[test] diff --git a/tests/raw_attributes.rs b/tests/raw_attributes.rs index 457ccc85ce7..b656d343012 100644 --- a/tests/raw_attributes.rs +++ b/tests/raw_attributes.rs @@ -19,7 +19,7 @@ use clap::{AppSettings, Clap}; // Check if the global settings compile #[derive(Clap, Debug, PartialEq, Eq)] -#[clap(raw(global_settings = "&[AppSettings::ColoredHelp]"))] +#[clap(raw(global_setting = "AppSettings::ColoredHelp"))] struct Opt { #[clap( long = "x", @@ -32,11 +32,7 @@ struct Opt { )] x: i32, - #[clap( - short = "l", - long = "level", - raw(aliases = r#"&["set-level", "lvl"]"#) - )] + #[clap(short = "l", long = "level", raw(aliases = r#"&["set-level", "lvl"]"#))] level: String, #[clap(long = "values")] From 6b2f870c341a4ab6568cc53ab7e23a9750ff094e Mon Sep 17 00:00:00 2001 From: Pavan Kumar Sunkara Date: Sat, 12 Oct 2019 09:46:04 +0200 Subject: [PATCH 0391/3337] refactor: Do not use cargo description as default --- src/derives/attrs.rs | 1 - tests/{author_version_about.rs => author_version.rs} | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) rename tests/{author_version_about.rs => author_version.rs} (92%) diff --git a/src/derives/attrs.rs b/src/derives/attrs.rs index 7ed7408b563..bb8a0178f6e 100644 --- a/src/derives/attrs.rs +++ b/src/derives/attrs.rs @@ -254,7 +254,6 @@ impl Attrs { let mut res = Self::new(name); let attrs_with_env = [ ("version", "CARGO_PKG_VERSION"), - ("about", "CARGO_PKG_DESCRIPTION"), ("author", "CARGO_PKG_AUTHORS"), ]; attrs_with_env diff --git a/tests/author_version_about.rs b/tests/author_version.rs similarity index 92% rename from tests/author_version_about.rs rename to tests/author_version.rs index 9de7eaaf48b..c1b10b215f2 100644 --- a/tests/author_version_about.rs +++ b/tests/author_version.rs @@ -22,7 +22,7 @@ fn no_author_version_about() { use clap::IntoApp; #[derive(Clap, PartialEq, Debug)] - #[clap(name = "foo", about = "", author = "", version = "")] + #[clap(name = "foo", author = "", version = "")] struct Opt {} let mut output = Vec::new(); @@ -34,7 +34,6 @@ fn no_author_version_about() { static ENV_HELP: &str = "clap_derive 0.3.0 Guillaume Pinot , Kevin K. , hoverbear -Parse command line argument by defining a struct, derive crate. USAGE: clap_derive From 4e732000def7753615c0565fd2f56def9fb1f836 Mon Sep 17 00:00:00 2001 From: Ricky Date: Fri, 18 Oct 2019 15:50:55 -0400 Subject: [PATCH 0392/3337] Adding pub to the parse functions --- src/derives/clap.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/derives/clap.rs b/src/derives/clap.rs index be03b5f8c0f..8345c807215 100644 --- a/src/derives/clap.rs +++ b/src/derives/clap.rs @@ -312,17 +312,17 @@ pub fn derive_clap(input: &syn::DeriveInput) -> proc_macro2::TokenStream { fn gen_parse_fns(name: &syn::Ident) -> proc_macro2::TokenStream { quote! { - fn parse() -> #name { + pub fn parse() -> #name { use ::clap::{FromArgMatches, IntoApp}; #name::from_argmatches(&#name::into_app().get_matches()) } - fn try_parse() -> ::std::result::Result<#name, ::clap::Error> { + pub fn try_parse() -> ::std::result::Result<#name, ::clap::Error> { use ::clap::{FromArgMatches, IntoApp}; Ok(#name::from_argmatches(&#name::into_app().try_get_matches()?)) } - fn parse_from(itr: I) -> #name + pub fn parse_from(itr: I) -> #name where I: ::std::iter::IntoIterator, T: Into<::std::ffi::OsString> + Clone { @@ -330,7 +330,7 @@ fn gen_parse_fns(name: &syn::Ident) -> proc_macro2::TokenStream { #name::from_argmatches(&#name::into_app().get_matches_from(itr)) } - fn try_parse_from(itr: I) -> ::std::result::Result<#name, ::clap::Error> + pub fn try_parse_from(itr: I) -> ::std::result::Result<#name, ::clap::Error> where I: ::std::iter::IntoIterator, T: Into<::std::ffi::OsString> + Clone { From dfbbf863c1e47d3d5edbd2a3aceefbc4b4ea510d Mon Sep 17 00:00:00 2001 From: Ricky Date: Wed, 23 Oct 2019 19:10:07 -0400 Subject: [PATCH 0393/3337] Added unreachable pub attribute to the parsing functions --- src/derives/clap.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/derives/clap.rs b/src/derives/clap.rs index 8345c807215..8588e340383 100644 --- a/src/derives/clap.rs +++ b/src/derives/clap.rs @@ -312,16 +312,17 @@ pub fn derive_clap(input: &syn::DeriveInput) -> proc_macro2::TokenStream { fn gen_parse_fns(name: &syn::Ident) -> proc_macro2::TokenStream { quote! { + #[allow(unreachable_pub)] pub fn parse() -> #name { use ::clap::{FromArgMatches, IntoApp}; #name::from_argmatches(&#name::into_app().get_matches()) } - + #[allow(unreachable_pub)] pub fn try_parse() -> ::std::result::Result<#name, ::clap::Error> { use ::clap::{FromArgMatches, IntoApp}; Ok(#name::from_argmatches(&#name::into_app().try_get_matches()?)) } - + #[allow(unreachable_pub)] pub fn parse_from(itr: I) -> #name where I: ::std::iter::IntoIterator, @@ -329,7 +330,7 @@ fn gen_parse_fns(name: &syn::Ident) -> proc_macro2::TokenStream { use ::clap::{FromArgMatches, IntoApp}; #name::from_argmatches(&#name::into_app().get_matches_from(itr)) } - + #[allow(unreachable_pub)] pub fn try_parse_from(itr: I) -> ::std::result::Result<#name, ::clap::Error> where I: ::std::iter::IntoIterator, From 28a3abdd16e3ffa506499b1a1da0d050a8ecf28d Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Wed, 2 Oct 2019 05:30:08 +0900 Subject: [PATCH 0394/3337] chore: fix cache issue on Travis --- .travis.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 53e566359c2..3d391b1e748 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,11 @@ sudo: true language: rust -cache: cargo +cache: + directories: + - $HOME/.cargo + - $HOME/.rustup +before_cache: + - rm -rf /home/travis/.cargo/registry rust: - nightly - nightly-2019-06-18 From a720de3f2489945bbedaf0805b0ded3c7c827976 Mon Sep 17 00:00:00 2001 From: Tshepang Lekhonkhobe Date: Wed, 2 Oct 2019 21:37:29 +0200 Subject: [PATCH 0395/3337] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a972cfa5bb..d09d41301d5 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ First, let me say that these comparisons are highly subjective, and not meant in #### How does `clap` compare to [structopt](https://github.com/TeXitoi/structopt)? -Simple! `clap` *is* `stuctopt`. With the 3.0 release, `clap` imported the `structopt` code into it's own codebase as the [`clap_derive`](https://github.com/clap-rs/clap_derive) crate. Since `structopt` already used `clap` under the hood, the transition was nearly painless, and is 100% feature compatible. +Simple! `clap` *is* `structopt`. With the 3.0 release, `clap` imported the `structopt` code into it's own codebase as the [`clap_derive`](https://github.com/clap-rs/clap_derive) crate. Since `structopt` already used `clap` under the hood, the transition was nearly painless, and is 100% feature compatible. If you were using `structopt` before, the only thing you should have to do is change the attributes from `#[structopt(...)]` to `#[clap(...)]`. From e56b80b3161de874513d23ecd71362c6b315fb39 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Wed, 2 Oct 2019 19:40:12 +0900 Subject: [PATCH 0396/3337] Fix license badge on README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d09d41301d5..6f4b561c8de 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ clap ==== -[![Crates.io](https://img.shields.io/crates/v/clap.svg)](https://crates.io/crates/clap) [![Crates.io](https://img.shields.io/crates/d/clap.svg)](https://crates.io/crates/clap) [![license](http://img.shields.io/badge/license-Apache 2.0-blue.svg)](https://github.com/clap-rs/clap/blob/master/LICENSE-APACHE) [![license](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/clap-rs/clap/blob/master/LICENSE-MIT) [![Coverage Status](https://coveralls.io/repos/clap-rs/clap/badge.svg?branch=master&service=github)](https://coveralls.io/github/clap-rs/clap?branch=master) [![Join the chat at https://gitter.im/clap-rs/clap](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/clap-rs/clap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Crates.io](https://img.shields.io/crates/v/clap.svg)](https://crates.io/crates/clap) [![Crates.io](https://img.shields.io/crates/d/clap.svg)](https://crates.io/crates/clap) [![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/clap-rs/clap/blob/master/LICENSE-APACHE) [![license](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/clap-rs/clap/blob/master/LICENSE-MIT) [![Coverage Status](https://coveralls.io/repos/clap-rs/clap/badge.svg?branch=master&service=github)](https://coveralls.io/github/clap-rs/clap?branch=master) [![Join the chat at https://gitter.im/clap-rs/clap](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/clap-rs/clap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Linux: [![Build Status](https://travis-ci.org/clap-rs/clap.svg?branch=master)](https://travis-ci.org/clap-rs/clap) Windows: [![Build status](https://ci.appveyor.com/api/projects/status/ejg8c33dn31nhv36/branch/master?svg=true)](https://ci.appveyor.com/project/kbknapp/clap-rs/branch/master) From 1906c504ea24b266408978b8da2538f65eacafc8 Mon Sep 17 00:00:00 2001 From: Oleksii Filonenko Date: Wed, 2 Oct 2019 16:10:41 +0300 Subject: [PATCH 0397/3337] Replace ONCE_INIT with Once::new() Deprecated since 1.38.0 From ed69a688ff2d79407203f6490c4f26f4cfdbe10f Mon Sep 17 00:00:00 2001 From: Tobias Kunze Date: Sat, 5 Oct 2019 13:46:18 +0200 Subject: [PATCH 0398/3337] Fix some typos in the docs --- src/build/app/mod.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index b2537fc50c9..6dfc910592b 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -117,7 +117,7 @@ pub struct App<'b> { impl<'b> App<'b> { /// Creates a new instance of an application requiring a name. The name may be, but doesn't - /// have to be same as the binary. The name will be displayed to the user when they request to + /// have to be, same as the binary. The name will be displayed to the user when they request to /// print version or help and usage information. /// /// # Examples @@ -147,11 +147,10 @@ impl<'b> App<'b> { /// request the help information with `--help` or `-h`. /// /// **Pro-tip:** Use `clap`s convenience macro [`crate_authors!`] to automatically set your - /// application's author(s) to the same thing as your crate at compile time. See the [`examples/`] - /// directory for more information + /// application's author(s) to the same thing as your crate at compile time. /// /// See the [`examples/`] - /// directory for more information + /// directory for more information. /// /// # Examples /// @@ -195,7 +194,7 @@ impl<'b> App<'b> { /// information with `-h`. /// /// **NOTE:** If only `about` is provided, and not [`App::long_about`] but the user requests - /// `--help` clap will still display the contents of `about` appropriately + /// `--help`, clap will still display the contents of `about` appropriately /// /// **NOTE:** Only [`App::about`] is used in completion script generation in order to be /// concise @@ -242,7 +241,7 @@ impl<'b> App<'b> { /// Sets the program's name. This will be displayed when displaying help information. /// - /// **Pro-top:** This function is particularly useful when configuring a program via + /// **Pro-tip:** This function is particularly useful when configuring a program via /// [`App::from_yaml`] in conjunction with the [`crate_name!`] macro to derive the program's /// name from its `Cargo.toml`. /// @@ -453,8 +452,8 @@ impl<'b> App<'b> { /// .help_template("{bin} ({version}) - {usage}") /// # ; /// ``` - /// **NOTE:**The template system is, on purpose, very simple. Therefore the tags have to writen - /// in the lowercase and without spacing. + /// **NOTE:**The template system is, on purpose, very simple. Therefore the tags have to + /// be written in the lowercase and without spacing. /// [`App::about`]: ./struct.App.html#method.about /// [`App::after_help`]: ./struct.App.html#method.after_help /// [`App::before_help`]: ./struct.App.html#method.before_help @@ -652,7 +651,7 @@ impl<'b> App<'b> { self } - /// Adds multiple [arguments] to the list of valid possibilties + /// Adds multiple [arguments] to the list of valid possibilities. /// /// # Examples /// From b6ebdba2e1a6051088a85dbfd2f4e3c799b66d22 Mon Sep 17 00:00:00 2001 From: Sascha Grunert Date: Sat, 5 Oct 2019 15:39:18 +0200 Subject: [PATCH 0399/3337] Fix opts variable name in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f4b561c8de..2be6ab7d7c8 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ fn main() { // You can handle information about subcommands by requesting their matches by name // (as below), requesting just the name used, or both at the same time - match matches.subcmd { + match opts.subcmd { SubCommand::Test @ t => { if t.debug { println!("Printing debug info..."); From 4e1da91fb133c31d0355788b91d66be79e0099fe Mon Sep 17 00:00:00 2001 From: fvkramer Date: Sat, 5 Oct 2019 12:44:29 -0700 Subject: [PATCH 0400/3337] examples: Change flag arg to option arg Comments in 04_using_matches.rs list the config arg as an optional arg. However, -c --config is currently a flag arg. This commit sets takes_value to true on the config arg to make it an "option" argument. --- examples/04_using_matches.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/04_using_matches.rs b/examples/04_using_matches.rs index af2e7d04572..10308bd9a74 100644 --- a/examples/04_using_matches.rs +++ b/examples/04_using_matches.rs @@ -30,7 +30,8 @@ fn main() { Arg::with_name("config") .help("sets the config file to use") .short('c') - .long("config"), + .long("config") + .takes_value(true), ) .arg( Arg::with_name("input") From 1e39967044974b5d7594aa863e588465d1413b49 Mon Sep 17 00:00:00 2001 From: Oleksii Filonenko Date: Wed, 2 Oct 2019 16:27:19 +0300 Subject: [PATCH 0401/3337] Fix some clippy lints - Manually fix some problems - Run 'cargo fix --clippy' Commits taken from similar PRs open at that time: - Replace indexmap remove with swap_remove Resolves #1562 and closes #1563 - Use cognitive_complexity for clippy lint Resolves #1564 and closes #1565 - Replace deprecated trim_left_matches with trim_start_matches Closes #1539 Co-authored-by: Antoine Martin Co-authored-by: Brian Foley --- benches/03_complex.rs | 4 +- benches/05_ripgrep.rs | 10 ++--- benches/06_rustup.rs | 24 ++++++------ examples/14_groups.rs | 2 +- examples/18_builder_macro.rs | 2 +- src/build/app/mod.rs | 7 +--- src/lib.rs | 2 +- src/output/help.rs | 2 +- src/parse/arg_matcher.rs | 4 +- src/parse/parser.rs | 56 +++++++++++++++++---------- src/util/fnv.rs | 2 +- src/util/osstringext.rs | 4 +- tests/app_settings.rs | 10 ++--- tests/arg_aliases.rs | 8 ++-- tests/conflicts.rs | 4 +- tests/derive_order.rs | 16 ++++---- tests/groups.rs | 6 +-- tests/help.rs | 74 ++++++++++++++++++------------------ tests/hidden_args.rs | 10 ++--- tests/opts.rs | 4 +- tests/positionals.rs | 2 +- tests/possible_values.rs | 2 +- tests/propagate_globals.rs | 2 +- tests/require.rs | 8 ++-- tests/subcommands.rs | 8 ++-- tests/template_help.rs | 8 ++-- tests/tests.rs | 10 ++--- tests/version.rs | 2 +- 28 files changed, 152 insertions(+), 141 deletions(-) diff --git a/benches/03_complex.rs b/benches/03_complex.rs index f4025dcc431..3f5011260df 100644 --- a/benches/03_complex.rs +++ b/benches/03_complex.rs @@ -8,8 +8,8 @@ use clap::{App, AppSettings, Arg, ArgSettings}; use test::Bencher; -static OPT3_VALS: [&'static str; 2] = ["fast", "slow"]; -static POS3_VALS: [&'static str; 2] = ["vi", "emacs"]; +static OPT3_VALS: [&str; 2] = ["fast", "slow"]; +static POS3_VALS: [&str; 2] = ["vi", "emacs"]; macro_rules! create_app { () => {{ diff --git a/benches/05_ripgrep.rs b/benches/05_ripgrep.rs index 8b11713393f..80cf9021fd5 100644 --- a/benches/05_ripgrep.rs +++ b/benches/05_ripgrep.rs @@ -17,10 +17,10 @@ use std::io::Cursor; use test::Bencher; #[bench] -fn build_app_short(b: &mut Bencher) { b.iter(|| app_short()); } +fn build_app_short(b: &mut Bencher) { b.iter(app_short); } #[bench] -fn build_app_long(b: &mut Bencher) { b.iter(|| app_long()); } +fn build_app_long(b: &mut Bencher) { b.iter(app_long); } #[bench] fn build_help_short(b: &mut Bencher) { @@ -224,7 +224,7 @@ fn parse_lots(b: &mut Bencher) { }); } -const ABOUT: &'static str = " +const ABOUT: &str = " ripgrep (rg) recursively searches your current directory for a regex pattern. ripgrep's regex engine uses finite automata and guarantees linear time @@ -235,13 +235,13 @@ Project home page: https://github.com/BurntSushi/ripgrep Use -h for short descriptions and --help for more details."; -const USAGE: &'static str = " +const USAGE: &str = " rg [OPTIONS] [ ...] rg [OPTIONS] [-e PATTERN | -f FILE ]... [ ...] rg [OPTIONS] --files [ ...] rg [OPTIONS] --type-list"; -const TEMPLATE: &'static str = "\ +const TEMPLATE: &str = "\ {bin} {version} {author} {about} diff --git a/benches/06_rustup.rs b/benches/06_rustup.rs index adedfa28506..9160795d32b 100644 --- a/benches/06_rustup.rs +++ b/benches/06_rustup.rs @@ -12,7 +12,7 @@ use clap::{App, AppSettings, Arg, ArgGroup, ArgSettings}; use test::Bencher; #[bench] -fn build_app(b: &mut Bencher) { b.iter(|| build_cli()); } +fn build_app(b: &mut Bencher) { b.iter(build_cli); } #[bench] fn parse_clean(b: &mut Bencher) { b.iter(|| build_cli().get_matches_from(vec![""])); } @@ -332,7 +332,7 @@ pub fn build_cli() -> App<'static> { ) } -static RUSTUP_HELP: &'static str = r" +static RUSTUP_HELP: &str = r" rustup installs The Rust Programming Language from the official release channels, enabling you to easily switch between stable, beta, and nightly compilers and keep them updated. It makes cross-compiling @@ -341,7 +341,7 @@ simpler with binary builds of the standard library for common platforms. If you are new to Rust consider running `rustup doc --book` to learn Rust."; -static SHOW_HELP: &'static str = r" +static SHOW_HELP: &str = r" Shows the name of the active toolchain and the version of `rustc`. If the active toolchain has installed support for additional @@ -350,7 +350,7 @@ compilation targets, then they are listed as well. If there are multiple toolchains installed then all installed toolchains are listed as well."; -static UPDATE_HELP: &'static str = r" +static UPDATE_HELP: &str = r" With no toolchain specified, the `update` command updates each of the installed toolchains from the official release channels, then updates rustup itself. @@ -361,7 +361,7 @@ the same as `rustup toolchain install`. 'toolchain' specifies a toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain`."; -static TOOLCHAIN_INSTALL_HELP: &'static str = r" +static TOOLCHAIN_INSTALL_HELP: &str = r" Installs a specific rust toolchain. The 'install' command is an alias for 'rustup update '. @@ -369,11 +369,11 @@ The 'install' command is an alias for 'rustup update '. 'toolchain' specifies a toolchain name, such as 'stable', 'nightly', or '1.8.0'. For more information see `rustup help toolchain`."; -static DEFAULT_HELP: &'static str = r" +static DEFAULT_HELP: &str = r" Sets the default toolchain to the one specified. If the toolchain is not already installed then it is installed first."; -static TOOLCHAIN_HELP: &'static str = r" +static TOOLCHAIN_HELP: &str = r" Many `rustup` commands deal with *toolchains*, a single installation of the Rust compiler. `rustup` supports multiple types of toolchains. The most basic track the official release channels: @@ -408,7 +408,7 @@ inferred, so the above could be written: Toolchain names that don't name a channel instead can be used to name custom toolchains with the `rustup toolchain link` command."; -static OVERRIDE_HELP: &'static str = r" +static OVERRIDE_HELP: &str = r" Overrides configure rustup to use a specific toolchain when running in a specific directory. @@ -429,13 +429,13 @@ Or a specific stable release: To see the active toolchain use `rustup show`. To remove the override and use the default toolchain again, `rustup override unset`."; -static OVERRIDE_UNSET_HELP: &'static str = r" +static OVERRIDE_UNSET_HELP: &str = r" If `--path` argument is present, removes the override toolchain for the specified directory. If `--nonexistent` argument is present, removes the override toolchain for all nonexistent directories. Otherwise, removes the override toolchain for the current directory."; -static RUN_HELP: &'static str = r" +static RUN_HELP: &str = r" Configures an environment to use the given toolchain and then runs the specified program. The command may be any program, not just rustc or cargo. This can be used for testing arbitrary toolchains @@ -449,14 +449,14 @@ using `+toolchain` as the first argument. These are equivalent: rustup run nightly cargo build"; -static DOC_HELP: &'static str = r" +static DOC_HELP: &str = r" Opens the documentation for the currently active toolchain with the default browser. By default, it opens the documentation index. Use the various flags to open specific pieces of documentation."; -static COMPLETIONS_HELP: &'static str = r" +static COMPLETIONS_HELP: &str = r" One can generate a completion script for `rustup` that is compatible with a given shell. The script is output on `stdout` allowing one to re-direct the output to the file of their choosing. Where you place the file will diff --git a/examples/14_groups.rs b/examples/14_groups.rs index fe56d3b3a81..4a1ea8396da 100644 --- a/examples/14_groups.rs +++ b/examples/14_groups.rs @@ -59,7 +59,7 @@ fn main() { // See if --set-ver was used to set the version manually let version = if let Some(ver) = matches.value_of("ver") { - format!("{}", ver) + ver.to_string() } else { // Increment the one requested (in a real program, we'd reset the lower numbers) let (maj, min, pat) = ( diff --git a/examples/18_builder_macro.rs b/examples/18_builder_macro.rs index ac28f31806d..84b0f9b6408 100644 --- a/examples/18_builder_macro.rs +++ b/examples/18_builder_macro.rs @@ -40,7 +40,7 @@ fn main() { (@arg list: -l "Lists test values") (@arg test_req: -r requires[list] "Tests requirement for listing") (@arg aaaa: --aaaa +takes_value { - |a| if a.contains("a") { + |a| if a.contains('a') { Ok(()) } else { Err(String::from("string does not contain at least one a")) diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index 6dfc910592b..77ffbb13345 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -1536,9 +1536,7 @@ impl<'b> App<'b> { let mut sc = self.subcommands.iter_mut().find(|sc| sc.id == id).expect(INTERNAL_ERROR_MSG); propagate_subcmd!(self, sc); }, - Propagation::None => { - return; - }, + Propagation::None => (), } } @@ -1841,8 +1839,7 @@ impl<'b> App<'b> { let requires_if_or_not = |&(val, req_arg)| { if let Some(v) = val { if matcher - .get(arg) - .and_then(|ma| Some(ma.contains_val(v))) + .get(arg).map(|ma| ma.contains_val(v)) .unwrap_or(false) { Some(req_arg) diff --git a/src/lib.rs b/src/lib.rs index 92e23e616d5..4a73c99f067 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -530,7 +530,7 @@ // #![cfg_attr(feature = "lints", deny(warnings))] #![cfg_attr(feature = "lints", feature(plugin))] #![cfg_attr(feature = "lints", plugin(clippy))] -// #![cfg_attr(feature = "lints", allow(cyclomatic_complexity))] +// #![cfg_attr(feature = "lints", allow(cognitive_complexity))] // #![cfg_attr(feature = "lints", allow(doc_markdown))] // #![cfg_attr(feature = "lints", allow(explicit_iter_loop))] diff --git a/src/output/help.rs b/src/output/help.rs index 29a655b6a36..d938dc7ac7f 100644 --- a/src/output/help.rs +++ b/src/output/help.rs @@ -93,7 +93,7 @@ impl<'b, 'c, 'd, 'w> Help<'b, 'c, 'd, 'w> { self.write_default_help()?; } - write!(self.writer, "\n")?; + writeln!(self.writer)?; Ok(()) } diff --git a/src/parse/arg_matcher.rs b/src/parse/arg_matcher.rs index fa63e4deaa6..fa2a488645e 100644 --- a/src/parse/arg_matcher.rs +++ b/src/parse/arg_matcher.rs @@ -77,12 +77,12 @@ impl ArgMatcher { pub fn get(&self, arg: Id) -> Option<&MatchedArg> { self.0.args.get(&arg) } - pub fn remove(&mut self, arg: Id) { self.0.args.remove(&arg); } + pub fn remove(&mut self, arg: Id) { self.0.args.swap_remove(&arg); } #[allow(dead_code)] pub fn remove_all(&mut self, args: &[Id]) { for arg in args { - self.0.args.remove(arg); + self.0.args.swap_remove(arg); } } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 64072b32de3..b18d2b53e03 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -23,9 +23,9 @@ use crate::parse::errors::Result as ClapResult; use crate::parse::features::suggestions; use crate::parse::Validator; use crate::parse::{ArgMatcher, SubCommand}; -use crate::util::{self, ChildGraph, Key, OsStrExt2, EMPTY_HASH}; #[cfg(all(feature = "debug", any(target_os = "windows", target_arch = "wasm32")))] use crate::util::OsStrExt3; +use crate::util::{self, ChildGraph, Key, OsStrExt2, EMPTY_HASH}; use crate::INTERNAL_ERROR_MSG; use crate::INVALID_UTF8; @@ -364,7 +364,7 @@ where 'b: 'c, { // The actual parsing function - #[allow(clippy::cyclomatic_complexity)] + #[allow(clippy::cognitive_complexity)] pub fn get_matches_with( &mut self, matcher: &mut ArgMatcher, @@ -437,9 +437,7 @@ where needs_val_of ); match needs_val_of { - ParseResult::Flag | ParseResult::Opt(..) | ParseResult::ValuesDone => { - continue - } + ParseResult::Flag | ParseResult::Opt(..) | ParseResult::ValuesDone => continue, _ => (), } } else if arg_os.starts_with(b"-") && arg_os.len() != 1 { @@ -465,9 +463,7 @@ where )); } } - ParseResult::Opt(..) | ParseResult::Flag | ParseResult::ValuesDone => { - continue - } + ParseResult::Opt(..) | ParseResult::Flag | ParseResult::ValuesDone => continue, _ => (), } } @@ -1014,13 +1010,13 @@ where let mut val = None; debug!("Parser::parse_long_arg: Does it contain '='..."); let arg = if full_arg.contains_byte(b'=') { - let (p0, p1) = full_arg.trim_left_matches(b'-').split_at_byte(b'='); + let (p0, p1) = full_arg.trim_start_matches(b'-').split_at_byte(b'='); sdebugln!("Yes '{:?}'", p1); val = Some(p1); p0 } else { sdebugln!("No"); - full_arg.trim_left_matches(b'-') + full_arg.trim_start_matches(b'-') }; if let Some(opt) = self.app.args.get(&KeyType::Long(arg.into())) { debugln!( @@ -1056,7 +1052,7 @@ where full_arg: &OsStr, ) -> ClapResult { debugln!("Parser::parse_short_arg: full_arg={:?}", full_arg); - let arg_os = full_arg.trim_left_matches(b'-'); + let arg_os = full_arg.trim_start_matches(b'-'); let arg = arg_os.to_string_lossy(); // If AllowLeadingHyphen is set, we want to ensure `-val` gets parsed as `-val` and not @@ -1156,7 +1152,7 @@ where debug!("Parser::parse_opt; Checking for val..."); if let Some(fv) = val { has_eq = fv.starts_with(&[b'=']) || had_eq; - let v = fv.trim_left_matches(b'='); + let v = fv.trim_start_matches(b'='); if !empty_vals && (v.is_empty() || (needs_eq && !has_eq)) { sdebugln!("Found Empty - Error"); return Err(ClapError::empty_value( @@ -1561,26 +1557,44 @@ impl<'b, 'c> Parser<'b, 'c> where 'b: 'c, { - fn contains_short(&self, s: char) -> bool { self.app.contains_short(s) } + fn contains_short(&self, s: char) -> bool { + self.app.contains_short(s) + } #[cfg_attr(feature = "lints", allow(needless_borrow))] - pub(crate) fn has_args(&self) -> bool { self.app.has_args() } + pub(crate) fn has_args(&self) -> bool { + self.app.has_args() + } - pub(crate) fn has_opts(&self) -> bool { self.app.has_opts() } + pub(crate) fn has_opts(&self) -> bool { + self.app.has_opts() + } - pub(crate) fn has_flags(&self) -> bool { self.app.has_flags() } + pub(crate) fn has_flags(&self) -> bool { + self.app.has_flags() + } pub(crate) fn has_positionals(&self) -> bool { self.app.args.keys.iter().any(|x| x.key.is_position()) } - pub(crate) fn has_subcommands(&self) -> bool { self.app.has_subcommands() } + pub(crate) fn has_subcommands(&self) -> bool { + self.app.has_subcommands() + } - pub(crate) fn has_visible_subcommands(&self) -> bool { self.app.has_visible_subcommands() } + pub(crate) fn has_visible_subcommands(&self) -> bool { + self.app.has_visible_subcommands() + } - pub(crate) fn is_set(&self, s: AS) -> bool { self.app.is_set(s) } + pub(crate) fn is_set(&self, s: AS) -> bool { + self.app.is_set(s) + } - pub(crate) fn set(&mut self, s: AS) { self.app.set(s) } + pub(crate) fn set(&mut self, s: AS) { + self.app.set(s) + } - pub(crate) fn unset(&mut self, s: AS) { self.app.unset(s) } + pub(crate) fn unset(&mut self, s: AS) { + self.app.unset(s) + } } diff --git a/src/util/fnv.rs b/src/util/fnv.rs index ac930a87d3d..9dc48486cea 100644 --- a/src/util/fnv.rs +++ b/src/util/fnv.rs @@ -34,7 +34,7 @@ impl Hasher for FnvHasher { for byte in bytes.iter() { hash ^= u64::from(*byte); - hash = hash.wrapping_mul(0x100000001b3); + hash = hash.wrapping_mul(0x100_0000_01b3); } *self = FnvHasher(hash); diff --git a/src/util/osstringext.rs b/src/util/osstringext.rs index 29c1b933cd5..bd1ff0304cd 100644 --- a/src/util/osstringext.rs +++ b/src/util/osstringext.rs @@ -15,7 +15,7 @@ pub trait OsStrExt2 { fn starts_with(&self, s: &[u8]) -> bool; fn split_at_byte(&self, b: u8) -> (&OsStr, &OsStr); fn split_at(&self, i: usize) -> (&OsStr, &OsStr); - fn trim_left_matches(&self, b: u8) -> &OsStr; + fn trim_start_matches(&self, b: u8) -> &OsStr; fn contains_byte(&self, b: u8) -> bool; fn split(&self, b: u8) -> OsSplit; } @@ -56,7 +56,7 @@ impl OsStrExt2 for OsStr { ) } - fn trim_left_matches(&self, byte: u8) -> &OsStr { + fn trim_start_matches(&self, byte: u8) -> &OsStr { let mut found = false; for (i, b) in self.as_bytes().iter().enumerate() { if b != &byte { diff --git a/tests/app_settings.rs b/tests/app_settings.rs index 0c963ce3dbf..5403734cf44 100644 --- a/tests/app_settings.rs +++ b/tests/app_settings.rs @@ -5,7 +5,7 @@ use clap::{App, AppSettings, Arg, ErrorKind, Propagation}; include!("../clap-test.rs"); -static ALLOW_EXT_SC: &'static str = "clap-test v1.4.8 +static ALLOW_EXT_SC: &str = "clap-test v1.4.8 USAGE: clap-test [SUBCOMMAND] @@ -14,7 +14,7 @@ FLAGS: -h, --help Prints help information -V, --version Prints version information"; -static DONT_COLLAPSE_ARGS: &'static str = "clap-test v1.4.8 +static DONT_COLLAPSE_ARGS: &str = "clap-test v1.4.8 USAGE: clap-test [arg1] [arg2] [arg3] @@ -28,7 +28,7 @@ FLAGS: -h, --help Prints help information -V, --version Prints version information"; -static REQUIRE_EQUALS: &'static str = "clap-test v1.4.8 +static REQUIRE_EQUALS: &str = "clap-test v1.4.8 USAGE: clap-test --opt= @@ -40,7 +40,7 @@ FLAGS: OPTIONS: -o, --opt= some"; -static UNIFIED_HELP: &'static str = "test 1.3 +static UNIFIED_HELP: &str = "test 1.3 Kevin K. tests stuff @@ -56,7 +56,7 @@ OPTIONS: --option some option -V, --version Prints version information"; -static SKIP_POS_VALS: &'static str = "test 1.3 +static SKIP_POS_VALS: &str = "test 1.3 Kevin K. tests stuff diff --git a/tests/arg_aliases.rs b/tests/arg_aliases.rs index 3f7241804c0..9c926c06138 100644 --- a/tests/arg_aliases.rs +++ b/tests/arg_aliases.rs @@ -5,7 +5,7 @@ include!("../clap-test.rs"); use clap::{App, Arg}; -static SC_VISIBLE_ALIAS_HELP: &'static str = "ct-test 1.2 +static SC_VISIBLE_ALIAS_HELP: &str = "ct-test 1.2 Some help USAGE: @@ -19,7 +19,7 @@ FLAGS: OPTIONS: -o, --opt [aliases: visible]"; -static SC_INVISIBLE_ALIAS_HELP: &'static str = "ct-test 1.2 +static SC_INVISIBLE_ALIAS_HELP: &str = "ct-test 1.2 Some help USAGE: @@ -57,7 +57,7 @@ fn multiple_aliases_of_option() { .long("aliases") .takes_value(true) .help("multiple aliases") - .aliases(&vec!["alias1", "alias2", "alias3"]), + .aliases(&["alias1", "alias2", "alias3"]), ); let long = a .clone() @@ -151,7 +151,7 @@ fn alias_on_a_subcommand_option() { .arg( Arg::with_name("other") .long("other") - .aliases(&vec!["o1", "o2", "o3"]), + .aliases(&["o1", "o2", "o3"]), ) .get_matches_from(vec!["test", "some", "--opt", "awesome"]); diff --git a/tests/conflicts.rs b/tests/conflicts.rs index 5a70944809a..526681b0b72 100644 --- a/tests/conflicts.rs +++ b/tests/conflicts.rs @@ -5,14 +5,14 @@ include!("../clap-test.rs"); use clap::{App, Arg, ArgGroup, ErrorKind}; -static CONFLICT_ERR: &'static str = "error: The argument '-F' cannot be used with '--flag' +static CONFLICT_ERR: &str = "error: The argument '-F' cannot be used with '--flag' USAGE: clap-test --flag --long-option-2 For more information try --help"; -static CONFLICT_ERR_REV: &'static str = "error: The argument '--flag' cannot be used with '-F' +static CONFLICT_ERR_REV: &str = "error: The argument '--flag' cannot be used with '-F' USAGE: clap-test -F --long-option-2 diff --git a/tests/derive_order.rs b/tests/derive_order.rs index f4c1fd5d010..ac56e33572e 100644 --- a/tests/derive_order.rs +++ b/tests/derive_order.rs @@ -7,7 +7,7 @@ use clap::{App, AppSettings, Arg}; include!("../clap-test.rs"); -static NO_DERIVE_ORDER: &'static str = "test 1.2 +static NO_DERIVE_ORDER: &str = "test 1.2 USAGE: test [FLAGS] [OPTIONS] @@ -22,7 +22,7 @@ OPTIONS: --option_a second option --option_b first option"; -static DERIVE_ORDER: &'static str = "test 1.2 +static DERIVE_ORDER: &str = "test 1.2 USAGE: test [FLAGS] [OPTIONS] @@ -37,7 +37,7 @@ OPTIONS: --option_b first option --option_a second option"; -static UNIFIED_HELP: &'static str = "test 1.2 +static UNIFIED_HELP: &str = "test 1.2 USAGE: test [OPTIONS] @@ -50,7 +50,7 @@ OPTIONS: --option_b first option -V, --version Prints version information"; -static UNIFIED_HELP_AND_DERIVE: &'static str = "test 1.2 +static UNIFIED_HELP_AND_DERIVE: &str = "test 1.2 USAGE: test [OPTIONS] @@ -63,7 +63,7 @@ OPTIONS: -h, --help Prints help information -V, --version Prints version information"; -static DERIVE_ORDER_SC_PROP: &'static str = "test-sub 1.2 +static DERIVE_ORDER_SC_PROP: &str = "test-sub 1.2 USAGE: test sub [FLAGS] [OPTIONS] @@ -78,7 +78,7 @@ OPTIONS: --option_b first option --option_a second option"; -static UNIFIED_SC_PROP: &'static str = "test-sub 1.2 +static UNIFIED_SC_PROP: &str = "test-sub 1.2 USAGE: test sub [OPTIONS] @@ -91,7 +91,7 @@ OPTIONS: --option_b first option -V, --version Prints version information"; -static UNIFIED_DERIVE_SC_PROP: &'static str = "test-sub 1.2 +static UNIFIED_DERIVE_SC_PROP: &str = "test-sub 1.2 USAGE: test sub [OPTIONS] @@ -104,7 +104,7 @@ OPTIONS: -h, --help Prints help information -V, --version Prints version information"; -static UNIFIED_DERIVE_SC_PROP_EXPLICIT_ORDER: &'static str = "test-sub 1.2 +static UNIFIED_DERIVE_SC_PROP_EXPLICIT_ORDER: &str = "test-sub 1.2 USAGE: test sub [OPTIONS] diff --git a/tests/groups.rs b/tests/groups.rs index 9f3a6eda345..044e8178324 100644 --- a/tests/groups.rs +++ b/tests/groups.rs @@ -5,7 +5,7 @@ include!("../clap-test.rs"); use clap::{App, Arg, ArgGroup, ErrorKind}; -static REQ_GROUP_USAGE: &'static str = "error: The following required arguments were not provided: +static REQ_GROUP_USAGE: &str = "error: The following required arguments were not provided: USAGE: @@ -13,7 +13,7 @@ USAGE: For more information try --help"; -static REQ_GROUP_CONFLICT_USAGE: &'static str = +static REQ_GROUP_CONFLICT_USAGE: &str = "error: The argument '' cannot be used with '--delete' USAGE: @@ -21,7 +21,7 @@ USAGE: For more information try --help"; -static REQ_GROUP_CONFLICT_REV: &'static str = +static REQ_GROUP_CONFLICT_REV: &str = "error: The argument '--delete' cannot be used with '' USAGE: diff --git a/tests/help.rs b/tests/help.rs index 13f2b787ac2..66375452234 100644 --- a/tests/help.rs +++ b/tests/help.rs @@ -6,7 +6,7 @@ include!("../clap-test.rs"); use clap::{App, AppSettings, Arg, ArgGroup, ArgSettings, ErrorKind}; -static REQUIRE_DELIM_HELP: &'static str = "test 1.3 +static REQUIRE_DELIM_HELP: &str = "test 1.3 Kevin K. tests stuff @@ -20,7 +20,7 @@ FLAGS: OPTIONS: -f, --fake : some help"; -static HELP: &'static str = "clap-test v1.4.8 +static HELP: &str = "clap-test v1.4.8 Kevin K. tests clap library @@ -51,7 +51,7 @@ SUBCOMMANDS: help Prints this message or the help of the given subcommand(s) subcmd tests subcommands"; -static SC_NEGATES_REQS: &'static str = "prog 1.0 +static SC_NEGATES_REQS: &str = "prog 1.0 USAGE: prog --opt [PATH] @@ -71,7 +71,7 @@ SUBCOMMANDS: help Prints this message or the help of the given subcommand(s) test"; -static ARGS_NEGATE_SC: &'static str = "prog 1.0 +static ARGS_NEGATE_SC: &str = "prog 1.0 USAGE: prog [FLAGS] [OPTIONS] [PATH] @@ -92,7 +92,7 @@ SUBCOMMANDS: help Prints this message or the help of the given subcommand(s) test"; -static AFTER_HELP: &'static str = "some text that comes before the help +static AFTER_HELP: &str = "some text that comes before the help clap-test v1.4.8 tests clap library @@ -106,7 +106,7 @@ FLAGS: some text that comes after the help"; -static HIDDEN_ARGS: &'static str = "prog 1.0 +static HIDDEN_ARGS: &str = "prog 1.0 USAGE: prog [FLAGS] [OPTIONS] @@ -119,7 +119,7 @@ FLAGS: OPTIONS: -o, --opt tests options"; -static SC_HELP: &'static str = "clap-test-subcmd 0.1 +static SC_HELP: &str = "clap-test-subcmd 0.1 Kevin K. tests subcommands @@ -138,7 +138,7 @@ OPTIONS: -o, --option ... tests options -s, --subcmdarg tests other args"; -static ISSUE_1046_HIDDEN_SCS: &'static str = "prog 1.0 +static ISSUE_1046_HIDDEN_SCS: &str = "prog 1.0 USAGE: prog [FLAGS] [OPTIONS] [PATH] @@ -155,7 +155,7 @@ OPTIONS: -o, --opt tests options"; // Using number_of_values(1) with multiple(true) misaligns help message -static ISSUE_760: &'static str = "ctest 0.1 +static ISSUE_760: &str = "ctest 0.1 USAGE: ctest [OPTIONS] @@ -168,7 +168,7 @@ OPTIONS: -O, --opt tests options -o, --option