diff --git a/clap_derive/src/attrs.rs b/clap_derive/src/attrs.rs index aaee167e31c..0d74b912747 100644 --- a/clap_derive/src/attrs.rs +++ b/clap_derive/src/attrs.rs @@ -42,6 +42,7 @@ pub struct Attrs { ty: Option, doc_comment: Vec, methods: Vec, + value_parser: Option, parser: Sp, verbatim_doc_comment: Option, next_display_order: Option, @@ -64,6 +65,12 @@ impl Attrs { res.push_attrs(attrs); res.push_doc_comment(attrs, "about"); + if let Some(parser) = res.value_parser.as_ref() { + abort!( + parser.span(), + "`value_parse` attribute is only allowed on fields" + ); + } if res.has_custom_parser { abort!( res.parser.span(), @@ -101,6 +108,12 @@ impl Attrs { match &*res.kind { Kind::Flatten => { + if let Some(parser) = res.value_parser.as_ref() { + abort!( + parser.span(), + "`value_parse` attribute is only allowed flattened entry" + ); + } if res.has_custom_parser { abort!( res.parser.span(), @@ -121,6 +134,12 @@ impl Attrs { Kind::ExternalSubcommand => (), Kind::Subcommand(_) => { + if let Some(parser) = res.value_parser.as_ref() { + abort!( + parser.span(), + "`value_parse` attribute is only allowed for subcommand" + ); + } if res.has_custom_parser { abort!( res.parser.span(), @@ -189,6 +208,12 @@ impl Attrs { res.push_attrs(&variant.attrs); res.push_doc_comment(&variant.attrs, "help"); + if let Some(parser) = res.value_parser.as_ref() { + abort!( + parser.span(), + "`value_parse` attribute is only allowed on fields" + ); + } if res.has_custom_parser { abort!( res.parser.span(), @@ -226,6 +251,12 @@ impl Attrs { match &*res.kind { Kind::Flatten => { + if let Some(parser) = res.value_parser.as_ref() { + abort!( + parser.span(), + "`value_parse` attribute is not allowed for flattened entry" + ); + } if res.has_custom_parser { abort!( res.parser.span(), @@ -250,6 +281,12 @@ impl Attrs { } Kind::Subcommand(_) => { + if let Some(parser) = res.value_parser.as_ref() { + abort!( + parser.span(), + "`value_parse` attribute is not allowed for subcommand" + ); + } if res.has_custom_parser { abort!( res.parser.span(), @@ -297,6 +334,12 @@ impl Attrs { Kind::Arg(orig_ty) => { let mut ty = Ty::from_syn_ty(&field.ty); if res.has_custom_parser { + if let Some(parser) = res.value_parser.as_ref() { + abort!( + parser.name.span(), + "`value_parse` attribute conflicts with `parse` attribute" + ); + } match *ty { Ty::Option | Ty::Vec | Ty::OptionVec => (), _ => ty = Sp::new(Ty::Other, ty.span()), @@ -369,6 +412,7 @@ impl Attrs { env_casing, doc_comment: vec![], methods: vec![], + value_parser: None, parser: Parser::default_spanned(default_span), verbatim_doc_comment: None, next_display_order: None, @@ -383,6 +427,8 @@ impl Attrs { fn push_method(&mut self, name: Ident, arg: impl ToTokens) { if name == "name" { self.name = Name::Assigned(quote!(#arg)); + } else if name == "value_parser" { + self.value_parser = Some(Method::new(name, quote!(#arg))); } else { self.methods.push(Method::new(name, quote!(#arg))); } @@ -689,6 +735,16 @@ impl Attrs { self.name.clone().translate(CasingStyle::ScreamingSnake) } + pub fn value_parser(&self) -> Method { + self.value_parser + .clone() + .unwrap_or_else(|| self.parser.value_parser()) + } + + pub fn custom_value_parser(&self) -> bool { + self.value_parser.is_some() + } + pub fn parser(&self) -> &Sp { &self.parser } @@ -865,9 +921,23 @@ impl Parser { let parser = Parser { kind, func }; Sp::new(parser, parse_ident.span()) } + + fn value_parser(&self) -> Method { + let func = Ident::new("value_parser", self.kind.span()); + match *self.kind { + ParserKind::FromStr | ParserKind::TryFromStr => { + Method::new(func, quote!(clap::builder::ValueParser::string())) + } + ParserKind::FromOsStr | ParserKind::TryFromOsStr => { + Method::new(func, quote!(clap::builder::ValueParser::os_string())) + } + ParserKind::FromOccurrences => Method::new(func, quote!(clap::value_parser!(usize))), + ParserKind::FromFlag => Method::new(func, quote!(clap::ValueParser::bool())), + } + } } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum ParserKind { FromStr, TryFromStr, diff --git a/clap_derive/src/derives/args.rs b/clap_derive/src/derives/args.rs index d80e876b716..a3c7080c51f 100644 --- a/clap_derive/src/derives/args.rs +++ b/clap_derive/src/derives/args.rs @@ -246,11 +246,12 @@ pub fn gen_augment( let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; let flag = *attrs.parser().kind == ParserKind::FromFlag; + let value_parser = attrs.value_parser(); let parser = attrs.parser(); let func = &parser.func; let validator = match *parser.kind { - _ if attrs.is_enum() => quote!(), + _ if attrs.custom_value_parser() || attrs.is_enum() => quote!(), ParserKind::TryFromStr => quote_spanned! { func.span()=> .validator(|s| { #func(s) @@ -265,21 +266,9 @@ pub fn gen_augment( | ParserKind::FromFlag | ParserKind::FromOccurrences => quote!(), }; - let allow_invalid_utf8 = match *parser.kind { - _ if attrs.is_enum() => quote!(), - ParserKind::FromOsStr | ParserKind::TryFromOsStr => { - quote_spanned! { func.span()=> - .allow_invalid_utf8(true) - } - } - ParserKind::FromStr - | ParserKind::TryFromStr - | ParserKind::FromFlag - | ParserKind::FromOccurrences => quote!(), - }; let value_name = attrs.value_name(); - let possible_values = if attrs.is_enum() { + let possible_values = if attrs.is_enum() && !attrs.custom_value_parser() { gen_arg_enum_possible_values(convert_type) } else { quote!() @@ -294,7 +283,7 @@ pub fn gen_augment( .value_name(#value_name) #possible_values #validator - #allow_invalid_utf8 + #value_parser } } @@ -306,7 +295,7 @@ pub fn gen_augment( .multiple_values(false) #possible_values #validator - #allow_invalid_utf8 + #value_parser }, Ty::OptionVec => quote_spanned! { ty.span()=> @@ -315,7 +304,7 @@ pub fn gen_augment( .multiple_occurrences(true) #possible_values #validator - #allow_invalid_utf8 + #value_parser }, Ty::Vec => { @@ -325,7 +314,7 @@ pub fn gen_augment( .multiple_occurrences(true) #possible_values #validator - #allow_invalid_utf8 + #value_parser } } @@ -345,7 +334,7 @@ pub fn gen_augment( .required(#required) #possible_values #validator - #allow_invalid_utf8 + #value_parser } } }; @@ -537,35 +526,51 @@ fn gen_parsers( let span = parser.kind.span(); let convert_type = inner_type(**ty, &field.ty); let id = attrs.id(); - let (get_one, get_many, mut parse) = match *parser.kind { + let (get_one, get_many, deref, mut parse) = match *parser.kind { + FromOccurrences => ( + quote_spanned!(span=> occurrences_of), + quote!(), + quote!(|s| ::std::ops::Deref::deref(s)), + func.clone(), + ), + FromFlag => ( + quote!(), + quote!(), + quote!(|s| ::std::ops::Deref::deref(s)), + func.clone(), + ), + _ if attrs.custom_value_parser() => ( + quote_spanned!(span=> get_one::<#convert_type>), + quote_spanned!(span=> get_many::<#convert_type>), + quote!(|s| s), + quote_spanned!(func.span()=> |s| ::std::result::Result::Ok::<_, clap::Error>(s.clone())), + ), FromStr => ( quote_spanned!(span=> get_one::), quote_spanned!(span=> get_many::), + quote!(|s| ::std::ops::Deref::deref(s)), quote_spanned!(func.span()=> |s| ::std::result::Result::Ok::<_, clap::Error>(#func(s))), ), TryFromStr => ( quote_spanned!(span=> get_one::), quote_spanned!(span=> get_many::), + quote!(|s| ::std::ops::Deref::deref(s)), quote_spanned!(func.span()=> |s| #func(s).map_err(|err| clap::Error::raw(clap::ErrorKind::ValueValidation, format!("Invalid value for {}: {}", #id, err)))), ), FromOsStr => ( quote_spanned!(span=> get_one::<::std::ffi::OsString>), quote_spanned!(span=> get_many::<::std::ffi::OsString>), + quote!(|s| ::std::ops::Deref::deref(s)), quote_spanned!(func.span()=> |s| ::std::result::Result::Ok::<_, clap::Error>(#func(s))), ), TryFromOsStr => ( quote_spanned!(span=> get_one::<::std::ffi::OsString>), quote_spanned!(span=> get_many::<::std::ffi::OsString>), + quote!(|s| ::std::ops::Deref::deref(s)), quote_spanned!(func.span()=> |s| #func(s).map_err(|err| clap::Error::raw(clap::ErrorKind::ValueValidation, format!("Invalid value for {}: {}", #id, err)))), ), - FromOccurrences => ( - quote_spanned!(span=> occurrences_of), - quote!(), - func.clone(), - ), - FromFlag => (quote!(), quote!(), func.clone()), }; - if attrs.is_enum() { + if attrs.is_enum() && !attrs.custom_value_parser() { let ci = attrs.ignore_case(); parse = quote_spanned! { convert_type.span()=> @@ -597,7 +602,7 @@ fn gen_parsers( quote_spanned! { ty.span()=> #arg_matches.#get_one(#id) .expect("unexpected type") - .map(|s| ::std::ops::Deref::deref(s)) + .map(#deref) .map(#parse) .transpose()? } @@ -608,7 +613,7 @@ fn gen_parsers( Some( #arg_matches.#get_one(#id) .expect("unexpected type") - .map(|s| ::std::ops::Deref::deref(s)) + .map(#deref) .map(#parse).transpose()? ) } else { @@ -620,7 +625,7 @@ fn gen_parsers( if #arg_matches.is_present(#id) { Some(#arg_matches.#get_many(#id) .expect("unexpected type") - .map(|v| v.map(|s| ::std::ops::Deref::deref(s)).map::<::std::result::Result<#convert_type, clap::Error>, _>(#parse).collect::<::std::result::Result, clap::Error>>()) + .map(|v| v.map(#deref).map::<::std::result::Result<#convert_type, clap::Error>, _>(#parse).collect::<::std::result::Result, clap::Error>>()) .transpose()? .unwrap_or_else(Vec::new)) } else { @@ -632,7 +637,7 @@ fn gen_parsers( quote_spanned! { ty.span()=> #arg_matches.#get_many(#id) .expect("unexpected type") - .map(|v| v.map(|s| ::std::ops::Deref::deref(s)).map::<::std::result::Result<#convert_type, clap::Error>, _>(#parse).collect::<::std::result::Result, clap::Error>>()) + .map(|v| v.map(#deref).map::<::std::result::Result<#convert_type, clap::Error>, _>(#parse).collect::<::std::result::Result, clap::Error>>()) .transpose()? .unwrap_or_else(Vec::new) } @@ -654,7 +659,7 @@ fn gen_parsers( quote_spanned! { ty.span()=> #arg_matches.#get_one(#id) .expect("unexpected type") - .map(|s| ::std::ops::Deref::deref(s)) + .map(#deref) .ok_or_else(|| clap::Error::raw(clap::ErrorKind::MissingRequiredArgument, format!("The following required argument was not provided: {}", #id))) .and_then(#parse)? } diff --git a/tests/derive/options.rs b/tests/derive/options.rs index 26db1f2e7ce..a0104ea8da0 100644 --- a/tests/derive/options.rs +++ b/tests/derive/options.rs @@ -471,3 +471,16 @@ fn two_option_vec_types() { Opt::try_parse_from(&["test"]).unwrap() ); } + +#[test] +fn explicit_value_parser() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(long, value_parser = clap::value_parser!(i32))] + arg: i32, + } + assert_eq!( + Opt { arg: 42 }, + Opt::try_parse_from(&["test", "--arg", "42"]).unwrap() + ); +} diff --git a/tests/derive_ui/parse_with_value_parser.rs b/tests/derive_ui/parse_with_value_parser.rs new file mode 100644 index 00000000000..ffbb6e848c5 --- /dev/null +++ b/tests/derive_ui/parse_with_value_parser.rs @@ -0,0 +1,21 @@ +// Copyright 2018 Guillaume Pinot (@TeXitoi) +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use clap::Parser; + +#[derive(Parser, Debug)] +#[clap(name = "basic")] +struct Opt { + #[clap(parse(from_str), value_parser = clap::value_parser!(String))] + s: String, +} + +fn main() { + let opt = Opt::parse(); + println!("{:?}", opt); +} diff --git a/tests/derive_ui/parse_with_value_parser.stderr b/tests/derive_ui/parse_with_value_parser.stderr new file mode 100644 index 00000000000..9b62635277b --- /dev/null +++ b/tests/derive_ui/parse_with_value_parser.stderr @@ -0,0 +1,5 @@ +error: `value_parse` attribute conflicts with `parse` attribute + --> tests/derive_ui/parse_with_value_parser.rs:14:29 + | +14 | #[clap(parse(from_str), value_parser = clap::value_parser!(String))] + | ^^^^^^^^^^^^