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