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