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