1 // Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
2 // Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
3 // Andrew Hobden (@hoverbear) <andrew@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 use crate::{
15     attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING},
16     derives::args,
17     dummies,
18     utils::{is_simple_ty, subty_if_name, Sp},
19 };
20 
21 use proc_macro2::{Ident, Span, TokenStream};
22 use proc_macro_error::{abort, abort_call_site};
23 use quote::{format_ident, quote, quote_spanned};
24 use syn::{
25     punctuated::Punctuated, spanned::Spanned, Attribute, Data, DataEnum, DeriveInput,
26     FieldsUnnamed, Token, Variant,
27 };
28 
derive_subcommand(input: &DeriveInput) -> TokenStream29 pub fn derive_subcommand(input: &DeriveInput) -> TokenStream {
30     let ident = &input.ident;
31 
32     dummies::subcommand(ident);
33 
34     match input.data {
35         Data::Enum(ref e) => gen_for_enum(ident, &input.attrs, e),
36         _ => abort_call_site!("`#[derive(Subcommand)]` only supports enums"),
37     }
38 }
39 
gen_for_enum(enum_name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream40 pub fn gen_for_enum(enum_name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream {
41     let from_arg_matches = gen_from_arg_matches_for_enum(enum_name, attrs, e);
42 
43     let attrs = Attrs::from_struct(
44         Span::call_site(),
45         attrs,
46         Name::Derived(enum_name.clone()),
47         Sp::call_site(DEFAULT_CASING),
48         Sp::call_site(DEFAULT_ENV_CASING),
49     );
50     let augmentation = gen_augment(&e.variants, &attrs, false);
51     let augmentation_update = gen_augment(&e.variants, &attrs, true);
52     let has_subcommand = gen_has_subcommand(&e.variants, &attrs);
53 
54     quote! {
55         #from_arg_matches
56 
57         #[allow(dead_code, unreachable_code, unused_variables)]
58         #[allow(
59             clippy::style,
60             clippy::complexity,
61             clippy::pedantic,
62             clippy::restriction,
63             clippy::perf,
64             clippy::deprecated,
65             clippy::nursery,
66             clippy::cargo
67         )]
68         #[deny(clippy::correctness)]
69         impl clap::Subcommand for #enum_name {
70             fn augment_subcommands <'b>(app: clap::App<'b>) -> clap::App<'b> {
71                 #augmentation
72             }
73             fn augment_subcommands_for_update <'b>(app: clap::App<'b>) -> clap::App<'b> {
74                 #augmentation_update
75             }
76             fn has_subcommand(name: &str) -> bool {
77                 #has_subcommand
78             }
79         }
80     }
81 }
82 
gen_from_arg_matches_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream83 fn gen_from_arg_matches_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream {
84     let attrs = Attrs::from_struct(
85         Span::call_site(),
86         attrs,
87         Name::Derived(name.clone()),
88         Sp::call_site(DEFAULT_CASING),
89         Sp::call_site(DEFAULT_ENV_CASING),
90     );
91 
92     let from_arg_matches = gen_from_arg_matches(name, &e.variants, &attrs);
93     let update_from_arg_matches = gen_update_from_arg_matches(name, &e.variants, &attrs);
94 
95     quote! {
96         #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
97         #[allow(
98             clippy::style,
99             clippy::complexity,
100             clippy::pedantic,
101             clippy::restriction,
102             clippy::perf,
103             clippy::deprecated,
104             clippy::nursery,
105             clippy::cargo
106         )]
107         #[deny(clippy::correctness)]
108         impl clap::FromArgMatches for #name {
109             #from_arg_matches
110             #update_from_arg_matches
111         }
112     }
113 }
114 
gen_augment( variants: &Punctuated<Variant, Token![,]>, parent_attribute: &Attrs, override_required: bool, ) -> TokenStream115 fn gen_augment(
116     variants: &Punctuated<Variant, Token![,]>,
117     parent_attribute: &Attrs,
118     override_required: bool,
119 ) -> TokenStream {
120     use syn::Fields::*;
121 
122     let app_var = Ident::new("app", Span::call_site());
123 
124     let subcommands: Vec<_> = variants
125         .iter()
126         .filter_map(|variant| {
127             let attrs = Attrs::from_variant(
128                 variant,
129                 parent_attribute.casing(),
130                 parent_attribute.env_casing(),
131             );
132             let kind = attrs.kind();
133 
134             match &*kind {
135                 Kind::Skip(_) => None,
136 
137                 Kind::ExternalSubcommand => {
138                     let ty = match variant.fields {
139                         Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty,
140 
141                         _ => abort!(
142                             variant,
143                             "The enum variant marked with `external_subcommand` must be \
144                              a single-typed tuple, and the type must be either `Vec<String>` \
145                              or `Vec<OsString>`."
146                         ),
147                     };
148                     let subcommand = match subty_if_name(ty, "Vec") {
149                         Some(subty) => {
150                             if is_simple_ty(subty, "OsString") {
151                                 quote_spanned! { kind.span()=>
152                                     let #app_var = #app_var.setting(clap::AppSettings::AllowExternalSubcommands).setting(clap::AppSettings::AllowInvalidUtf8ForExternalSubcommands);
153                                 }
154                             } else {
155                                 quote_spanned! { kind.span()=>
156                                     let #app_var = #app_var.setting(clap::AppSettings::AllowExternalSubcommands);
157                                 }
158                             }
159                         }
160 
161                         None => abort!(
162                             ty.span(),
163                             "The type must be `Vec<_>` \
164                              to be used with `external_subcommand`."
165                         ),
166                     };
167                     Some(subcommand)
168                 }
169 
170                 Kind::Flatten => match variant.fields {
171                     Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
172                         let ty = &unnamed[0];
173                         let old_heading_var = format_ident!("old_heading");
174                         let subcommand = if override_required {
175                             quote! {
176                                 let #old_heading_var = #app_var.get_help_heading();
177                                 let #app_var = <#ty as clap::Subcommand>::augment_subcommands_for_update(#app_var);
178                                 let #app_var = #app_var.help_heading(#old_heading_var);
179                             }
180                         } else {
181                             quote! {
182                                 let #old_heading_var = #app_var.get_help_heading();
183                                 let #app_var = <#ty as clap::Subcommand>::augment_subcommands(#app_var);
184                                 let #app_var = #app_var.help_heading(#old_heading_var);
185                             }
186                         };
187                         Some(subcommand)
188                     }
189                     _ => abort!(
190                         variant,
191                         "`flatten` is usable only with single-typed tuple variants"
192                     ),
193                 },
194 
195                 Kind::Subcommand(_) => {
196                     let subcommand_var = Ident::new("subcommand", Span::call_site());
197                     let arg_block = match variant.fields {
198                         Named(_) => {
199                             abort!(variant, "non single-typed tuple enums are not supported")
200                         }
201                         Unit => quote!( #subcommand_var ),
202                         Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
203                             let ty = &unnamed[0];
204                             if override_required {
205                                 quote_spanned! { ty.span()=>
206                                     {
207                                         <#ty as clap::Subcommand>::augment_subcommands_for_update(#subcommand_var)
208                                     }
209                                 }
210                             } else {
211                                 quote_spanned! { ty.span()=>
212                                     {
213                                         <#ty as clap::Subcommand>::augment_subcommands(#subcommand_var)
214                                     }
215                                 }
216                             }
217                         }
218                         Unnamed(..) => {
219                             abort!(variant, "non single-typed tuple enums are not supported")
220                         }
221                     };
222 
223                     let name = attrs.cased_name();
224                     let initial_app_methods = parent_attribute.initial_top_level_methods();
225                     let final_from_attrs = attrs.final_top_level_methods();
226                     let subcommand = quote! {
227                         let #app_var = #app_var.subcommand({
228                             let #subcommand_var = clap::App::new(#name);
229                             let #subcommand_var = #subcommand_var #initial_app_methods;
230                             let #subcommand_var = #arg_block;
231                             let #subcommand_var = #subcommand_var.setting(::clap::AppSettings::SubcommandRequiredElseHelp);
232                             #subcommand_var #final_from_attrs
233                         });
234                     };
235                     Some(subcommand)
236                 }
237 
238                 _ => {
239                     let subcommand_var = Ident::new("subcommand", Span::call_site());
240                     let arg_block = match variant.fields {
241                         Named(ref fields) => {
242                             args::gen_augment(&fields.named, &subcommand_var, &attrs, override_required)
243                         }
244                         Unit => quote!( #subcommand_var ),
245                         Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
246                             let ty = &unnamed[0];
247                             if override_required {
248                                 quote_spanned! { ty.span()=>
249                                     {
250                                         <#ty as clap::Args>::augment_args_for_update(#subcommand_var)
251                                     }
252                                 }
253                             } else {
254                                 quote_spanned! { ty.span()=>
255                                     {
256                                         <#ty as clap::Args>::augment_args(#subcommand_var)
257                                     }
258                                 }
259                             }
260                         }
261                         Unnamed(..) => {
262                             abort!(variant, "non single-typed tuple enums are not supported")
263                         }
264                     };
265 
266                     let name = attrs.cased_name();
267                     let initial_app_methods = parent_attribute.initial_top_level_methods();
268                     let final_from_attrs = attrs.final_top_level_methods();
269                     let subcommand = quote! {
270                         let #app_var = #app_var.subcommand({
271                             let #subcommand_var = clap::App::new(#name);
272                             let #subcommand_var = #subcommand_var #initial_app_methods;
273                             let #subcommand_var = #arg_block;
274                             #subcommand_var #final_from_attrs
275                         });
276                     };
277                     Some(subcommand)
278                 }
279             }
280         })
281         .collect();
282 
283     let initial_app_methods = parent_attribute.initial_top_level_methods();
284     let final_app_methods = parent_attribute.final_top_level_methods();
285     quote! {
286             let #app_var = #app_var #initial_app_methods;
287             #( #subcommands )*;
288             #app_var #final_app_methods
289     }
290 }
291 
gen_has_subcommand( variants: &Punctuated<Variant, Token![,]>, parent_attribute: &Attrs, ) -> TokenStream292 fn gen_has_subcommand(
293     variants: &Punctuated<Variant, Token![,]>,
294     parent_attribute: &Attrs,
295 ) -> TokenStream {
296     use syn::Fields::*;
297 
298     let mut ext_subcmd = false;
299 
300     let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants
301         .iter()
302         .filter_map(|variant| {
303             let attrs = Attrs::from_variant(
304                 variant,
305                 parent_attribute.casing(),
306                 parent_attribute.env_casing(),
307             );
308 
309             if let Kind::ExternalSubcommand = &*attrs.kind() {
310                 ext_subcmd = true;
311                 None
312             } else {
313                 Some((variant, attrs))
314             }
315         })
316         .partition(|(_, attrs)| {
317             let kind = attrs.kind();
318             matches!(&*kind, Kind::Flatten)
319         });
320 
321     let subcommands = variants.iter().map(|(_variant, attrs)| {
322         let sub_name = attrs.cased_name();
323         quote! {
324             if #sub_name == name {
325                 return true
326             }
327         }
328     });
329     let child_subcommands = flatten_variants
330         .iter()
331         .map(|(variant, _attrs)| match variant.fields {
332             Unnamed(ref fields) if fields.unnamed.len() == 1 => {
333                 let ty = &fields.unnamed[0];
334                 quote! {
335                     if <#ty as clap::Subcommand>::has_subcommand(name) {
336                         return true;
337                     }
338                 }
339             }
340             _ => abort!(
341                 variant,
342                 "`flatten` is usable only with single-typed tuple variants"
343             ),
344         });
345 
346     if ext_subcmd {
347         quote! { true }
348     } else {
349         quote! {
350             #( #subcommands )*
351 
352             #( #child_subcommands )else*
353 
354             false
355         }
356     }
357 }
358 
gen_from_arg_matches( name: &Ident, variants: &Punctuated<Variant, Token![,]>, parent_attribute: &Attrs, ) -> TokenStream359 fn gen_from_arg_matches(
360     name: &Ident,
361     variants: &Punctuated<Variant, Token![,]>,
362     parent_attribute: &Attrs,
363 ) -> TokenStream {
364     use syn::Fields::*;
365 
366     let mut ext_subcmd = None;
367 
368     let subcommand_name_var = format_ident!("name");
369     let sub_arg_matches_var = format_ident!("sub_arg_matches");
370     let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants
371         .iter()
372         .filter_map(|variant| {
373             let attrs = Attrs::from_variant(
374                 variant,
375                 parent_attribute.casing(),
376                 parent_attribute.env_casing(),
377             );
378 
379             if let Kind::ExternalSubcommand = &*attrs.kind() {
380                 if ext_subcmd.is_some() {
381                     abort!(
382                         attrs.kind().span(),
383                         "Only one variant can be marked with `external_subcommand`, \
384                          this is the second"
385                     );
386                 }
387 
388                 let ty = match variant.fields {
389                     Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty,
390 
391                     _ => abort!(
392                         variant,
393                         "The enum variant marked with `external_subcommand` must be \
394                          a single-typed tuple, and the type must be either `Vec<String>` \
395                          or `Vec<OsString>`."
396                     ),
397                 };
398 
399                 let (span, str_ty, values_of) = match subty_if_name(ty, "Vec") {
400                     Some(subty) => {
401                         if is_simple_ty(subty, "String") {
402                             (
403                                 subty.span(),
404                                 quote!(::std::string::String),
405                                 quote!(values_of),
406                             )
407                         } else if is_simple_ty(subty, "OsString") {
408                             (
409                                 subty.span(),
410                                 quote!(::std::ffi::OsString),
411                                 quote!(values_of_os),
412                             )
413                         } else {
414                             abort!(
415                                 ty.span(),
416                                 "The type must be either `Vec<String>` or `Vec<OsString>` \
417                                  to be used with `external_subcommand`."
418                             );
419                         }
420                     }
421 
422                     None => abort!(
423                         ty.span(),
424                         "The type must be either `Vec<String>` or `Vec<OsString>` \
425                          to be used with `external_subcommand`."
426                     ),
427                 };
428 
429                 ext_subcmd = Some((span, &variant.ident, str_ty, values_of));
430                 None
431             } else {
432                 Some((variant, attrs))
433             }
434         })
435         .partition(|(_, attrs)| {
436             let kind = attrs.kind();
437             matches!(&*kind, Kind::Flatten)
438         });
439 
440     let subcommands = variants.iter().map(|(variant, attrs)| {
441         let sub_name = attrs.cased_name();
442         let variant_name = &variant.ident;
443         let constructor_block = match variant.fields {
444             Named(ref fields) => args::gen_constructor(&fields.named, attrs),
445             Unit => quote!(),
446             Unnamed(ref fields) if fields.unnamed.len() == 1 => {
447                 let ty = &fields.unnamed[0];
448                 quote!( ( <#ty as clap::FromArgMatches>::from_arg_matches(arg_matches).unwrap() ) )
449             }
450             Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
451         };
452 
453         quote! {
454             if #sub_name == #subcommand_name_var {
455                 return Some(#name :: #variant_name #constructor_block)
456             }
457         }
458     });
459     let child_subcommands = flatten_variants.iter().map(|(variant, _attrs)| {
460         let variant_name = &variant.ident;
461         match variant.fields {
462             Unnamed(ref fields) if fields.unnamed.len() == 1 => {
463                 let ty = &fields.unnamed[0];
464                 quote! {
465                     if <#ty as clap::Subcommand>::has_subcommand(name) {
466                         let res = <#ty as clap::FromArgMatches>::from_arg_matches(arg_matches).unwrap();
467                         return Some(#name :: #variant_name (res));
468                     }
469                 }
470             }
471             _ => abort!(
472                 variant,
473                 "`flatten` is usable only with single-typed tuple variants"
474             ),
475         }
476     });
477 
478     let wildcard = match ext_subcmd {
479         Some((span, var_name, str_ty, values_of)) => quote_spanned! { span=>
480                 ::std::option::Option::Some(#name::#var_name(
481                     ::std::iter::once(#str_ty::from(#subcommand_name_var))
482                     .chain(
483                         #sub_arg_matches_var.#values_of("").into_iter().flatten().map(#str_ty::from)
484                     )
485                     .collect::<::std::vec::Vec<_>>()
486                 ))
487         },
488 
489         None => quote!(None),
490     };
491 
492     quote! {
493         fn from_arg_matches(arg_matches: &clap::ArgMatches) -> Option<Self> {
494             if let Some((#subcommand_name_var, #sub_arg_matches_var)) = arg_matches.subcommand() {
495                 {
496                     let arg_matches = #sub_arg_matches_var;
497                     #( #subcommands )*
498                 }
499 
500                 #( #child_subcommands )else*
501 
502                 #wildcard
503             } else {
504                 None
505             }
506         }
507     }
508 }
509 
gen_update_from_arg_matches( name: &Ident, variants: &Punctuated<Variant, Token![,]>, parent_attribute: &Attrs, ) -> TokenStream510 fn gen_update_from_arg_matches(
511     name: &Ident,
512     variants: &Punctuated<Variant, Token![,]>,
513     parent_attribute: &Attrs,
514 ) -> TokenStream {
515     use syn::Fields::*;
516 
517     let (flatten, variants): (Vec<_>, Vec<_>) = variants
518         .iter()
519         .filter_map(|variant| {
520             let attrs = Attrs::from_variant(
521                 variant,
522                 parent_attribute.casing(),
523                 parent_attribute.env_casing(),
524             );
525 
526             match &*attrs.kind() {
527                 // Fallback to `from_arg_matches`
528                 Kind::ExternalSubcommand => None,
529                 _ => Some((variant, attrs)),
530             }
531         })
532         .partition(|(_, attrs)| {
533             let kind = attrs.kind();
534             matches!(&*kind, Kind::Flatten)
535         });
536 
537     let subcommands = variants.iter().map(|(variant, attrs)| {
538         let sub_name = attrs.cased_name();
539         let variant_name = &variant.ident;
540         let (pattern, updater) = match variant.fields {
541             Named(ref fields) => {
542                 let (fields, update): (Vec<_>, Vec<_>) = fields
543                     .named
544                     .iter()
545                     .map(|field| {
546                         let attrs = Attrs::from_field(
547                             field,
548                             parent_attribute.casing(),
549                             parent_attribute.env_casing(),
550                         );
551                         let field_name = field.ident.as_ref().unwrap();
552                         (
553                             quote!( ref mut #field_name ),
554                             args::gen_updater(&fields.named, &attrs, false),
555                         )
556                     })
557                     .unzip();
558                 (quote!( { #( #fields, )* }), quote!( { #( #update )* } ))
559             }
560             Unit => (quote!(), quote!({})),
561             Unnamed(ref fields) => {
562                 if fields.unnamed.len() == 1 {
563                     (
564                         quote!((ref mut arg)),
565                         quote!(clap::FromArgMatches::update_from_arg_matches(
566                             arg,
567                             sub_arg_matches
568                         )),
569                     )
570                 } else {
571                     abort_call_site!("{}: tuple enums are not supported", variant.ident)
572                 }
573             }
574         };
575 
576         quote! {
577             #name :: #variant_name #pattern if #sub_name == name => {
578                 let arg_matches = sub_arg_matches;
579                 #updater
580             }
581         }
582     });
583 
584     let child_subcommands = flatten.iter().map(|(variant, _attrs)| {
585         let variant_name = &variant.ident;
586         match variant.fields {
587             Unnamed(ref fields) if fields.unnamed.len() == 1 => {
588                 let ty = &fields.unnamed[0];
589                 quote! {
590                     if <#ty as clap::Subcommand>::has_subcommand(name) {
591                         if let #name :: #variant_name (child) = s {
592                             <#ty as clap::FromArgMatches>::update_from_arg_matches(child, arg_matches);
593                             return;
594                         }
595                     }
596                 }
597             }
598             _ => abort!(
599                 variant,
600                 "`flatten` is usable only with single-typed tuple variants"
601             ),
602         }
603     });
604 
605     quote! {
606         fn update_from_arg_matches<'b>(
607             &mut self,
608             arg_matches: &clap::ArgMatches,
609         ) {
610             if let Some((name, sub_arg_matches)) = arg_matches.subcommand() {
611                 match self {
612                     #( #subcommands ),*
613                     s => {
614                         #( #child_subcommands )*
615                         *s = <Self as clap::FromArgMatches>::from_arg_matches(arg_matches).unwrap();
616                     }
617                 }
618             }
619         }
620     }
621 }
622