mirror of
https://github.com/clap-rs/clap
synced 2024-12-13 14:22:34 +00:00
Added arg_enum support
This commit is contained in:
parent
c1ab22c3c9
commit
01c179f527
20 changed files with 388 additions and 272 deletions
|
@ -21,10 +21,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 +71,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
|
||||
|
|
19
clap_derive/examples/arg_enum.rs
Normal file
19
clap_derive/examples/arg_enum.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use clap::Clap;
|
||||
|
||||
#[derive(Clap, Debug, PartialEq)]
|
||||
enum ArgChoice {
|
||||
Foo,
|
||||
Bar,
|
||||
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,86 @@
|
|||
// <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(&e.variants, &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> {
|
||||
variants
|
||||
.iter()
|
||||
.map(|ref variant| String::from(variant.ident.as_ref()))
|
||||
.collect::<Vec<_>>();
|
||||
.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 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<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(" ,"))
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
attrs.cased_name()
|
||||
})
|
||||
.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();
|
||||
|
||||
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"),
|
||||
fn gen_variants(lits: &Vec<TokenStream>) -> TokenStream {
|
||||
quote! {
|
||||
const VARIANTS: &'static [&'static str] = &[#(#lits),*];
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_from_str(variants: &Punctuated<Variant, Comma>, lits: &Vec<TokenStream>) -> TokenStream {
|
||||
let matches = variants.iter().map(|v| &v.ident).collect::<Vec<_>>();
|
||||
|
||||
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, #lits) => Ok(Self::#matches),)*
|
||||
_ => Err(String::from("something went wrong parsing the value")),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -98,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>,
|
||||
}
|
||||
|
@ -260,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),
|
||||
}
|
||||
|
@ -289,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());
|
||||
|
@ -514,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, "enum is meaningless for bool")
|
||||
}
|
||||
if let Some(m) = res.find_method("default_value") {
|
||||
abort!(m.name, "default_value is meaningless for bool")
|
||||
}
|
||||
|
@ -610,6 +616,20 @@ 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 casing(&self) -> Sp<CasingStyle> {
|
||||
self.casing.clone()
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
|
||||
// MIT/Apache 2.0 license.
|
||||
|
||||
use super::{dummies, from_argmatches, into_app, subcommand};
|
||||
use super::{arg_enum, dummies, from_argmatches, into_app, subcommand};
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro_error::abort_call_site;
|
||||
use quote::quote;
|
||||
|
@ -70,11 +70,25 @@ 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| {
|
||||
if let syn::Fields::Unit = v.fields {
|
||||
true
|
||||
} else {
|
||||
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!()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -192,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! {
|
||||
|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 )
|
||||
|
|
|
@ -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! {
|
||||
.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)),
|
||||
|
|
189
clap_derive/tests/arg_enum.rs
Normal file
189
clap_derive/tests/arg_enum.rs
Normal file
|
@ -0,0 +1,189 @@
|
|||
// 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());
|
||||
}
|
|
@ -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: enum is meaningless for bool
|
||||
--> $DIR/bool_arg_enum.rs:7:11
|
||||
|
|
||||
7 | opts: bool,
|
||||
| ^^^^
|
|
@ -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};
|
||||
|
||||
|
|
Loading…
Reference in a new issue