• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::iter::FromIterator;
2 
3 use proc_macro2::TokenStream;
4 use proc_macro_error::abort;
5 use proc_macro_error::ResultExt;
6 use quote::quote;
7 use quote::ToTokens;
8 use syn::spanned::Spanned;
9 use syn::{
10     parenthesized,
11     parse::{Parse, ParseStream},
12     punctuated::Punctuated,
13     Attribute, Expr, Ident, LitStr, Token,
14 };
15 
16 use crate::utils::Sp;
17 
18 #[derive(Clone)]
19 pub struct ClapAttr {
20     pub kind: Sp<AttrKind>,
21     pub name: Ident,
22     pub magic: Option<MagicAttrName>,
23     pub value: Option<AttrValue>,
24 }
25 
26 impl ClapAttr {
parse_all(all_attrs: &[Attribute]) -> Vec<Self>27     pub fn parse_all(all_attrs: &[Attribute]) -> Vec<Self> {
28         all_attrs
29             .iter()
30             .filter_map(|attr| {
31                 let kind = if attr.path.is_ident("clap") {
32                     Some(Sp::new(AttrKind::Clap, attr.path.span()))
33                 } else if attr.path.is_ident("structopt") {
34                     Some(Sp::new(AttrKind::StructOpt, attr.path.span()))
35                 } else if attr.path.is_ident("command") {
36                     Some(Sp::new(AttrKind::Command, attr.path.span()))
37                 } else if attr.path.is_ident("group") {
38                     Some(Sp::new(AttrKind::Group, attr.path.span()))
39                 } else if attr.path.is_ident("arg") {
40                     Some(Sp::new(AttrKind::Arg, attr.path.span()))
41                 } else if attr.path.is_ident("value") {
42                     Some(Sp::new(AttrKind::Value, attr.path.span()))
43                 } else {
44                     None
45                 };
46                 kind.map(|k| (k, attr))
47             })
48             .flat_map(|(k, attr)| {
49                 attr.parse_args_with(Punctuated::<ClapAttr, Token![,]>::parse_terminated)
50                     .unwrap_or_abort()
51                     .into_iter()
52                     .map(move |mut a| {
53                         a.kind = k;
54                         a
55                     })
56             })
57             .collect()
58     }
59 
value_or_abort(&self) -> &AttrValue60     pub fn value_or_abort(&self) -> &AttrValue {
61         self.value
62             .as_ref()
63             .unwrap_or_else(|| abort!(self.name, "attribute `{}` requires a value", self.name))
64     }
65 
lit_str_or_abort(&self) -> &LitStr66     pub fn lit_str_or_abort(&self) -> &LitStr {
67         let value = self.value_or_abort();
68         match value {
69             AttrValue::LitStr(tokens) => tokens,
70             AttrValue::Expr(_) | AttrValue::Call(_) => {
71                 abort!(
72                     self.name,
73                     "attribute `{}` can only accept string literals",
74                     self.name
75                 )
76             }
77         }
78     }
79 }
80 
81 impl Parse for ClapAttr {
parse(input: ParseStream) -> syn::Result<Self>82     fn parse(input: ParseStream) -> syn::Result<Self> {
83         let name: Ident = input.parse()?;
84         let name_str = name.to_string();
85 
86         let magic = match name_str.as_str() {
87             "rename_all" => Some(MagicAttrName::RenameAll),
88             "rename_all_env" => Some(MagicAttrName::RenameAllEnv),
89             "skip" => Some(MagicAttrName::Skip),
90             "next_display_order" => Some(MagicAttrName::NextDisplayOrder),
91             "next_help_heading" => Some(MagicAttrName::NextHelpHeading),
92             "default_value_t" => Some(MagicAttrName::DefaultValueT),
93             "default_values_t" => Some(MagicAttrName::DefaultValuesT),
94             "default_value_os_t" => Some(MagicAttrName::DefaultValueOsT),
95             "default_values_os_t" => Some(MagicAttrName::DefaultValuesOsT),
96             "long" => Some(MagicAttrName::Long),
97             "short" => Some(MagicAttrName::Short),
98             "value_parser" => Some(MagicAttrName::ValueParser),
99             "action" => Some(MagicAttrName::Action),
100             "env" => Some(MagicAttrName::Env),
101             "flatten" => Some(MagicAttrName::Flatten),
102             "value_enum" => Some(MagicAttrName::ValueEnum),
103             "from_global" => Some(MagicAttrName::FromGlobal),
104             "subcommand" => Some(MagicAttrName::Subcommand),
105             "external_subcommand" => Some(MagicAttrName::ExternalSubcommand),
106             "verbatim_doc_comment" => Some(MagicAttrName::VerbatimDocComment),
107             "about" => Some(MagicAttrName::About),
108             "long_about" => Some(MagicAttrName::LongAbout),
109             "long_help" => Some(MagicAttrName::LongHelp),
110             "author" => Some(MagicAttrName::Author),
111             "version" => Some(MagicAttrName::Version),
112             _ => None,
113         };
114 
115         let value = if input.peek(Token![=]) {
116             // `name = value` attributes.
117             let assign_token = input.parse::<Token![=]>()?; // skip '='
118             if input.peek(LitStr) {
119                 let lit: LitStr = input.parse()?;
120                 Some(AttrValue::LitStr(lit))
121             } else {
122                 match input.parse::<Expr>() {
123                     Ok(expr) => Some(AttrValue::Expr(expr)),
124 
125                     Err(_) => abort! {
126                         assign_token,
127                         "expected `string literal` or `expression` after `=`"
128                     },
129                 }
130             }
131         } else if input.peek(syn::token::Paren) {
132             // `name(...)` attributes.
133             let nested;
134             parenthesized!(nested in input);
135 
136             let method_args: Punctuated<_, Token![,]> = nested.parse_terminated(Expr::parse)?;
137             Some(AttrValue::Call(Vec::from_iter(method_args)))
138         } else {
139             None
140         };
141 
142         Ok(Self {
143             kind: Sp::new(AttrKind::Clap, name.span()),
144             name,
145             magic,
146             value,
147         })
148     }
149 }
150 
151 #[derive(Copy, Clone, PartialEq, Eq)]
152 pub enum MagicAttrName {
153     Short,
154     Long,
155     ValueParser,
156     Action,
157     Env,
158     Flatten,
159     ValueEnum,
160     FromGlobal,
161     Subcommand,
162     VerbatimDocComment,
163     ExternalSubcommand,
164     About,
165     LongAbout,
166     LongHelp,
167     Author,
168     Version,
169     RenameAllEnv,
170     RenameAll,
171     Skip,
172     DefaultValueT,
173     DefaultValuesT,
174     DefaultValueOsT,
175     DefaultValuesOsT,
176     NextDisplayOrder,
177     NextHelpHeading,
178 }
179 
180 #[derive(Clone)]
181 #[allow(clippy::large_enum_variant)]
182 pub enum AttrValue {
183     LitStr(LitStr),
184     Expr(Expr),
185     Call(Vec<Expr>),
186 }
187 
188 impl ToTokens for AttrValue {
to_tokens(&self, tokens: &mut TokenStream)189     fn to_tokens(&self, tokens: &mut TokenStream) {
190         match self {
191             Self::LitStr(t) => t.to_tokens(tokens),
192             Self::Expr(t) => t.to_tokens(tokens),
193             Self::Call(t) => {
194                 let t = quote!(#(#t),*);
195                 t.to_tokens(tokens)
196             }
197         }
198     }
199 }
200 
201 #[derive(Copy, Clone, PartialEq, Eq)]
202 pub enum AttrKind {
203     Clap,
204     StructOpt,
205     Command,
206     Group,
207     Arg,
208     Value,
209 }
210 
211 impl AttrKind {
as_str(&self) -> &'static str212     pub fn as_str(&self) -> &'static str {
213         match self {
214             Self::Clap => "clap",
215             Self::StructOpt => "structopt",
216             Self::Command => "command",
217             Self::Group => "group",
218             Self::Arg => "arg",
219             Self::Value => "value",
220         }
221     }
222 }
223