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