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