diff --git a/README.md b/README.md index 94b9bb9ea9f..ae18179d336 100644 --- a/README.md +++ b/README.md @@ -44,11 +44,11 @@ use clap::Parser; #[clap(author, version, about, long_about = None)] struct Args { /// Name of the person to greet - #[clap(short, long)] + #[clap(short, long, value_parser)] name: String, /// Number of times to greet - #[clap(short, long, default_value_t = 1)] + #[clap(short, long, value_parser, default_value_t = 1)] count: u8, } diff --git a/clap_complete/src/generator/utils.rs b/clap_complete/src/generator/utils.rs index 735cad60d7e..251f1416897 100644 --- a/clap_complete/src/generator/utils.rs +++ b/clap_complete/src/generator/utils.rs @@ -128,7 +128,9 @@ pub fn flags<'help>(p: &Command<'help>) -> Vec> { /// Get the possible values for completion pub fn possible_values<'help>(a: &Arg<'help>) -> Option>> { - if let Some(pvs) = a.get_possible_values() { + if !a.is_takes_value_set() { + None + } else if let Some(pvs) = a.get_possible_values() { // Check old first in case the user explicitly set possible values and the derive inferred // a `ValueParser` with some. Some(pvs.to_vec()) diff --git a/clap_derive/src/attrs.rs b/clap_derive/src/attrs.rs index aaee167e31c..03e87b2b99c 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.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(ValueParser::Explicit(Method::new(name, quote!(#arg)))); } else { self.methods.push(Method::new(name, quote!(#arg))); } @@ -403,6 +449,11 @@ impl Attrs { self.push_method(ident, self.name.clone().translate(*self.casing)); } + ValueParser(ident) => { + use crate::attrs::ValueParser; + self.value_parser = Some(ValueParser::Implicit(ident)); + } + Env(ident) => { self.push_method(ident, self.name.clone().translate(*self.env_casing)); } @@ -689,6 +740,16 @@ impl Attrs { self.name.clone().translate(CasingStyle::ScreamingSnake) } + pub fn value_parser(&self) -> ValueParser { + self.value_parser + .clone() + .unwrap_or_else(|| ValueParser::Explicit(self.parser.value_parser())) + } + + pub fn custom_value_parser(&self) -> bool { + self.value_parser.is_some() + } + pub fn parser(&self) -> &Sp { &self.parser } @@ -732,6 +793,31 @@ impl Attrs { } } +#[derive(Clone)] +pub enum ValueParser { + Explicit(Method), + Implicit(Ident), +} + +impl ValueParser { + pub fn resolve(self, inner_type: &Type) -> Method { + match self { + Self::Explicit(method) => method, + Self::Implicit(ident) => { + let func = Ident::new("value_parser", ident.span()); + Method::new(func, quote!(clap::value_parser!(#inner_type))) + } + } + } + + pub fn span(&self) -> Span { + match self { + Self::Explicit(method) => method.name.span(), + Self::Implicit(ident) => ident.span(), + } + } +} + #[allow(clippy::large_enum_variant)] #[derive(Clone)] pub enum Kind { @@ -865,9 +951,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 dddddbef008..ec251bd4ba8 100644 --- a/clap_derive/src/derives/args.rs +++ b/clap_derive/src/derives/args.rs @@ -132,11 +132,19 @@ pub fn gen_from_arg_matches_for_struct( #[deny(clippy::correctness)] impl #impl_generics clap::FromArgMatches for #struct_name #ty_generics #where_clause { fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result { + Self::from_arg_matches_mut(&mut __clap_arg_matches.clone()) + } + + fn from_arg_matches_mut(__clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result { let v = #struct_name #constructor; ::std::result::Result::Ok(v) } fn update_from_arg_matches(&mut self, __clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<(), clap::Error> { + self.update_from_arg_matches_mut(&mut __clap_arg_matches.clone()) + } + + fn update_from_arg_matches_mut(&mut self, __clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<(), clap::Error> { #updater ::std::result::Result::Ok(()) } @@ -238,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().resolve(convert_type); 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) @@ -257,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!() @@ -286,7 +283,7 @@ pub fn gen_augment( .value_name(#value_name) #possible_values #validator - #allow_invalid_utf8 + #value_parser } } @@ -298,7 +295,7 @@ pub fn gen_augment( .multiple_values(false) #possible_values #validator - #allow_invalid_utf8 + #value_parser }, Ty::OptionVec => quote_spanned! { ty.span()=> @@ -307,7 +304,7 @@ pub fn gen_augment( .multiple_occurrences(true) #possible_values #validator - #allow_invalid_utf8 + #value_parser }, Ty::Vec => { @@ -317,7 +314,7 @@ pub fn gen_augment( .multiple_occurrences(true) #possible_values #validator - #allow_invalid_utf8 + #value_parser } } @@ -337,7 +334,7 @@ pub fn gen_augment( .required(#required) #possible_values #validator - #allow_invalid_utf8 + #value_parser } } }; @@ -398,7 +395,7 @@ pub fn gen_constructor(fields: &Punctuated, parent_attribute: &Att quote_spanned! { kind.span()=> #field_name: { if #arg_matches.subcommand_name().map(<#subcmd_type as clap::Subcommand>::has_subcommand).unwrap_or(false) { - Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches(#arg_matches)?) + Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?) } else { None } @@ -408,7 +405,7 @@ pub fn gen_constructor(fields: &Punctuated, parent_attribute: &Att _ => { quote_spanned! { kind.span()=> #field_name: { - <#subcmd_type as clap::FromArgMatches>::from_arg_matches(#arg_matches)? + <#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)? } } }, @@ -416,7 +413,7 @@ pub fn gen_constructor(fields: &Punctuated, parent_attribute: &Att } Kind::Flatten => quote_spanned! { kind.span()=> - #field_name: clap::FromArgMatches::from_arg_matches(#arg_matches)? + #field_name: clap::FromArgMatches::from_arg_matches_mut(#arg_matches)? }, Kind::Skip(val) => match val { @@ -472,7 +469,7 @@ pub fn gen_updater( }; let updater = quote_spanned! { ty.span()=> - <#subcmd_type as clap::FromArgMatches>::update_from_arg_matches(#field_name, #arg_matches)?; + <#subcmd_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?; }; let updater = match **ty { @@ -480,7 +477,7 @@ pub fn gen_updater( if let Some(#field_name) = #field_name.as_mut() { #updater } else { - *#field_name = Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches( + *#field_name = Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut( #arg_matches )?); } @@ -500,7 +497,7 @@ pub fn gen_updater( Kind::Flatten => quote_spanned! { kind.span()=> { #access - clap::FromArgMatches::update_from_arg_matches(#field_name, #arg_matches)?; + clap::FromArgMatches::update_from_arg_matches_mut(#field_name, #arg_matches)?; } }, @@ -529,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=> remove_one::<#convert_type>), + quote_spanned!(span=> remove_many::<#convert_type>), + quote!(|s| ::std::sync::Arc::try_unwrap(s).unwrap_or_else(|arc| (*arc).clone())), + quote_spanned!(func.span()=> |s| ::std::result::Result::Ok::<_, clap::Error>(s)), + ), 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()=> @@ -589,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()? } @@ -600,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 { @@ -612,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 { @@ -624,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) } @@ -646,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/clap_derive/src/derives/subcommand.rs b/clap_derive/src/derives/subcommand.rs index ac7a8a1dcdf..7edc2eb00f1 100644 --- a/clap_derive/src/derives/subcommand.rs +++ b/clap_derive/src/derives/subcommand.rs @@ -122,7 +122,15 @@ fn gen_from_arg_matches_for_enum( )] #[deny(clippy::correctness)] impl #impl_generics clap::FromArgMatches for #name #ty_generics #where_clause { + fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result { + Self::from_arg_matches_mut(&mut __clap_arg_matches.clone()) + } + #from_arg_matches + + fn update_from_arg_matches(&mut self, __clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<(), clap::Error> { + self.update_from_arg_matches_mut(&mut __clap_arg_matches.clone()) + } #update_from_arg_matches } } @@ -400,7 +408,7 @@ fn gen_from_arg_matches( let mut ext_subcmd = None; let subcommand_name_var = format_ident!("__clap_name"); - let sub_arg_matches_var = format_ident!("__clap_sub_arg_matches"); + let sub_arg_matches_var = format_ident!("__clap_arg_matches"); let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants .iter() .filter_map(|variant| { @@ -471,7 +479,7 @@ fn gen_from_arg_matches( Unit => quote!(), Unnamed(ref fields) if fields.unnamed.len() == 1 => { let ty = &fields.unnamed[0]; - quote!( ( <#ty as clap::FromArgMatches>::from_arg_matches(__clap_arg_matches)? ) ) + quote!( ( <#ty as clap::FromArgMatches>::from_arg_matches_mut(__clap_arg_matches)? ) ) } Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident), }; @@ -496,8 +504,12 @@ fn gen_from_arg_matches( Unnamed(ref fields) if fields.unnamed.len() == 1 => { let ty = &fields.unnamed[0]; quote! { - if <#ty as clap::Subcommand>::has_subcommand(__clap_name) { - let __clap_res = <#ty as clap::FromArgMatches>::from_arg_matches(__clap_arg_matches)?; + if __clap_arg_matches + .subcommand_name() + .map(|__clap_name| <#ty as clap::Subcommand>::has_subcommand(__clap_name)) + .unwrap_or_default() + { + let __clap_res = <#ty as clap::FromArgMatches>::from_arg_matches_mut(__clap_arg_matches)?; return ::std::result::Result::Ok(#name :: #variant_name (__clap_res)); } } @@ -515,9 +527,10 @@ fn gen_from_arg_matches( ::std::iter::once(#str_ty::from(#subcommand_name_var)) .chain( #sub_arg_matches_var - .get_many::<#str_ty>("") + .remove_many::<#str_ty>("") .expect("unexpected type") .into_iter().flatten() // `""` isn't present, bug in `unstable-v4` + .map(|f| ::std::sync::Arc::try_unwrap(f).unwrap_or_else(|arc| (*arc).clone())) .map(#str_ty::from) ) .collect::<::std::vec::Vec<_>>() @@ -530,14 +543,12 @@ fn gen_from_arg_matches( }; quote! { - fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result { - if let Some((#subcommand_name_var, #sub_arg_matches_var)) = __clap_arg_matches.subcommand() { - { - let __clap_arg_matches = #sub_arg_matches_var; - #( #subcommands )* - } + fn from_arg_matches_mut(__clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result { + #( #child_subcommands )else* - #( #child_subcommands )else* + if let Some((#subcommand_name_var, mut __clap_arg_sub_matches)) = __clap_arg_matches.remove_subcommand() { + let #sub_arg_matches_var = &mut __clap_arg_sub_matches; + #( #subcommands )* #wildcard } else { @@ -564,7 +575,7 @@ fn gen_update_from_arg_matches( ); match &*attrs.kind() { - // Fallback to `from_arg_matches` + // Fallback to `from_arg_matches_mut` Kind::ExternalSubcommand => None, _ => Some((variant, attrs)), } @@ -602,9 +613,9 @@ fn gen_update_from_arg_matches( if fields.unnamed.len() == 1 { ( quote!((ref mut __clap_arg)), - quote!(clap::FromArgMatches::update_from_arg_matches( + quote!(clap::FromArgMatches::update_from_arg_matches_mut( __clap_arg, - __clap_sub_arg_matches + __clap_arg_matches )?), ) } else { @@ -615,7 +626,8 @@ fn gen_update_from_arg_matches( quote! { #name :: #variant_name #pattern if #sub_name == __clap_name => { - let __clap_arg_matches = __clap_sub_arg_matches; + let (_, mut __clap_arg_sub_matches) = __clap_arg_matches.remove_subcommand().unwrap(); + let __clap_arg_matches = &mut __clap_arg_sub_matches; #updater } } @@ -629,7 +641,7 @@ fn gen_update_from_arg_matches( quote! { if <#ty as clap::Subcommand>::has_subcommand(__clap_name) { if let #name :: #variant_name (child) = s { - <#ty as clap::FromArgMatches>::update_from_arg_matches(child, __clap_arg_matches)?; + <#ty as clap::FromArgMatches>::update_from_arg_matches_mut(child, __clap_arg_matches)?; return ::std::result::Result::Ok(()); } } @@ -643,16 +655,16 @@ fn gen_update_from_arg_matches( }); quote! { - fn update_from_arg_matches<'b>( + fn update_from_arg_matches_mut<'b>( &mut self, - __clap_arg_matches: &clap::ArgMatches, + __clap_arg_matches: &mut clap::ArgMatches, ) -> ::std::result::Result<(), clap::Error> { - if let Some((__clap_name, __clap_sub_arg_matches)) = __clap_arg_matches.subcommand() { + if let Some(__clap_name) = __clap_arg_matches.subcommand_name() { match self { #( #subcommands ),* s => { #( #child_subcommands )* - *s = ::from_arg_matches(__clap_arg_matches)?; + *s = ::from_arg_matches_mut(__clap_arg_matches)?; } } } diff --git a/clap_derive/src/parse.rs b/clap_derive/src/parse.rs index 2973cf670f4..9dbc1454ada 100644 --- a/clap_derive/src/parse.rs +++ b/clap_derive/src/parse.rs @@ -26,6 +26,7 @@ pub enum ClapAttr { // single-identifier attributes Short(Ident), Long(Ident), + ValueParser(Ident), Env(Ident), Flatten(Ident), ArgEnum(Ident), @@ -182,6 +183,7 @@ impl Parse for ClapAttr { match name_str.as_ref() { "long" => Ok(Long(name)), "short" => Ok(Short(name)), + "value_parser" => Ok(ValueParser(name)), "env" => Ok(Env(name)), "flatten" => Ok(Flatten(name)), "arg_enum" => Ok(ArgEnum(name)), diff --git a/examples/cargo-example-derive.rs b/examples/cargo-example-derive.rs index 1ee0a02551f..6667a4a7df9 100644 --- a/examples/cargo-example-derive.rs +++ b/examples/cargo-example-derive.rs @@ -12,7 +12,7 @@ enum Cargo { #[derive(clap::Args)] #[clap(author, version, about, long_about = None)] struct ExampleDerive { - #[clap(long, parse(from_os_str))] + #[clap(long, value_parser)] manifest_path: Option, } diff --git a/examples/demo.rs b/examples/demo.rs index 262a81a2c1a..95748972444 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -7,11 +7,11 @@ use clap::Parser; #[clap(author, version, about, long_about = None)] struct Args { /// Name of the person to greet - #[clap(short, long)] + #[clap(short, long, value_parser)] name: String, /// Number of times to greet - #[clap(short, long, default_value_t = 1)] + #[clap(short, long, value_parser, default_value_t = 1)] count: u8, } diff --git a/examples/derive_ref/README.md b/examples/derive_ref/README.md index 11001dde85f..2cde95ff913 100644 --- a/examples/derive_ref/README.md +++ b/examples/derive_ref/README.md @@ -173,6 +173,8 @@ These correspond to a `clap::Arg`. **Magic attributes**: - `name = `: `clap::Arg::new` - When not present: case-converted field name is used +- `value_parser [= ]`: `clap::Arg::value_parser` + - When not present: will auto-select an implementation based on the field type - `help = `: `clap::Arg::help` - When not present: [Doc comment summary](#doc-comments) - `long_help = `: `clap::Arg::long_help` @@ -198,6 +200,7 @@ These correspond to a `clap::Arg`. - When `Option`, the subcommand becomes optional - `from_global`: Read a `clap::Arg::global` argument (raw attribute), regardless of what subcommand you are in - `parse( [= ])`: `clap::Arg::validator` and `clap::ArgMatches::values_of_t` + - **Deprecated:** except for `from_flag` or `from_occurrences`, instead use `value_parser` - Default: `try_from_str` - Warning: for `Path` / `OsString`, be sure to use `try_from_os_str` - See [Arg Types](#arg-types) for more details diff --git a/examples/derive_ref/hand_subcommand.rs b/examples/derive_ref/hand_subcommand.rs index 5afd9413271..d85a29493cd 100644 --- a/examples/derive_ref/hand_subcommand.rs +++ b/examples/derive_ref/hand_subcommand.rs @@ -3,12 +3,14 @@ use clap::{ArgMatches, Args as _, Command, FromArgMatches, Parser, Subcommand}; #[derive(Parser, Debug)] struct AddArgs { + #[clap(value_parser)] name: Vec, } #[derive(Parser, Debug)] struct RemoveArgs { #[clap(short, long)] force: bool, + #[clap(value_parser)] name: Vec, } diff --git a/examples/escaped-positional-derive.rs b/examples/escaped-positional-derive.rs index 8038395aff2..dd52c090cc1 100644 --- a/examples/escaped-positional-derive.rs +++ b/examples/escaped-positional-derive.rs @@ -8,10 +8,10 @@ struct Cli { #[clap(short = 'f')] eff: bool, - #[clap(short = 'p', value_name = "PEAR")] + #[clap(short = 'p', value_name = "PEAR", value_parser)] pea: Option, - #[clap(last = true)] + #[clap(last = true, value_parser)] slop: Vec, } diff --git a/examples/git-derive.rs b/examples/git-derive.rs index 7e44edb1aff..ecbda3fe9d3 100644 --- a/examples/git-derive.rs +++ b/examples/git-derive.rs @@ -20,19 +20,21 @@ enum Commands { #[clap(arg_required_else_help = true)] Clone { /// The remote to clone + #[clap(value_parser)] remote: String, }, /// pushes things #[clap(arg_required_else_help = true)] Push { /// The remote to target + #[clap(value_parser)] remote: String, }, /// adds things #[clap(arg_required_else_help = true)] Add { /// Stuff to add - #[clap(required = true, parse(from_os_str))] + #[clap(required = true, value_parser)] path: Vec, }, Stash(Stash), @@ -53,13 +55,19 @@ struct Stash { #[derive(Debug, Subcommand)] enum StashCommands { Push(StashPush), - Pop { stash: Option }, - Apply { stash: Option }, + Pop { + #[clap(value_parser)] + stash: Option, + }, + Apply { + #[clap(value_parser)] + stash: Option, + }, } #[derive(Debug, Args)] struct StashPush { - #[clap(short, long)] + #[clap(short, long, value_parser)] message: Option, } diff --git a/examples/tutorial_derive/01_quick.rs b/examples/tutorial_derive/01_quick.rs index f840b8a9b2f..7bc55b2a62d 100644 --- a/examples/tutorial_derive/01_quick.rs +++ b/examples/tutorial_derive/01_quick.rs @@ -6,10 +6,11 @@ use clap::{Parser, Subcommand}; #[clap(author, version, about, long_about = None)] struct Cli { /// Optional name to operate on + #[clap(value_parser)] name: Option, /// Sets a custom config file - #[clap(short, long, parse(from_os_str), value_name = "FILE")] + #[clap(short, long, value_parser, value_name = "FILE")] config: Option, /// Turn debugging information on @@ -25,7 +26,7 @@ enum Commands { /// does testing things Test { /// lists test values - #[clap(short, long)] + #[clap(short, long, value_parser)] list: bool, }, } diff --git a/examples/tutorial_derive/02_app_settings.rs b/examples/tutorial_derive/02_app_settings.rs index bccd353f602..87965cd36bb 100644 --- a/examples/tutorial_derive/02_app_settings.rs +++ b/examples/tutorial_derive/02_app_settings.rs @@ -6,9 +6,9 @@ use clap::{AppSettings, Parser}; #[clap(allow_negative_numbers = true)] #[clap(global_setting(AppSettings::DeriveDisplayOrder))] struct Cli { - #[clap(long)] + #[clap(long, value_parser)] two: String, - #[clap(long)] + #[clap(long, value_parser)] one: String, } diff --git a/examples/tutorial_derive/02_apps.rs b/examples/tutorial_derive/02_apps.rs index 442e928a9f6..b97ce1eed6a 100644 --- a/examples/tutorial_derive/02_apps.rs +++ b/examples/tutorial_derive/02_apps.rs @@ -6,9 +6,9 @@ use clap::Parser; #[clap(version = "1.0")] #[clap(about = "Does awesome things", long_about = None)] struct Cli { - #[clap(long)] + #[clap(long, value_parser)] two: String, - #[clap(long)] + #[clap(long, value_parser)] one: String, } diff --git a/examples/tutorial_derive/02_crate.rs b/examples/tutorial_derive/02_crate.rs index 93f7888af31..df16468d0bb 100644 --- a/examples/tutorial_derive/02_crate.rs +++ b/examples/tutorial_derive/02_crate.rs @@ -3,9 +3,9 @@ use clap::Parser; #[derive(Parser)] #[clap(author, version, about, long_about = None)] struct Cli { - #[clap(long)] + #[clap(long, value_parser)] two: String, - #[clap(long)] + #[clap(long, value_parser)] one: String, } diff --git a/examples/tutorial_derive/03_01_flag_bool.rs b/examples/tutorial_derive/03_01_flag_bool.rs index 8b574b7481e..34c9a352a46 100644 --- a/examples/tutorial_derive/03_01_flag_bool.rs +++ b/examples/tutorial_derive/03_01_flag_bool.rs @@ -3,7 +3,7 @@ use clap::Parser; #[derive(Parser)] #[clap(author, version, about, long_about = None)] struct Cli { - #[clap(short, long)] + #[clap(short, long, value_parser)] verbose: bool, } diff --git a/examples/tutorial_derive/03_02_option.rs b/examples/tutorial_derive/03_02_option.rs index b09aadf20de..75b67afe753 100644 --- a/examples/tutorial_derive/03_02_option.rs +++ b/examples/tutorial_derive/03_02_option.rs @@ -3,7 +3,7 @@ use clap::Parser; #[derive(Parser)] #[clap(author, version, about, long_about = None)] struct Cli { - #[clap(short, long)] + #[clap(short, long, value_parser)] name: Option, } diff --git a/examples/tutorial_derive/03_03_positional.rs b/examples/tutorial_derive/03_03_positional.rs index f7850ddccfe..7478951f1a8 100644 --- a/examples/tutorial_derive/03_03_positional.rs +++ b/examples/tutorial_derive/03_03_positional.rs @@ -3,6 +3,7 @@ use clap::Parser; #[derive(Parser)] #[clap(author, version, about, long_about = None)] struct Cli { + #[clap(value_parser)] name: Option, } diff --git a/examples/tutorial_derive/03_04_subcommands.rs b/examples/tutorial_derive/03_04_subcommands.rs index 86cf444c21b..62a45a97eaf 100644 --- a/examples/tutorial_derive/03_04_subcommands.rs +++ b/examples/tutorial_derive/03_04_subcommands.rs @@ -11,7 +11,10 @@ struct Cli { #[derive(Subcommand)] enum Commands { /// Adds files to myapp - Add { name: Option }, + Add { + #[clap(value_parser)] + name: Option, + }, } fn main() { diff --git a/examples/tutorial_derive/03_04_subcommands_alt.rs b/examples/tutorial_derive/03_04_subcommands_alt.rs index 0a5b60682d4..b6124936c93 100644 --- a/examples/tutorial_derive/03_04_subcommands_alt.rs +++ b/examples/tutorial_derive/03_04_subcommands_alt.rs @@ -16,6 +16,7 @@ enum Commands { #[derive(Args)] struct Add { + #[clap(value_parser)] name: Option, } diff --git a/examples/tutorial_derive/03_05_default_values.rs b/examples/tutorial_derive/03_05_default_values.rs index af4532bbc7d..10a1ec80880 100644 --- a/examples/tutorial_derive/03_05_default_values.rs +++ b/examples/tutorial_derive/03_05_default_values.rs @@ -3,7 +3,7 @@ use clap::Parser; #[derive(Parser)] #[clap(author, version, about, long_about = None)] struct Cli { - #[clap(default_value_t = String::from("alice"))] + #[clap(default_value_t = String::from("alice"), value_parser)] name: String, } diff --git a/examples/tutorial_derive/04_01_enum.rs b/examples/tutorial_derive/04_01_enum.rs index 3a2df391ffb..84b4cace495 100644 --- a/examples/tutorial_derive/04_01_enum.rs +++ b/examples/tutorial_derive/04_01_enum.rs @@ -4,7 +4,7 @@ use clap::{ArgEnum, Parser}; #[clap(author, version, about, long_about = None)] struct Cli { /// What mode to run the program in - #[clap(arg_enum)] + #[clap(arg_enum, value_parser)] mode: Mode, } diff --git a/examples/tutorial_derive/04_02_parse.rs b/examples/tutorial_derive/04_02_parse.rs index 5f4cbadc045..6336a9cf155 100644 --- a/examples/tutorial_derive/04_02_parse.rs +++ b/examples/tutorial_derive/04_02_parse.rs @@ -4,8 +4,8 @@ use clap::Parser; #[clap(author, version, about, long_about = None)] struct Cli { /// Network port to use - #[clap(parse(try_from_str))] - port: usize, + #[clap(value_parser = clap::value_parser!(u16).range(1..))] + port: u16, } fn main() { diff --git a/examples/tutorial_derive/04_02_validate.rs b/examples/tutorial_derive/04_02_validate.rs index 434f40c869f..7dac79c9fb7 100644 --- a/examples/tutorial_derive/04_02_validate.rs +++ b/examples/tutorial_derive/04_02_validate.rs @@ -6,8 +6,8 @@ use clap::Parser; #[clap(author, version, about, long_about = None)] struct Cli { /// Network port to use - #[clap(parse(try_from_str=port_in_range))] - port: usize, + #[clap(value_parser = port_in_range)] + port: u16, } fn main() { @@ -18,12 +18,12 @@ fn main() { const PORT_RANGE: RangeInclusive = 1..=65535; -fn port_in_range(s: &str) -> Result { +fn port_in_range(s: &str) -> Result { let port: usize = s .parse() .map_err(|_| format!("`{}` isn't a port number", s))?; if PORT_RANGE.contains(&port) { - Ok(port) + Ok(port as u16) } else { Err(format!( "Port not in range {}-{}", diff --git a/examples/tutorial_derive/04_03_relations.rs b/examples/tutorial_derive/04_03_relations.rs index f0e1e5913ba..32b85805658 100644 --- a/examples/tutorial_derive/04_03_relations.rs +++ b/examples/tutorial_derive/04_03_relations.rs @@ -9,7 +9,7 @@ use clap::{ArgGroup, Parser}; ))] struct Cli { /// set version manually - #[clap(long, value_name = "VER")] + #[clap(long, value_name = "VER", value_parser)] set_ver: Option, /// auto inc major @@ -25,14 +25,14 @@ struct Cli { patch: bool, /// some regular input - #[clap(group = "input")] + #[clap(group = "input", value_parser)] input_file: Option, /// some special input argument - #[clap(long, group = "input")] + #[clap(long, group = "input", value_parser)] spec_in: Option, - #[clap(short, requires = "input")] + #[clap(short, requires = "input", value_parser)] config: Option, } diff --git a/examples/tutorial_derive/04_04_custom.rs b/examples/tutorial_derive/04_04_custom.rs index a03345b8297..d163e20ef24 100644 --- a/examples/tutorial_derive/04_04_custom.rs +++ b/examples/tutorial_derive/04_04_custom.rs @@ -4,7 +4,7 @@ use clap::{CommandFactory, ErrorKind, Parser}; #[clap(author, version, about, long_about = None)] struct Cli { /// set version manually - #[clap(long, value_name = "VER")] + #[clap(long, value_name = "VER", value_parser)] set_ver: Option, /// auto inc major @@ -20,13 +20,14 @@ struct Cli { patch: bool, /// some regular input + #[clap(value_parser)] input_file: Option, /// some special input argument - #[clap(long)] + #[clap(long, value_parser)] spec_in: Option, - #[clap(short)] + #[clap(short, value_parser)] config: Option, } diff --git a/examples/tutorial_derive/05_01_assert.rs b/examples/tutorial_derive/05_01_assert.rs index 12fdba9b923..f5fa3f6c599 100644 --- a/examples/tutorial_derive/05_01_assert.rs +++ b/examples/tutorial_derive/05_01_assert.rs @@ -4,8 +4,8 @@ use clap::Parser; #[clap(author, version, about, long_about = None)] struct Cli { /// Network port to use - #[clap(parse(try_from_str))] - port: usize, + #[clap(value_parser)] + port: u16, } fn main() { diff --git a/examples/tutorial_derive/README.md b/examples/tutorial_derive/README.md index 706e76c09e7..01b122421b6 100644 --- a/examples/tutorial_derive/README.md +++ b/examples/tutorial_derive/README.md @@ -453,6 +453,12 @@ error: Invalid value "foobar" for '': invalid digit found in string For more information try --help +$ 04_02_parse_derive 0 +? failed +error: Invalid value "0" for '': 0 is not in 1..=65535 + +For more information try --help + ``` A custom parser can be used to improve the error messages or provide additional validation: diff --git a/examples/typed-derive.rs b/examples/typed-derive.rs index 237bbe15e8d..b99370780df 100644 --- a/examples/typed-derive.rs +++ b/examples/typed-derive.rs @@ -6,23 +6,23 @@ use std::error::Error; #[derive(Parser, Debug)] struct Args { /// Implicitly using `std::str::FromStr` - #[clap(short = 'O')] + #[clap(short = 'O', value_parser)] optimization: Option, /// Allow invalid UTF-8 paths - #[clap(short = 'I', parse(from_os_str), value_name = "DIR", value_hint = clap::ValueHint::DirPath)] + #[clap(short = 'I', value_parser, value_name = "DIR", value_hint = clap::ValueHint::DirPath)] include: Option, /// Handle IP addresses - #[clap(long)] + #[clap(long, value_parser)] bind: Option, /// Allow human-readable durations - #[clap(long)] + #[clap(long, value_parser)] sleep: Option, /// Hand-written parser for tuples - #[clap(short = 'D', parse(try_from_str = parse_key_val), multiple_occurrences(true))] + #[clap(short = 'D', value_parser = parse_key_val::, multiple_occurrences(true))] defines: Vec<(String, i32)>, } diff --git a/src/builder/value_parser.rs b/src/builder/value_parser.rs index 02af4460272..edf57c623a2 100644 --- a/src/builder/value_parser.rs +++ b/src/builder/value_parser.rs @@ -1125,7 +1125,7 @@ impl> RangedI64ValueParser { std::ops::Bound::Unbounded => i64::MIN.to_string(), }; result.push_str(".."); - match self.bounds.0 { + match self.bounds.1 { std::ops::Bound::Included(i) => { result.push('='); result.push_str(&i.to_string()); diff --git a/src/derive.rs b/src/derive.rs index 110af93727d..55be23bb13e 100644 --- a/src/derive.rs +++ b/src/derive.rs @@ -79,9 +79,9 @@ use std::ffi::OsString; pub trait Parser: FromArgMatches + CommandFactory + Sized { /// Parse from `std::env::args_os()`, exit on error fn parse() -> Self { - let matches = ::command().get_matches(); - let res = - ::from_arg_matches(&matches).map_err(format_error::); + let mut matches = ::command().get_matches(); + let res = ::from_arg_matches_mut(&mut matches) + .map_err(format_error::); match res { Ok(s) => s, Err(e) => { @@ -94,8 +94,8 @@ pub trait Parser: FromArgMatches + CommandFactory + Sized { /// Parse from `std::env::args_os()`, return Err on error. fn try_parse() -> Result { - let matches = ::command().try_get_matches()?; - ::from_arg_matches(&matches).map_err(format_error::) + let mut matches = ::command().try_get_matches()?; + ::from_arg_matches_mut(&mut matches).map_err(format_error::) } /// Parse from iterator, exit on error @@ -104,9 +104,9 @@ pub trait Parser: FromArgMatches + CommandFactory + Sized { I: IntoIterator, T: Into + Clone, { - let matches = ::command().get_matches_from(itr); - let res = - ::from_arg_matches(&matches).map_err(format_error::); + let mut matches = ::command().get_matches_from(itr); + let res = ::from_arg_matches_mut(&mut matches) + .map_err(format_error::); match res { Ok(s) => s, Err(e) => { @@ -123,8 +123,8 @@ pub trait Parser: FromArgMatches + CommandFactory + Sized { I: IntoIterator, T: Into + Clone, { - let matches = ::command().try_get_matches_from(itr)?; - ::from_arg_matches(&matches).map_err(format_error::) + let mut matches = ::command().try_get_matches_from(itr)?; + ::from_arg_matches_mut(&mut matches).map_err(format_error::) } /// Update from iterator, exit on error @@ -133,8 +133,8 @@ pub trait Parser: FromArgMatches + CommandFactory + Sized { I: IntoIterator, T: Into + Clone, { - let matches = ::command_for_update().get_matches_from(itr); - let res = ::update_from_arg_matches(self, &matches) + let mut matches = ::command_for_update().get_matches_from(itr); + let res = ::update_from_arg_matches_mut(self, &mut matches) .map_err(format_error::); if let Err(e) = res { // Since this is more of a development-time error, we aren't doing as fancy of a quit @@ -149,8 +149,9 @@ pub trait Parser: FromArgMatches + CommandFactory + Sized { I: IntoIterator, T: Into + Clone, { - let matches = ::command_for_update().try_get_matches_from(itr)?; - ::update_from_arg_matches(self, &matches) + let mut matches = + ::command_for_update().try_get_matches_from(itr)?; + ::update_from_arg_matches_mut(self, &mut matches) .map_err(format_error::) } @@ -165,11 +166,11 @@ pub trait Parser: FromArgMatches + CommandFactory + Sized { ::command() } - /// Deprecated, `StructOpt::from_clap` replaced with [`FromArgMatches::from_arg_matches`] (derive as part of + /// Deprecated, `StructOpt::from_clap` replaced with [`FromArgMatches::from_arg_matches_mut`] (derive as part of /// [`Parser`]) #[deprecated( since = "3.0.0", - note = "`StructOpt::from_clap` is replaced with `FromArgMatches::from_arg_matches` (derived as part of `Parser`)" + note = "`StructOpt::from_clap` is replaced with `FromArgMatches::from_arg_matches_mut` (derived as part of `Parser`)" )] #[doc(hidden)] fn from_clap(matches: &ArgMatches) -> Self { @@ -232,7 +233,7 @@ pub trait Parser: FromArgMatches + CommandFactory + Sized { pub trait CommandFactory: Sized { /// Build a [`Command`] that can instantiate `Self`. /// - /// See [`FromArgMatches::from_arg_matches`] for instantiating `Self`. + /// See [`FromArgMatches::from_arg_matches_mut`] for instantiating `Self`. fn command<'help>() -> Command<'help> { #[allow(deprecated)] Self::into_app() @@ -242,7 +243,7 @@ pub trait CommandFactory: Sized { fn into_app<'help>() -> Command<'help>; /// Build a [`Command`] that can update `self`. /// - /// See [`FromArgMatches::update_from_arg_matches`] for updating `self`. + /// See [`FromArgMatches::update_from_arg_matches_mut`] for updating `self`. fn command_for_update<'help>() -> Command<'help> { #[allow(deprecated)] Self::into_app_for_update() @@ -293,8 +294,49 @@ pub trait FromArgMatches: Sized { /// ``` fn from_arg_matches(matches: &ArgMatches) -> Result; + /// Instantiate `Self` from [`ArgMatches`], parsing the arguments as needed. + /// + /// Motivation: If our application had two CLI options, `--name + /// ` and the flag `--debug`, we may create a struct as follows: + /// + #[cfg_attr(not(feature = "derive"), doc = " ```ignore")] + #[cfg_attr(feature = "derive", doc = " ```no_run")] + /// struct Context { + /// name: String, + /// debug: bool + /// } + /// ``` + /// + /// We then need to convert the `ArgMatches` that `clap` generated into our struct. + /// `from_arg_matches_mut` serves as the equivalent of: + /// + #[cfg_attr(not(feature = "derive"), doc = " ```ignore")] + #[cfg_attr(feature = "derive", doc = " ```no_run")] + /// # use clap::ArgMatches; + /// # struct Context { + /// # name: String, + /// # debug: bool + /// # } + /// impl From for Context { + /// fn from(m: ArgMatches) -> Self { + /// Context { + /// name: m.value_of("name").unwrap().to_string(), + /// debug: m.is_present("debug"), + /// } + /// } + /// } + /// ``` + fn from_arg_matches_mut(matches: &mut ArgMatches) -> Result { + Self::from_arg_matches(matches) + } + /// Assign values from `ArgMatches` to `self`. fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), Error>; + + /// Assign values from `ArgMatches` to `self`. + fn update_from_arg_matches_mut(&mut self, matches: &mut ArgMatches) -> Result<(), Error> { + self.update_from_arg_matches(matches) + } } /// Parse a set of arguments into a user-defined container. @@ -480,9 +522,15 @@ impl FromArgMatches for Box { fn from_arg_matches(matches: &ArgMatches) -> Result { ::from_arg_matches(matches).map(Box::new) } + fn from_arg_matches_mut(matches: &mut ArgMatches) -> Result { + ::from_arg_matches_mut(matches).map(Box::new) + } fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), Error> { ::update_from_arg_matches(self, matches) } + fn update_from_arg_matches_mut(&mut self, matches: &mut ArgMatches) -> Result<(), Error> { + ::update_from_arg_matches_mut(self, matches) + } } impl Args for Box { diff --git a/src/output/help.rs b/src/output/help.rs index f7401118821..e1ee78ac3dc 100644 --- a/src/output/help.rs +++ b/src/output/help.rs @@ -456,27 +456,23 @@ impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> { if let Some(arg) = arg { const DASH_SPACE: usize = "- ".len(); const COLON_SPACE: usize = ": ".len(); + let possible_vals = get_possible_values(arg); if self.use_long && !arg.is_hide_possible_values_set() - && arg - .possible_vals - .iter() - .any(PossibleValue::should_show_help) + && possible_vals.iter().any(PossibleValue::should_show_help) { - debug!("Help::help: Found possible vals...{:?}", arg.possible_vals); + debug!("Help::help: Found possible vals...{:?}", possible_vals); if !help.is_empty() { self.none("\n\n")?; self.spaces(spaces)?; } self.none("Possible values:")?; - let longest = arg - .possible_vals + let longest = possible_vals .iter() .filter_map(|f| f.get_visible_quoted_name().map(|name| display_width(&name))) .max() .expect("Only called with possible value"); - let help_longest = arg - .possible_vals + let help_longest = possible_vals .iter() .filter_map(|f| f.get_visible_help().map(display_width)) .max() @@ -494,7 +490,7 @@ impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> { spaces + longest + DASH_SPACE + COLON_SPACE }; - for pv in arg.possible_vals.iter().filter(|pv| !pv.is_hide_set()) { + for pv in possible_vals.iter().filter(|pv| !pv.is_hide_set()) { self.none("\n")?; self.spaces(spaces)?; self.none("- ")?; @@ -662,19 +658,16 @@ impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> { } } + let possible_vals = get_possible_values(a); if !(a.is_hide_possible_values_set() - || a.possible_vals.is_empty() + || possible_vals.is_empty() || cfg!(feature = "unstable-v4") && self.use_long - && a.possible_vals.iter().any(PossibleValue::should_show_help)) + && possible_vals.iter().any(PossibleValue::should_show_help)) { - debug!( - "Help::spec_vals: Found possible vals...{:?}", - a.possible_vals - ); + debug!("Help::spec_vals: Found possible vals...{:?}", possible_vals); - let pvs = a - .possible_vals + let pvs = possible_vals .iter() .filter_map(PossibleValue::get_visible_quoted_name) .collect::>() @@ -1148,6 +1141,21 @@ fn text_wrapper(help: &str, width: usize) -> String { .join("\n") } +fn get_possible_values<'help>(a: &Arg<'help>) -> Vec> { + if !a.is_takes_value_set() { + vec![] + } else if let Some(pvs) = a.get_possible_values() { + // Check old first in case the user explicitly set possible values and the derive inferred + // a `ValueParser` with some. + pvs.to_vec() + } else { + a.get_value_parser() + .possible_values() + .map(|pvs| pvs.collect()) + .unwrap_or_default() + } +} + #[cfg(test)] mod test { use super::*; diff --git a/tests/derive/options.rs b/tests/derive/options.rs index 26db1f2e7ce..a2c80494283 100644 --- a/tests/derive/options.rs +++ b/tests/derive/options.rs @@ -471,3 +471,29 @@ 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() + ); +} + +#[test] +fn implicit_value_parser() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + #[clap(long, value_parser)] + 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))] + | ^^^^^^^^^^^^