Skip to content

Commit

Permalink
fix(derive)!: Infer action for help/version
Browse files Browse the repository at this point in the history
This will make it easier for people to override these

Fixes clap-rs#4057
  • Loading branch information
epage committed Aug 11, 2022
1 parent 1a08b91 commit fc65d69
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 19 deletions.
49 changes: 40 additions & 9 deletions clap_derive/src/attrs.rs
Expand Up @@ -889,14 +889,14 @@ impl Attrs {
.unwrap_or_else(|| {
if let Some(value_parser) = self.value_parser.as_ref() {
let span = value_parser.span();
default_action(field_type, span)
default_action(self.name.raw_literal().as_deref(), field_type, span)
} else {
let span = self
.value_parser
.as_ref()
.map(|a| a.span())
.unwrap_or_else(|| self.kind.span());
default_action(field_type, span)
default_action(self.name.raw_literal().as_deref(), field_type, span)
}
})
}
Expand Down Expand Up @@ -949,14 +949,27 @@ impl ValueParser {
}
}

fn default_value_parser(inner_type: &Type, span: Span) -> Method {
fn default_value_parser(name: Option<&str>, inner_type: &Type, span: Span) -> Method {
let func = Ident::new("value_parser", span);
Method::new(
func,
let call = if crate::utils::is_unit_type(inner_type) {
match name {
Some("help") | Some("version") => {
quote_spanned! { span=>
clap::value_parser!(bool)
}
}
_ => {
quote_spanned! { span=>
clap::value_parser!(String)
}
}
}
} else {
quote_spanned! { span=>
clap::value_parser!(#inner_type)
},
)
}
};
Method::new(func, call)
}

#[derive(Clone)]
Expand All @@ -971,7 +984,7 @@ impl Action {
match self {
Self::Explicit(method) => method,
#[cfg(not(feature = "unstable-v5"))]
Self::Implicit(ident) => default_action(_field_type, ident.span()),
Self::Implicit(ident) => default_action(None, _field_type, ident.span()),
}
}

Expand All @@ -984,9 +997,20 @@ impl Action {
}
}

fn default_action(field_type: &Type, span: Span) -> Method {
fn default_action(name: Option<&str>, field_type: &Type, span: Span) -> Method {
let ty = Ty::from_syn_ty(field_type);
let args = match *ty {
Ty::Unit => match name {
Some("help") => quote_spanned! { span=>
clap::ArgAction::Help
},
Some("version") => quote_spanned! { span=>
clap::ArgAction::Version
},
_ => quote_spanned! { span=>
clap::ArgAction::Set
},
},
Ty::Vec | Ty::OptionVec => {
quote_spanned! { span=>
clap::ArgAction::Append
Expand Down Expand Up @@ -1160,6 +1184,13 @@ impl Name {
}
}

pub fn raw_literal(&self) -> Option<String> {
match self {
Name::Assigned(_) => None,
Name::Derived(ident) => Some(ident.unraw().to_string()),
}
}

pub fn translate(self, style: CasingStyle) -> TokenStream {
use CasingStyle::*;

Expand Down
12 changes: 12 additions & 0 deletions clap_derive/src/derives/args.rs
Expand Up @@ -248,6 +248,14 @@ pub fn gen_augment(
let value_name = attrs.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 @@ -504,6 +512,10 @@ 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
2 changes: 1 addition & 1 deletion clap_derive/src/utils/mod.rs
Expand Up @@ -5,5 +5,5 @@ mod ty;
pub use self::{
doc_comments::process_doc_comment,
spanned::Sp,
ty::{inner_type, is_simple_ty, sub_type, subty_if_name, Ty},
ty::{inner_type, is_simple_ty, is_unit_type, sub_type, subty_if_name, Ty},
};
13 changes: 12 additions & 1 deletion clap_derive/src/utils/ty.rs
Expand Up @@ -13,6 +13,7 @@ pub enum Ty {
Option,
OptionOption,
OptionVec,
Unit,
Other,
}

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_type(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 Down Expand Up @@ -52,6 +55,14 @@ pub fn sub_type(ty: &syn::Type) -> Option<&syn::Type> {
subty_if(ty, |_| true)
}

pub fn is_unit_type(ty: &syn::Type) -> bool {
if let syn::Type::Tuple(tuple) = ty {
tuple.elems.is_empty()
} else {
false
}
}

fn only_last_segment(mut ty: &syn::Type) -> Option<&PathSegment> {
while let syn::Type::Group(syn::TypeGroup { elem, .. }) = ty {
ty = elem;
Expand Down
19 changes: 11 additions & 8 deletions src/_derive/mod.rs
Expand Up @@ -254,14 +254,17 @@
//!
//! `clap` assumes some intent based on the type used:
//!
//! | Type | Effect | Implies |
//! |---------------------|--------------------------------------|--------------------------------------------------------------------------|
//! | `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).min_values(0).max_values(1)` |
//! | `T` | required argument | `.action(ArgAction::Set).required(!has_default)` |
//! | `Vec<T>` | `0..` occurrences of argument | `.action(ArgAction::Append).required(false).multiple_occurrences(true)` |
//! | `Option<Vec<T>>` | `0..` occurrences of argument | `.action(ArgAction::Append).required(false).multiple_occurrences(true)` |
//! | Type | Effect | Implies |
//! |-------------------------|--------------------------------------|--------------------------------------------------------------------------|
//! | `()` and `id="help"` | print help | `.action(ArgAction::Help) |
//! | `()` and `id="version"` | print version | `.action(ArgAction::Version) |
//! | `()` | caller dependent | |
//! | `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).min_values(0).max_values(1)` |
//! | `T` | required argument | `.action(ArgAction::Set).required(!has_default)` |
//! | `Vec<T>` | `0..` occurrences of argument | `.action(ArgAction::Append).required(false).multiple_occurrences(true)` |
//! | `Option<Vec<T>>` | `0..` occurrences of argument | `.action(ArgAction::Append).required(false).multiple_occurrences(true)` |
//!
//! Notes:
//! - For custom type behavior, you can override the implied attributes/settings and/or set additional ones
Expand Down
28 changes: 28 additions & 0 deletions tests/derive/help.rs
Expand Up @@ -441,3 +441,31 @@ OPTIONS:
let help = String::from_utf8(buffer).unwrap();
snapbox::assert_eq(HELP, help);
}

#[test]
fn custom_help_flag() {
#[derive(Debug, Clone, Parser)]
#[clap(disable_help_flag = true)]
struct CliOptions {
#[clap(short = 'h', long = "verbose-help")]
help: (),
}

let result = CliOptions::try_parse_from(["cmd", "--verbose-help"]);
let err = result.unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
}

#[test]
fn custom_version_flag() {
#[derive(Debug, Clone, Parser)]
#[clap(disable_version_flag = true, version = "2.0.0")]
struct CliOptions {
#[clap(short = 'V', long = "verbose-version")]
version: (),
}

let result = CliOptions::try_parse_from(["cmd", "--verbose-version"]);
let err = result.unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion);
}

0 comments on commit fc65d69

Please sign in to comment.