From b26c01aa0eea71450577956501f4b467fa0e646b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 11 Oct 2022 10:29:19 -0500 Subject: [PATCH] feat(derive): Allow type-less fields When overriding other fields, help or version flag, globals, etc, a user might not care about the value, so let's ignore the lookup. Been talking about this for a while but Issue #4367 moved this forward because there wasn't a good way to handle this without changing behavior. --- clap_derive/src/derives/args.rs | 15 +++++++++++++++ clap_derive/src/utils/ty.rs | 14 +++++++++++++- src/_derive/mod.rs | 3 ++- tests/derive/help.rs | 12 ++++++++---- 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/clap_derive/src/derives/args.rs b/clap_derive/src/derives/args.rs index f49883ce3bb..15f676be27b 100644 --- a/clap_derive/src/derives/args.rs +++ b/clap_derive/src/derives/args.rs @@ -232,6 +232,13 @@ pub fn gen_augment( let value_name = item.value_name(); let implicit_methods = match **ty { + Ty::Unit => { + quote_spanned! { ty.span()=> + .value_name(#value_name) + #value_parser + #action + } + } Ty::Option => { quote_spanned! { ty.span()=> .value_name(#value_name) @@ -421,6 +428,7 @@ pub fn gen_constructor(fields: &[(&Field, Item)]) -> TokenStream { } } }, + Ty::Unit | Ty::Vec | Ty::OptionOption | Ty::OptionVec => { @@ -459,6 +467,7 @@ pub fn gen_constructor(fields: &[(&Field, Item)]) -> TokenStream { } } }, + Ty::Unit | Ty::Vec | Ty::OptionOption | Ty::OptionVec => { @@ -609,6 +618,12 @@ fn gen_parsers( let arg_matches = format_ident!("__clap_arg_matches"); let field_value = match **ty { + Ty::Unit => { + quote_spanned! { ty.span()=> + () + } + } + Ty::Option => { quote_spanned! { ty.span()=> #arg_matches.#get_one(#id) diff --git a/clap_derive/src/utils/ty.rs b/clap_derive/src/utils/ty.rs index a540e5f623f..1cf051441f1 100644 --- a/clap_derive/src/utils/ty.rs +++ b/clap_derive/src/utils/ty.rs @@ -9,6 +9,7 @@ use syn::{ #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum Ty { + Unit, Vec, Option, OptionOption, @@ -21,7 +22,9 @@ impl Ty { use self::Ty::*; let t = |kind| Sp::new(kind, ty.span()); - if is_generic_ty(ty, "Vec") { + if is_unit_ty(ty) { + t(Unit) + } else if is_generic_ty(ty, "Vec") { t(Vec) } else if let Some(subty) = subty_if_name(ty, "Option") { if is_generic_ty(subty, "Option") { @@ -38,6 +41,7 @@ impl Ty { pub fn as_str(&self) -> &'static str { match self { + Self::Unit => "()", Self::Vec => "Vec", Self::Option => "Option", Self::OptionOption => "Option>", @@ -121,6 +125,14 @@ fn is_generic_ty(ty: &syn::Type, name: &str) -> bool { subty_if_name(ty, name).is_some() } +fn is_unit_ty(ty: &syn::Type) -> bool { + if let syn::Type::Tuple(tuple) = ty { + tuple.elems.is_empty() + } else { + false + } +} + fn only_one(mut iter: I) -> Option where I: Iterator, diff --git a/src/_derive/mod.rs b/src/_derive/mod.rs index 42f17824456..f2955e50be2 100644 --- a/src/_derive/mod.rs +++ b/src/_derive/mod.rs @@ -266,7 +266,8 @@ //! //! | Type | Effect | Implies | //! |---------------------|--------------------------------------|-------------------------------------------------------------| -//! | `bool` | flag | `.action(ArgAction::SetTrue) | +//! | `()` | user-defined | `.action(ArgAction::Set).required(false)` | +//! | `bool` | flag | `.action(ArgAction::SetTrue)` | //! | `Option` | optional argument | `.action(ArgAction::Set).required(false)` | //! | `Option>` | optional value for optional argument | `.action(ArgAction::Set).required(false).num_args(0..=1)` | //! | `T` | required argument | `.action(ArgAction::Set).required(!has_default)` | diff --git a/tests/derive/help.rs b/tests/derive/help.rs index 5cdcb9a3278..76d5ab6ed79 100644 --- a/tests/derive/help.rs +++ b/tests/derive/help.rs @@ -431,13 +431,15 @@ fn custom_help_flag() { #[derive(Debug, Clone, Parser)] #[command(disable_help_flag = true)] struct CliOptions { - #[arg(short = 'h', long = "verbose-help", action = ArgAction::Help)] - help: bool, + #[arg(short = 'h', long = "verbose-help", action = ArgAction::Help, value_parser = clap::value_parser!(bool))] + help: (), } let result = CliOptions::try_parse_from(["cmd", "--verbose-help"]); let err = result.unwrap_err(); assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp); + + CliOptions::try_parse_from(["cmd"]).unwrap(); } #[test] @@ -445,11 +447,13 @@ fn custom_version_flag() { #[derive(Debug, Clone, Parser)] #[command(disable_version_flag = true, version = "2.0.0")] struct CliOptions { - #[arg(short = 'V', long = "verbose-version", action = ArgAction::Version)] - version: bool, + #[arg(short = 'V', long = "verbose-version", action = ArgAction::Version, value_parser = clap::value_parser!(bool))] + version: (), } let result = CliOptions::try_parse_from(["cmd", "--verbose-version"]); let err = result.unwrap_err(); assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion); + + CliOptions::try_parse_from(["cmd"]).unwrap(); }