clap_derive/derives/
subcommand.rs

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(crate) fn derive_subcommand(input: &DeriveInput) -> Result<TokenStream, syn::Error> {
25    let ident = &input.ident;
26
27    match input.data {
28        Data::Enum(ref e) => {
29            let name = Name::Derived(ident.clone());
30            let item = Item::from_subcommand_enum(input, name)?;
31            let variants = e
32                .variants
33                .iter()
34                .map(|variant| {
35                    let item =
36                        Item::from_subcommand_variant(variant, item.casing(), item.env_casing())?;
37                    Ok((variant, item))
38                })
39                .collect::<Result<Vec<_>, syn::Error>>()?;
40            gen_for_enum(&item, ident, &input.generics, &variants)
41        }
42        _ => abort_call_site!("`#[derive(Subcommand)]` only supports enums"),
43    }
44}
45
46pub(crate) 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::{Named, Unit, Unnamed};
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::Unnamed;
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::{Named, Unit, Unnamed};
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::{Named, Unit, Unnamed};
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}