mirror of
https://github.com/clap-rs/clap
synced 2025-03-04 23:37:32 +00:00
Merge #1849
1849: Arg enum r=CreepySkeleton a=pksunkara Co-authored-by: Pavan Kumar Sunkara <pavan.sss1991@gmail.com>
This commit is contained in:
commit
c1cb0ce359
27 changed files with 523 additions and 722 deletions
|
@ -4,6 +4,10 @@
|
|||
|
||||
How to append a postscript to the help message generated.
|
||||
|
||||
### [Arg Enum](arg_enum.rs)
|
||||
|
||||
How to use `ArgEnum`
|
||||
|
||||
### [At least N](at_least_two.rs)
|
||||
|
||||
How to require presence of at least N values, like `val1 val2 ... valN ... valM`.
|
||||
|
@ -21,10 +25,6 @@ as soon as [this](https://github.com/rust-lang/rust/issues/24584) is fixed (if e
|
|||
|
||||
How to use doc comments in place of `help/long_help`.
|
||||
|
||||
### [Enums as arguments](enum_in_args.rs)
|
||||
|
||||
How to use `arg_enum!` with `clap_derive`.
|
||||
|
||||
### [Arguments of subcommands in separate `struct`](enum_tuple.rs)
|
||||
|
||||
How to extract subcommands' args into external structs.
|
||||
|
@ -75,4 +75,4 @@ How to express "`"true"` or `"false"` argument.
|
|||
|
||||
### [Author, description, and version from `Cargo.toml`](from_crate.rs)
|
||||
|
||||
//! How to derive a author, description, and version from Cargo.toml
|
||||
How to derive a author, description, and version from Cargo.toml
|
||||
|
|
25
clap_derive/examples/arg_enum.rs
Normal file
25
clap_derive/examples/arg_enum.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
//! Usage example of `arg_enum`
|
||||
//!
|
||||
//! All the variants of the enum and the enum itself support `rename_all`
|
||||
|
||||
use clap::Clap;
|
||||
|
||||
#[derive(Clap, Debug, PartialEq)]
|
||||
enum ArgChoice {
|
||||
Foo,
|
||||
Bar,
|
||||
// Aliases are supported
|
||||
#[clap(alias = "b", alias = "z")]
|
||||
Baz,
|
||||
}
|
||||
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(arg_enum)]
|
||||
arg: ArgChoice,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let opt = Opt::parse();
|
||||
println!("{:#?}", opt);
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// #[macro_use]
|
||||
// extern crate clap;
|
||||
|
||||
// use clap::{App, Arg};
|
||||
|
||||
fn main() {}
|
||||
|
||||
// #[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);
|
||||
// }
|
|
@ -1,28 +0,0 @@
|
|||
fn main() {}
|
||||
// #[macro_use]
|
||||
// extern crate clap;
|
||||
|
||||
// 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);
|
||||
// }
|
|
@ -1,25 +0,0 @@
|
|||
//! How to use `arg_enum!` with `StructOpt`.
|
||||
// TODO: make it work
|
||||
fn main() {}
|
||||
// use clap::Clap;
|
||||
|
||||
// arg_enum! {
|
||||
// #[derive(Debug)]
|
||||
// enum Baz {
|
||||
// Foo,
|
||||
// Bar,
|
||||
// FooBar
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[derive(Clap, Debug)]
|
||||
// struct Opt {
|
||||
// /// Important argument.
|
||||
// #[clap(possible_values = &Baz::variants(), case_insensitive = true)]
|
||||
// i: Baz,
|
||||
// }
|
||||
|
||||
// fn main() {
|
||||
// let opt = Opt::parse();
|
||||
// println!("{:?}", opt);
|
||||
// }
|
|
@ -7,86 +7,98 @@
|
|||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
/*
|
||||
use proc_macro2;
|
||||
use quote;
|
||||
use syn;
|
||||
use syn::punctuated;
|
||||
use syn::token;
|
||||
use crate::derives::attrs::{Attrs, Name, DEFAULT_CASING, DEFAULT_ENV_CASING};
|
||||
use crate::derives::spanned::Sp;
|
||||
|
||||
pub fn derive_arg_enum(_ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
||||
let from_str_block = impl_from_str(ast)?;
|
||||
let variants_block = impl_variants(ast)?;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, DataEnum, Ident, Variant,
|
||||
};
|
||||
|
||||
pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream {
|
||||
let attrs = Attrs::from_struct(
|
||||
Span::call_site(),
|
||||
attrs,
|
||||
Name::Derived(name.clone()),
|
||||
Sp::call_site(DEFAULT_CASING),
|
||||
Sp::call_site(DEFAULT_ENV_CASING),
|
||||
);
|
||||
|
||||
let lits = lits(&e.variants, &attrs);
|
||||
let variants = gen_variants(&lits);
|
||||
let from_str = gen_from_str(&lits);
|
||||
|
||||
quote! {
|
||||
#from_str_block
|
||||
#variants_block
|
||||
#[allow(dead_code, unreachable_code, unused_variables)]
|
||||
#[allow(
|
||||
clippy::style,
|
||||
clippy::complexity,
|
||||
clippy::pedantic,
|
||||
clippy::restriction,
|
||||
clippy::perf,
|
||||
clippy::deprecated,
|
||||
clippy::nursery,
|
||||
clippy::cargo
|
||||
)]
|
||||
#[deny(clippy::correctness)]
|
||||
impl ::clap::ArgEnum for #name {
|
||||
#variants
|
||||
#from_str
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_from_str(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
||||
let ident = &ast.ident;
|
||||
let is_case_sensitive = ast.attrs.iter().any(|v| v.name() == "case_sensitive");
|
||||
let variants = variants(ast)?;
|
||||
|
||||
let strings = variants
|
||||
fn lits(
|
||||
variants: &Punctuated<Variant, Comma>,
|
||||
parent_attribute: &Attrs,
|
||||
) -> Vec<(TokenStream, Ident)> {
|
||||
variants
|
||||
.iter()
|
||||
.map(|ref variant| String::from(variant.ident.as_ref()))
|
||||
.collect::<Vec<_>>();
|
||||
.flat_map(|variant| {
|
||||
let attrs = Attrs::from_struct(
|
||||
variant.span(),
|
||||
&variant.attrs,
|
||||
Name::Derived(variant.ident.clone()),
|
||||
parent_attribute.casing(),
|
||||
parent_attribute.env_casing(),
|
||||
);
|
||||
|
||||
// All of these need to be iterators.
|
||||
let ident_slice = [ident.clone()];
|
||||
let idents = ident_slice.iter().cycle();
|
||||
let mut ret = vec![(attrs.cased_name(), variant.ident.clone())];
|
||||
|
||||
let for_error_message = strings.clone();
|
||||
attrs
|
||||
.enum_aliases()
|
||||
.into_iter()
|
||||
.for_each(|x| ret.push((x, variant.ident.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<Self, Self::Err> {
|
||||
match input {
|
||||
#(val if #condition_function(val, #strings) => Ok(#idents::#variants),)*
|
||||
_ => Err({
|
||||
let v = #for_error_message;
|
||||
format!("valid values: {}",
|
||||
v.join(" ,"))
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
ret
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn impl_variants(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
||||
let ident = &ast.ident;
|
||||
let variants = variants(ast)?
|
||||
.iter()
|
||||
.map(|ref variant| String::from(variant.ident.as_ref()))
|
||||
.collect::<Vec<_>>();
|
||||
let length = variants.len();
|
||||
fn gen_variants(lits: &[(TokenStream, Ident)]) -> TokenStream {
|
||||
let lit = lits.iter().map(|l| &l.0).collect::<Vec<_>>();
|
||||
|
||||
Ok(quote! {
|
||||
impl #ident {
|
||||
fn variants() -> [&'static str; #length] {
|
||||
#variants
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn variants(ast: &syn::DeriveInput) -> &punctuated::Punctuated<syn::Variant, token::Comma> {
|
||||
use syn::Data::*;
|
||||
|
||||
match ast.data {
|
||||
Enum(ref data) => data.variants,
|
||||
_ => panic!("Only enums are supported for deriving the ArgEnum trait"),
|
||||
quote! {
|
||||
const VARIANTS: &'static [&'static str] = &[#(#lit),*];
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_from_str(lits: &[(TokenStream, Ident)]) -> TokenStream {
|
||||
let (lit, variant): (Vec<TokenStream>, Vec<Ident>) = lits.iter().cloned().unzip();
|
||||
|
||||
quote! {
|
||||
fn from_str(input: &str, case_insensitive: bool) -> ::std::result::Result<Self, String> {
|
||||
let func = if case_insensitive {
|
||||
::std::ascii::AsciiExt::eq_ignore_ascii_case
|
||||
} else {
|
||||
str::eq
|
||||
};
|
||||
|
||||
match input {
|
||||
#(val if func(val, #lit) => Ok(Self::#variant),)*
|
||||
e => unreachable!("The impossible variant have been spotted: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -20,7 +20,10 @@ use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase};
|
|||
use proc_macro2::{self, Span, TokenStream};
|
||||
use proc_macro_error::abort;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::{self, ext::IdentExt, spanned::Spanned, Expr, Ident, MetaNameValue, Type};
|
||||
use syn::{
|
||||
self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Field, Ident, LitStr, MetaNameValue,
|
||||
Type,
|
||||
};
|
||||
|
||||
/// Default casing style for generated arguments.
|
||||
pub const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab;
|
||||
|
@ -39,14 +42,14 @@ pub enum Kind {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct Method {
|
||||
name: syn::Ident,
|
||||
args: proc_macro2::TokenStream,
|
||||
name: Ident,
|
||||
args: TokenStream,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Parser {
|
||||
pub kind: Sp<ParserKind>,
|
||||
pub func: proc_macro2::TokenStream,
|
||||
pub func: TokenStream,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
|
@ -95,6 +98,7 @@ pub struct Attrs {
|
|||
about: Option<Method>,
|
||||
version: Option<Method>,
|
||||
verbatim_doc_comment: Option<Ident>,
|
||||
is_enum: bool,
|
||||
has_custom_parser: bool,
|
||||
kind: Sp<Kind>,
|
||||
}
|
||||
|
@ -104,19 +108,19 @@ pub struct Attrs {
|
|||
/// The output of a generation method is not only the stream of new tokens but also the attribute
|
||||
/// information of the current element. These attribute information may contain valuable information
|
||||
/// for any kind of child arguments.
|
||||
pub type GenOutput = (proc_macro2::TokenStream, Attrs);
|
||||
pub type GenOutput = (TokenStream, Attrs);
|
||||
|
||||
impl Method {
|
||||
pub fn new(name: Ident, args: TokenStream) -> Self {
|
||||
Method { name, args }
|
||||
}
|
||||
|
||||
fn from_lit_or_env(ident: syn::Ident, lit: Option<syn::LitStr>, env_var: &str) -> Option<Self> {
|
||||
fn from_lit_or_env(ident: Ident, lit: Option<LitStr>, env_var: &str) -> Option<Self> {
|
||||
let mut lit = match lit {
|
||||
Some(lit) => lit,
|
||||
|
||||
None => match env::var(env_var) {
|
||||
Ok(val) => syn::LitStr::new(&val, ident.span()),
|
||||
Ok(val) => LitStr::new(&val, ident.span()),
|
||||
Err(_) => {
|
||||
abort!(ident,
|
||||
"cannot derive `{}` from Cargo.toml", ident;
|
||||
|
@ -129,7 +133,7 @@ impl Method {
|
|||
|
||||
if ident == "author" {
|
||||
let edited = process_author_str(&lit.value());
|
||||
lit = syn::LitStr::new(&edited, lit.span());
|
||||
lit = LitStr::new(&edited, lit.span());
|
||||
}
|
||||
|
||||
Some(Method::new(ident, quote!(#lit)))
|
||||
|
@ -157,7 +161,7 @@ impl Parser {
|
|||
Sp::new(Parser { kind, func }, span)
|
||||
}
|
||||
|
||||
fn from_spec(parse_ident: syn::Ident, spec: ParserSpec) -> Sp<Self> {
|
||||
fn from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp<Self> {
|
||||
use self::ParserKind::*;
|
||||
|
||||
let kind = match &*spec.kind.to_string() {
|
||||
|
@ -185,7 +189,7 @@ impl Parser {
|
|||
},
|
||||
|
||||
Some(func) => match func {
|
||||
syn::Expr::Path(_) => quote!(#func),
|
||||
Expr::Path(_) => quote!(#func),
|
||||
_ => abort!(func, "`parse` argument must be a function path"),
|
||||
},
|
||||
};
|
||||
|
@ -197,7 +201,7 @@ impl Parser {
|
|||
}
|
||||
|
||||
impl CasingStyle {
|
||||
fn from_lit(name: syn::LitStr) -> Sp<Self> {
|
||||
fn from_lit(name: LitStr) -> Sp<Self> {
|
||||
use self::CasingStyle::*;
|
||||
|
||||
let normalized = name.value().to_camel_case().to_lowercase();
|
||||
|
@ -257,7 +261,7 @@ impl Attrs {
|
|||
author: None,
|
||||
version: None,
|
||||
verbatim_doc_comment: None,
|
||||
|
||||
is_enum: false,
|
||||
has_custom_parser: false,
|
||||
kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span),
|
||||
}
|
||||
|
@ -273,7 +277,7 @@ impl Attrs {
|
|||
}
|
||||
}
|
||||
|
||||
fn push_attrs(&mut self, attrs: &[syn::Attribute]) {
|
||||
fn push_attrs(&mut self, attrs: &[Attribute]) {
|
||||
use ClapAttr::*;
|
||||
|
||||
for attr in parse_clap_attributes(attrs) {
|
||||
|
@ -286,6 +290,8 @@ impl Attrs {
|
|||
self.push_method(ident, self.name.clone().translate(*self.env_casing));
|
||||
}
|
||||
|
||||
ArgEnum(_) => self.is_enum = true,
|
||||
|
||||
Subcommand(ident) => {
|
||||
let ty = Sp::call_site(Ty::Other);
|
||||
let kind = Sp::new(Kind::Subcommand(ty), ident.span());
|
||||
|
@ -373,7 +379,7 @@ impl Attrs {
|
|||
}
|
||||
}
|
||||
|
||||
fn push_doc_comment(&mut self, attrs: &[syn::Attribute], name: &str) {
|
||||
fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) {
|
||||
use syn::Lit::*;
|
||||
use syn::Meta::*;
|
||||
|
||||
|
@ -397,7 +403,7 @@ impl Attrs {
|
|||
|
||||
pub fn from_struct(
|
||||
span: Span,
|
||||
attrs: &[syn::Attribute],
|
||||
attrs: &[Attribute],
|
||||
name: Name,
|
||||
argument_casing: Sp<CasingStyle>,
|
||||
env_casing: Sp<CasingStyle>,
|
||||
|
@ -420,7 +426,7 @@ impl Attrs {
|
|||
}
|
||||
|
||||
pub fn from_field(
|
||||
field: &syn::Field,
|
||||
field: &Field,
|
||||
struct_casing: Sp<CasingStyle>,
|
||||
env_casing: Sp<CasingStyle>,
|
||||
) -> Self {
|
||||
|
@ -511,6 +517,9 @@ impl Attrs {
|
|||
note = "see also https://github.com/clap-rs/clap_derive/tree/master/examples/true_or_false.rs";
|
||||
)
|
||||
}
|
||||
if res.is_enum {
|
||||
abort!(field.ty, "`arg_enum` is meaningless for bool")
|
||||
}
|
||||
if let Some(m) = res.find_method("default_value") {
|
||||
abort!(m.name, "default_value is meaningless for bool")
|
||||
}
|
||||
|
@ -607,6 +616,28 @@ impl Attrs {
|
|||
self.kind.clone()
|
||||
}
|
||||
|
||||
pub fn is_enum(&self) -> bool {
|
||||
self.is_enum
|
||||
}
|
||||
|
||||
pub fn case_insensitive(&self) -> TokenStream {
|
||||
let method = self.find_method("case_insensitive");
|
||||
|
||||
if let Some(method) = method {
|
||||
method.args.clone()
|
||||
} else {
|
||||
quote! { false }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enum_aliases(&self) -> Vec<TokenStream> {
|
||||
self.methods
|
||||
.iter()
|
||||
.filter(|m| m.name == "alias")
|
||||
.map(|m| m.args.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn casing(&self) -> Sp<CasingStyle> {
|
||||
self.casing.clone()
|
||||
}
|
||||
|
|
|
@ -12,13 +12,13 @@
|
|||
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
|
||||
// MIT/Apache 2.0 license.
|
||||
|
||||
use super::{dummies, from_argmatches, into_app, subcommand};
|
||||
use proc_macro2::Ident;
|
||||
use super::{arg_enum, dummies, from_argmatches, into_app, subcommand};
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro_error::abort_call_site;
|
||||
use quote::quote;
|
||||
use syn::{self, punctuated, token, Attribute, DataEnum};
|
||||
use syn::{self, punctuated, token, Attribute, DataEnum, DeriveInput, Field, Ident};
|
||||
|
||||
pub fn derive_clap(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
||||
pub fn derive_clap(input: &DeriveInput) -> TokenStream {
|
||||
use syn::Data::*;
|
||||
|
||||
let ident = &input.ident;
|
||||
|
@ -38,7 +38,7 @@ pub fn derive_clap(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
|||
dummies::clap_struct(ident);
|
||||
gen_for_struct(
|
||||
ident,
|
||||
&punctuated::Punctuated::<syn::Field, token::Comma>::new(),
|
||||
&punctuated::Punctuated::<Field, token::Comma>::new(),
|
||||
&input.attrs,
|
||||
)
|
||||
}
|
||||
|
@ -51,10 +51,10 @@ pub fn derive_clap(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
|||
}
|
||||
|
||||
fn gen_for_struct(
|
||||
name: &syn::Ident,
|
||||
fields: &punctuated::Punctuated<syn::Field, token::Comma>,
|
||||
attrs: &[syn::Attribute],
|
||||
) -> proc_macro2::TokenStream {
|
||||
name: &Ident,
|
||||
fields: &punctuated::Punctuated<Field, token::Comma>,
|
||||
attrs: &[Attribute],
|
||||
) -> TokenStream {
|
||||
let (into_app, attrs) = into_app::gen_for_struct(name, fields, attrs);
|
||||
let from_arg_matches = from_argmatches::gen_for_struct(name, fields, &attrs);
|
||||
|
||||
|
@ -66,15 +66,26 @@ fn gen_for_struct(
|
|||
}
|
||||
}
|
||||
|
||||
fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> proc_macro2::TokenStream {
|
||||
fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream {
|
||||
let into_app = into_app::gen_for_enum(name);
|
||||
let from_arg_matches = from_argmatches::gen_for_enum(name);
|
||||
let subcommand = subcommand::gen_for_enum(name, attrs, e);
|
||||
|
||||
let arg_enum = if e.variants.iter().all(|v| match v.fields {
|
||||
syn::Fields::Unit => true,
|
||||
_ => false,
|
||||
}) {
|
||||
arg_enum::gen_for_enum(name, attrs, e)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl ::clap::Clap for #name { }
|
||||
|
||||
#into_app
|
||||
#from_arg_matches
|
||||
#subcommand
|
||||
#arg_enum
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ pub fn clap_enum(name: &Ident) {
|
|||
into_app(name);
|
||||
from_arg_matches(name);
|
||||
subcommand(name);
|
||||
arg_enum(name);
|
||||
append_dummy(quote!( impl ::clap::Clap for #name {} ));
|
||||
}
|
||||
|
||||
|
@ -52,3 +53,14 @@ pub fn subcommand(name: &Ident) {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn arg_enum(name: &Ident) {
|
||||
append_dummy(quote! {
|
||||
impl ::clap::ArgEnum for #name {
|
||||
const VARIANTS: &'static [&'static str] = &[];
|
||||
fn from_str(_input: &str, _case_insensitive: bool) -> Result<Self, String> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -12,13 +12,13 @@
|
|||
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
|
||||
// MIT/Apache 2.0 license.
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::{punctuated::Punctuated, spanned::Spanned, Token};
|
||||
use syn::{punctuated::Punctuated, spanned::Spanned, Field, Ident, Token};
|
||||
|
||||
use super::{sub_type, Attrs, Kind, ParserKind, Ty};
|
||||
|
||||
pub fn gen_for_struct(
|
||||
struct_name: &syn::Ident,
|
||||
fields: &Punctuated<syn::Field, Token![,]>,
|
||||
struct_name: &Ident,
|
||||
fields: &Punctuated<Field, Token![,]>,
|
||||
parent_attribute: &Attrs,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let constructor = gen_constructor(fields, parent_attribute);
|
||||
|
@ -44,7 +44,7 @@ pub fn gen_for_struct(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn gen_for_enum(name: &syn::Ident) -> proc_macro2::TokenStream {
|
||||
pub fn gen_for_enum(name: &Ident) -> proc_macro2::TokenStream {
|
||||
quote! {
|
||||
#[allow(dead_code, unreachable_code, unused_variables)]
|
||||
#[allow(
|
||||
|
@ -69,7 +69,7 @@ pub fn gen_for_enum(name: &syn::Ident) -> proc_macro2::TokenStream {
|
|||
}
|
||||
|
||||
pub fn gen_constructor(
|
||||
fields: &Punctuated<syn::Field, Token![,]>,
|
||||
fields: &Punctuated<Field, Token![,]>,
|
||||
parent_attribute: &Attrs,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let fields = fields.iter().map(|field| {
|
||||
|
@ -149,6 +149,7 @@ pub fn gen_constructor(
|
|||
let flag = *attrs.parser().kind == ParserKind::FromFlag;
|
||||
let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences;
|
||||
let name = attrs.cased_name();
|
||||
|
||||
let field_value = match **ty {
|
||||
Ty::Bool => quote_spanned! { ty.span()=>
|
||||
matches.is_present(#name)
|
||||
|
@ -191,11 +192,24 @@ pub fn gen_constructor(
|
|||
#parse(matches.is_present(#name))
|
||||
},
|
||||
|
||||
Ty::Other => quote_spanned! { ty.span()=>
|
||||
matches.#value_of(#name)
|
||||
.map(#parse)
|
||||
.unwrap()
|
||||
},
|
||||
Ty::Other => {
|
||||
let parse = if attrs.is_enum() {
|
||||
let field_ty = &field.ty;
|
||||
let ci = attrs.case_insensitive();
|
||||
|
||||
quote_spanned! { field_ty.span()=>
|
||||
|s| <#field_ty as ::clap::ArgEnum>::from_str(s, #ci).unwrap()
|
||||
}
|
||||
} else {
|
||||
parse
|
||||
};
|
||||
|
||||
quote_spanned! { ty.span()=>
|
||||
matches.#value_of(#name)
|
||||
.map(#parse)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
quote_spanned!(field.span()=> #field_name: #field_value )
|
||||
|
|
|
@ -17,7 +17,7 @@ use std::env;
|
|||
use proc_macro2::TokenStream;
|
||||
use proc_macro_error::abort;
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::{punctuated::Punctuated, spanned::Spanned, Token};
|
||||
use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Field, Ident, Token};
|
||||
|
||||
use super::{
|
||||
spanned::Sp, ty::Ty, Attrs, GenOutput, Kind, Name, ParserKind, DEFAULT_CASING,
|
||||
|
@ -26,9 +26,9 @@ use super::{
|
|||
use crate::derives::ty::sub_type;
|
||||
|
||||
pub fn gen_for_struct(
|
||||
struct_name: &syn::Ident,
|
||||
fields: &Punctuated<syn::Field, Token![,]>,
|
||||
attrs: &[syn::Attribute],
|
||||
struct_name: &Ident,
|
||||
fields: &Punctuated<Field, Token![,]>,
|
||||
attrs: &[Attribute],
|
||||
) -> GenOutput {
|
||||
let (into_app, attrs) = gen_into_app_fn(attrs);
|
||||
let augment_clap = gen_augment_clap_fn(fields, &attrs);
|
||||
|
@ -55,7 +55,7 @@ pub fn gen_for_struct(
|
|||
(tokens, attrs)
|
||||
}
|
||||
|
||||
pub fn gen_for_enum(name: &syn::Ident) -> TokenStream {
|
||||
pub fn gen_for_enum(name: &Ident) -> TokenStream {
|
||||
let app_name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
|
||||
|
||||
quote! {
|
||||
|
@ -85,7 +85,7 @@ pub fn gen_for_enum(name: &syn::Ident) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
fn gen_into_app_fn(attrs: &[syn::Attribute]) -> GenOutput {
|
||||
fn gen_into_app_fn(attrs: &[Attribute]) -> GenOutput {
|
||||
let app_name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
|
||||
|
||||
let attrs = Attrs::from_struct(
|
||||
|
@ -107,10 +107,10 @@ fn gen_into_app_fn(attrs: &[syn::Attribute]) -> GenOutput {
|
|||
}
|
||||
|
||||
fn gen_augment_clap_fn(
|
||||
fields: &Punctuated<syn::Field, Token![,]>,
|
||||
fields: &Punctuated<Field, Token![,]>,
|
||||
parent_attribute: &Attrs,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let app_var = syn::Ident::new("app", proc_macro2::Span::call_site());
|
||||
let app_var = Ident::new("app", proc_macro2::Span::call_site());
|
||||
let augmentation = gen_app_augmentation(fields, &app_var, parent_attribute);
|
||||
quote! {
|
||||
fn augment_clap<'b>(#app_var: ::clap::App<'b>) -> ::clap::App<'b> {
|
||||
|
@ -122,8 +122,8 @@ fn gen_augment_clap_fn(
|
|||
/// Generate a block of code to add arguments/subcommands corresponding to
|
||||
/// the `fields` to an app.
|
||||
pub fn gen_app_augmentation(
|
||||
fields: &Punctuated<syn::Field, Token![,]>,
|
||||
app_var: &syn::Ident,
|
||||
fields: &Punctuated<Field, Token![,]>,
|
||||
app_var: &Ident,
|
||||
parent_attribute: &Attrs,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let mut subcmds = fields.iter().filter_map(|field| {
|
||||
|
@ -196,6 +196,7 @@ pub fn gen_app_augmentation(
|
|||
let parser = attrs.parser();
|
||||
let func = &parser.func;
|
||||
let validator = match *parser.kind {
|
||||
_ if attrs.is_enum() => quote!(),
|
||||
ParserKind::TryFromStr => quote_spanned! { func.span()=>
|
||||
.validator(|s| {
|
||||
#func(s.as_str())
|
||||
|
@ -249,9 +250,21 @@ pub fn gen_app_augmentation(
|
|||
|
||||
Ty::Other => {
|
||||
let required = !attrs.has_method("default_value");
|
||||
|
||||
let possible_values = if attrs.is_enum() {
|
||||
let field_ty = &field.ty;
|
||||
|
||||
quote_spanned! { field_ty.span()=>
|
||||
.possible_values(&<#field_ty as ::clap::ArgEnum>::VARIANTS)
|
||||
}
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
quote_spanned! { ty.span()=>
|
||||
.takes_value(true)
|
||||
.required(#required)
|
||||
#possible_values
|
||||
#validator
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
|
||||
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
|
||||
// MIT/Apache 2.0 license.
|
||||
pub mod arg_enum;
|
||||
mod arg_enum;
|
||||
pub mod attrs;
|
||||
mod clap;
|
||||
mod doc_comments;
|
||||
|
|
|
@ -16,6 +16,7 @@ pub enum ClapAttr {
|
|||
Long(Ident),
|
||||
Env(Ident),
|
||||
Flatten(Ident),
|
||||
ArgEnum(Ident),
|
||||
Subcommand(Ident),
|
||||
VerbatimDocComment(Ident),
|
||||
|
||||
|
@ -167,6 +168,7 @@ impl Parse for ClapAttr {
|
|||
"short" => Ok(Short(name)),
|
||||
"env" => Ok(Env(name)),
|
||||
"flatten" => Ok(Flatten(name)),
|
||||
"arg_enum" => Ok(ArgEnum(name)),
|
||||
"subcommand" => Ok(Subcommand(name)),
|
||||
|
||||
"verbatim_doc_comment" => Ok(VerbatimDocComment(name)),
|
||||
|
|
|
@ -41,9 +41,9 @@ pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStr
|
|||
}
|
||||
|
||||
fn gen_augment_subcommands(
|
||||
variants: &Punctuated<syn::Variant, Token![,]>,
|
||||
variants: &Punctuated<Variant, Token![,]>,
|
||||
parent_attribute: &Attrs,
|
||||
) -> proc_macro2::TokenStream {
|
||||
) -> TokenStream {
|
||||
use syn::Fields::*;
|
||||
|
||||
let subcommands = variants.iter().map(|variant| {
|
||||
|
@ -115,10 +115,10 @@ fn gen_augment_subcommands(
|
|||
}
|
||||
|
||||
fn gen_from_subcommand(
|
||||
name: &syn::Ident,
|
||||
name: &Ident,
|
||||
variants: &Punctuated<Variant, Token![,]>,
|
||||
parent_attribute: &Attrs,
|
||||
) -> proc_macro2::TokenStream {
|
||||
) -> TokenStream {
|
||||
use syn::Fields::*;
|
||||
let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants
|
||||
.iter()
|
||||
|
|
251
clap_derive/tests/arg_enum.rs
Normal file
251
clap_derive/tests/arg_enum.rs
Normal file
|
@ -0,0 +1,251 @@
|
|||
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
|
||||
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
|
||||
// Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
use clap::Clap;
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
enum ArgChoice {
|
||||
Foo,
|
||||
Bar,
|
||||
}
|
||||
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(arg_enum)]
|
||||
arg: ArgChoice,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::Foo
|
||||
},
|
||||
Opt::parse_from(&["", "foo"])
|
||||
);
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::Bar
|
||||
},
|
||||
Opt::parse_from(&["", "bar"])
|
||||
);
|
||||
assert!(Opt::try_parse_from(&["", "fOo"]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_word_is_renamed_kebab() {
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
#[allow(non_camel_case_types)]
|
||||
enum ArgChoice {
|
||||
FooBar,
|
||||
BAR_BAZ,
|
||||
}
|
||||
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(arg_enum)]
|
||||
arg: ArgChoice,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::FooBar
|
||||
},
|
||||
Opt::parse_from(&["", "foo-bar"])
|
||||
);
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::BAR_BAZ
|
||||
},
|
||||
Opt::parse_from(&["", "bar-baz"])
|
||||
);
|
||||
assert!(Opt::try_parse_from(&["", "FooBar"]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variant_with_defined_casing() {
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
enum ArgChoice {
|
||||
#[clap(rename_all = "screaming_snake")]
|
||||
FooBar,
|
||||
}
|
||||
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(arg_enum)]
|
||||
arg: ArgChoice,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::FooBar
|
||||
},
|
||||
Opt::parse_from(&["", "FOO_BAR"])
|
||||
);
|
||||
assert!(Opt::try_parse_from(&["", "FooBar"]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn casing_is_propogated_from_parent() {
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
#[clap(rename_all = "screaming_snake")]
|
||||
enum ArgChoice {
|
||||
FooBar,
|
||||
}
|
||||
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(arg_enum)]
|
||||
arg: ArgChoice,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::FooBar
|
||||
},
|
||||
Opt::parse_from(&["", "FOO_BAR"])
|
||||
);
|
||||
assert!(Opt::try_parse_from(&["", "FooBar"]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn casing_propogation_is_overridden() {
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
#[clap(rename_all = "screaming_snake")]
|
||||
enum ArgChoice {
|
||||
#[clap(rename_all = "camel")]
|
||||
FooBar,
|
||||
}
|
||||
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(arg_enum)]
|
||||
arg: ArgChoice,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::FooBar
|
||||
},
|
||||
Opt::parse_from(&["", "fooBar"])
|
||||
);
|
||||
assert!(Opt::try_parse_from(&["", "FooBar"]).is_err());
|
||||
assert!(Opt::try_parse_from(&["", "FOO_BAR"]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn case_insensitive() {
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
enum ArgChoice {
|
||||
Foo,
|
||||
}
|
||||
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(arg_enum, case_insensitive(true))]
|
||||
arg: ArgChoice,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::Foo
|
||||
},
|
||||
Opt::parse_from(&["", "foo"])
|
||||
);
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::Foo
|
||||
},
|
||||
Opt::parse_from(&["", "fOo"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn case_insensitive_set_to_false() {
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
enum ArgChoice {
|
||||
Foo,
|
||||
}
|
||||
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(arg_enum, case_insensitive(false))]
|
||||
arg: ArgChoice,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::Foo
|
||||
},
|
||||
Opt::parse_from(&["", "foo"])
|
||||
);
|
||||
assert!(Opt::try_parse_from(&["", "fOo"]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias() {
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
enum ArgChoice {
|
||||
#[clap(alias = "TOTP")]
|
||||
TOTP,
|
||||
}
|
||||
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(arg_enum, case_insensitive(false))]
|
||||
arg: ArgChoice,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::TOTP
|
||||
},
|
||||
Opt::parse_from(&["", "totp"])
|
||||
);
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::TOTP
|
||||
},
|
||||
Opt::parse_from(&["", "TOTP"])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_alias() {
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
enum ArgChoice {
|
||||
#[clap(alias = "TOTP", alias = "t")]
|
||||
TOTP,
|
||||
}
|
||||
|
||||
#[derive(Clap, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[clap(arg_enum, case_insensitive(false))]
|
||||
arg: ArgChoice,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::TOTP
|
||||
},
|
||||
Opt::parse_from(&["", "totp"])
|
||||
);
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::TOTP
|
||||
},
|
||||
Opt::parse_from(&["", "TOTP"])
|
||||
);
|
||||
assert_eq!(
|
||||
Opt {
|
||||
arg: ArgChoice::TOTP
|
||||
},
|
||||
Opt::parse_from(&["", "t"])
|
||||
);
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
// // Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
|
||||
// // Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
|
||||
// // Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
|
||||
// //
|
||||
// // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// // option. This file may not be copied, modified, or distributed
|
||||
// // except according to those terms.
|
||||
// #[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()),
|
||||
// )
|
||||
// .try_get_matches_from(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()),
|
||||
// )
|
||||
// .try_get_matches_from(vec!["", "Foo"])
|
||||
// .unwrap();
|
||||
// let t = value_t!(matches.value_of("arg"), ArgChoice);
|
||||
// assert!(t.is_ok());
|
||||
// assert_eq!(t.unwrap(), ArgChoice::Foo);
|
||||
// }
|
|
@ -1,51 +0,0 @@
|
|||
// // Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
|
||||
// // Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
|
||||
// // Andrew Hobden (@hoverbear) <andrew@hoverbear.org>
|
||||
// //
|
||||
// // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// // option. This file may not be copied, modified, or distributed
|
||||
// // except according to those terms.
|
||||
// #[macro_use]
|
||||
// extern crate clap;
|
||||
|
||||
// use clap::{App, Arg, ArgEnum};
|
||||
|
||||
// #[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()),
|
||||
// )
|
||||
// .try_get_matches_from(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()),
|
||||
// )
|
||||
// .try_get_matches_from(vec!["", "Foo"])
|
||||
// .unwrap();
|
||||
// let t = value_t!(matches.value_of("arg"), ArgChoice);
|
||||
// assert!(t.is_ok());
|
||||
// assert_eq!(t.unwrap(), ArgChoice::Foo);
|
||||
// }
|
13
clap_derive/tests/ui/bool_arg_enum.rs
Normal file
13
clap_derive/tests/ui/bool_arg_enum.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use clap::Clap;
|
||||
|
||||
#[derive(Clap, Debug)]
|
||||
#[clap(name = "basic")]
|
||||
struct Opt {
|
||||
#[clap(short, arg_enum)]
|
||||
opts: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let opt = Opt::parse();
|
||||
println!("{:?}", opt);
|
||||
}
|
5
clap_derive/tests/ui/bool_arg_enum.stderr
Normal file
5
clap_derive/tests/ui/bool_arg_enum.stderr
Normal file
|
@ -0,0 +1,5 @@
|
|||
error: `arg_enum` is meaningless for bool
|
||||
--> $DIR/bool_arg_enum.rs:7:11
|
||||
|
|
||||
7 | opts: bool,
|
||||
| ^^^^
|
|
@ -1,67 +0,0 @@
|
|||
// You can use clap's `ArgMatches::value_of_t` method with a custom enum
|
||||
// by implementing the std::str::FromStr trait which is very straight forward.
|
||||
// There are three ways to do this, for simple enums
|
||||
// meaning those that don't require 'pub' or any '#[derive()]' directives you can use clap's
|
||||
// simple_enum! macro. For those that require 'pub' or any '#[derive()]'s you can use clap's
|
||||
// arg_enum! macro. The third way is to implement std::str::FromStr manually.
|
||||
//
|
||||
// In most circumstances using either simple_enum! or arg_enum! is fine.
|
||||
//
|
||||
// In the following example we will create two enums using macros, assign a positional argument
|
||||
// that accepts only one of those values, and use clap to parse the argument.
|
||||
|
||||
// Add clap like normal
|
||||
use clap::{arg_enum, App, Arg};
|
||||
|
||||
// Using arg_enum! is more like traditional enum declarations
|
||||
//
|
||||
// **NOTE:** Only bare variants are supported
|
||||
arg_enum! {
|
||||
#[derive(Debug)]
|
||||
pub enum Oof {
|
||||
Rab,
|
||||
Zab,
|
||||
Xuq
|
||||
}
|
||||
}
|
||||
|
||||
arg_enum! {
|
||||
#[derive(Debug)]
|
||||
enum Foo {
|
||||
Bar,
|
||||
Baz,
|
||||
Qux
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Create the application like normal
|
||||
let enum_vals = ["fast", "slow"];
|
||||
let m = App::new("myapp")
|
||||
// Use a single positional argument that is required
|
||||
.arg(Arg::from("<foo> 'The Foo to use'").possible_values(&Foo::variants()))
|
||||
.arg(
|
||||
Arg::from("<speed> 'The speed to use'")
|
||||
// You can define a list of possible values if you want the values to be
|
||||
// displayed in the help information. Whether you use possible_values() or
|
||||
// not, the valid values will ALWAYS be displayed on a failed parse.
|
||||
.possible_values(&enum_vals),
|
||||
)
|
||||
// For the second positional, lets not use possible_values() just to show the difference
|
||||
.arg("<oof> 'The Oof to use'")
|
||||
.get_matches();
|
||||
|
||||
// Note that you don't have to specify the type since rustc can infer it for you
|
||||
let t = m.value_of_t("foo").unwrap_or_else(|e| e.exit());
|
||||
let t2: Oof = m.value_of_t("oof").unwrap_or_else(|e| e.exit());
|
||||
|
||||
// Now we can use our enum like normal.
|
||||
match t {
|
||||
Foo::Bar => println!("Found a Bar"),
|
||||
Foo::Baz => println!("Found a Baz"),
|
||||
Foo::Qux => println!("Found a Qux"),
|
||||
}
|
||||
|
||||
// Since our Oof derives Debug, we can do this:
|
||||
println!("Oof: {:?}", t2);
|
||||
}
|
|
@ -3376,8 +3376,6 @@ impl<'help> Arg<'help> {
|
|||
/// When used with [`Arg::possible_values`] it allows the argument value to pass validation even
|
||||
/// if the case differs from that of the specified `possible_value`.
|
||||
///
|
||||
/// **Pro Tip:** Use this setting with [`arg_enum!`]
|
||||
///
|
||||
/// **NOTE:** Setting this implies [`ArgSettings::TakesValue`]
|
||||
///
|
||||
/// # Examples
|
||||
|
@ -3414,7 +3412,6 @@ impl<'help> Arg<'help> {
|
|||
/// let matched_vals = m.values_of("option").unwrap().collect::<Vec<_>>();
|
||||
/// assert_eq!(&*matched_vals, &["TeSt123", "teST123", "tESt321"]);
|
||||
/// ```
|
||||
/// [`arg_enum!`]: ./macro.arg_enum.html
|
||||
#[inline]
|
||||
pub fn case_insensitive(self, ci: bool) -> Self {
|
||||
if ci {
|
||||
|
|
|
@ -65,7 +65,13 @@ pub trait Subcommand: Sized {
|
|||
}
|
||||
|
||||
/// @TODO @release @docs
|
||||
pub trait ArgEnum {}
|
||||
pub trait ArgEnum: Sized {
|
||||
/// @TODO @release @docs
|
||||
const VARIANTS: &'static [&'static str];
|
||||
|
||||
/// @TODO @release @docs
|
||||
fn from_str(input: &str, case_insensitive: bool) -> Result<Self, String>;
|
||||
}
|
||||
|
||||
impl<T: Clap> Clap for Box<T> {
|
||||
fn parse() -> Self {
|
||||
|
|
|
@ -448,7 +448,7 @@
|
|||
compile_error!("`std` feature is currently required to build `clap`");
|
||||
|
||||
pub use crate::build::{App, AppSettings, Arg, ArgGroup, ArgSettings};
|
||||
pub use crate::derive::{Clap, FromArgMatches, IntoApp, Subcommand};
|
||||
pub use crate::derive::{ArgEnum, Clap, FromArgMatches, IntoApp, Subcommand};
|
||||
pub use crate::parse::errors::{Error, ErrorKind, Result};
|
||||
pub use crate::parse::{ArgMatches, OsValues, SubCommand, Values};
|
||||
|
||||
|
|
149
src/macros.rs
149
src/macros.rs
|
@ -60,155 +60,6 @@ macro_rules! _clap_count_exprs {
|
|||
($e:expr, $($es:expr),+) => { 1 + $crate::_clap_count_exprs!($($es),*) };
|
||||
}
|
||||
|
||||
/// Convenience macro to generate more complete enums with variants to be used as a type when
|
||||
/// parsing arguments. This enum also provides a `variants()` function which can be used to
|
||||
/// retrieve a `Vec<&'static str>` of the variant names, as well as implementing [`FromStr`] and
|
||||
/// [`Display`] automatically.
|
||||
///
|
||||
/// **NOTE:** Case insensitivity is supported for ASCII characters only. It's highly recommended to
|
||||
/// use [`Arg::case_insensitive(true)`] for args that will be used with these enums
|
||||
///
|
||||
/// **NOTE:** This macro automatically implements [`std::str::FromStr`] and [`std::fmt::Display`]
|
||||
///
|
||||
/// **NOTE:** These enums support pub (or not) and uses of the `#[derive()]` traits
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use]
|
||||
/// # extern crate clap;
|
||||
/// # use clap::{App, Arg};
|
||||
/// arg_enum!{
|
||||
/// #[derive(PartialEq, Debug)]
|
||||
/// pub enum Foo {
|
||||
/// Bar,
|
||||
/// Baz,
|
||||
/// Qux
|
||||
/// }
|
||||
/// }
|
||||
/// // Foo enum can now be used via Foo::Bar, or Foo::Baz, etc
|
||||
/// // and implements std::str::FromStr to use with the value_t! macros
|
||||
/// fn main() {
|
||||
/// let m = App::new("app")
|
||||
/// .arg(Arg::from("<foo> 'the foo'")
|
||||
/// .possible_values(&Foo::variants())
|
||||
/// .case_insensitive(true))
|
||||
/// .get_matches_from(vec![
|
||||
/// "app", "baz"
|
||||
/// ]);
|
||||
/// let f: Foo = m.value_of_t_or_exit("foo");
|
||||
///
|
||||
/// assert_eq!(f, Foo::Baz);
|
||||
/// }
|
||||
/// ```
|
||||
/// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html
|
||||
/// [`std::str::FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html
|
||||
/// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
|
||||
/// [`std::fmt::Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
|
||||
/// [`Arg::case_insensitive(true)`]: ./struct.Arg.html#method.case_insensitive
|
||||
#[macro_export]
|
||||
macro_rules! arg_enum {
|
||||
(@as_item $($i:item)*) => ($($i)*);
|
||||
(@impls ( $($tts:tt)* ) -> ($e:ident, $($v:ident),+)) => {
|
||||
$crate::arg_enum!(@as_item
|
||||
$($tts)*
|
||||
|
||||
impl ::std::str::FromStr for $e {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> ::std::result::Result<Self,Self::Err> {
|
||||
match s {
|
||||
$(stringify!($v) |
|
||||
_ if s.eq_ignore_ascii_case(stringify!($v)) => Ok($e::$v)),+,
|
||||
_ => Err({
|
||||
let v = vec![
|
||||
$(stringify!($v),)+
|
||||
];
|
||||
format!("valid values: {}",
|
||||
v.join(", "))
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ::std::fmt::Display for $e {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
match *self {
|
||||
$($e::$v => write!(f, stringify!($v)),)+
|
||||
}
|
||||
}
|
||||
}
|
||||
impl $e {
|
||||
#[allow(dead_code)]
|
||||
#[allow(missing_docs)]
|
||||
pub fn variants() -> [&'static str; $crate::_clap_count_exprs!($(stringify!($v)),+)] {
|
||||
[
|
||||
$(stringify!($v),)+
|
||||
]
|
||||
}
|
||||
});
|
||||
};
|
||||
($(#[$($m:meta),+])+ pub enum $e:ident { $($v:ident $(=$val:expr)*,)+ } ) => {
|
||||
$crate::arg_enum!(@impls
|
||||
($(#[$($m),+])+
|
||||
pub enum $e {
|
||||
$($v$(=$val)*),+
|
||||
}) -> ($e, $($v),+)
|
||||
);
|
||||
};
|
||||
($(#[$($m:meta),+])+ pub enum $e:ident { $($v:ident $(=$val:expr)*),+ } ) => {
|
||||
$crate::arg_enum!(@impls
|
||||
($(#[$($m),+])+
|
||||
pub enum $e {
|
||||
$($v$(=$val)*),+
|
||||
}) -> ($e, $($v),+)
|
||||
);
|
||||
};
|
||||
($(#[$($m:meta),+])+ enum $e:ident { $($v:ident $(=$val:expr)*,)+ } ) => {
|
||||
$crate::arg_enum!(@impls
|
||||
($(#[$($m),+])+
|
||||
enum $e {
|
||||
$($v$(=$val)*),+
|
||||
}) -> ($e, $($v),+)
|
||||
);
|
||||
};
|
||||
($(#[$($m:meta),+])+ enum $e:ident { $($v:ident $(=$val:expr)*),+ } ) => {
|
||||
$crate::arg_enum!(@impls
|
||||
($(#[$($m),+])+
|
||||
enum $e {
|
||||
$($v$(=$val)*),+
|
||||
}) -> ($e, $($v),+)
|
||||
);
|
||||
};
|
||||
(pub enum $e:ident { $($v:ident $(=$val:expr)*,)+ } ) => {
|
||||
$crate::arg_enum!(@impls
|
||||
(pub enum $e {
|
||||
$($v$(=$val)*),+
|
||||
}) -> ($e, $($v),+)
|
||||
);
|
||||
};
|
||||
(pub enum $e:ident { $($v:ident $(=$val:expr)*),+ } ) => {
|
||||
$crate::arg_enum!(@impls
|
||||
(pub enum $e {
|
||||
$($v$(=$val)*),+
|
||||
}) -> ($e, $($v),+)
|
||||
);
|
||||
};
|
||||
(enum $e:ident { $($v:ident $(=$val:expr)*,)+ } ) => {
|
||||
$crate::arg_enum!(@impls
|
||||
(enum $e {
|
||||
$($v$(=$val)*),+
|
||||
}) -> ($e, $($v),+)
|
||||
);
|
||||
};
|
||||
(enum $e:ident { $($v:ident $(=$val:expr)*),+ } ) => {
|
||||
$crate::arg_enum!(@impls
|
||||
(enum $e {
|
||||
$($v$(=$val)*),+
|
||||
}) -> ($e, $($v),+)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/// Allows you to pull the version from your Cargo.toml at compile time as
|
||||
/// `MAJOR.MINOR.PATCH_PKGVERSION_PRE`
|
||||
///
|
||||
|
|
117
tests/macros.rs
117
tests/macros.rs
|
@ -1,6 +1,6 @@
|
|||
mod utils;
|
||||
|
||||
use clap::{arg_enum, clap_app, ErrorKind};
|
||||
use clap::{clap_app, ErrorKind};
|
||||
|
||||
static LITERALS: &str = "clap-tests 0.1
|
||||
|
||||
|
@ -330,118 +330,3 @@ fn literals() {
|
|||
false
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arg_enum() {
|
||||
// Helper macros to avoid repetition
|
||||
macro_rules! test_greek {
|
||||
($arg_enum:item, $tests:block) => {{
|
||||
$arg_enum
|
||||
// FromStr implementation
|
||||
assert!("Charlie".parse::<Greek>().is_err());
|
||||
// Display implementation
|
||||
assert_eq!(format!("{}", Greek::Alpha), "Alpha");
|
||||
assert_eq!(format!("{}", Greek::Bravo), "Bravo");
|
||||
// fn variants()
|
||||
assert_eq!(Greek::variants(), ["Alpha", "Bravo"]);
|
||||
// rest of tests
|
||||
$tests
|
||||
}};
|
||||
}
|
||||
macro_rules! test_greek_no_meta {
|
||||
{$arg_enum:item} => {
|
||||
test_greek!($arg_enum, {
|
||||
// FromStr implementation
|
||||
assert!("Alpha".parse::<Greek>().is_ok());
|
||||
assert!("Bravo".parse::<Greek>().is_ok());
|
||||
})
|
||||
};
|
||||
}
|
||||
macro_rules! test_greek_meta {
|
||||
{$arg_enum:item} => {
|
||||
test_greek!($arg_enum, {
|
||||
// FromStr implementation
|
||||
assert_eq!("Alpha".parse::<Greek>(), Ok(Greek::Alpha));
|
||||
assert_eq!("Bravo".parse::<Greek>(), Ok(Greek::Bravo));
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// Tests for each pattern
|
||||
// meta NO, pub NO, trailing comma NO
|
||||
test_greek_no_meta! {
|
||||
arg_enum!{
|
||||
enum Greek {
|
||||
Alpha,
|
||||
Bravo
|
||||
}
|
||||
}
|
||||
};
|
||||
// meta NO, pub NO, trailing comma YES
|
||||
test_greek_no_meta! {
|
||||
arg_enum!{
|
||||
enum Greek {
|
||||
Alpha,
|
||||
Bravo,
|
||||
}
|
||||
}
|
||||
};
|
||||
// meta NO, pub YES, trailing comma NO
|
||||
test_greek_no_meta! {
|
||||
arg_enum!{
|
||||
pub enum Greek {
|
||||
Alpha,
|
||||
Bravo
|
||||
}
|
||||
}
|
||||
};
|
||||
// meta NO, pub YES, trailing comma YES
|
||||
test_greek_no_meta! {
|
||||
arg_enum!{
|
||||
pub enum Greek {
|
||||
Alpha,
|
||||
Bravo,
|
||||
}
|
||||
}
|
||||
};
|
||||
// meta YES, pub NO, trailing comma NO
|
||||
test_greek_meta! {
|
||||
arg_enum!{
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
enum Greek {
|
||||
Alpha,
|
||||
Bravo
|
||||
}
|
||||
}
|
||||
};
|
||||
// meta YES, pub NO, trailing comma YES
|
||||
test_greek_meta! {
|
||||
arg_enum!{
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
enum Greek {
|
||||
Alpha,
|
||||
Bravo,
|
||||
}
|
||||
}
|
||||
};
|
||||
// meta YES, pub YES, trailing comma NO
|
||||
test_greek_meta! {
|
||||
arg_enum!{
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum Greek {
|
||||
Alpha,
|
||||
Bravo
|
||||
}
|
||||
}
|
||||
};
|
||||
// meta YES, pub YES, trailing comma YES
|
||||
test_greek_meta! {
|
||||
arg_enum!{
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum Greek {
|
||||
Alpha,
|
||||
Bravo,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ mod utils;
|
|||
use std::io::Write;
|
||||
use std::str;
|
||||
|
||||
use clap::{arg_enum, App, Arg};
|
||||
use clap::{App, Arg};
|
||||
|
||||
static SCF2OP: &str = "flag present 2 times
|
||||
option NOT present
|
||||
|
@ -220,80 +220,6 @@ pub fn check_complex_output(args: &str, out: &str) {
|
|||
assert_eq!(res, out);
|
||||
}
|
||||
|
||||
arg_enum! {
|
||||
#[derive(Debug)]
|
||||
enum Val1 {
|
||||
ValOne,
|
||||
ValTwo
|
||||
}
|
||||
}
|
||||
arg_enum! {
|
||||
#[derive(Debug)]
|
||||
pub enum Val2 {
|
||||
ValOne,
|
||||
ValTwo
|
||||
}
|
||||
}
|
||||
arg_enum! {
|
||||
enum Val3 {
|
||||
ValOne,
|
||||
ValTwo
|
||||
}
|
||||
}
|
||||
arg_enum! {
|
||||
pub enum Val4 {
|
||||
ValOne,
|
||||
ValTwo
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enums() {
|
||||
let v1_lower = "valone";
|
||||
let v1_camel = "ValOne";
|
||||
|
||||
let v1_lp = v1_lower.parse::<Val1>().unwrap();
|
||||
let v1_cp = v1_camel.parse::<Val1>().unwrap();
|
||||
match v1_lp {
|
||||
Val1::ValOne => (),
|
||||
_ => panic!("Val1 didn't parse correctly"),
|
||||
}
|
||||
match v1_cp {
|
||||
Val1::ValOne => (),
|
||||
_ => panic!("Val1 didn't parse correctly"),
|
||||
}
|
||||
let v1_lp = v1_lower.parse::<Val2>().unwrap();
|
||||
let v1_cp = v1_camel.parse::<Val2>().unwrap();
|
||||
match v1_lp {
|
||||
Val2::ValOne => (),
|
||||
_ => panic!("Val1 didn't parse correctly"),
|
||||
}
|
||||
match v1_cp {
|
||||
Val2::ValOne => (),
|
||||
_ => panic!("Val1 didn't parse correctly"),
|
||||
}
|
||||
let v1_lp = v1_lower.parse::<Val3>().unwrap();
|
||||
let v1_cp = v1_camel.parse::<Val3>().unwrap();
|
||||
match v1_lp {
|
||||
Val3::ValOne => (),
|
||||
_ => panic!("Val1 didn't parse correctly"),
|
||||
}
|
||||
match v1_cp {
|
||||
Val3::ValOne => (),
|
||||
_ => panic!("Val1 didn't parse correctly"),
|
||||
}
|
||||
let v1_lp = v1_lower.parse::<Val4>().unwrap();
|
||||
let v1_cp = v1_camel.parse::<Val4>().unwrap();
|
||||
match v1_lp {
|
||||
Val4::ValOne => (),
|
||||
_ => panic!("Val1 didn't parse correctly"),
|
||||
}
|
||||
match v1_cp {
|
||||
Val4::ValOne => (),
|
||||
_ => panic!("Val1 didn't parse correctly"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_app() {
|
||||
let _ = App::new("test")
|
||||
|
|
Loading…
Add table
Reference in a new issue