Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(derive): Allow users to opt-in to ValueParser #3742

Merged
merged 13 commits into from May 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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