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