1 use std::iter::FromIterator;
2 
3 use proc_macro_error::{abort, ResultExt};
4 use quote::ToTokens;
5 use syn::{
6     self, parenthesized,
7     parse::{Parse, ParseBuffer, ParseStream},
8     punctuated::Punctuated,
9     Attribute, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Token,
10 };
11 
parse_clap_attributes(all_attrs: &[Attribute]) -> Vec<ClapAttr>12 pub fn parse_clap_attributes(all_attrs: &[Attribute]) -> Vec<ClapAttr> {
13     all_attrs
14         .iter()
15         .filter(|attr| attr.path.is_ident("clap") || attr.path.is_ident("structopt"))
16         .flat_map(|attr| {
17             attr.parse_args_with(Punctuated::<ClapAttr, Token![,]>::parse_terminated)
18                 .unwrap_or_abort()
19         })
20         .collect()
21 }
22 
23 #[allow(clippy::large_enum_variant)]
24 #[derive(Clone)]
25 pub enum ClapAttr {
26     // single-identifier attributes
27     Short(Ident),
28     Long(Ident),
29     Env(Ident),
30     Flatten(Ident),
31     ArgEnum(Ident),
32     FromGlobal(Ident),
33     Subcommand(Ident),
34     VerbatimDocComment(Ident),
35     ExternalSubcommand(Ident),
36 
37     // ident [= "string literal"]
38     About(Ident, Option<LitStr>),
39     Author(Ident, Option<LitStr>),
40     Version(Ident, Option<LitStr>),
41 
42     // ident = "string literal"
43     RenameAllEnv(Ident, LitStr),
44     RenameAll(Ident, LitStr),
45     NameLitStr(Ident, LitStr),
46 
47     // parse(parser_kind [= parser_func])
48     Parse(Ident, ParserSpec),
49 
50     // ident [= arbitrary_expr]
51     Skip(Ident, Option<Expr>),
52 
53     // ident = arbitrary_expr
54     NameExpr(Ident, Expr),
55     DefaultValueT(Ident, Option<Expr>),
56     DefaultValueOsT(Ident, Option<Expr>),
57     HelpHeading(Ident, Expr),
58 
59     // ident(arbitrary_expr,*)
60     MethodCall(Ident, Vec<Expr>),
61 }
62 
63 impl Parse for ClapAttr {
parse(input: ParseStream) -> syn::Result<Self>64     fn parse(input: ParseStream) -> syn::Result<Self> {
65         use self::ClapAttr::*;
66 
67         let name: Ident = input.parse()?;
68         let name_str = name.to_string();
69 
70         if input.peek(Token![=]) {
71             // `name = value` attributes.
72             let assign_token = input.parse::<Token![=]>()?; // skip '='
73 
74             if input.peek(LitStr) {
75                 let lit: LitStr = input.parse()?;
76                 let lit_str = lit.value();
77 
78                 let check_empty_lit = |s| {
79                     if lit_str.is_empty() {
80                         abort!(
81                             lit,
82                             "`#[clap({} = \"\")` is deprecated, \
83                              now it's default behavior",
84                             s
85                         );
86                     }
87                 };
88 
89                 match &*name_str {
90                     "rename_all" => Ok(RenameAll(name, lit)),
91                     "rename_all_env" => Ok(RenameAllEnv(name, lit)),
92 
93                     "version" => {
94                         check_empty_lit("version");
95                         Ok(Version(name, Some(lit)))
96                     }
97 
98                     "author" => {
99                         check_empty_lit("author");
100                         Ok(Author(name, Some(lit)))
101                     }
102 
103                     "about" => {
104                         check_empty_lit("about");
105                         Ok(About(name, Some(lit)))
106                     }
107 
108                     "skip" => {
109                         let expr = ExprLit {
110                             attrs: vec![],
111                             lit: Lit::Str(lit),
112                         };
113                         let expr = Expr::Lit(expr);
114                         Ok(Skip(name, Some(expr)))
115                     }
116 
117                     "help_heading" => {
118                         let expr = ExprLit {
119                             attrs: vec![],
120                             lit: Lit::Str(lit),
121                         };
122                         let expr = Expr::Lit(expr);
123                         Ok(HelpHeading(name, expr))
124                     }
125 
126                     _ => Ok(NameLitStr(name, lit)),
127                 }
128             } else {
129                 match input.parse::<Expr>() {
130                     Ok(expr) => match &*name_str {
131                         "skip" => Ok(Skip(name, Some(expr))),
132                         "default_value_t" => Ok(DefaultValueT(name, Some(expr))),
133                         "default_value_os_t" => Ok(DefaultValueOsT(name, Some(expr))),
134                         "help_heading" => Ok(HelpHeading(name, expr)),
135                         _ => Ok(NameExpr(name, expr)),
136                     },
137 
138                     Err(_) => abort! {
139                         assign_token,
140                         "expected `string literal` or `expression` after `=`"
141                     },
142                 }
143             }
144         } else if input.peek(syn::token::Paren) {
145             // `name(...)` attributes.
146             let nested;
147             parenthesized!(nested in input);
148 
149             match name_str.as_ref() {
150                 "parse" => {
151                     let parser_specs: Punctuated<ParserSpec, Token![,]> =
152                         nested.parse_terminated(ParserSpec::parse)?;
153 
154                     if parser_specs.len() == 1 {
155                         Ok(Parse(name, parser_specs[0].clone()))
156                     } else {
157                         abort!(name, "parse must have exactly one argument")
158                     }
159                 }
160 
161                 "raw" => match nested.parse::<LitBool>() {
162                     Ok(bool_token) => {
163                         let expr = ExprLit {
164                             attrs: vec![],
165                             lit: Lit::Bool(bool_token),
166                         };
167                         let expr = Expr::Lit(expr);
168                         Ok(MethodCall(name, vec![expr]))
169                     }
170 
171                     Err(_) => {
172                         abort!(name,
173                             "`#[clap(raw(...))` attributes are removed, \
174                             they are replaced with raw methods";
175                             help = "if you meant to call `clap::Arg::raw()` method \
176                                 you should use bool literal, like `raw(true)` or `raw(false)`";
177                             note = raw_method_suggestion(nested);
178                         );
179                     }
180                 },
181 
182                 _ => {
183                     let method_args: Punctuated<_, Token![,]> =
184                         nested.parse_terminated(Expr::parse)?;
185                     Ok(MethodCall(name, Vec::from_iter(method_args)))
186                 }
187             }
188         } else {
189             // Attributes represented with a sole identifier.
190             match name_str.as_ref() {
191                 "long" => Ok(Long(name)),
192                 "short" => Ok(Short(name)),
193                 "env" => Ok(Env(name)),
194                 "flatten" => Ok(Flatten(name)),
195                 "arg_enum" => Ok(ArgEnum(name)),
196                 "from_global" => Ok(FromGlobal(name)),
197                 "subcommand" => Ok(Subcommand(name)),
198                 "external_subcommand" => Ok(ExternalSubcommand(name)),
199                 "verbatim_doc_comment" => Ok(VerbatimDocComment(name)),
200 
201                 "default_value" => {
202                     abort!(name,
203                         "`#[clap(default_value)` attribute (without a value) has been replaced by `#[clap(default_value_t)]`.";
204                         help = "Change the attribute to `#[clap(default_value_t)]`";
205                     )
206                 }
207                 "default_value_t" => Ok(DefaultValueT(name, None)),
208                 "default_value_os_t" => Ok(DefaultValueOsT(name, None)),
209                 "about" => (Ok(About(name, None))),
210                 "author" => (Ok(Author(name, None))),
211                 "version" => Ok(Version(name, None)),
212 
213                 "skip" => Ok(Skip(name, None)),
214 
215                 _ => abort!(name, "unexpected attribute: {}", name_str),
216             }
217         }
218     }
219 }
220 
221 #[derive(Clone)]
222 pub struct ParserSpec {
223     pub kind: Ident,
224     pub eq_token: Option<Token![=]>,
225     pub parse_func: Option<Expr>,
226 }
227 
228 impl Parse for ParserSpec {
parse(input: ParseStream<'_>) -> syn::Result<Self>229     fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
230         let kind = input
231             .parse()
232             .map_err(|_| input.error("parser specification must start with identifier"))?;
233         let eq_token = input.parse()?;
234         let parse_func = match eq_token {
235             None => None,
236             Some(_) => Some(input.parse()?),
237         };
238         Ok(ParserSpec {
239             kind,
240             eq_token,
241             parse_func,
242         })
243     }
244 }
245 
raw_method_suggestion(ts: ParseBuffer) -> String246 fn raw_method_suggestion(ts: ParseBuffer) -> String {
247     let do_parse = move || -> Result<(Ident, Punctuated<Expr, Token![,]>), syn::Error> {
248         let name = ts.parse()?;
249         let _eq: Token![=] = ts.parse()?;
250         let val: LitStr = ts.parse()?;
251         let exprs = val.parse_with(Punctuated::<Expr, Token![,]>::parse_terminated)?;
252         Ok((name, exprs))
253     };
254 
255     fn to_string<T: ToTokens>(val: &T) -> String {
256         val.to_token_stream()
257             .to_string()
258             .replace(' ', "")
259             .replace(',', ", ")
260     }
261 
262     if let Ok((name, exprs)) = do_parse() {
263         let suggestion = if exprs.len() == 1 {
264             let val = to_string(&exprs[0]);
265             format!(" = {}", val)
266         } else {
267             let val = exprs
268                 .into_iter()
269                 .map(|expr| to_string(&expr))
270                 .collect::<Vec<_>>()
271                 .join(", ");
272 
273             format!("({})", val)
274         };
275 
276         format!(
277             "if you need to call `clap::Arg/App::{}` method you \
278              can do it like this: #[clap({}{})]",
279             name, name, suggestion
280         )
281     } else {
282         "if you need to call some method from `clap::Arg/App` \
283          you should use raw method, see \
284          https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#raw-attributes"
285             .into()
286     }
287 }
288