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