From f08575e4a3e1ea345feef1ee4ac47e2a7a51aecb Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 26 Aug 2022 14:44:04 -0500 Subject: [PATCH 1/2] refactor(derive): Simplify verbatim docs tracking --- clap_derive/src/attrs.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/clap_derive/src/attrs.rs b/clap_derive/src/attrs.rs index 2aa068b60f9..9d402817984 100644 --- a/clap_derive/src/attrs.rs +++ b/clap_derive/src/attrs.rs @@ -44,7 +44,7 @@ pub struct Attrs { methods: Vec, value_parser: Option, action: Option, - verbatim_doc_comment: Option, + verbatim_doc_comment: bool, next_display_order: Option, next_help_heading: Option, help_heading: Option, @@ -389,7 +389,7 @@ impl Attrs { methods: vec![], value_parser: None, action: None, - verbatim_doc_comment: None, + verbatim_doc_comment: false, next_display_order: None, next_help_heading: None, help_heading: None, @@ -474,7 +474,7 @@ impl Attrs { self.set_kind(kind); } - VerbatimDocComment(ident) => self.verbatim_doc_comment = Some(ident), + VerbatimDocComment(_ident) => self.verbatim_doc_comment = true, DefaultValueT(ident, expr) => { let ty = if let Some(ty) = self.ty.as_ref() { @@ -750,8 +750,7 @@ impl Attrs { }) .collect(); - self.doc_comment = - process_doc_comment(comment_parts, name, self.verbatim_doc_comment.is_none()); + self.doc_comment = process_doc_comment(comment_parts, name, !self.verbatim_doc_comment); } fn set_kind(&mut self, kind: Sp) { From c04fe2fa4b563545a70ab5f19d6a94f0a6c12de9 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 26 Aug 2022 13:27:48 -0500 Subject: [PATCH 2/2] refactor(derive): Allow carrying context with parsed attributes --- clap_derive/src/attrs.rs | 244 ++++++++++++++++++++++--------------- clap_derive/src/parse.rs | 252 +++++++++++++++++++-------------------- 2 files changed, 272 insertions(+), 224 deletions(-) diff --git a/clap_derive/src/attrs.rs b/clap_derive/src/attrs.rs index 9d402817984..8a274789c7f 100644 --- a/clap_derive/src/attrs.rs +++ b/clap_derive/src/attrs.rs @@ -24,8 +24,8 @@ use proc_macro2::{self, Span, TokenStream}; use proc_macro_error::abort; use quote::{quote, quote_spanned, ToTokens}; use syn::{ - self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Field, Ident, LitStr, MetaNameValue, - Type, Variant, + self, ext::IdentExt, spanned::Spanned, Attribute, Field, Ident, LitStr, MetaNameValue, Type, + Variant, }; /// Default casing style for generated arguments. @@ -415,73 +415,103 @@ impl Attrs { } fn push_attrs(&mut self, attrs: &[Attribute]) { - use ClapAttr::*; - - let parsed = parse_clap_attributes(attrs); + let parsed = ClapAttr::parse_all(attrs); for attr in &parsed { - let attr = attr.clone(); - match attr { - Short(ident) => { - self.push_method(ident, self.name.clone().translate_char(*self.casing)); + if let Some(AttrValue::Call(tokens)) = &attr.value { + // Force raw mode with method call syntax + self.push_method(attr.name.clone(), quote!(#(#tokens),*)); + continue; + } + + match &attr.magic { + Some(MagicAttrName::Short) if attr.value.is_none() => { + self.push_method( + attr.name.clone(), + self.name.clone().translate_char(*self.casing), + ); } - Long(ident) => { - self.push_method(ident, self.name.clone().translate(*self.casing)); + Some(MagicAttrName::Long) if attr.value.is_none() => { + self.push_method(attr.name.clone(), self.name.clone().translate(*self.casing)); } #[cfg(not(feature = "unstable-v5"))] - ValueParser(ident) => { - use crate::attrs::ValueParser; - self.value_parser = Some(ValueParser::Implicit(ident)); + Some(MagicAttrName::ValueParser) if attr.value.is_none() => { + self.value_parser = Some(ValueParser::Implicit(attr.name.clone())); } #[cfg(not(feature = "unstable-v5"))] - Action(ident) => { - use crate::attrs::Action; - self.action = Some(Action::Implicit(ident)); + Some(MagicAttrName::Action) if attr.value.is_none() => { + self.action = Some(Action::Implicit(attr.name.clone())); } - Env(ident) => { - self.push_method(ident, self.name.clone().translate(*self.env_casing)); + Some(MagicAttrName::Env) if attr.value.is_none() => { + self.push_method( + attr.name.clone(), + self.name.clone().translate(*self.env_casing), + ); } - ValueEnum(_) => self.is_enum = true, + Some(MagicAttrName::ValueEnum) if attr.value.is_none() => self.is_enum = true, - FromGlobal(ident) => { + Some(MagicAttrName::FromGlobal) if attr.value.is_none() => { let ty = Sp::call_site(Ty::Other); - let kind = Sp::new(Kind::FromGlobal(ty), ident.span()); + let kind = Sp::new(Kind::FromGlobal(ty), attr.name.clone().span()); self.set_kind(kind); } - Subcommand(ident) => { + Some(MagicAttrName::Subcommand) if attr.value.is_none() => { let ty = Sp::call_site(Ty::Other); - let kind = Sp::new(Kind::Subcommand(ty), ident.span()); + let kind = Sp::new(Kind::Subcommand(ty), attr.name.clone().span()); self.set_kind(kind); } - ExternalSubcommand(ident) => { - let kind = Sp::new(Kind::ExternalSubcommand, ident.span()); + Some(MagicAttrName::ExternalSubcommand) if attr.value.is_none() => { + let kind = Sp::new(Kind::ExternalSubcommand, attr.name.clone().span()); self.set_kind(kind); } - Flatten(ident) => { - let kind = Sp::new(Kind::Flatten, ident.span()); + Some(MagicAttrName::Flatten) if attr.value.is_none() => { + let kind = Sp::new(Kind::Flatten, attr.name.clone().span()); self.set_kind(kind); } - Skip(ident, expr) => { - let kind = Sp::new(Kind::Skip(expr), ident.span()); - self.set_kind(kind); + Some(MagicAttrName::VerbatimDocComment) if attr.value.is_none() => { + self.verbatim_doc_comment = true + } + + Some(MagicAttrName::About) if attr.value.is_none() => { + if let Some(method) = + Method::from_env(attr.name.clone(), "CARGO_PKG_DESCRIPTION") + { + self.methods.push(method); + } } - VerbatimDocComment(_ident) => self.verbatim_doc_comment = true, + Some(MagicAttrName::Author) if attr.value.is_none() => { + if let Some(method) = Method::from_env(attr.name.clone(), "CARGO_PKG_AUTHORS") { + self.methods.push(method); + } + } - DefaultValueT(ident, expr) => { + Some(MagicAttrName::Version) if attr.value.is_none() => { + if let Some(method) = Method::from_env(attr.name.clone(), "CARGO_PKG_VERSION") { + self.methods.push(method); + } + } + + Some(MagicAttrName::Skip) => { + let expr = attr.value.clone(); + let kind = Sp::new(Kind::Skip(expr), attr.name.clone().span()); + self.set_kind(kind); + } + + Some(MagicAttrName::DefaultValueT) => { let ty = if let Some(ty) = self.ty.as_ref() { ty } else { abort!( - ident, + attr.name.clone(), "#[clap(default_value_t)] (without an argument) can be used \ only on field level"; @@ -489,21 +519,24 @@ impl Attrs { https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") }; - let val = if let Some(expr) = expr { + let val = if let Some(expr) = &attr.value { quote!(#expr) } else { quote!(<#ty as ::std::default::Default>::default()) }; - let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) { - quote_spanned!(ident.span()=> { + let val = if parsed + .iter() + .any(|a| a.magic == Some(MagicAttrName::ValueEnum)) + { + quote_spanned!(attr.name.clone().span()=> { { let val: #ty = #val; clap::ValueEnum::to_possible_value(&val).unwrap().get_name().to_owned() } }) } else { - quote_spanned!(ident.span()=> { + quote_spanned!(attr.name.clone().span()=> { { let val: #ty = #val; ::std::string::ToString::to_string(&val) @@ -511,27 +544,28 @@ impl Attrs { }) }; - let raw_ident = Ident::new("default_value", ident.span()); + let raw_ident = Ident::new("default_value", attr.name.clone().span()); self.methods.push(Method::new(raw_ident, val)); } - DefaultValuesT(ident, expr) => { + Some(MagicAttrName::DefaultValuesT) => { let ty = if let Some(ty) = self.ty.as_ref() { ty } else { abort!( - ident, + attr.name.clone(), "#[clap(default_values_t)] (without an argument) can be used \ only on field level"; note = "see \ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") }; + let expr = attr.value_or_abort(); let container_type = Ty::from_syn_ty(ty); if *container_type != Ty::Vec { abort!( - ident, + attr.name.clone(), "#[clap(default_values_t)] can be used only on Vec types"; note = "see \ @@ -541,8 +575,11 @@ impl Attrs { // Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and // `Vec<#inner_type>`. - let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) { - quote_spanned!(ident.span()=> { + let val = if parsed + .iter() + .any(|a| a.magic == Some(MagicAttrName::ValueEnum)) + { + quote_spanned!(attr.name.clone().span()=> { { fn iter_to_vals(iterable: impl IntoIterator) -> impl Iterator where @@ -559,7 +596,7 @@ impl Attrs { } }) } else { - quote_spanned!(ident.span()=> { + quote_spanned!(attr.name.clone().span()=> { { fn iter_to_vals(iterable: impl IntoIterator) -> Vec where @@ -574,16 +611,18 @@ impl Attrs { }) }; - self.methods - .push(Method::new(Ident::new("default_values", ident.span()), val)); + self.methods.push(Method::new( + Ident::new("default_values", attr.name.clone().span()), + val, + )); } - DefaultValueOsT(ident, expr) => { + Some(MagicAttrName::DefaultValueOsT) => { let ty = if let Some(ty) = self.ty.as_ref() { ty } else { abort!( - ident, + attr.name.clone(), "#[clap(default_value_os_t)] (without an argument) can be used \ only on field level"; @@ -591,21 +630,24 @@ impl Attrs { https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") }; - let val = if let Some(expr) = expr { + let val = if let Some(expr) = &attr.value { quote!(#expr) } else { quote!(<#ty as ::std::default::Default>::default()) }; - let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) { - quote_spanned!(ident.span()=> { + let val = if parsed + .iter() + .any(|a| a.magic == Some(MagicAttrName::ValueEnum)) + { + quote_spanned!(attr.name.clone().span()=> { { let val: #ty = #val; clap::ValueEnum::to_possible_value(&val).unwrap().get_name().to_owned() } }) } else { - quote_spanned!(ident.span()=> { + quote_spanned!(attr.name.clone().span()=> { { let val: #ty = #val; ::std::ffi::OsString::from(val) @@ -613,27 +655,28 @@ impl Attrs { }) }; - let raw_ident = Ident::new("default_value_os", ident.span()); + let raw_ident = Ident::new("default_value_os", attr.name.clone().span()); self.methods.push(Method::new(raw_ident, val)); } - DefaultValuesOsT(ident, expr) => { + Some(MagicAttrName::DefaultValuesOsT) => { let ty = if let Some(ty) = self.ty.as_ref() { ty } else { abort!( - ident, + attr.name.clone(), "#[clap(default_values_os_t)] (without an argument) can be used \ only on field level"; note = "see \ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes") }; + let expr = attr.value_or_abort(); let container_type = Ty::from_syn_ty(ty); if *container_type != Ty::Vec { abort!( - ident, + attr.name.clone(), "#[clap(default_values_os_t)] can be used only on Vec types"; note = "see \ @@ -643,8 +686,11 @@ impl Attrs { // Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and // `Vec<#inner_type>`. - let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) { - quote_spanned!(ident.span()=> { + let val = if parsed + .iter() + .any(|a| a.magic == Some(MagicAttrName::ValueEnum)) + { + quote_spanned!(attr.name.clone().span()=> { { fn iter_to_vals(iterable: impl IntoIterator) -> impl Iterator where @@ -661,7 +707,7 @@ impl Attrs { } }) } else { - quote_spanned!(ident.span()=> { + quote_spanned!(attr.name.clone().span()=> { { fn iter_to_vals(iterable: impl IntoIterator) -> Vec<::std::ffi::OsString> where @@ -677,56 +723,64 @@ impl Attrs { }; self.methods.push(Method::new( - Ident::new("default_values_os", ident.span()), + Ident::new("default_values_os", attr.name.clone().span()), val, )); } - NextDisplayOrder(ident, expr) => { - self.next_display_order = Some(Method::new(ident, quote!(#expr))); + Some(MagicAttrName::NextDisplayOrder) => { + let expr = attr.value_or_abort(); + self.next_display_order = Some(Method::new(attr.name.clone(), quote!(#expr))); } - HelpHeading(ident, expr) => { - self.help_heading = Some(Method::new(ident, quote!(#expr))); + Some(MagicAttrName::HelpHeading) => { + let expr = attr.value_or_abort(); + self.help_heading = Some(Method::new(attr.name.clone(), quote!(#expr))); } - NextHelpHeading(ident, expr) => { - self.next_help_heading = Some(Method::new(ident, quote!(#expr))); + Some(MagicAttrName::NextHelpHeading) => { + let expr = attr.value_or_abort(); + self.next_help_heading = Some(Method::new(attr.name.clone(), quote!(#expr))); } - About(ident) => { - if let Some(method) = Method::from_env(ident, "CARGO_PKG_DESCRIPTION") { - self.methods.push(method); - } + Some(MagicAttrName::RenameAll) => { + let lit = attr.lit_str_or_abort(); + self.casing = CasingStyle::from_lit(lit); } - Author(ident) => { - if let Some(method) = Method::from_env(ident, "CARGO_PKG_AUTHORS") { - self.methods.push(method); - } + Some(MagicAttrName::RenameAllEnv) => { + let lit = attr.lit_str_or_abort(); + self.env_casing = CasingStyle::from_lit(lit); } - Version(ident) => { - if let Some(method) = Method::from_env(ident, "CARGO_PKG_VERSION") { - self.methods.push(method); - } - } - - NameLitStr(name, lit) => { - self.push_method(name, lit); + None + // Magic only for the default, otherwise just forward to the builder + | Some(MagicAttrName::Short) + | Some(MagicAttrName::Long) + | Some(MagicAttrName::Env) + | Some(MagicAttrName::About) + | Some(MagicAttrName::Author) + | Some(MagicAttrName::Version) + => { + let expr = attr.value_or_abort(); + self.push_method(attr.name.clone(), expr); } - NameExpr(name, expr) => { - self.push_method(name, expr); - } - - MethodCall(name, args) => self.push_method(name, quote!(#(#args),*)), - - RenameAll(_, casing_lit) => { - self.casing = CasingStyle::from_lit(casing_lit); + // Magic only for the default, otherwise just forward to the builder + #[cfg(not(feature = "unstable-v5"))] + Some(MagicAttrName::ValueParser) | Some(MagicAttrName::Action) => { + let expr = attr.value_or_abort(); + self.push_method(attr.name.clone(), expr); } - RenameAllEnv(_, casing_lit) => { - self.env_casing = CasingStyle::from_lit(casing_lit); + // Directives that never receive a value + Some(MagicAttrName::ValueEnum) + | Some(MagicAttrName::FromGlobal) + | Some(MagicAttrName::Subcommand) + | Some(MagicAttrName::ExternalSubcommand) + | Some(MagicAttrName::Flatten) + | Some(MagicAttrName::VerbatimDocComment) => { + let expr = attr.value_or_abort(); + abort!(expr, "attribute `{}` does not accept a value", attr.name); } } } @@ -993,7 +1047,7 @@ pub enum Kind { FromGlobal(Sp), Subcommand(Sp), Flatten, - Skip(Option), + Skip(Option), ExternalSubcommand, } @@ -1095,7 +1149,7 @@ pub enum CasingStyle { } impl CasingStyle { - fn from_lit(name: LitStr) -> Sp { + fn from_lit(name: &LitStr) -> Sp { use self::CasingStyle::*; let normalized = name.value().to_upper_camel_case().to_lowercase(); diff --git a/clap_derive/src/parse.rs b/clap_derive/src/parse.rs index 109b0eecd19..91aed44beb9 100644 --- a/clap_derive/src/parse.rs +++ b/clap_derive/src/parse.rs @@ -1,135 +1,101 @@ use std::iter::FromIterator; +use quote::quote; +use quote::ToTokens; + +use proc_macro2::TokenStream; use proc_macro_error::{abort, ResultExt}; use syn::{ self, parenthesized, parse::{Parse, ParseStream}, punctuated::Punctuated, - Attribute, Expr, ExprLit, Ident, Lit, LitStr, Token, + Attribute, Expr, Ident, LitStr, Token, }; -pub fn parse_clap_attributes(all_attrs: &[Attribute]) -> Vec { - all_attrs - .iter() - .filter(|attr| attr.path.is_ident("clap") || attr.path.is_ident("structopt")) - .flat_map(|attr| { - attr.parse_args_with(Punctuated::::parse_terminated) - .unwrap_or_abort() - }) - .collect() -} - -#[allow(clippy::large_enum_variant)] #[derive(Clone)] -pub enum ClapAttr { - // single-identifier attributes - Short(Ident), - Long(Ident), - #[cfg(not(feature = "unstable-v5"))] - ValueParser(Ident), - #[cfg(not(feature = "unstable-v5"))] - Action(Ident), - Env(Ident), - Flatten(Ident), - ValueEnum(Ident), - FromGlobal(Ident), - Subcommand(Ident), - VerbatimDocComment(Ident), - ExternalSubcommand(Ident), - About(Ident), - Author(Ident), - Version(Ident), - - // ident = "string literal" - RenameAllEnv(Ident, LitStr), - RenameAll(Ident, LitStr), - NameLitStr(Ident, LitStr), +pub struct ClapAttr { + pub name: Ident, + pub magic: Option, + pub value: Option, +} - // ident [= arbitrary_expr] - Skip(Ident, Option), +impl ClapAttr { + pub fn parse_all(all_attrs: &[Attribute]) -> Vec { + all_attrs + .iter() + .filter(|attr| attr.path.is_ident("clap") || attr.path.is_ident("structopt")) + .flat_map(|attr| { + attr.parse_args_with(Punctuated::::parse_terminated) + .unwrap_or_abort() + }) + .collect() + } - // ident = arbitrary_expr - NameExpr(Ident, Expr), - DefaultValueT(Ident, Option), - DefaultValuesT(Ident, Expr), - DefaultValueOsT(Ident, Option), - DefaultValuesOsT(Ident, Expr), - NextDisplayOrder(Ident, Expr), - NextHelpHeading(Ident, Expr), - HelpHeading(Ident, Expr), + pub fn value_or_abort(&self) -> &AttrValue { + self.value + .as_ref() + .unwrap_or_else(|| abort!(self.name, "attribute `{}` requires a value", self.name)) + } - // ident(arbitrary_expr,*) - MethodCall(Ident, Vec), + pub fn lit_str_or_abort(&self) -> &LitStr { + let value = self.value_or_abort(); + match value { + AttrValue::LitStr(tokens) => tokens, + AttrValue::Expr(_) | AttrValue::Call(_) => { + abort!( + self.name, + "attribute `{}` can only accept string litersl", + self.name + ) + } + } + } } impl Parse for ClapAttr { fn parse(input: ParseStream) -> syn::Result { - use self::ClapAttr::*; - let name: Ident = input.parse()?; let name_str = name.to_string(); - if input.peek(Token![=]) { + let magic = match name_str.as_str() { + "rename_all" => Some(MagicAttrName::RenameAll), + "rename_all_env" => Some(MagicAttrName::RenameAllEnv), + "skip" => Some(MagicAttrName::Skip), + "next_display_order" => Some(MagicAttrName::NextDisplayOrder), + "next_help_heading" => Some(MagicAttrName::NextHelpHeading), + "help_heading" => Some(MagicAttrName::HelpHeading), + "default_value_t" => Some(MagicAttrName::DefaultValueT), + "default_values_t" => Some(MagicAttrName::DefaultValuesT), + "default_value_os_t" => Some(MagicAttrName::DefaultValueOsT), + "default_values_os_t" => Some(MagicAttrName::DefaultValuesOsT), + "long" => Some(MagicAttrName::Long), + "short" => Some(MagicAttrName::Short), + #[cfg(not(feature = "unstable-v5"))] + "value_parser" => Some(MagicAttrName::ValueParser), + #[cfg(not(feature = "unstable-v5"))] + "action" => Some(MagicAttrName::Action), + "env" => Some(MagicAttrName::Env), + "flatten" => Some(MagicAttrName::Flatten), + "value_enum" => Some(MagicAttrName::ValueEnum), + "from_global" => Some(MagicAttrName::FromGlobal), + "subcommand" => Some(MagicAttrName::Subcommand), + "external_subcommand" => Some(MagicAttrName::ExternalSubcommand), + "verbatim_doc_comment" => Some(MagicAttrName::VerbatimDocComment), + "about" => Some(MagicAttrName::About), + "author" => Some(MagicAttrName::Author), + "version" => Some(MagicAttrName::Version), + _ => None, + }; + + let value = if input.peek(Token![=]) { // `name = value` attributes. let assign_token = input.parse::()?; // skip '=' - if input.peek(LitStr) { let lit: LitStr = input.parse()?; - - match &*name_str { - "rename_all" => Ok(RenameAll(name, lit)), - "rename_all_env" => Ok(RenameAllEnv(name, lit)), - - "skip" => { - let expr = ExprLit { - attrs: vec![], - lit: Lit::Str(lit), - }; - let expr = Expr::Lit(expr); - Ok(Skip(name, Some(expr))) - } - - "next_display_order" => { - let expr = ExprLit { - attrs: vec![], - lit: Lit::Str(lit), - }; - let expr = Expr::Lit(expr); - Ok(NextDisplayOrder(name, expr)) - } - - "next_help_heading" => { - let expr = ExprLit { - attrs: vec![], - lit: Lit::Str(lit), - }; - let expr = Expr::Lit(expr); - Ok(NextHelpHeading(name, expr)) - } - "help_heading" => { - let expr = ExprLit { - attrs: vec![], - lit: Lit::Str(lit), - }; - let expr = Expr::Lit(expr); - Ok(HelpHeading(name, expr)) - } - - _ => Ok(NameLitStr(name, lit)), - } + Some(AttrValue::LitStr(lit)) } else { match input.parse::() { - Ok(expr) => match &*name_str { - "skip" => Ok(Skip(name, Some(expr))), - "default_value_t" => Ok(DefaultValueT(name, Some(expr))), - "default_values_t" => Ok(DefaultValuesT(name, expr)), - "default_value_os_t" => Ok(DefaultValueOsT(name, Some(expr))), - "default_values_os_t" => Ok(DefaultValuesOsT(name, expr)), - "next_display_order" => Ok(NextDisplayOrder(name, expr)), - "next_help_heading" => Ok(NextHelpHeading(name, expr)), - "help_heading" => Ok(HelpHeading(name, expr)), - _ => Ok(NameExpr(name, expr)), - }, + Ok(expr) => Some(AttrValue::Expr(expr)), Err(_) => abort! { assign_token, @@ -143,33 +109,61 @@ impl Parse for ClapAttr { parenthesized!(nested in input); let method_args: Punctuated<_, Token![,]> = nested.parse_terminated(Expr::parse)?; - Ok(MethodCall(name, Vec::from_iter(method_args))) + Some(AttrValue::Call(Vec::from_iter(method_args))) } else { - // Attributes represented with a sole identifier. - match name_str.as_ref() { - "long" => Ok(Long(name)), - "short" => Ok(Short(name)), - #[cfg(not(feature = "unstable-v5"))] - "value_parser" => Ok(ValueParser(name)), - #[cfg(not(feature = "unstable-v5"))] - "action" => Ok(Action(name)), - "env" => Ok(Env(name)), - "flatten" => Ok(Flatten(name)), - "value_enum" => Ok(ValueEnum(name)), - "from_global" => Ok(FromGlobal(name)), - "subcommand" => Ok(Subcommand(name)), - "external_subcommand" => Ok(ExternalSubcommand(name)), - "verbatim_doc_comment" => Ok(VerbatimDocComment(name)), + None + }; - "default_value_t" => Ok(DefaultValueT(name, None)), - "default_value_os_t" => Ok(DefaultValueOsT(name, None)), - "about" => (Ok(About(name))), - "author" => (Ok(Author(name))), - "version" => Ok(Version(name)), + Ok(Self { name, magic, value }) + } +} - "skip" => Ok(Skip(name, None)), +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum MagicAttrName { + Short, + Long, + #[cfg(not(feature = "unstable-v5"))] + ValueParser, + #[cfg(not(feature = "unstable-v5"))] + Action, + Env, + Flatten, + ValueEnum, + FromGlobal, + Subcommand, + VerbatimDocComment, + ExternalSubcommand, + About, + Author, + Version, + RenameAllEnv, + RenameAll, + Skip, + DefaultValueT, + DefaultValuesT, + DefaultValueOsT, + DefaultValuesOsT, + NextDisplayOrder, + NextHelpHeading, + HelpHeading, +} + +#[derive(Clone)] +#[allow(clippy::large_enum_variant)] +pub enum AttrValue { + LitStr(LitStr), + Expr(Expr), + Call(Vec), +} - _ => abort!(name, "unexpected attribute: {}", name_str), +impl ToTokens for AttrValue { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::LitStr(t) => t.to_tokens(tokens), + Self::Expr(t) => t.to_tokens(tokens), + Self::Call(t) => { + let t = quote!(#(#t),*); + t.to_tokens(tokens) } } }