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