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