Skip to content

Commit

Permalink
feat(derive): Allow type-less fields
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
epage committed Oct 11, 2022
1 parent a2f2a9a commit b26c01a
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 6 deletions.
15 changes: 15 additions & 0 deletions clap_derive/src/derives/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -421,6 +428,7 @@ pub fn gen_constructor(fields: &[(&Field, Item)]) -> TokenStream {
}
}
},
Ty::Unit |
Ty::Vec |
Ty::OptionOption |
Ty::OptionVec => {
Expand Down Expand Up @@ -459,6 +467,7 @@ pub fn gen_constructor(fields: &[(&Field, Item)]) -> TokenStream {
}
}
},
Ty::Unit |
Ty::Vec |
Ty::OptionOption |
Ty::OptionVec => {
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 13 additions & 1 deletion clap_derive/src/utils/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use syn::{

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Ty {
Unit,
Vec,
Option,
OptionOption,
Expand All @@ -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") {
Expand All @@ -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>>",
Expand Down Expand Up @@ -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>,
Expand Down
3 changes: 2 additions & 1 deletion src/_derive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)` |
Expand Down
12 changes: 8 additions & 4 deletions tests/derive/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,25 +431,29 @@ 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]
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();
}

0 comments on commit b26c01a

Please sign in to comment.