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