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