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