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.
This commit is contained in:
Ed Page 2022-10-11 10:29:19 -05:00
parent a2f2a9ade1
commit b26c01aa0e
4 changed files with 38 additions and 6 deletions

View file

@ -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)

View file

@ -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<T>",
Self::Option => "Option<T>",
Self::OptionOption => "Option<Option<T>>",
@ -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<I, T>(mut iter: I) -> Option<T>
where
I: Iterator<Item = T>,

View file

@ -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<T>` | optional argument | `.action(ArgAction::Set).required(false)` |
//! | `Option<Option<T>>` | optional value for optional argument | `.action(ArgAction::Set).required(false).num_args(0..=1)` |
//! | `T` | required argument | `.action(ArgAction::Set).required(!has_default)` |

View file

@ -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();
}