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