1 use crate::{
2     attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING},
3     derives::{from_arg_matches, into_app},
4     dummies,
5     utils::{is_simple_ty, subty_if_name, Sp},
6 };
7 
8 use proc_macro2::{Ident, Span, TokenStream};
9 use proc_macro_error::{abort, abort_call_site};
10 use quote::{quote, quote_spanned};
11 use syn::{
12     punctuated::Punctuated, spanned::Spanned, Attribute, Data, DataEnum, DeriveInput,
13     FieldsUnnamed, Token, Variant,
14 };
15 
derive_subcommand(input: &DeriveInput) -> TokenStream16 pub fn derive_subcommand(input: &DeriveInput) -> TokenStream {
17     let ident = &input.ident;
18 
19     dummies::subcommand(ident);
20 
21     match input.data {
22         Data::Enum(ref e) => gen_for_enum(ident, &input.attrs, e),
23         _ => abort_call_site!("`#[derive(Subcommand)]` only supports enums"),
24     }
25 }
26 
gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream27 pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream {
28     let attrs = Attrs::from_struct(
29         Span::call_site(),
30         attrs,
31         Name::Derived(name.clone()),
32         Sp::call_site(DEFAULT_CASING),
33         Sp::call_site(DEFAULT_ENV_CASING),
34     );
35 
36     let from_subcommand = gen_from_subcommand(name, &e.variants, &attrs);
37     let augment_subcommands = gen_augment_subcommands(&e.variants, &attrs);
38 
39     quote! {
40         #[allow(dead_code, unreachable_code, unused_variables)]
41         #[allow(
42             clippy::style,
43             clippy::complexity,
44             clippy::pedantic,
45             clippy::restriction,
46             clippy::perf,
47             clippy::deprecated,
48             clippy::nursery,
49             clippy::cargo
50         )]
51         #[deny(clippy::correctness)]
52         impl ::clap::Subcommand for #name {
53             #augment_subcommands
54             #from_subcommand
55         }
56     }
57 }
58 
gen_augment_subcommands( variants: &Punctuated<Variant, Token![,]>, parent_attribute: &Attrs, ) -> TokenStream59 fn gen_augment_subcommands(
60     variants: &Punctuated<Variant, Token![,]>,
61     parent_attribute: &Attrs,
62 ) -> TokenStream {
63     use syn::Fields::*;
64 
65     let subcommands: Vec<_> = variants
66         .iter()
67         .map(|variant| {
68             let attrs = Attrs::from_struct(
69                 variant.span(),
70                 &variant.attrs,
71                 Name::Derived(variant.ident.clone()),
72                 parent_attribute.casing(),
73                 parent_attribute.env_casing(),
74             );
75             let kind = attrs.kind();
76 
77             match &*kind {
78                 Kind::ExternalSubcommand => {
79                     quote_spanned! { kind.span()=>
80                         let app = app.setting(::clap::AppSettings::AllowExternalSubcommands);
81                     }
82                 }
83 
84                 Kind::Flatten => match variant.fields {
85                     Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
86                         let ty = &unnamed[0];
87                         quote! {
88                             let app = <#ty as ::clap::Subcommand>::augment_subcommands(app);
89                         }
90                     }
91                     _ => abort!(
92                         variant,
93                         "`flatten` is usable only with single-typed tuple variants"
94                     ),
95                 },
96 
97                 _ => {
98                     let app_var = Ident::new("subcommand", Span::call_site());
99                     let arg_block = match variant.fields {
100                         Named(ref fields) => {
101                             into_app::gen_app_augmentation(&fields.named, &app_var, &attrs)
102                         }
103                         Unit => quote!( #app_var ),
104                         Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
105                             let ty = &unnamed[0];
106                             quote_spanned! { ty.span()=>
107                                 {
108                                     <#ty as ::clap::IntoApp>::augment_clap(#app_var)
109                                 }
110                             }
111                         }
112                         Unnamed(..) => {
113                             abort!(variant, "non single-typed tuple enums are not supported")
114                         }
115                     };
116 
117                     let name = attrs.cased_name();
118                     let from_attrs = attrs.top_level_methods();
119                     let version = attrs.version();
120                     quote! {
121                         let app = app.subcommand({
122                             let #app_var = ::clap::App::new(#name);
123                             let #app_var = #arg_block;
124                             #app_var#from_attrs#version
125                         });
126                     }
127                 }
128             }
129         })
130         .collect();
131 
132     let app_methods = parent_attribute.top_level_methods();
133     let version = parent_attribute.version();
134     quote! {
135         fn augment_subcommands<'b>(app: ::clap::App<'b>) -> ::clap::App<'b> {
136             let app = app #app_methods;
137             #( #subcommands )*;
138             app #version
139         }
140     }
141 }
142 
gen_from_subcommand( name: &Ident, variants: &Punctuated<Variant, Token![,]>, parent_attribute: &Attrs, ) -> TokenStream143 fn gen_from_subcommand(
144     name: &Ident,
145     variants: &Punctuated<Variant, Token![,]>,
146     parent_attribute: &Attrs,
147 ) -> TokenStream {
148     use syn::Fields::*;
149 
150     let mut ext_subcmd = None;
151 
152     let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants
153         .iter()
154         .filter_map(|variant| {
155             let attrs = Attrs::from_struct(
156                 variant.span(),
157                 &variant.attrs,
158                 Name::Derived(variant.ident.clone()),
159                 parent_attribute.casing(),
160                 parent_attribute.env_casing(),
161             );
162 
163             if let Kind::ExternalSubcommand = &*attrs.kind() {
164                 if ext_subcmd.is_some() {
165                     abort!(
166                         attrs.kind().span(),
167                         "Only one variant can be marked with `external_subcommand`, \
168                          this is the second"
169                     );
170                 }
171 
172                 let ty = match variant.fields {
173                     Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty,
174 
175                     _ => abort!(
176                         variant,
177                         "The enum variant marked with `external_attribute` must be \
178                          a single-typed tuple, and the type must be either `Vec<String>` \
179                          or `Vec<OsString>`."
180                     ),
181                 };
182 
183                 let (span, str_ty, values_of) = match subty_if_name(ty, "Vec") {
184                     Some(subty) => {
185                         if is_simple_ty(subty, "String") {
186                             (
187                                 subty.span(),
188                                 quote!(::std::string::String),
189                                 quote!(values_of),
190                             )
191                         } else if is_simple_ty(subty, "OsString") {
192                             (
193                                 subty.span(),
194                                 quote!(::std::ffi::OsString),
195                                 quote!(values_of_os),
196                             )
197                         } else {
198                             abort!(
199                                 ty.span(),
200                                 "The type must be either `Vec<String>` or `Vec<OsString>` \
201                                  to be used with `external_subcommand`."
202                             );
203                         }
204                     }
205 
206                     None => abort!(
207                         ty.span(),
208                         "The type must be either `Vec<String>` or `Vec<OsString>` \
209                          to be used with `external_subcommand`."
210                     ),
211                 };
212 
213                 ext_subcmd = Some((span, &variant.ident, str_ty, values_of));
214                 None
215             } else {
216                 Some((variant, attrs))
217             }
218         })
219         .partition(|(_, attrs)| {
220             let kind = attrs.kind();
221             match &*kind {
222                 Kind::Flatten => true,
223                 _ => false,
224             }
225         });
226 
227     let match_arms = variants.iter().map(|(variant, attrs)| {
228         let sub_name = attrs.cased_name();
229         let variant_name = &variant.ident;
230         let constructor_block = match variant.fields {
231             Named(ref fields) => from_arg_matches::gen_constructor(&fields.named, &attrs),
232             Unit => quote!(),
233             Unnamed(ref fields) if fields.unnamed.len() == 1 => {
234                 let ty = &fields.unnamed[0];
235                 quote!( ( <#ty as ::clap::FromArgMatches>::from_arg_matches(matches) ) )
236             }
237             Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
238         };
239 
240         quote! {
241             Some((#sub_name, matches)) => {
242                 Some(#name :: #variant_name #constructor_block)
243             }
244         }
245     });
246     let child_subcommands = flatten_variants.iter().map(|(variant, _attrs)| {
247         let variant_name = &variant.ident;
248         match variant.fields {
249             Unnamed(ref fields) if fields.unnamed.len() == 1 => {
250                 let ty = &fields.unnamed[0];
251                 quote! {
252                     if let Some(res) = <#ty as ::clap::Subcommand>::from_subcommand(other) {
253                         return Some(#name :: #variant_name (res));
254                     }
255                 }
256             }
257             _ => abort!(
258                 variant,
259                 "`flatten` is usable only with single-typed tuple variants"
260             ),
261         }
262     });
263 
264     let wildcard = match ext_subcmd {
265         Some((span, var_name, str_ty, values_of)) => quote_spanned! { span=>
266             None => ::std::option::Option::None,
267 
268             Some((external, matches)) => {
269                 ::std::option::Option::Some(#name::#var_name(
270                     ::std::iter::once(#str_ty::from(external))
271                     .chain(
272                         matches.#values_of("").into_iter().flatten().map(#str_ty::from)
273                     )
274                     .collect::<::std::vec::Vec<_>>()
275                 ))
276             }
277         },
278 
279         None => quote!(_ => None),
280     };
281 
282     quote! {
283         fn from_subcommand(subcommand: Option<(&str, &::clap::ArgMatches)>) -> Option<Self> {
284             match subcommand {
285                 #( #match_arms, )*
286                 other => {
287                     #( #child_subcommands )else*
288 
289                     match other {
290                         #wildcard
291                     }
292                 }
293             }
294         }
295     }
296 }
297