1 | // Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>, |
2 | // Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and |
3 | // Ana Hobden (@hoverbear) <operator@hoverbear.org> |
4 | // |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
8 | // option. This file may not be copied, modified, or distributed |
9 | // except according to those terms. |
10 | // |
11 | // This work was derived from Structopt (https://github.com/TeXitoi/structopt) |
12 | // commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the |
13 | // MIT/Apache 2.0 license. |
14 | |
15 | use std::env; |
16 | |
17 | use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; |
18 | use proc_macro2::{self, Span, TokenStream}; |
19 | use quote::{format_ident, quote, quote_spanned, ToTokens}; |
20 | use syn::DeriveInput; |
21 | use syn::{self, ext::IdentExt, spanned::Spanned, Attribute, Field, Ident, LitStr, Type, Variant}; |
22 | |
23 | use crate::attr::*; |
24 | use crate::utils::{extract_doc_comment, format_doc_comment, inner_type, is_simple_ty, Sp, Ty}; |
25 | |
26 | /// Default casing style for generated arguments. |
27 | pub const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab; |
28 | |
29 | /// Default casing style for environment variables |
30 | pub const DEFAULT_ENV_CASING: CasingStyle = CasingStyle::ScreamingSnake; |
31 | |
32 | #[derive (Clone)] |
33 | pub struct Item { |
34 | name: Name, |
35 | casing: Sp<CasingStyle>, |
36 | env_casing: Sp<CasingStyle>, |
37 | ty: Option<Type>, |
38 | doc_comment: Vec<Method>, |
39 | methods: Vec<Method>, |
40 | deprecations: Vec<Deprecation>, |
41 | value_parser: Option<ValueParser>, |
42 | action: Option<Action>, |
43 | verbatim_doc_comment: bool, |
44 | force_long_help: bool, |
45 | next_display_order: Option<Method>, |
46 | next_help_heading: Option<Method>, |
47 | is_enum: bool, |
48 | is_positional: bool, |
49 | skip_group: bool, |
50 | group_id: Name, |
51 | group_methods: Vec<Method>, |
52 | kind: Sp<Kind>, |
53 | } |
54 | |
55 | impl Item { |
56 | pub fn from_args_struct(input: &DeriveInput, name: Name) -> Result<Self, syn::Error> { |
57 | let ident = input.ident.clone(); |
58 | let span = input.ident.span(); |
59 | let attrs = &input.attrs; |
60 | let argument_casing = Sp::new(DEFAULT_CASING, span); |
61 | let env_casing = Sp::new(DEFAULT_ENV_CASING, span); |
62 | let kind = Sp::new(Kind::Command(Sp::new(Ty::Other, span)), span); |
63 | |
64 | let mut res = Self::new(name, ident, None, argument_casing, env_casing, kind); |
65 | let parsed_attrs = ClapAttr::parse_all(attrs)?; |
66 | res.infer_kind(&parsed_attrs)?; |
67 | res.push_attrs(&parsed_attrs)?; |
68 | res.push_doc_comment(attrs, "about" , Some("long_about" )); |
69 | |
70 | Ok(res) |
71 | } |
72 | |
73 | pub fn from_subcommand_enum(input: &DeriveInput, name: Name) -> Result<Self, syn::Error> { |
74 | let ident = input.ident.clone(); |
75 | let span = input.ident.span(); |
76 | let attrs = &input.attrs; |
77 | let argument_casing = Sp::new(DEFAULT_CASING, span); |
78 | let env_casing = Sp::new(DEFAULT_ENV_CASING, span); |
79 | let kind = Sp::new(Kind::Command(Sp::new(Ty::Other, span)), span); |
80 | |
81 | let mut res = Self::new(name, ident, None, argument_casing, env_casing, kind); |
82 | let parsed_attrs = ClapAttr::parse_all(attrs)?; |
83 | res.infer_kind(&parsed_attrs)?; |
84 | res.push_attrs(&parsed_attrs)?; |
85 | res.push_doc_comment(attrs, "about" , Some("long_about" )); |
86 | |
87 | Ok(res) |
88 | } |
89 | |
90 | pub fn from_value_enum(input: &DeriveInput, name: Name) -> Result<Self, syn::Error> { |
91 | let ident = input.ident.clone(); |
92 | let span = input.ident.span(); |
93 | let attrs = &input.attrs; |
94 | let argument_casing = Sp::new(DEFAULT_CASING, span); |
95 | let env_casing = Sp::new(DEFAULT_ENV_CASING, span); |
96 | let kind = Sp::new(Kind::Value, span); |
97 | |
98 | let mut res = Self::new(name, ident, None, argument_casing, env_casing, kind); |
99 | let parsed_attrs = ClapAttr::parse_all(attrs)?; |
100 | res.infer_kind(&parsed_attrs)?; |
101 | res.push_attrs(&parsed_attrs)?; |
102 | // Ignoring `push_doc_comment` as there is no top-level clap builder to add documentation |
103 | // to |
104 | |
105 | if res.has_explicit_methods() { |
106 | abort!( |
107 | res.methods[0].name.span(), |
108 | " {} doesn't exist for `ValueEnum` enums" , |
109 | res.methods[0].name |
110 | ); |
111 | } |
112 | |
113 | Ok(res) |
114 | } |
115 | |
116 | pub fn from_subcommand_variant( |
117 | variant: &Variant, |
118 | struct_casing: Sp<CasingStyle>, |
119 | env_casing: Sp<CasingStyle>, |
120 | ) -> Result<Self, syn::Error> { |
121 | let name = variant.ident.clone(); |
122 | let ident = variant.ident.clone(); |
123 | let span = variant.span(); |
124 | let ty = match variant.fields { |
125 | syn::Fields::Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { |
126 | Ty::from_syn_ty(&unnamed[0].ty) |
127 | } |
128 | syn::Fields::Named(_) | syn::Fields::Unnamed(..) | syn::Fields::Unit => { |
129 | Sp::new(Ty::Other, span) |
130 | } |
131 | }; |
132 | let kind = Sp::new(Kind::Command(ty), span); |
133 | let mut res = Self::new( |
134 | Name::Derived(name), |
135 | ident, |
136 | None, |
137 | struct_casing, |
138 | env_casing, |
139 | kind, |
140 | ); |
141 | let parsed_attrs = ClapAttr::parse_all(&variant.attrs)?; |
142 | res.infer_kind(&parsed_attrs)?; |
143 | res.push_attrs(&parsed_attrs)?; |
144 | if matches!(&*res.kind, Kind::Command(_) | Kind::Subcommand(_)) { |
145 | res.push_doc_comment(&variant.attrs, "about" , Some("long_about" )); |
146 | } |
147 | |
148 | match &*res.kind { |
149 | Kind::Flatten(_) => { |
150 | if res.has_explicit_methods() { |
151 | abort!( |
152 | res.kind.span(), |
153 | "methods are not allowed for flattened entry" |
154 | ); |
155 | } |
156 | } |
157 | |
158 | Kind::Subcommand(_) |
159 | | Kind::ExternalSubcommand |
160 | | Kind::FromGlobal(_) |
161 | | Kind::Skip(_, _) |
162 | | Kind::Command(_) |
163 | | Kind::Value |
164 | | Kind::Arg(_) => (), |
165 | } |
166 | |
167 | Ok(res) |
168 | } |
169 | |
170 | pub fn from_value_enum_variant( |
171 | variant: &Variant, |
172 | argument_casing: Sp<CasingStyle>, |
173 | env_casing: Sp<CasingStyle>, |
174 | ) -> Result<Self, syn::Error> { |
175 | let ident = variant.ident.clone(); |
176 | let span = variant.span(); |
177 | let kind = Sp::new(Kind::Value, span); |
178 | let mut res = Self::new( |
179 | Name::Derived(variant.ident.clone()), |
180 | ident, |
181 | None, |
182 | argument_casing, |
183 | env_casing, |
184 | kind, |
185 | ); |
186 | let parsed_attrs = ClapAttr::parse_all(&variant.attrs)?; |
187 | res.infer_kind(&parsed_attrs)?; |
188 | res.push_attrs(&parsed_attrs)?; |
189 | if matches!(&*res.kind, Kind::Value) { |
190 | res.push_doc_comment(&variant.attrs, "help" , None); |
191 | } |
192 | |
193 | Ok(res) |
194 | } |
195 | |
196 | pub fn from_args_field( |
197 | field: &Field, |
198 | struct_casing: Sp<CasingStyle>, |
199 | env_casing: Sp<CasingStyle>, |
200 | ) -> Result<Self, syn::Error> { |
201 | let name = field.ident.clone().unwrap(); |
202 | let ident = field.ident.clone().unwrap(); |
203 | let span = field.span(); |
204 | let ty = Ty::from_syn_ty(&field.ty); |
205 | let kind = Sp::new(Kind::Arg(ty), span); |
206 | let mut res = Self::new( |
207 | Name::Derived(name), |
208 | ident, |
209 | Some(field.ty.clone()), |
210 | struct_casing, |
211 | env_casing, |
212 | kind, |
213 | ); |
214 | let parsed_attrs = ClapAttr::parse_all(&field.attrs)?; |
215 | res.infer_kind(&parsed_attrs)?; |
216 | res.push_attrs(&parsed_attrs)?; |
217 | if matches!(&*res.kind, Kind::Arg(_)) { |
218 | res.push_doc_comment(&field.attrs, "help" , Some("long_help" )); |
219 | } |
220 | |
221 | match &*res.kind { |
222 | Kind::Flatten(_) => { |
223 | if res.has_explicit_methods() { |
224 | abort!( |
225 | res.kind.span(), |
226 | "methods are not allowed for flattened entry" |
227 | ); |
228 | } |
229 | } |
230 | |
231 | Kind::Subcommand(_) => { |
232 | if res.has_explicit_methods() { |
233 | abort!( |
234 | res.kind.span(), |
235 | "methods in attributes are not allowed for subcommand" |
236 | ); |
237 | } |
238 | } |
239 | Kind::Skip(_, _) |
240 | | Kind::FromGlobal(_) |
241 | | Kind::Arg(_) |
242 | | Kind::Command(_) |
243 | | Kind::Value |
244 | | Kind::ExternalSubcommand => {} |
245 | } |
246 | |
247 | Ok(res) |
248 | } |
249 | |
250 | fn new( |
251 | name: Name, |
252 | ident: Ident, |
253 | ty: Option<Type>, |
254 | casing: Sp<CasingStyle>, |
255 | env_casing: Sp<CasingStyle>, |
256 | kind: Sp<Kind>, |
257 | ) -> Self { |
258 | let group_id = Name::Derived(ident); |
259 | Self { |
260 | name, |
261 | ty, |
262 | casing, |
263 | env_casing, |
264 | doc_comment: vec![], |
265 | methods: vec![], |
266 | deprecations: vec![], |
267 | value_parser: None, |
268 | action: None, |
269 | verbatim_doc_comment: false, |
270 | force_long_help: false, |
271 | next_display_order: None, |
272 | next_help_heading: None, |
273 | is_enum: false, |
274 | is_positional: true, |
275 | skip_group: false, |
276 | group_id, |
277 | group_methods: vec![], |
278 | kind, |
279 | } |
280 | } |
281 | |
282 | fn push_method(&mut self, kind: AttrKind, name: Ident, arg: impl ToTokens) { |
283 | self.push_method_(kind, name, arg.to_token_stream()); |
284 | } |
285 | |
286 | fn push_method_(&mut self, kind: AttrKind, name: Ident, arg: TokenStream) { |
287 | if name == "id" { |
288 | match kind { |
289 | AttrKind::Command | AttrKind::Value => { |
290 | self.deprecations.push(Deprecation { |
291 | span: name.span(), |
292 | id: "id_is_only_for_arg" , |
293 | version: "4.0.0" , |
294 | description: format!( |
295 | "`#[ {}(id)] was allowed by mistake, instead use `#[ {}(name)]`" , |
296 | kind.as_str(), |
297 | kind.as_str() |
298 | ), |
299 | }); |
300 | self.name = Name::Assigned(arg); |
301 | } |
302 | AttrKind::Group => { |
303 | self.group_id = Name::Assigned(arg); |
304 | } |
305 | AttrKind::Arg | AttrKind::Clap | AttrKind::StructOpt => { |
306 | self.name = Name::Assigned(arg); |
307 | } |
308 | } |
309 | } else if name == "name" { |
310 | match kind { |
311 | AttrKind::Arg => { |
312 | self.deprecations.push(Deprecation { |
313 | span: name.span(), |
314 | id: "id_is_only_for_arg" , |
315 | version: "4.0.0" , |
316 | description: format!( |
317 | "`#[ {}(name)] was allowed by mistake, instead use `#[ {}(id)]` or `#[ {}(value_name)]`" , |
318 | kind.as_str(), |
319 | kind.as_str(), |
320 | kind.as_str() |
321 | ), |
322 | }); |
323 | self.name = Name::Assigned(arg); |
324 | } |
325 | AttrKind::Group => self.group_methods.push(Method::new(name, arg)), |
326 | AttrKind::Command | AttrKind::Value | AttrKind::Clap | AttrKind::StructOpt => { |
327 | self.name = Name::Assigned(arg); |
328 | } |
329 | } |
330 | } else if name == "value_parser" { |
331 | self.value_parser = Some(ValueParser::Explicit(Method::new(name, arg))); |
332 | } else if name == "action" { |
333 | self.action = Some(Action::Explicit(Method::new(name, arg))); |
334 | } else { |
335 | if name == "short" || name == "long" { |
336 | self.is_positional = false; |
337 | } |
338 | match kind { |
339 | AttrKind::Group => self.group_methods.push(Method::new(name, arg)), |
340 | _ => self.methods.push(Method::new(name, arg)), |
341 | }; |
342 | } |
343 | } |
344 | |
345 | fn infer_kind(&mut self, attrs: &[ClapAttr]) -> Result<(), syn::Error> { |
346 | for attr in attrs { |
347 | if let Some(AttrValue::Call(_)) = &attr.value { |
348 | continue; |
349 | } |
350 | |
351 | let actual_attr_kind = *attr.kind.get(); |
352 | let kind = match &attr.magic { |
353 | Some(MagicAttrName::FromGlobal) => { |
354 | if attr.value.is_some() { |
355 | let expr = attr.value_or_abort()?; |
356 | abort!(expr, "attribute ` {}` does not accept a value" , attr.name); |
357 | } |
358 | let ty = self |
359 | .kind() |
360 | .ty() |
361 | .cloned() |
362 | .unwrap_or_else(|| Sp::new(Ty::Other, self.kind.span())); |
363 | let kind = Sp::new(Kind::FromGlobal(ty), attr.name.clone().span()); |
364 | Some(kind) |
365 | } |
366 | Some(MagicAttrName::Subcommand) if attr.value.is_none() => { |
367 | if attr.value.is_some() { |
368 | let expr = attr.value_or_abort()?; |
369 | abort!(expr, "attribute ` {}` does not accept a value" , attr.name); |
370 | } |
371 | let ty = self |
372 | .kind() |
373 | .ty() |
374 | .cloned() |
375 | .unwrap_or_else(|| Sp::new(Ty::Other, self.kind.span())); |
376 | let kind = Sp::new(Kind::Subcommand(ty), attr.name.clone().span()); |
377 | Some(kind) |
378 | } |
379 | Some(MagicAttrName::ExternalSubcommand) if attr.value.is_none() => { |
380 | if attr.value.is_some() { |
381 | let expr = attr.value_or_abort()?; |
382 | abort!(expr, "attribute ` {}` does not accept a value" , attr.name); |
383 | } |
384 | let kind = Sp::new(Kind::ExternalSubcommand, attr.name.clone().span()); |
385 | Some(kind) |
386 | } |
387 | Some(MagicAttrName::Flatten) if attr.value.is_none() => { |
388 | if attr.value.is_some() { |
389 | let expr = attr.value_or_abort()?; |
390 | abort!(expr, "attribute ` {}` does not accept a value" , attr.name); |
391 | } |
392 | let ty = self |
393 | .kind() |
394 | .ty() |
395 | .cloned() |
396 | .unwrap_or_else(|| Sp::new(Ty::Other, self.kind.span())); |
397 | let kind = Sp::new(Kind::Flatten(ty), attr.name.clone().span()); |
398 | Some(kind) |
399 | } |
400 | Some(MagicAttrName::Skip) if actual_attr_kind != AttrKind::Group => { |
401 | let expr = attr.value.clone(); |
402 | let kind = Sp::new( |
403 | Kind::Skip(expr, self.kind.attr_kind()), |
404 | attr.name.clone().span(), |
405 | ); |
406 | Some(kind) |
407 | } |
408 | _ => None, |
409 | }; |
410 | |
411 | if let Some(kind) = kind { |
412 | self.set_kind(kind)?; |
413 | } |
414 | } |
415 | |
416 | Ok(()) |
417 | } |
418 | |
419 | fn push_attrs(&mut self, attrs: &[ClapAttr]) -> Result<(), syn::Error> { |
420 | for attr in attrs { |
421 | let actual_attr_kind = *attr.kind.get(); |
422 | let expected_attr_kind = self.kind.attr_kind(); |
423 | match (actual_attr_kind, expected_attr_kind) { |
424 | (AttrKind::Clap, _) | (AttrKind::StructOpt, _) => { |
425 | self.deprecations.push(Deprecation::attribute( |
426 | "4.0.0" , |
427 | actual_attr_kind, |
428 | expected_attr_kind, |
429 | attr.kind.span(), |
430 | )); |
431 | } |
432 | |
433 | (AttrKind::Group, AttrKind::Command) => {} |
434 | |
435 | _ if attr.kind != expected_attr_kind => { |
436 | abort!( |
437 | attr.kind.span(), |
438 | "Expected ` {}` attribute instead of ` {}`" , |
439 | expected_attr_kind.as_str(), |
440 | actual_attr_kind.as_str() |
441 | ); |
442 | } |
443 | |
444 | _ => {} |
445 | } |
446 | |
447 | if let Some(AttrValue::Call(tokens)) = &attr.value { |
448 | // Force raw mode with method call syntax |
449 | self.push_method(*attr.kind.get(), attr.name.clone(), quote!(#(#tokens),*)); |
450 | continue; |
451 | } |
452 | |
453 | match &attr.magic { |
454 | Some(MagicAttrName::Short) if attr.value.is_none() => { |
455 | assert_attr_kind(attr, &[AttrKind::Arg])?; |
456 | |
457 | self.push_method( |
458 | *attr.kind.get(), |
459 | attr.name.clone(), |
460 | self.name.clone().translate_char(*self.casing), |
461 | ); |
462 | } |
463 | |
464 | Some(MagicAttrName::Long) if attr.value.is_none() => { |
465 | assert_attr_kind(attr, &[AttrKind::Arg])?; |
466 | |
467 | self.push_method(*attr.kind.get(), attr.name.clone(), self.name.clone().translate(*self.casing)); |
468 | } |
469 | |
470 | Some(MagicAttrName::ValueParser) if attr.value.is_none() => { |
471 | assert_attr_kind(attr, &[AttrKind::Arg])?; |
472 | |
473 | self.deprecations.push(Deprecation { |
474 | span: attr.name.span(), |
475 | id: "bare_value_parser" , |
476 | version: "4.0.0" , |
477 | description: "`#[arg(value_parser)]` is now the default and is no longer needed`" .to_owned(), |
478 | }); |
479 | self.value_parser = Some(ValueParser::Implicit(attr.name.clone())); |
480 | } |
481 | |
482 | Some(MagicAttrName::Action) if attr.value.is_none() => { |
483 | assert_attr_kind(attr, &[AttrKind::Arg])?; |
484 | |
485 | self.deprecations.push(Deprecation { |
486 | span: attr.name.span(), |
487 | id: "bare_action" , |
488 | version: "4.0.0" , |
489 | description: "`#[arg(action)]` is now the default and is no longer needed`" .to_owned(), |
490 | }); |
491 | self.action = Some(Action::Implicit(attr.name.clone())); |
492 | } |
493 | |
494 | Some(MagicAttrName::Env) if attr.value.is_none() => { |
495 | assert_attr_kind(attr, &[AttrKind::Arg])?; |
496 | |
497 | self.push_method( |
498 | *attr.kind.get(), |
499 | attr.name.clone(), |
500 | self.name.clone().translate(*self.env_casing), |
501 | ); |
502 | } |
503 | |
504 | Some(MagicAttrName::ValueEnum) if attr.value.is_none() => { |
505 | assert_attr_kind(attr, &[AttrKind::Arg])?; |
506 | |
507 | self.is_enum = true |
508 | } |
509 | |
510 | Some(MagicAttrName::VerbatimDocComment) if attr.value.is_none() => { |
511 | self.verbatim_doc_comment = true |
512 | } |
513 | |
514 | Some(MagicAttrName::About) if attr.value.is_none() => { |
515 | assert_attr_kind(attr, &[AttrKind::Command])?; |
516 | |
517 | if let Some(method) = |
518 | Method::from_env(attr.name.clone(), "CARGO_PKG_DESCRIPTION" )? |
519 | { |
520 | self.methods.push(method); |
521 | } |
522 | } |
523 | |
524 | Some(MagicAttrName::LongAbout) if attr.value.is_none() => { |
525 | assert_attr_kind(attr, &[AttrKind::Command])?; |
526 | |
527 | self.force_long_help = true; |
528 | } |
529 | |
530 | Some(MagicAttrName::LongHelp) if attr.value.is_none() => { |
531 | assert_attr_kind(attr, &[AttrKind::Arg])?; |
532 | |
533 | self.force_long_help = true; |
534 | } |
535 | |
536 | Some(MagicAttrName::Author) if attr.value.is_none() => { |
537 | assert_attr_kind(attr, &[AttrKind::Command])?; |
538 | |
539 | if let Some(method) = Method::from_env(attr.name.clone(), "CARGO_PKG_AUTHORS" )? { |
540 | self.methods.push(method); |
541 | } |
542 | } |
543 | |
544 | Some(MagicAttrName::Version) if attr.value.is_none() => { |
545 | assert_attr_kind(attr, &[AttrKind::Command])?; |
546 | |
547 | if let Some(method) = Method::from_env(attr.name.clone(), "CARGO_PKG_VERSION" )? { |
548 | self.methods.push(method); |
549 | } |
550 | } |
551 | |
552 | Some(MagicAttrName::DefaultValueT) => { |
553 | assert_attr_kind(attr, &[AttrKind::Arg])?; |
554 | |
555 | let ty = if let Some(ty) = self.ty.as_ref() { |
556 | ty |
557 | } else { |
558 | abort!( |
559 | attr.name.clone(), |
560 | "#[arg(default_value_t)] (without an argument) can be used \ |
561 | only on field level \n\n= note: {note}\n\n" , |
562 | |
563 | note = "see \ |
564 | https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes" ) |
565 | }; |
566 | |
567 | let val = if let Some(expr) = &attr.value { |
568 | quote!(#expr) |
569 | } else { |
570 | quote!(<#ty as ::std::default::Default>::default()) |
571 | }; |
572 | |
573 | let val = if attrs |
574 | .iter() |
575 | .any(|a| a.magic == Some(MagicAttrName::ValueEnum)) |
576 | { |
577 | quote_spanned!(attr.name.clone().span()=> { |
578 | static DEFAULT_VALUE: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new(); |
579 | let s = DEFAULT_VALUE.get_or_init(|| { |
580 | let val: #ty = #val; |
581 | clap::ValueEnum::to_possible_value(&val).unwrap().get_name().to_owned() |
582 | }); |
583 | let s: &'static str = &*s; |
584 | s |
585 | }) |
586 | } else { |
587 | quote_spanned!(attr.name.clone().span()=> { |
588 | static DEFAULT_VALUE: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new(); |
589 | let s = DEFAULT_VALUE.get_or_init(|| { |
590 | let val: #ty = #val; |
591 | ::std::string::ToString::to_string(&val) |
592 | }); |
593 | let s: &'static str = &*s; |
594 | s |
595 | }) |
596 | }; |
597 | |
598 | let raw_ident = Ident::new("default_value" , attr.name.clone().span()); |
599 | self.methods.push(Method::new(raw_ident, val)); |
600 | } |
601 | |
602 | Some(MagicAttrName::DefaultValuesT) => { |
603 | assert_attr_kind(attr, &[AttrKind::Arg])?; |
604 | |
605 | let ty = if let Some(ty) = self.ty.as_ref() { |
606 | ty |
607 | } else { |
608 | abort!( |
609 | attr.name.clone(), |
610 | "#[arg(default_values_t)] (without an argument) can be used \ |
611 | only on field level \n\n= note: {note}\n\n" , |
612 | |
613 | note = "see \ |
614 | https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes" ) |
615 | }; |
616 | let expr = attr.value_or_abort()?; |
617 | |
618 | let container_type = Ty::from_syn_ty(ty); |
619 | if *container_type != Ty::Vec { |
620 | abort!( |
621 | attr.name.clone(), |
622 | "#[arg(default_values_t)] can be used only on Vec types \n\n= note: {note}\n\n" , |
623 | |
624 | note = "see \ |
625 | https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes" ) |
626 | } |
627 | let inner_type = inner_type(ty); |
628 | |
629 | // Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and |
630 | // `Vec<#inner_type>`. |
631 | let val = if attrs |
632 | .iter() |
633 | .any(|a| a.magic == Some(MagicAttrName::ValueEnum)) |
634 | { |
635 | quote_spanned!(attr.name.clone().span()=> { |
636 | { |
637 | fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=String> |
638 | where |
639 | T: ::std::borrow::Borrow<#inner_type> |
640 | { |
641 | iterable |
642 | .into_iter() |
643 | .map(|val| { |
644 | clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name().to_owned() |
645 | }) |
646 | } |
647 | |
648 | static DEFAULT_STRINGS: ::std::sync::OnceLock<Vec<String>> = ::std::sync::OnceLock::new(); |
649 | static DEFAULT_VALUES: ::std::sync::OnceLock<Vec<&str>> = ::std::sync::OnceLock::new(); |
650 | DEFAULT_VALUES.get_or_init(|| { |
651 | DEFAULT_STRINGS.get_or_init(|| iter_to_vals(#expr).collect()).iter().map(::std::string::String::as_str).collect() |
652 | }).iter().copied() |
653 | } |
654 | }) |
655 | } else { |
656 | quote_spanned!(attr.name.clone().span()=> { |
657 | { |
658 | fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=String> |
659 | where |
660 | T: ::std::borrow::Borrow<#inner_type> |
661 | { |
662 | iterable.into_iter().map(|val| val.borrow().to_string()) |
663 | } |
664 | |
665 | static DEFAULT_STRINGS: ::std::sync::OnceLock<Vec<String>> = ::std::sync::OnceLock::new(); |
666 | static DEFAULT_VALUES: ::std::sync::OnceLock<Vec<&str>> = ::std::sync::OnceLock::new(); |
667 | DEFAULT_VALUES.get_or_init(|| { |
668 | DEFAULT_STRINGS.get_or_init(|| iter_to_vals(#expr).collect()).iter().map(::std::string::String::as_str).collect() |
669 | }).iter().copied() |
670 | } |
671 | }) |
672 | }; |
673 | |
674 | self.methods.push(Method::new( |
675 | Ident::new("default_values" , attr.name.clone().span()), |
676 | val, |
677 | )); |
678 | } |
679 | |
680 | Some(MagicAttrName::DefaultValueOsT) => { |
681 | assert_attr_kind(attr, &[AttrKind::Arg])?; |
682 | |
683 | let ty = if let Some(ty) = self.ty.as_ref() { |
684 | ty |
685 | } else { |
686 | abort!( |
687 | attr.name.clone(), |
688 | "#[arg(default_value_os_t)] (without an argument) can be used \ |
689 | only on field level \n\n= note: {note}\n\n" , |
690 | |
691 | note = "see \ |
692 | https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes" ) |
693 | }; |
694 | |
695 | let val = if let Some(expr) = &attr.value { |
696 | quote!(#expr) |
697 | } else { |
698 | quote!(<#ty as ::std::default::Default>::default()) |
699 | }; |
700 | |
701 | let val = if attrs |
702 | .iter() |
703 | .any(|a| a.magic == Some(MagicAttrName::ValueEnum)) |
704 | { |
705 | quote_spanned!(attr.name.clone().span()=> { |
706 | static DEFAULT_VALUE: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new(); |
707 | let s = DEFAULT_VALUE.get_or_init(|| { |
708 | let val: #ty = #val; |
709 | clap::ValueEnum::to_possible_value(&val).unwrap().get_name().to_owned() |
710 | }); |
711 | let s: &'static str = &*s; |
712 | s |
713 | }) |
714 | } else { |
715 | quote_spanned!(attr.name.clone().span()=> { |
716 | static DEFAULT_VALUE: ::std::sync::OnceLock<::std::ffi::OsString> = ::std::sync::OnceLock::new(); |
717 | let s = DEFAULT_VALUE.get_or_init(|| { |
718 | let val: #ty = #val; |
719 | ::std::ffi::OsString::from(val) |
720 | }); |
721 | let s: &'static ::std::ffi::OsStr = &*s; |
722 | s |
723 | }) |
724 | }; |
725 | |
726 | let raw_ident = Ident::new("default_value" , attr.name.clone().span()); |
727 | self.methods.push(Method::new(raw_ident, val)); |
728 | } |
729 | |
730 | Some(MagicAttrName::DefaultValuesOsT) => { |
731 | assert_attr_kind(attr, &[AttrKind::Arg])?; |
732 | |
733 | let ty = if let Some(ty) = self.ty.as_ref() { |
734 | ty |
735 | } else { |
736 | abort!( |
737 | attr.name.clone(), |
738 | "#[arg(default_values_os_t)] (without an argument) can be used \ |
739 | only on field level \n\n= note: {note}\n\n" , |
740 | |
741 | note = "see \ |
742 | https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes" ) |
743 | }; |
744 | let expr = attr.value_or_abort()?; |
745 | |
746 | let container_type = Ty::from_syn_ty(ty); |
747 | if *container_type != Ty::Vec { |
748 | abort!( |
749 | attr.name.clone(), |
750 | "#[arg(default_values_os_t)] can be used only on Vec types \n\n= note: {note}\n\n" , |
751 | |
752 | note = "see \ |
753 | https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes" ) |
754 | } |
755 | let inner_type = inner_type(ty); |
756 | |
757 | // Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and |
758 | // `Vec<#inner_type>`. |
759 | let val = if attrs |
760 | .iter() |
761 | .any(|a| a.magic == Some(MagicAttrName::ValueEnum)) |
762 | { |
763 | quote_spanned!(attr.name.clone().span()=> { |
764 | { |
765 | fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=::std::ffi::OsString> |
766 | where |
767 | T: ::std::borrow::Borrow<#inner_type> |
768 | { |
769 | iterable |
770 | .into_iter() |
771 | .map(|val| { |
772 | clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name().to_owned().into() |
773 | }) |
774 | } |
775 | |
776 | static DEFAULT_STRINGS: ::std::sync::OnceLock<Vec<::std::ffi::OsString>> = ::std::sync::OnceLock::new(); |
777 | static DEFAULT_VALUES: ::std::sync::OnceLock<Vec<&::std::ffi::OsStr>> = ::std::sync::OnceLock::new(); |
778 | DEFAULT_VALUES.get_or_init(|| { |
779 | DEFAULT_STRINGS.get_or_init(|| iter_to_vals(#expr).collect()).iter().map(::std::ffi::OsString::as_os_str).collect() |
780 | }).iter().copied() |
781 | } |
782 | }) |
783 | } else { |
784 | quote_spanned!(attr.name.clone().span()=> { |
785 | { |
786 | fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=::std::ffi::OsString> |
787 | where |
788 | T: ::std::borrow::Borrow<#inner_type> |
789 | { |
790 | iterable.into_iter().map(|val| val.borrow().into()) |
791 | } |
792 | |
793 | static DEFAULT_STRINGS: ::std::sync::OnceLock<Vec<::std::ffi::OsString>> = ::std::sync::OnceLock::new(); |
794 | static DEFAULT_VALUES: ::std::sync::OnceLock<Vec<&::std::ffi::OsStr>> = ::std::sync::OnceLock::new(); |
795 | DEFAULT_VALUES.get_or_init(|| { |
796 | DEFAULT_STRINGS.get_or_init(|| iter_to_vals(#expr).collect()).iter().map(::std::ffi::OsString::as_os_str).collect() |
797 | }).iter().copied() |
798 | } |
799 | }) |
800 | }; |
801 | |
802 | self.methods.push(Method::new( |
803 | Ident::new("default_values" , attr.name.clone().span()), |
804 | val, |
805 | )); |
806 | } |
807 | |
808 | Some(MagicAttrName::NextDisplayOrder) => { |
809 | assert_attr_kind(attr, &[AttrKind::Command])?; |
810 | |
811 | let expr = attr.value_or_abort()?; |
812 | self.next_display_order = Some(Method::new(attr.name.clone(), quote!(#expr))); |
813 | } |
814 | |
815 | Some(MagicAttrName::NextHelpHeading) => { |
816 | assert_attr_kind(attr, &[AttrKind::Command])?; |
817 | |
818 | let expr = attr.value_or_abort()?; |
819 | self.next_help_heading = Some(Method::new(attr.name.clone(), quote!(#expr))); |
820 | } |
821 | |
822 | Some(MagicAttrName::RenameAll) => { |
823 | let lit = attr.lit_str_or_abort()?; |
824 | self.casing = CasingStyle::from_lit(lit)?; |
825 | } |
826 | |
827 | Some(MagicAttrName::RenameAllEnv) => { |
828 | assert_attr_kind(attr, &[AttrKind::Command, AttrKind::Arg])?; |
829 | |
830 | let lit = attr.lit_str_or_abort()?; |
831 | self.env_casing = CasingStyle::from_lit(lit)?; |
832 | } |
833 | |
834 | Some(MagicAttrName::Skip) if actual_attr_kind == AttrKind::Group => { |
835 | self.skip_group = true; |
836 | } |
837 | |
838 | None |
839 | // Magic only for the default, otherwise just forward to the builder |
840 | | Some(MagicAttrName::Short) |
841 | | Some(MagicAttrName::Long) |
842 | | Some(MagicAttrName::Env) |
843 | | Some(MagicAttrName::About) |
844 | | Some(MagicAttrName::LongAbout) |
845 | | Some(MagicAttrName::LongHelp) |
846 | | Some(MagicAttrName::Author) |
847 | | Some(MagicAttrName::Version) |
848 | => { |
849 | let expr = attr.value_or_abort()?; |
850 | self.push_method(*attr.kind.get(), attr.name.clone(), expr); |
851 | } |
852 | |
853 | // Magic only for the default, otherwise just forward to the builder |
854 | Some(MagicAttrName::ValueParser) | Some(MagicAttrName::Action) => { |
855 | let expr = attr.value_or_abort()?; |
856 | self.push_method(*attr.kind.get(), attr.name.clone(), expr); |
857 | } |
858 | |
859 | // Directives that never receive a value |
860 | Some(MagicAttrName::ValueEnum) |
861 | | Some(MagicAttrName::VerbatimDocComment) => { |
862 | let expr = attr.value_or_abort()?; |
863 | abort!(expr, "attribute ` {}` does not accept a value" , attr.name); |
864 | } |
865 | |
866 | // Kinds |
867 | Some(MagicAttrName::FromGlobal) |
868 | | Some(MagicAttrName::Subcommand) |
869 | | Some(MagicAttrName::ExternalSubcommand) |
870 | | Some(MagicAttrName::Flatten) |
871 | | Some(MagicAttrName::Skip) => { |
872 | } |
873 | } |
874 | } |
875 | |
876 | if self.has_explicit_methods() { |
877 | if let Kind::Skip(_, attr) = &*self.kind { |
878 | abort!( |
879 | self.methods[0].name.span(), |
880 | "` {}` cannot be used with `#[ {}(skip)]" , |
881 | self.methods[0].name, |
882 | attr.as_str(), |
883 | ); |
884 | } |
885 | if let Kind::FromGlobal(_) = &*self.kind { |
886 | abort!( |
887 | self.methods[0].name.span(), |
888 | "` {}` cannot be used with `#[arg(from_global)]" , |
889 | self.methods[0].name, |
890 | ); |
891 | } |
892 | } |
893 | |
894 | Ok(()) |
895 | } |
896 | |
897 | fn push_doc_comment(&mut self, attrs: &[Attribute], short_name: &str, long_name: Option<&str>) { |
898 | let lines = extract_doc_comment(attrs); |
899 | |
900 | if !lines.is_empty() { |
901 | let (short_help, long_help) = |
902 | format_doc_comment(&lines, !self.verbatim_doc_comment, self.force_long_help); |
903 | let short_name = format_ident!(" {short_name}" ); |
904 | let short = Method::new( |
905 | short_name, |
906 | short_help |
907 | .map(|h| quote!(#h)) |
908 | .unwrap_or_else(|| quote!(None)), |
909 | ); |
910 | self.doc_comment.push(short); |
911 | if let Some(long_name) = long_name { |
912 | let long_name = format_ident!(" {long_name}" ); |
913 | let long = Method::new( |
914 | long_name, |
915 | long_help |
916 | .map(|h| quote!(#h)) |
917 | .unwrap_or_else(|| quote!(None)), |
918 | ); |
919 | self.doc_comment.push(long); |
920 | } |
921 | } |
922 | } |
923 | |
924 | fn set_kind(&mut self, kind: Sp<Kind>) -> Result<(), syn::Error> { |
925 | match (self.kind.get(), kind.get()) { |
926 | (Kind::Arg(_), Kind::FromGlobal(_)) |
927 | | (Kind::Arg(_), Kind::Subcommand(_)) |
928 | | (Kind::Arg(_), Kind::Flatten(_)) |
929 | | (Kind::Arg(_), Kind::Skip(_, _)) |
930 | | (Kind::Command(_), Kind::Subcommand(_)) |
931 | | (Kind::Command(_), Kind::Flatten(_)) |
932 | | (Kind::Command(_), Kind::Skip(_, _)) |
933 | | (Kind::Command(_), Kind::ExternalSubcommand) |
934 | | (Kind::Value, Kind::Skip(_, _)) => { |
935 | self.kind = kind; |
936 | } |
937 | |
938 | (_, _) => { |
939 | let old = self.kind.name(); |
940 | let new = kind.name(); |
941 | abort!(kind.span(), "` {new}` cannot be used with ` {old}`" ); |
942 | } |
943 | } |
944 | Ok(()) |
945 | } |
946 | |
947 | pub fn find_default_method(&self) -> Option<&Method> { |
948 | self.methods |
949 | .iter() |
950 | .find(|m| m.name == "default_value" || m.name == "default_value_os" ) |
951 | } |
952 | |
953 | /// generate methods from attributes on top of struct or enum |
954 | pub fn initial_top_level_methods(&self) -> TokenStream { |
955 | let next_display_order = self.next_display_order.as_ref().into_iter(); |
956 | let next_help_heading = self.next_help_heading.as_ref().into_iter(); |
957 | quote!( |
958 | #(#next_display_order)* |
959 | #(#next_help_heading)* |
960 | ) |
961 | } |
962 | |
963 | pub fn final_top_level_methods(&self) -> TokenStream { |
964 | let methods = &self.methods; |
965 | let doc_comment = &self.doc_comment; |
966 | |
967 | quote!( #(#doc_comment)* #(#methods)*) |
968 | } |
969 | |
970 | /// generate methods on top of a field |
971 | pub fn field_methods(&self) -> proc_macro2::TokenStream { |
972 | let methods = &self.methods; |
973 | let doc_comment = &self.doc_comment; |
974 | quote!( #(#doc_comment)* #(#methods)* ) |
975 | } |
976 | |
977 | pub fn group_id(&self) -> TokenStream { |
978 | self.group_id.clone().raw() |
979 | } |
980 | |
981 | pub fn group_methods(&self) -> TokenStream { |
982 | let group_methods = &self.group_methods; |
983 | quote!( #(#group_methods)* ) |
984 | } |
985 | |
986 | pub fn deprecations(&self) -> proc_macro2::TokenStream { |
987 | let deprecations = &self.deprecations; |
988 | quote!( #(#deprecations)* ) |
989 | } |
990 | |
991 | pub fn next_display_order(&self) -> TokenStream { |
992 | let next_display_order = self.next_display_order.as_ref().into_iter(); |
993 | quote!( #(#next_display_order)* ) |
994 | } |
995 | |
996 | pub fn next_help_heading(&self) -> TokenStream { |
997 | let next_help_heading = self.next_help_heading.as_ref().into_iter(); |
998 | quote!( #(#next_help_heading)* ) |
999 | } |
1000 | |
1001 | pub fn id(&self) -> TokenStream { |
1002 | self.name.clone().raw() |
1003 | } |
1004 | |
1005 | pub fn cased_name(&self) -> TokenStream { |
1006 | self.name.clone().translate(*self.casing) |
1007 | } |
1008 | |
1009 | pub fn value_name(&self) -> TokenStream { |
1010 | self.name.clone().translate(CasingStyle::ScreamingSnake) |
1011 | } |
1012 | |
1013 | pub fn value_parser(&self, field_type: &Type) -> Method { |
1014 | self.value_parser |
1015 | .clone() |
1016 | .map(|p| { |
1017 | let inner_type = inner_type(field_type); |
1018 | p.resolve(inner_type) |
1019 | }) |
1020 | .unwrap_or_else(|| { |
1021 | let inner_type = inner_type(field_type); |
1022 | if let Some(action) = self.action.as_ref() { |
1023 | let span = action.span(); |
1024 | default_value_parser(inner_type, span) |
1025 | } else { |
1026 | let span = self |
1027 | .action |
1028 | .as_ref() |
1029 | .map(|a| a.span()) |
1030 | .unwrap_or_else(|| self.kind.span()); |
1031 | default_value_parser(inner_type, span) |
1032 | } |
1033 | }) |
1034 | } |
1035 | |
1036 | pub fn action(&self, field_type: &Type) -> Method { |
1037 | self.action |
1038 | .clone() |
1039 | .map(|p| p.resolve(field_type)) |
1040 | .unwrap_or_else(|| { |
1041 | if let Some(value_parser) = self.value_parser.as_ref() { |
1042 | let span = value_parser.span(); |
1043 | default_action(field_type, span) |
1044 | } else { |
1045 | let span = self |
1046 | .value_parser |
1047 | .as_ref() |
1048 | .map(|a| a.span()) |
1049 | .unwrap_or_else(|| self.kind.span()); |
1050 | default_action(field_type, span) |
1051 | } |
1052 | }) |
1053 | } |
1054 | |
1055 | pub fn kind(&self) -> Sp<Kind> { |
1056 | self.kind.clone() |
1057 | } |
1058 | |
1059 | pub fn is_positional(&self) -> bool { |
1060 | self.is_positional |
1061 | } |
1062 | |
1063 | pub fn casing(&self) -> Sp<CasingStyle> { |
1064 | self.casing |
1065 | } |
1066 | |
1067 | pub fn env_casing(&self) -> Sp<CasingStyle> { |
1068 | self.env_casing |
1069 | } |
1070 | |
1071 | pub fn has_explicit_methods(&self) -> bool { |
1072 | self.methods |
1073 | .iter() |
1074 | .any(|m| m.name != "help" && m.name != "long_help" ) |
1075 | } |
1076 | |
1077 | pub fn skip_group(&self) -> bool { |
1078 | self.skip_group |
1079 | } |
1080 | } |
1081 | |
1082 | #[derive (Clone)] |
1083 | enum ValueParser { |
1084 | Explicit(Method), |
1085 | Implicit(Ident), |
1086 | } |
1087 | |
1088 | impl ValueParser { |
1089 | fn resolve(self, _inner_type: &Type) -> Method { |
1090 | match self { |
1091 | Self::Explicit(method: Method) => method, |
1092 | Self::Implicit(ident: Ident) => default_value_parser(inner_type:_inner_type, ident.span()), |
1093 | } |
1094 | } |
1095 | |
1096 | fn span(&self) -> Span { |
1097 | match self { |
1098 | Self::Explicit(method: &Method) => method.name.span(), |
1099 | Self::Implicit(ident: &Ident) => ident.span(), |
1100 | } |
1101 | } |
1102 | } |
1103 | |
1104 | fn default_value_parser(inner_type: &Type, span: Span) -> Method { |
1105 | let func: Ident = Ident::new(string:"value_parser" , span); |
1106 | Method::new( |
1107 | name:func, |
1108 | args:quote_spanned! { span=> |
1109 | clap::value_parser!(#inner_type) |
1110 | }, |
1111 | ) |
1112 | } |
1113 | |
1114 | #[derive (Clone)] |
1115 | pub enum Action { |
1116 | Explicit(Method), |
1117 | Implicit(Ident), |
1118 | } |
1119 | |
1120 | impl Action { |
1121 | pub fn resolve(self, _field_type: &Type) -> Method { |
1122 | match self { |
1123 | Self::Explicit(method: Method) => method, |
1124 | Self::Implicit(ident: Ident) => default_action(field_type:_field_type, ident.span()), |
1125 | } |
1126 | } |
1127 | |
1128 | pub fn span(&self) -> Span { |
1129 | match self { |
1130 | Self::Explicit(method: &Method) => method.name.span(), |
1131 | Self::Implicit(ident: &Ident) => ident.span(), |
1132 | } |
1133 | } |
1134 | } |
1135 | |
1136 | fn default_action(field_type: &Type, span: Span) -> Method { |
1137 | let ty = Ty::from_syn_ty(field_type); |
1138 | let args = match *ty { |
1139 | Ty::Vec | Ty::OptionVec | Ty::VecVec | Ty::OptionVecVec => { |
1140 | quote_spanned! { span=> |
1141 | clap::ArgAction::Append |
1142 | } |
1143 | } |
1144 | Ty::Option | Ty::OptionOption => { |
1145 | quote_spanned! { span=> |
1146 | clap::ArgAction::Set |
1147 | } |
1148 | } |
1149 | _ => { |
1150 | if is_simple_ty(field_type, "bool" ) { |
1151 | quote_spanned! { span=> |
1152 | clap::ArgAction::SetTrue |
1153 | } |
1154 | } else { |
1155 | quote_spanned! { span=> |
1156 | clap::ArgAction::Set |
1157 | } |
1158 | } |
1159 | } |
1160 | }; |
1161 | |
1162 | let func = Ident::new("action" , span); |
1163 | Method::new(func, args) |
1164 | } |
1165 | |
1166 | #[allow (clippy::large_enum_variant)] |
1167 | #[derive (Clone)] |
1168 | pub enum Kind { |
1169 | Arg(Sp<Ty>), |
1170 | Command(Sp<Ty>), |
1171 | Value, |
1172 | FromGlobal(Sp<Ty>), |
1173 | Subcommand(Sp<Ty>), |
1174 | Flatten(Sp<Ty>), |
1175 | Skip(Option<AttrValue>, AttrKind), |
1176 | ExternalSubcommand, |
1177 | } |
1178 | |
1179 | impl Kind { |
1180 | pub fn name(&self) -> &'static str { |
1181 | match self { |
1182 | Self::Arg(_) => "arg" , |
1183 | Self::Command(_) => "command" , |
1184 | Self::Value => "value" , |
1185 | Self::FromGlobal(_) => "from_global" , |
1186 | Self::Subcommand(_) => "subcommand" , |
1187 | Self::Flatten(_) => "flatten" , |
1188 | Self::Skip(_, _) => "skip" , |
1189 | Self::ExternalSubcommand => "external_subcommand" , |
1190 | } |
1191 | } |
1192 | |
1193 | pub fn attr_kind(&self) -> AttrKind { |
1194 | match self { |
1195 | Self::Arg(_) => AttrKind::Arg, |
1196 | Self::Command(_) => AttrKind::Command, |
1197 | Self::Value => AttrKind::Value, |
1198 | Self::FromGlobal(_) => AttrKind::Arg, |
1199 | Self::Subcommand(_) => AttrKind::Command, |
1200 | Self::Flatten(_) => AttrKind::Command, |
1201 | Self::Skip(_, kind) => *kind, |
1202 | Self::ExternalSubcommand => AttrKind::Command, |
1203 | } |
1204 | } |
1205 | |
1206 | pub fn ty(&self) -> Option<&Sp<Ty>> { |
1207 | match self { |
1208 | Self::Arg(ty) |
1209 | | Self::Command(ty) |
1210 | | Self::Flatten(ty) |
1211 | | Self::FromGlobal(ty) |
1212 | | Self::Subcommand(ty) => Some(ty), |
1213 | Self::Value | Self::Skip(_, _) | Self::ExternalSubcommand => None, |
1214 | } |
1215 | } |
1216 | } |
1217 | |
1218 | #[derive (Clone)] |
1219 | pub struct Method { |
1220 | name: Ident, |
1221 | args: TokenStream, |
1222 | } |
1223 | |
1224 | impl Method { |
1225 | pub fn new(name: Ident, args: TokenStream) -> Self { |
1226 | Method { name, args } |
1227 | } |
1228 | |
1229 | fn from_env(ident: Ident, env_var: &str) -> Result<Option<Self>, syn::Error> { |
1230 | let mut lit = match env::var(env_var) { |
1231 | Ok(val) => { |
1232 | if val.is_empty() { |
1233 | return Ok(None); |
1234 | } |
1235 | LitStr::new(&val, ident.span()) |
1236 | } |
1237 | Err(_) => { |
1238 | abort!( |
1239 | ident, |
1240 | "cannot derive ` {}` from Cargo.toml \n\n= note: {note}\n\n= help: {help}\n\n" , |
1241 | ident, |
1242 | note = format_args!("` {env_var}` environment variable is not set" ), |
1243 | help = format_args!("use ` {ident} = \"... \"` to set {ident} manually" ) |
1244 | ); |
1245 | } |
1246 | }; |
1247 | |
1248 | if ident == "author" { |
1249 | let edited = process_author_str(&lit.value()); |
1250 | lit = LitStr::new(&edited, lit.span()); |
1251 | } |
1252 | |
1253 | Ok(Some(Method::new(ident, quote!(#lit)))) |
1254 | } |
1255 | |
1256 | pub(crate) fn args(&self) -> &TokenStream { |
1257 | &self.args |
1258 | } |
1259 | } |
1260 | |
1261 | impl ToTokens for Method { |
1262 | fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) { |
1263 | let Method { ref name: &Ident, ref args: &TokenStream } = self; |
1264 | |
1265 | let tokens: TokenStream = quote!( .#name(#args) ); |
1266 | |
1267 | tokens.to_tokens(ts); |
1268 | } |
1269 | } |
1270 | |
1271 | #[derive (Clone)] |
1272 | pub struct Deprecation { |
1273 | pub span: Span, |
1274 | pub id: &'static str, |
1275 | pub version: &'static str, |
1276 | pub description: String, |
1277 | } |
1278 | |
1279 | impl Deprecation { |
1280 | fn attribute(version: &'static str, old: AttrKind, new: AttrKind, span: Span) -> Self { |
1281 | Self { |
1282 | span, |
1283 | id: "old_attribute" , |
1284 | version, |
1285 | description: format!( |
1286 | "Attribute `#[ {}(...)]` has been deprecated in favor of `#[ {}(...)]`" , |
1287 | old.as_str(), |
1288 | new.as_str() |
1289 | ), |
1290 | } |
1291 | } |
1292 | } |
1293 | |
1294 | impl ToTokens for Deprecation { |
1295 | fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) { |
1296 | let tokens: TokenStream = if cfg!(feature = "deprecated" ) { |
1297 | let Deprecation { |
1298 | span: &Span, |
1299 | id: &&str, |
1300 | version: &&str, |
1301 | description: &String, |
1302 | } = self; |
1303 | let span: Span = *span; |
1304 | let id: Ident = Ident::new(string:id, span); |
1305 | |
1306 | quote_spanned!(span=> { |
1307 | #[deprecated(since = #version, note = #description)] |
1308 | fn #id() {} |
1309 | #id(); |
1310 | }) |
1311 | } else { |
1312 | quote!() |
1313 | }; |
1314 | |
1315 | tokens.to_tokens(ts); |
1316 | } |
1317 | } |
1318 | |
1319 | fn assert_attr_kind(attr: &ClapAttr, possible_kind: &[AttrKind]) -> Result<(), syn::Error> { |
1320 | if *attr.kind.get() == AttrKind::Clap || *attr.kind.get() == AttrKind::StructOpt { |
1321 | // deprecated |
1322 | } else if !possible_kind.contains(attr.kind.get()) { |
1323 | let options: Vec = possible_kindimpl Iterator |
1324 | .iter() |
1325 | .map(|k: &AttrKind| format!("`#[ {}( {})]`" , k.as_str(), attr.name)) |
1326 | .collect::<Vec<_>>(); |
1327 | abort!( |
1328 | attr.name, |
1329 | "Unknown `#[ {}( {})]` attribute ( {} exists)" , |
1330 | attr.kind.as_str(), |
1331 | attr.name, |
1332 | options.join(", " ) |
1333 | ); |
1334 | } |
1335 | Ok(()) |
1336 | } |
1337 | |
1338 | /// replace all `:` with `, ` when not inside the `<>` |
1339 | /// |
1340 | /// `"author1:author2:author3" => "author1, author2, author3"` |
1341 | /// `"author1 <http://website1.com>:author2" => "author1 <http://website1.com>, author2" |
1342 | fn process_author_str(author: &str) -> String { |
1343 | let mut res: String = String::with_capacity(author.len()); |
1344 | let mut inside_angle_braces: usize = 0usize; |
1345 | |
1346 | for ch: char in author.chars() { |
1347 | if inside_angle_braces > 0 && ch == '>' { |
1348 | inside_angle_braces -= 1; |
1349 | res.push(ch); |
1350 | } else if ch == '<' { |
1351 | inside_angle_braces += 1; |
1352 | res.push(ch); |
1353 | } else if inside_angle_braces == 0 && ch == ':' { |
1354 | res.push_str(string:", " ); |
1355 | } else { |
1356 | res.push(ch); |
1357 | } |
1358 | } |
1359 | |
1360 | res |
1361 | } |
1362 | |
1363 | /// Defines the casing for the attributes long representation. |
1364 | #[derive (Copy, Clone, Debug, PartialEq, Eq)] |
1365 | pub enum CasingStyle { |
1366 | /// Indicate word boundaries with uppercase letter, excluding the first word. |
1367 | Camel, |
1368 | /// Keep all letters lowercase and indicate word boundaries with hyphens. |
1369 | Kebab, |
1370 | /// Indicate word boundaries with uppercase letter, including the first word. |
1371 | Pascal, |
1372 | /// Keep all letters uppercase and indicate word boundaries with underscores. |
1373 | ScreamingSnake, |
1374 | /// Keep all letters lowercase and indicate word boundaries with underscores. |
1375 | Snake, |
1376 | /// Keep all letters lowercase and remove word boundaries. |
1377 | Lower, |
1378 | /// Keep all letters uppercase and remove word boundaries. |
1379 | Upper, |
1380 | /// Use the original attribute name defined in the code. |
1381 | Verbatim, |
1382 | } |
1383 | |
1384 | impl CasingStyle { |
1385 | fn from_lit(name: &LitStr) -> Result<Sp<Self>, syn::Error> { |
1386 | use self::CasingStyle::*; |
1387 | |
1388 | let normalized: String = name.value().to_upper_camel_case().to_lowercase(); |
1389 | let cs: impl Fn(CasingStyle) -> Sp<…> = |kind: CasingStyle| Sp::new(val:kind, name.span()); |
1390 | |
1391 | let s: Sp = match normalized.as_ref() { |
1392 | "camel" | "camelcase" => cs(kind:Camel), |
1393 | "kebab" | "kebabcase" => cs(kind:Kebab), |
1394 | "pascal" | "pascalcase" => cs(kind:Pascal), |
1395 | "screamingsnake" | "screamingsnakecase" => cs(kind:ScreamingSnake), |
1396 | "snake" | "snakecase" => cs(kind:Snake), |
1397 | "lower" | "lowercase" => cs(kind:Lower), |
1398 | "upper" | "uppercase" => cs(kind:Upper), |
1399 | "verbatim" | "verbatimcase" => cs(kind:Verbatim), |
1400 | s: &str => abort!(name, "unsupported casing: ` {s}`" ), |
1401 | }; |
1402 | Ok(s) |
1403 | } |
1404 | } |
1405 | |
1406 | #[derive (Clone)] |
1407 | pub enum Name { |
1408 | Derived(Ident), |
1409 | Assigned(TokenStream), |
1410 | } |
1411 | |
1412 | impl Name { |
1413 | pub fn raw(self) -> TokenStream { |
1414 | match self { |
1415 | Name::Assigned(tokens) => tokens, |
1416 | Name::Derived(ident) => { |
1417 | let s = ident.unraw().to_string(); |
1418 | quote_spanned!(ident.span()=> #s) |
1419 | } |
1420 | } |
1421 | } |
1422 | |
1423 | pub fn translate(self, style: CasingStyle) -> TokenStream { |
1424 | use CasingStyle::*; |
1425 | |
1426 | match self { |
1427 | Name::Assigned(tokens) => tokens, |
1428 | Name::Derived(ident) => { |
1429 | let s = ident.unraw().to_string(); |
1430 | let s = match style { |
1431 | Pascal => s.to_upper_camel_case(), |
1432 | Kebab => s.to_kebab_case(), |
1433 | Camel => s.to_lower_camel_case(), |
1434 | ScreamingSnake => s.to_shouty_snake_case(), |
1435 | Snake => s.to_snake_case(), |
1436 | Lower => s.to_snake_case().replace('_' , "" ), |
1437 | Upper => s.to_shouty_snake_case().replace('_' , "" ), |
1438 | Verbatim => s, |
1439 | }; |
1440 | quote_spanned!(ident.span()=> #s) |
1441 | } |
1442 | } |
1443 | } |
1444 | |
1445 | pub fn translate_char(self, style: CasingStyle) -> TokenStream { |
1446 | use CasingStyle::*; |
1447 | |
1448 | match self { |
1449 | Name::Assigned(tokens) => quote!( (#tokens).chars().next().unwrap() ), |
1450 | Name::Derived(ident) => { |
1451 | let s = ident.unraw().to_string(); |
1452 | let s = match style { |
1453 | Pascal => s.to_upper_camel_case(), |
1454 | Kebab => s.to_kebab_case(), |
1455 | Camel => s.to_lower_camel_case(), |
1456 | ScreamingSnake => s.to_shouty_snake_case(), |
1457 | Snake => s.to_snake_case(), |
1458 | Lower => s.to_snake_case(), |
1459 | Upper => s.to_shouty_snake_case(), |
1460 | Verbatim => s, |
1461 | }; |
1462 | |
1463 | let s = s.chars().next().unwrap(); |
1464 | quote_spanned!(ident.span()=> #s) |
1465 | } |
1466 | } |
1467 | } |
1468 | } |
1469 | |