Skip to content

Commit

Permalink
Merge pull request #3742 from epage/derive3
Browse files Browse the repository at this point in the history
feat(derive): Allow users to opt-in to `ValueParser`
  • Loading branch information
epage committed May 23, 2022
2 parents b202eed + e23800e commit 19dac49
Show file tree
Hide file tree
Showing 36 changed files with 405 additions and 142 deletions.
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -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,
}
Expand Down
4 changes: 3 additions & 1 deletion clap_complete/src/generator/utils.rs
Expand Up @@ -128,7 +128,9 @@ pub fn flags<'help>(p: &Command<'help>) -> Vec<Arg<'help>> {

/// Get the possible values for completion
pub fn possible_values<'help>(a: &Arg<'help>) -> Option<Vec<clap::PossibleValue<'help>>> {
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())
Expand Down
102 changes: 101 additions & 1 deletion clap_derive/src/attrs.rs
Expand Up @@ -42,6 +42,7 @@ pub struct Attrs {
ty: Option<Type>,
doc_comment: Vec<Method>,
methods: Vec<Method>,
value_parser: Option<ValueParser>,
parser: Sp<Parser>,
verbatim_doc_comment: Option<Ident>,
next_display_order: Option<Method>,
Expand All @@ -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(),
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -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,
Expand All @@ -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)));
}
Expand All @@ -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));
}
Expand Down Expand Up @@ -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<Parser> {
&self.parser
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 19dac49

Please sign in to comment.