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 crate::{
16     parse::*,
17     utils::{process_doc_comment, Sp, Ty},
18 };
19 
20 use std::env;
21 
22 use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
23 use proc_macro2::{self, Span, TokenStream};
24 use proc_macro_error::abort;
25 use quote::{quote, quote_spanned, ToTokens};
26 use syn::{
27     self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Field, Ident, LitStr, MetaNameValue,
28     Type, Variant,
29 };
30 
31 /// Default casing style for generated arguments.
32 pub const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab;
33 
34 /// Default casing style for environment variables
35 pub const DEFAULT_ENV_CASING: CasingStyle = CasingStyle::ScreamingSnake;
36 
37 #[derive(Clone)]
38 pub struct Attrs {
39     name: Name,
40     casing: Sp<CasingStyle>,
41     env_casing: Sp<CasingStyle>,
42     ty: Option<Type>,
43     doc_comment: Vec<Method>,
44     methods: Vec<Method>,
45     parser: Sp<Parser>,
46     author: Option<Method>,
47     version: Option<Method>,
48     verbatim_doc_comment: Option<Ident>,
49     help_heading: Option<Method>,
50     is_enum: bool,
51     has_custom_parser: bool,
52     kind: Sp<Kind>,
53 }
54 
55 impl Attrs {
from_struct( span: Span, attrs: &[Attribute], name: Name, argument_casing: Sp<CasingStyle>, env_casing: Sp<CasingStyle>, ) -> Self56     pub fn from_struct(
57         span: Span,
58         attrs: &[Attribute],
59         name: Name,
60         argument_casing: Sp<CasingStyle>,
61         env_casing: Sp<CasingStyle>,
62     ) -> Self {
63         let mut res = Self::new(span, name, None, argument_casing, env_casing);
64         res.push_attrs(attrs);
65         res.push_doc_comment(attrs, "about");
66 
67         if res.has_custom_parser {
68             abort!(
69                 res.parser.span(),
70                 "`parse` attribute is only allowed on fields"
71             );
72         }
73         match &*res.kind {
74             Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"),
75             Kind::Skip(_) => abort!(res.kind.span(), "skip is only allowed on fields"),
76             Kind::Arg(_) => res,
77             Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"),
78             Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"),
79             Kind::ExternalSubcommand => abort!(
80                 res.kind.span(),
81                 "external_subcommand is only allowed on fields"
82             ),
83         }
84     }
85 
from_variant( variant: &Variant, struct_casing: Sp<CasingStyle>, env_casing: Sp<CasingStyle>, ) -> Self86     pub fn from_variant(
87         variant: &Variant,
88         struct_casing: Sp<CasingStyle>,
89         env_casing: Sp<CasingStyle>,
90     ) -> Self {
91         let name = variant.ident.clone();
92         let mut res = Self::new(
93             variant.span(),
94             Name::Derived(name),
95             None,
96             struct_casing,
97             env_casing,
98         );
99         res.push_attrs(&variant.attrs);
100         res.push_doc_comment(&variant.attrs, "about");
101 
102         match &*res.kind {
103             Kind::Flatten => {
104                 if res.has_custom_parser {
105                     abort!(
106                         res.parser.span(),
107                         "parse attribute is not allowed for flattened entry"
108                     );
109                 }
110                 if res.has_explicit_methods() {
111                     abort!(
112                         res.kind.span(),
113                         "methods are not allowed for flattened entry"
114                     );
115                 }
116 
117                 // ignore doc comments
118                 res.doc_comment = vec![];
119             }
120 
121             Kind::ExternalSubcommand => (),
122 
123             Kind::Subcommand(_) => {
124                 if res.has_custom_parser {
125                     abort!(
126                         res.parser.span(),
127                         "parse attribute is not allowed for subcommand"
128                     );
129                 }
130                 if res.has_explicit_methods() {
131                     abort!(
132                         res.kind.span(),
133                         "methods in attributes are not allowed for subcommand"
134                     );
135                 }
136                 use syn::Fields::*;
137                 use syn::FieldsUnnamed;
138 
139                 let field_ty = match variant.fields {
140                     Named(_) => {
141                         abort!(variant.span(), "structs are not allowed for subcommand");
142                     }
143                     Unit => abort!(variant.span(), "unit-type is not allowed for subcommand"),
144                     Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
145                         &unnamed[0].ty
146                     }
147                     Unnamed(..) => {
148                         abort!(
149                             variant,
150                             "non single-typed tuple is not allowed for subcommand"
151                         )
152                     }
153                 };
154                 let ty = Ty::from_syn_ty(field_ty);
155                 match *ty {
156                     Ty::OptionOption => {
157                         abort!(
158                             field_ty,
159                             "Option<Option<T>> type is not allowed for subcommand"
160                         );
161                     }
162                     Ty::OptionVec => {
163                         abort!(
164                             field_ty,
165                             "Option<Vec<T>> type is not allowed for subcommand"
166                         );
167                     }
168                     _ => (),
169                 }
170 
171                 res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span());
172             }
173             Kind::Skip(_) => (),
174             Kind::FromGlobal(_) => {
175                 abort!(res.kind.span(), "from_global is not supported on variants");
176             }
177             Kind::Arg(_) => (),
178         }
179 
180         res
181     }
182 
from_arg_enum_variant( variant: &Variant, argument_casing: Sp<CasingStyle>, env_casing: Sp<CasingStyle>, ) -> Self183     pub fn from_arg_enum_variant(
184         variant: &Variant,
185         argument_casing: Sp<CasingStyle>,
186         env_casing: Sp<CasingStyle>,
187     ) -> Self {
188         let mut res = Self::new(
189             variant.span(),
190             Name::Derived(variant.ident.clone()),
191             None,
192             argument_casing,
193             env_casing,
194         );
195         res.push_attrs(&variant.attrs);
196         res.push_doc_comment(&variant.attrs, "help");
197 
198         if res.has_custom_parser {
199             abort!(
200                 res.parser.span(),
201                 "`parse` attribute is only allowed on fields"
202             );
203         }
204         match &*res.kind {
205             Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"),
206             Kind::Skip(_) => res,
207             Kind::Arg(_) => res,
208             Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"),
209             Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"),
210             Kind::ExternalSubcommand => abort!(
211                 res.kind.span(),
212                 "external_subcommand is only allowed on fields"
213             ),
214         }
215     }
216 
from_field( field: &Field, struct_casing: Sp<CasingStyle>, env_casing: Sp<CasingStyle>, ) -> Self217     pub fn from_field(
218         field: &Field,
219         struct_casing: Sp<CasingStyle>,
220         env_casing: Sp<CasingStyle>,
221     ) -> Self {
222         let name = field.ident.clone().unwrap();
223         let mut res = Self::new(
224             field.span(),
225             Name::Derived(name),
226             Some(field.ty.clone()),
227             struct_casing,
228             env_casing,
229         );
230         res.push_attrs(&field.attrs);
231         res.push_doc_comment(&field.attrs, "help");
232 
233         match &*res.kind {
234             Kind::Flatten => {
235                 if res.has_custom_parser {
236                     abort!(
237                         res.parser.span(),
238                         "parse attribute is not allowed for flattened entry"
239                     );
240                 }
241                 if res.has_explicit_methods() {
242                     abort!(
243                         res.kind.span(),
244                         "methods are not allowed for flattened entry"
245                     );
246                 }
247 
248                 // ignore doc comments
249                 res.doc_comment = vec![];
250             }
251 
252             Kind::ExternalSubcommand => {
253                 abort! { res.kind.span(),
254                     "`external_subcommand` can be used only on enum variants"
255                 }
256             }
257 
258             Kind::Subcommand(_) => {
259                 if res.has_custom_parser {
260                     abort!(
261                         res.parser.span(),
262                         "parse attribute is not allowed for subcommand"
263                     );
264                 }
265                 if res.has_explicit_methods() {
266                     abort!(
267                         res.kind.span(),
268                         "methods in attributes are not allowed for subcommand"
269                     );
270                 }
271 
272                 let ty = Ty::from_syn_ty(&field.ty);
273                 match *ty {
274                     Ty::OptionOption => {
275                         abort!(
276                             field.ty,
277                             "Option<Option<T>> type is not allowed for subcommand"
278                         );
279                     }
280                     Ty::OptionVec => {
281                         abort!(
282                             field.ty,
283                             "Option<Vec<T>> type is not allowed for subcommand"
284                         );
285                     }
286                     _ => (),
287                 }
288 
289                 res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span());
290             }
291             Kind::Skip(_) => {
292                 if res.has_explicit_methods() {
293                     abort!(
294                         res.kind.span(),
295                         "methods are not allowed for skipped fields"
296                     );
297                 }
298             }
299             Kind::FromGlobal(orig_ty) => {
300                 let ty = Ty::from_syn_ty(&field.ty);
301                 res.kind = Sp::new(Kind::FromGlobal(ty), orig_ty.span());
302             }
303             Kind::Arg(orig_ty) => {
304                 let mut ty = Ty::from_syn_ty(&field.ty);
305                 if res.has_custom_parser {
306                     match *ty {
307                         Ty::Option | Ty::Vec | Ty::OptionVec => (),
308                         _ => ty = Sp::new(Ty::Other, ty.span()),
309                     }
310                 }
311 
312                 match *ty {
313                     Ty::Bool => {
314                         if res.is_positional() && !res.has_custom_parser {
315                             abort!(field.ty,
316                                 "`bool` cannot be used as positional parameter with default parser";
317                                 help = "if you want to create a flag add `long` or `short`";
318                                 help = "If you really want a boolean parameter \
319                                     add an explicit parser, for example `parse(try_from_str)`";
320                                 note = "see also https://github.com/clap-rs/clap/blob/master/examples/derive_ref/custom-bool.md";
321                             )
322                         }
323                         if res.is_enum {
324                             abort!(field.ty, "`arg_enum` is meaningless for bool")
325                         }
326                         if let Some(m) = res.find_default_method() {
327                             abort!(m.name, "default_value is meaningless for bool")
328                         }
329                         if let Some(m) = res.find_method("required") {
330                             abort!(m.name, "required is meaningless for bool")
331                         }
332                     }
333                     Ty::Option => {
334                         if let Some(m) = res.find_default_method() {
335                             abort!(m.name, "default_value is meaningless for Option")
336                         }
337                     }
338                     Ty::OptionOption => {
339                         if res.is_positional() {
340                             abort!(
341                                 field.ty,
342                                 "Option<Option<T>> type is meaningless for positional argument"
343                             )
344                         }
345                     }
346                     Ty::OptionVec => {
347                         if res.is_positional() {
348                             abort!(
349                                 field.ty,
350                                 "Option<Vec<T>> type is meaningless for positional argument"
351                             )
352                         }
353                     }
354 
355                     _ => (),
356                 }
357                 res.kind = Sp::new(Kind::Arg(ty), orig_ty.span());
358             }
359         }
360 
361         res
362     }
363 
new( default_span: Span, name: Name, ty: Option<Type>, casing: Sp<CasingStyle>, env_casing: Sp<CasingStyle>, ) -> Self364     fn new(
365         default_span: Span,
366         name: Name,
367         ty: Option<Type>,
368         casing: Sp<CasingStyle>,
369         env_casing: Sp<CasingStyle>,
370     ) -> Self {
371         Self {
372             name,
373             ty,
374             casing,
375             env_casing,
376             doc_comment: vec![],
377             methods: vec![],
378             parser: Parser::default_spanned(default_span),
379             author: None,
380             version: None,
381             verbatim_doc_comment: None,
382             help_heading: None,
383             is_enum: false,
384             has_custom_parser: false,
385             kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span),
386         }
387     }
388 
push_method(&mut self, name: Ident, arg: impl ToTokens)389     fn push_method(&mut self, name: Ident, arg: impl ToTokens) {
390         if name == "name" {
391             self.name = Name::Assigned(quote!(#arg));
392         } else if name == "version" {
393             self.version = Some(Method::new(name, quote!(#arg)));
394         } else {
395             self.methods.push(Method::new(name, quote!(#arg)))
396         }
397     }
398 
push_attrs(&mut self, attrs: &[Attribute])399     fn push_attrs(&mut self, attrs: &[Attribute]) {
400         use ClapAttr::*;
401 
402         let parsed = parse_clap_attributes(attrs);
403         for attr in &parsed {
404             let attr = attr.clone();
405             match attr {
406                 Short(ident) => {
407                     self.push_method(ident, self.name.clone().translate_char(*self.casing));
408                 }
409 
410                 Long(ident) => {
411                     self.push_method(ident, self.name.clone().translate(*self.casing));
412                 }
413 
414                 Env(ident) => {
415                     self.push_method(ident, self.name.clone().translate(*self.env_casing));
416                 }
417 
418                 ArgEnum(_) => self.is_enum = true,
419 
420                 FromGlobal(ident) => {
421                     let ty = Sp::call_site(Ty::Other);
422                     let kind = Sp::new(Kind::FromGlobal(ty), ident.span());
423                     self.set_kind(kind);
424                 }
425 
426                 Subcommand(ident) => {
427                     let ty = Sp::call_site(Ty::Other);
428                     let kind = Sp::new(Kind::Subcommand(ty), ident.span());
429                     self.set_kind(kind);
430                 }
431 
432                 ExternalSubcommand(ident) => {
433                     let kind = Sp::new(Kind::ExternalSubcommand, ident.span());
434                     self.set_kind(kind);
435                 }
436 
437                 Flatten(ident) => {
438                     let kind = Sp::new(Kind::Flatten, ident.span());
439                     self.set_kind(kind);
440                 }
441 
442                 Skip(ident, expr) => {
443                     let kind = Sp::new(Kind::Skip(expr), ident.span());
444                     self.set_kind(kind);
445                 }
446 
447                 VerbatimDocComment(ident) => self.verbatim_doc_comment = Some(ident),
448 
449                 DefaultValueT(ident, expr) => {
450                     let ty = if let Some(ty) = self.ty.as_ref() {
451                         ty
452                     } else {
453                         abort!(
454                             ident,
455                             "#[clap(default_value_t)] (without an argument) can be used \
456                             only on field level";
457 
458                             note = "see \
459                                 https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
460                     };
461 
462                     let val = if let Some(expr) = expr {
463                         quote!(#expr)
464                     } else {
465                         quote!(<#ty as ::std::default::Default>::default())
466                     };
467 
468                     let val = if parsed.iter().any(|a| matches!(a, ArgEnum(_))) {
469                         quote_spanned!(ident.span()=> {
470                             {
471                                 let val: #ty = #val;
472                                 clap::ArgEnum::to_possible_value(&val).unwrap().get_name()
473                             }
474                         })
475                     } else {
476                         quote_spanned!(ident.span()=> {
477                             clap::lazy_static::lazy_static! {
478                                 static ref DEFAULT_VALUE: &'static str = {
479                                     let val: #ty = #val;
480                                     let s = ::std::string::ToString::to_string(&val);
481                                     ::std::boxed::Box::leak(s.into_boxed_str())
482                                 };
483                             }
484                             *DEFAULT_VALUE
485                         })
486                     };
487 
488                     let raw_ident = Ident::new("default_value", ident.span());
489                     self.methods.push(Method::new(raw_ident, val));
490                 }
491 
492                 DefaultValueOsT(ident, expr) => {
493                     let ty = if let Some(ty) = self.ty.as_ref() {
494                         ty
495                     } else {
496                         abort!(
497                             ident,
498                             "#[clap(default_value_os_t)] (without an argument) can be used \
499                             only on field level";
500 
501                             note = "see \
502                                 https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
503                     };
504 
505                     let val = if let Some(expr) = expr {
506                         quote!(#expr)
507                     } else {
508                         quote!(<#ty as ::std::default::Default>::default())
509                     };
510 
511                     let val = if parsed.iter().any(|a| matches!(a, ArgEnum(_))) {
512                         quote_spanned!(ident.span()=> {
513                             {
514                                 let val: #ty = #val;
515                                 clap::ArgEnum::to_possible_value(&val).unwrap().get_name()
516                             }
517                         })
518                     } else {
519                         quote_spanned!(ident.span()=> {
520                             clap::lazy_static::lazy_static! {
521                                 static ref DEFAULT_VALUE: &'static ::std::ffi::OsStr = {
522                                     let val: #ty = #val;
523                                     let s: ::std::ffi::OsString = val.into();
524                                     ::std::boxed::Box::leak(s.into_boxed_os_str())
525                                 };
526                             }
527                             *DEFAULT_VALUE
528                         })
529                     };
530 
531                     let raw_ident = Ident::new("default_value_os", ident.span());
532                     self.methods.push(Method::new(raw_ident, val));
533                 }
534 
535                 HelpHeading(ident, expr) => {
536                     self.help_heading = Some(Method::new(ident, quote!(#expr)));
537                 }
538 
539                 About(ident, about) => {
540                     if let Some(method) =
541                         Method::from_lit_or_env(ident, about, "CARGO_PKG_DESCRIPTION")
542                     {
543                         self.methods.push(method);
544                     }
545                 }
546 
547                 Author(ident, author) => {
548                     self.author = Method::from_lit_or_env(ident, author, "CARGO_PKG_AUTHORS");
549                 }
550 
551                 Version(ident, version) => {
552                     self.version = Method::from_lit_or_env(ident, version, "CARGO_PKG_VERSION");
553                 }
554 
555                 NameLitStr(name, lit) => {
556                     self.push_method(name, lit);
557                 }
558 
559                 NameExpr(name, expr) => {
560                     self.push_method(name, expr);
561                 }
562 
563                 MethodCall(name, args) => self.push_method(name, quote!(#(#args),*)),
564 
565                 RenameAll(_, casing_lit) => {
566                     self.casing = CasingStyle::from_lit(casing_lit);
567                 }
568 
569                 RenameAllEnv(_, casing_lit) => {
570                     self.env_casing = CasingStyle::from_lit(casing_lit);
571                 }
572 
573                 Parse(ident, spec) => {
574                     self.has_custom_parser = true;
575                     self.parser = Parser::from_spec(ident, spec);
576                 }
577             }
578         }
579     }
580 
push_doc_comment(&mut self, attrs: &[Attribute], name: &str)581     fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) {
582         use syn::Lit::*;
583         use syn::Meta::*;
584 
585         let comment_parts: Vec<_> = attrs
586             .iter()
587             .filter(|attr| attr.path.is_ident("doc"))
588             .filter_map(|attr| {
589                 if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() {
590                     Some(s.value())
591                 } else {
592                     // non #[doc = "..."] attributes are not our concern
593                     // we leave them for rustc to handle
594                     None
595                 }
596             })
597             .collect();
598 
599         self.doc_comment =
600             process_doc_comment(comment_parts, name, self.verbatim_doc_comment.is_none());
601     }
602 
set_kind(&mut self, kind: Sp<Kind>)603     fn set_kind(&mut self, kind: Sp<Kind>) {
604         if let Kind::Arg(_) = *self.kind {
605             self.kind = kind;
606         } else {
607             abort!(
608                 kind.span(),
609                 "`subcommand`, `flatten`, `external_subcommand` and `skip` cannot be used together"
610             );
611         }
612     }
613 
find_method(&self, name: &str) -> Option<&Method>614     pub fn find_method(&self, name: &str) -> Option<&Method> {
615         self.methods.iter().find(|m| m.name == name)
616     }
617 
find_default_method(&self) -> Option<&Method>618     pub fn find_default_method(&self) -> Option<&Method> {
619         self.methods
620             .iter()
621             .find(|m| m.name == "default_value" || m.name == "default_value_os")
622     }
623 
624     /// generate methods from attributes on top of struct or enum
initial_top_level_methods(&self) -> TokenStream625     pub fn initial_top_level_methods(&self) -> TokenStream {
626         let help_heading = self.help_heading.as_ref().into_iter();
627         quote!( #(#help_heading)* )
628     }
629 
final_top_level_methods(&self) -> TokenStream630     pub fn final_top_level_methods(&self) -> TokenStream {
631         let version = &self.version;
632         let author = &self.author;
633         let methods = &self.methods;
634         let doc_comment = &self.doc_comment;
635 
636         quote!( #(#doc_comment)* #author #version #(#methods)*)
637     }
638 
639     /// generate methods on top of a field
field_methods(&self, supports_long_help: bool) -> proc_macro2::TokenStream640     pub fn field_methods(&self, supports_long_help: bool) -> proc_macro2::TokenStream {
641         let methods = &self.methods;
642         let help_heading = self.help_heading.as_ref().into_iter();
643         match supports_long_help {
644             true => {
645                 let doc_comment = &self.doc_comment;
646                 quote!( #(#doc_comment)* #(#help_heading)* #(#methods)* )
647             }
648             false => {
649                 let doc_comment = self
650                     .doc_comment
651                     .iter()
652                     .filter(|mth| mth.name != "long_help");
653                 quote!( #(#doc_comment)* #(#help_heading)* #(#methods)* )
654             }
655         }
656     }
657 
help_heading(&self) -> TokenStream658     pub fn help_heading(&self) -> TokenStream {
659         let help_heading = self.help_heading.as_ref().into_iter();
660         quote!( #(#help_heading)* )
661     }
662 
cased_name(&self) -> TokenStream663     pub fn cased_name(&self) -> TokenStream {
664         self.name.clone().translate(*self.casing)
665     }
666 
value_name(&self) -> TokenStream667     pub fn value_name(&self) -> TokenStream {
668         self.name.clone().translate(CasingStyle::ScreamingSnake)
669     }
670 
parser(&self) -> &Sp<Parser>671     pub fn parser(&self) -> &Sp<Parser> {
672         &self.parser
673     }
674 
kind(&self) -> Sp<Kind>675     pub fn kind(&self) -> Sp<Kind> {
676         self.kind.clone()
677     }
678 
is_enum(&self) -> bool679     pub fn is_enum(&self) -> bool {
680         self.is_enum
681     }
682 
ignore_case(&self) -> TokenStream683     pub fn ignore_case(&self) -> TokenStream {
684         let method = self.find_method("ignore_case");
685 
686         if let Some(method) = method {
687             method.args.clone()
688         } else {
689             quote! { false }
690         }
691     }
692 
casing(&self) -> Sp<CasingStyle>693     pub fn casing(&self) -> Sp<CasingStyle> {
694         self.casing.clone()
695     }
696 
env_casing(&self) -> Sp<CasingStyle>697     pub fn env_casing(&self) -> Sp<CasingStyle> {
698         self.env_casing.clone()
699     }
700 
is_positional(&self) -> bool701     pub fn is_positional(&self) -> bool {
702         self.methods
703             .iter()
704             .all(|m| m.name != "long" && m.name != "short")
705     }
706 
has_explicit_methods(&self) -> bool707     pub fn has_explicit_methods(&self) -> bool {
708         self.methods
709             .iter()
710             .any(|m| m.name != "help" && m.name != "long_help")
711     }
712 }
713 
714 #[allow(clippy::large_enum_variant)]
715 #[derive(Clone)]
716 pub enum Kind {
717     Arg(Sp<Ty>),
718     FromGlobal(Sp<Ty>),
719     Subcommand(Sp<Ty>),
720     Flatten,
721     Skip(Option<Expr>),
722     ExternalSubcommand,
723 }
724 
725 #[derive(Clone)]
726 pub struct Method {
727     name: Ident,
728     args: TokenStream,
729 }
730 
731 impl Method {
new(name: Ident, args: TokenStream) -> Self732     pub fn new(name: Ident, args: TokenStream) -> Self {
733         Method { name, args }
734     }
735 
from_lit_or_env(ident: Ident, lit: Option<LitStr>, env_var: &str) -> Option<Self>736     fn from_lit_or_env(ident: Ident, lit: Option<LitStr>, env_var: &str) -> Option<Self> {
737         let mut lit = match lit {
738             Some(lit) => lit,
739 
740             None => match env::var(env_var) {
741                 Ok(val) => {
742                     if val.is_empty() {
743                         return None;
744                     }
745                     LitStr::new(&val, ident.span())
746                 }
747                 Err(_) => {
748                     abort!(ident,
749                         "cannot derive `{}` from Cargo.toml", ident;
750                         note = "`{}` environment variable is not set", env_var;
751                         help = "use `{} = \"...\"` to set {} manually", ident, ident;
752                     );
753                 }
754             },
755         };
756 
757         if ident == "author" {
758             let edited = process_author_str(&lit.value());
759             lit = LitStr::new(&edited, lit.span());
760         }
761 
762         Some(Method::new(ident, quote!(#lit)))
763     }
764 }
765 
766 impl ToTokens for Method {
to_tokens(&self, ts: &mut proc_macro2::TokenStream)767     fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
768         let Method { ref name, ref args } = self;
769 
770         let tokens = quote!( .#name(#args) );
771 
772         tokens.to_tokens(ts);
773     }
774 }
775 
776 /// replace all `:` with `, ` when not inside the `<>`
777 ///
778 /// `"author1:author2:author3" => "author1, author2, author3"`
779 /// `"author1 <http://website1.com>:author2" => "author1 <http://website1.com>, author2"
process_author_str(author: &str) -> String780 fn process_author_str(author: &str) -> String {
781     let mut res = String::with_capacity(author.len());
782     let mut inside_angle_braces = 0usize;
783 
784     for ch in author.chars() {
785         if inside_angle_braces > 0 && ch == '>' {
786             inside_angle_braces -= 1;
787             res.push(ch);
788         } else if ch == '<' {
789             inside_angle_braces += 1;
790             res.push(ch);
791         } else if inside_angle_braces == 0 && ch == ':' {
792             res.push_str(", ");
793         } else {
794             res.push(ch);
795         }
796     }
797 
798     res
799 }
800 
801 #[derive(Clone)]
802 pub struct Parser {
803     pub kind: Sp<ParserKind>,
804     pub func: TokenStream,
805 }
806 
807 impl Parser {
default_spanned(span: Span) -> Sp<Self>808     fn default_spanned(span: Span) -> Sp<Self> {
809         let kind = Sp::new(ParserKind::TryFromStr, span);
810         let func = quote_spanned!(span=> ::std::str::FromStr::from_str);
811         Sp::new(Parser { kind, func }, span)
812     }
813 
from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp<Self>814     fn from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp<Self> {
815         use self::ParserKind::*;
816 
817         let kind = match &*spec.kind.to_string() {
818             "from_str" => FromStr,
819             "try_from_str" => TryFromStr,
820             "from_os_str" => FromOsStr,
821             "try_from_os_str" => TryFromOsStr,
822             "from_occurrences" => FromOccurrences,
823             "from_flag" => FromFlag,
824             s => abort!(spec.kind.span(), "unsupported parser `{}`", s),
825         };
826 
827         let func = match spec.parse_func {
828             None => match kind {
829                 FromStr | FromOsStr => {
830                     quote_spanned!(spec.kind.span()=> ::std::convert::From::from)
831                 }
832                 TryFromStr => quote_spanned!(spec.kind.span()=> ::std::str::FromStr::from_str),
833                 TryFromOsStr => abort!(
834                     spec.kind.span(),
835                     "you must set parser for `try_from_os_str` explicitly"
836                 ),
837                 FromOccurrences => quote_spanned!(spec.kind.span()=> { |v| v as _ }),
838                 FromFlag => quote_spanned!(spec.kind.span()=> ::std::convert::From::from),
839             },
840 
841             Some(func) => match func {
842                 Expr::Path(_) => quote!(#func),
843                 _ => abort!(func, "`parse` argument must be a function path"),
844             },
845         };
846 
847         let kind = Sp::new(kind, spec.kind.span());
848         let parser = Parser { kind, func };
849         Sp::new(parser, parse_ident.span())
850     }
851 }
852 
853 #[derive(Debug, PartialEq, Clone)]
854 pub enum ParserKind {
855     FromStr,
856     TryFromStr,
857     FromOsStr,
858     TryFromOsStr,
859     FromOccurrences,
860     FromFlag,
861 }
862 
863 /// Defines the casing for the attributes long representation.
864 #[derive(Copy, Clone, Debug, PartialEq)]
865 pub enum CasingStyle {
866     /// Indicate word boundaries with uppercase letter, excluding the first word.
867     Camel,
868     /// Keep all letters lowercase and indicate word boundaries with hyphens.
869     Kebab,
870     /// Indicate word boundaries with uppercase letter, including the first word.
871     Pascal,
872     /// Keep all letters uppercase and indicate word boundaries with underscores.
873     ScreamingSnake,
874     /// Keep all letters lowercase and indicate word boundaries with underscores.
875     Snake,
876     /// Keep all letters lowercase and remove word boundaries.
877     Lower,
878     /// Keep all letters uppercase and remove word boundaries.
879     Upper,
880     /// Use the original attribute name defined in the code.
881     Verbatim,
882 }
883 
884 impl CasingStyle {
from_lit(name: LitStr) -> Sp<Self>885     fn from_lit(name: LitStr) -> Sp<Self> {
886         use self::CasingStyle::*;
887 
888         let normalized = name.value().to_upper_camel_case().to_lowercase();
889         let cs = |kind| Sp::new(kind, name.span());
890 
891         match normalized.as_ref() {
892             "camel" | "camelcase" => cs(Camel),
893             "kebab" | "kebabcase" => cs(Kebab),
894             "pascal" | "pascalcase" => cs(Pascal),
895             "screamingsnake" | "screamingsnakecase" => cs(ScreamingSnake),
896             "snake" | "snakecase" => cs(Snake),
897             "lower" | "lowercase" => cs(Lower),
898             "upper" | "uppercase" => cs(Upper),
899             "verbatim" | "verbatimcase" => cs(Verbatim),
900             s => abort!(name, "unsupported casing: `{}`", s),
901         }
902     }
903 }
904 
905 #[derive(Clone)]
906 pub enum Name {
907     Derived(Ident),
908     Assigned(TokenStream),
909 }
910 
911 impl Name {
translate(self, style: CasingStyle) -> TokenStream912     pub fn translate(self, style: CasingStyle) -> TokenStream {
913         use CasingStyle::*;
914 
915         match self {
916             Name::Assigned(tokens) => tokens,
917             Name::Derived(ident) => {
918                 let s = ident.unraw().to_string();
919                 let s = match style {
920                     Pascal => s.to_upper_camel_case(),
921                     Kebab => s.to_kebab_case(),
922                     Camel => s.to_lower_camel_case(),
923                     ScreamingSnake => s.to_shouty_snake_case(),
924                     Snake => s.to_snake_case(),
925                     Lower => s.to_snake_case().replace('_', ""),
926                     Upper => s.to_shouty_snake_case().replace('_', ""),
927                     Verbatim => s,
928                 };
929                 quote_spanned!(ident.span()=> #s)
930             }
931         }
932     }
933 
translate_char(self, style: CasingStyle) -> TokenStream934     pub fn translate_char(self, style: CasingStyle) -> TokenStream {
935         use CasingStyle::*;
936 
937         match self {
938             Name::Assigned(tokens) => quote!( (#tokens).chars().next().unwrap() ),
939             Name::Derived(ident) => {
940                 let s = ident.unraw().to_string();
941                 let s = match style {
942                     Pascal => s.to_upper_camel_case(),
943                     Kebab => s.to_kebab_case(),
944                     Camel => s.to_lower_camel_case(),
945                     ScreamingSnake => s.to_shouty_snake_case(),
946                     Snake => s.to_snake_case(),
947                     Lower => s.to_snake_case(),
948                     Upper => s.to_shouty_snake_case(),
949                     Verbatim => s,
950                 };
951 
952                 let s = s.chars().next().unwrap();
953                 quote_spanned!(ident.span()=> #s)
954             }
955         }
956     }
957 }
958