]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | // Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu> |
2 | // | |
3 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
4 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
5 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
6 | // option. This file may not be copied, modified, or distributed | |
7 | // except according to those terms. | |
8 | ||
9 | //! This crate is custom derive for `StructOpt`. It should not be used | |
10 | //! directly. See [structopt documentation](https://docs.rs/structopt) | |
11 | //! for the usage of `#[derive(StructOpt)]`. | |
12 | ||
13 | #![allow(clippy::large_enum_variant)] | |
a2a8927a XL |
14 | // FIXME: remove when and if our MSRV hits 1.42 |
15 | #![allow(clippy::match_like_matches_macro)] | |
16 | #![forbid(unsafe_code)] | |
f20569fa XL |
17 | |
18 | extern crate proc_macro; | |
19 | ||
20 | mod attrs; | |
21 | mod doc_comments; | |
22 | mod parse; | |
23 | mod spanned; | |
24 | mod ty; | |
25 | ||
26 | use crate::{ | |
27 | attrs::{Attrs, CasingStyle, Kind, Name, ParserKind}, | |
28 | spanned::Sp, | |
29 | ty::{is_simple_ty, sub_type, subty_if_name, Ty}, | |
30 | }; | |
31 | ||
32 | use proc_macro2::{Span, TokenStream}; | |
33 | use proc_macro_error::{abort, abort_call_site, proc_macro_error, set_dummy}; | |
34 | use quote::{format_ident, quote, quote_spanned}; | |
35 | use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, *}; | |
36 | ||
37 | /// Default casing style for generated arguments. | |
38 | const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab; | |
39 | ||
40 | /// Default casing style for environment variables | |
41 | const DEFAULT_ENV_CASING: CasingStyle = CasingStyle::ScreamingSnake; | |
42 | ||
43 | /// Output for the `gen_xxx()` methods were we need more than a simple stream of tokens. | |
44 | /// | |
45 | /// The output of a generation method is not only the stream of new tokens but also the attribute | |
46 | /// information of the current element. These attribute information may contain valuable information | |
47 | /// for any kind of child arguments. | |
48 | struct GenOutput { | |
49 | tokens: TokenStream, | |
50 | attrs: Attrs, | |
51 | } | |
52 | ||
53 | /// Generates the `StructOpt` impl. | |
54 | #[proc_macro_derive(StructOpt, attributes(structopt))] | |
55 | #[proc_macro_error] | |
56 | pub fn structopt(input: proc_macro::TokenStream) -> proc_macro::TokenStream { | |
57 | let input: DeriveInput = syn::parse(input).unwrap(); | |
58 | let gen = impl_structopt(&input); | |
59 | gen.into() | |
60 | } | |
61 | ||
62 | /// Generate a block of code to add arguments/subcommands corresponding to | |
63 | /// the `fields` to an app. | |
64 | fn gen_augmentation( | |
65 | fields: &Punctuated<Field, Comma>, | |
66 | app_var: &Ident, | |
67 | parent_attribute: &Attrs, | |
68 | ) -> TokenStream { | |
69 | let mut subcmds = fields.iter().filter_map(|field| { | |
70 | let attrs = Attrs::from_field( | |
71 | field, | |
72 | Some(parent_attribute), | |
73 | parent_attribute.casing(), | |
74 | parent_attribute.env_casing(), | |
75 | ); | |
76 | let kind = attrs.kind(); | |
77 | if let Kind::Subcommand(ty) = &*kind { | |
78 | let subcmd_type = match (**ty, sub_type(&field.ty)) { | |
79 | (Ty::Option, Some(sub_type)) => sub_type, | |
80 | _ => &field.ty, | |
81 | }; | |
82 | let required = if **ty == Ty::Option { | |
83 | quote!() | |
84 | } else { | |
85 | quote_spanned! { kind.span()=> | |
86 | let #app_var = #app_var.setting( | |
87 | ::structopt::clap::AppSettings::SubcommandRequiredElseHelp | |
88 | ); | |
89 | } | |
90 | }; | |
91 | ||
92 | let span = field.span(); | |
93 | let ts = quote! { | |
94 | let #app_var = <#subcmd_type as ::structopt::StructOptInternal>::augment_clap( | |
95 | #app_var | |
96 | ); | |
97 | #required | |
98 | }; | |
99 | Some((span, ts)) | |
100 | } else { | |
101 | None | |
102 | } | |
103 | }); | |
104 | ||
105 | let subcmd = subcmds.next().map(|(_, ts)| ts); | |
106 | if let Some((span, _)) = subcmds.next() { | |
107 | abort!( | |
108 | span, | |
109 | "multiple subcommand sets are not allowed, that's the second" | |
110 | ); | |
111 | } | |
112 | ||
113 | let args = fields.iter().filter_map(|field| { | |
114 | let attrs = Attrs::from_field( | |
115 | field, | |
116 | Some(parent_attribute), | |
117 | parent_attribute.casing(), | |
118 | parent_attribute.env_casing(), | |
119 | ); | |
120 | let kind = attrs.kind(); | |
121 | match &*kind { | |
122 | Kind::ExternalSubcommand => abort!( | |
123 | kind.span(), | |
124 | "`external_subcommand` is only allowed on enum variants" | |
125 | ), | |
126 | Kind::Subcommand(_) | Kind::Skip(_) => None, | |
127 | Kind::Flatten => { | |
128 | let ty = &field.ty; | |
129 | Some(quote_spanned! { kind.span()=> | |
130 | let #app_var = <#ty as ::structopt::StructOptInternal>::augment_clap(#app_var); | |
131 | let #app_var = if <#ty as ::structopt::StructOptInternal>::is_subcommand() { | |
132 | #app_var.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp) | |
133 | } else { | |
134 | #app_var | |
135 | }; | |
136 | }) | |
137 | } | |
138 | Kind::Arg(ty) => { | |
139 | let convert_type = match **ty { | |
140 | Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty), | |
141 | Ty::OptionOption | Ty::OptionVec => { | |
142 | sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty) | |
143 | } | |
144 | _ => &field.ty, | |
145 | }; | |
146 | ||
147 | let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; | |
148 | let flag = *attrs.parser().kind == ParserKind::FromFlag; | |
149 | ||
150 | let parser = attrs.parser(); | |
151 | let func = &parser.func; | |
152 | let validator = match *parser.kind { | |
153 | ParserKind::TryFromStr => quote_spanned! { func.span()=> | |
154 | .validator(|s| { | |
155 | #func(s.as_str()) | |
156 | .map(|_: #convert_type| ()) | |
157 | .map_err(|e| e.to_string()) | |
158 | }) | |
159 | }, | |
160 | ParserKind::TryFromOsStr => quote_spanned! { func.span()=> | |
161 | .validator_os(|s| #func(&s).map(|_: #convert_type| ())) | |
162 | }, | |
163 | _ => quote!(), | |
164 | }; | |
165 | ||
166 | let modifier = match **ty { | |
167 | Ty::Bool => quote_spanned! { ty.span()=> | |
168 | .takes_value(false) | |
169 | .multiple(false) | |
170 | }, | |
171 | ||
172 | Ty::Option => quote_spanned! { ty.span()=> | |
173 | .takes_value(true) | |
174 | .multiple(false) | |
175 | #validator | |
176 | }, | |
177 | ||
178 | Ty::OptionOption => quote_spanned! { ty.span()=> | |
179 | .takes_value(true) | |
180 | .multiple(false) | |
181 | .min_values(0) | |
182 | .max_values(1) | |
183 | #validator | |
184 | }, | |
185 | ||
186 | Ty::OptionVec => quote_spanned! { ty.span()=> | |
187 | .takes_value(true) | |
188 | .multiple(true) | |
189 | .min_values(0) | |
190 | #validator | |
191 | }, | |
192 | ||
193 | Ty::Vec => quote_spanned! { ty.span()=> | |
194 | .takes_value(true) | |
195 | .multiple(true) | |
196 | #validator | |
197 | }, | |
198 | ||
199 | Ty::Other if occurrences => quote_spanned! { ty.span()=> | |
200 | .takes_value(false) | |
201 | .multiple(true) | |
202 | }, | |
203 | ||
204 | Ty::Other if flag => quote_spanned! { ty.span()=> | |
205 | .takes_value(false) | |
206 | .multiple(false) | |
207 | }, | |
208 | ||
209 | Ty::Other => { | |
210 | let required = !attrs.has_method("default_value"); | |
211 | quote_spanned! { ty.span()=> | |
212 | .takes_value(true) | |
213 | .multiple(false) | |
214 | .required(#required) | |
215 | #validator | |
216 | } | |
217 | } | |
218 | }; | |
219 | ||
220 | let name = attrs.cased_name(); | |
221 | let methods = attrs.field_methods(); | |
222 | ||
223 | Some(quote_spanned! { field.span()=> | |
224 | let #app_var = #app_var.arg( | |
225 | ::structopt::clap::Arg::with_name(#name) | |
226 | #modifier | |
227 | #methods | |
228 | ); | |
229 | }) | |
230 | } | |
231 | } | |
232 | }); | |
233 | ||
234 | let app_methods = parent_attribute.top_level_methods(); | |
235 | let version = parent_attribute.version(); | |
236 | quote! {{ | |
237 | let #app_var = #app_var#app_methods; | |
238 | #( #args )* | |
239 | #subcmd | |
240 | #app_var#version | |
241 | }} | |
242 | } | |
243 | ||
244 | fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs) -> TokenStream { | |
245 | // This ident is used in several match branches below, | |
246 | // and the `quote[_spanned]` invocations have different spans. | |
247 | // | |
248 | // Given that this ident is used in several places and | |
249 | // that the branches are located inside of a loop, it is possible that | |
250 | // this ident will be given _different_ spans in different places, and | |
251 | // thus will not be the _same_ ident anymore. To make sure the `matches` | |
252 | // is always the same, we factor it out. | |
253 | let matches = format_ident!("matches"); | |
254 | ||
255 | let fields = fields.iter().map(|field| { | |
256 | let attrs = Attrs::from_field( | |
257 | field, | |
258 | Some(parent_attribute), | |
259 | parent_attribute.casing(), | |
260 | parent_attribute.env_casing(), | |
261 | ); | |
262 | let field_name = field.ident.as_ref().unwrap(); | |
263 | let kind = attrs.kind(); | |
264 | match &*kind { | |
265 | Kind::ExternalSubcommand => abort!( | |
266 | kind.span(), | |
267 | "`external_subcommand` is allowed only on enum variants" | |
268 | ), | |
269 | ||
270 | Kind::Subcommand(ty) => { | |
271 | let subcmd_type = match (**ty, sub_type(&field.ty)) { | |
272 | (Ty::Option, Some(sub_type)) => sub_type, | |
273 | _ => &field.ty, | |
274 | }; | |
275 | let unwrapper = match **ty { | |
276 | Ty::Option => quote!(), | |
277 | _ => quote_spanned!( ty.span()=> .unwrap() ), | |
278 | }; | |
279 | quote_spanned! { kind.span()=> | |
280 | #field_name: <#subcmd_type as ::structopt::StructOptInternal>::from_subcommand( | |
281 | #matches.subcommand()) | |
282 | #unwrapper | |
283 | } | |
284 | } | |
285 | ||
286 | Kind::Flatten => quote_spanned! { kind.span()=> | |
287 | #field_name: ::structopt::StructOpt::from_clap(#matches) | |
288 | }, | |
289 | ||
290 | Kind::Skip(val) => match val { | |
291 | None => quote_spanned!(kind.span()=> #field_name: Default::default()), | |
292 | Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()), | |
293 | }, | |
294 | ||
295 | Kind::Arg(ty) => { | |
296 | use crate::attrs::ParserKind::*; | |
297 | ||
298 | let parser = attrs.parser(); | |
299 | let func = &parser.func; | |
300 | let span = parser.kind.span(); | |
301 | let (value_of, values_of, parse) = match *parser.kind { | |
302 | FromStr => ( | |
303 | quote_spanned!(span=> value_of), | |
304 | quote_spanned!(span=> values_of), | |
305 | func.clone(), | |
306 | ), | |
307 | TryFromStr => ( | |
308 | quote_spanned!(span=> value_of), | |
309 | quote_spanned!(span=> values_of), | |
310 | quote_spanned!(func.span()=> |s| #func(s).unwrap()), | |
311 | ), | |
312 | FromOsStr => ( | |
313 | quote_spanned!(span=> value_of_os), | |
314 | quote_spanned!(span=> values_of_os), | |
315 | func.clone(), | |
316 | ), | |
317 | TryFromOsStr => ( | |
318 | quote_spanned!(span=> value_of_os), | |
319 | quote_spanned!(span=> values_of_os), | |
320 | quote_spanned!(func.span()=> |s| #func(s).unwrap()), | |
321 | ), | |
322 | FromOccurrences => ( | |
323 | quote_spanned!(span=> occurrences_of), | |
324 | quote!(), | |
325 | func.clone(), | |
326 | ), | |
327 | FromFlag => (quote!(), quote!(), func.clone()), | |
328 | }; | |
329 | ||
330 | let flag = *attrs.parser().kind == ParserKind::FromFlag; | |
331 | let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; | |
332 | let name = attrs.cased_name(); | |
a2a8927a XL |
333 | let convert_type = match **ty { |
334 | Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty), | |
335 | Ty::OptionOption | Ty::OptionVec => { | |
336 | sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty) | |
337 | } | |
338 | _ => &field.ty, | |
339 | }; | |
f20569fa XL |
340 | let field_value = match **ty { |
341 | Ty::Bool => quote_spanned!(ty.span()=> #matches.is_present(#name)), | |
342 | ||
343 | Ty::Option => quote_spanned! { ty.span()=> | |
344 | #matches.#value_of(#name) | |
345 | .map(#parse) | |
346 | }, | |
347 | ||
348 | Ty::OptionOption => quote_spanned! { ty.span()=> | |
349 | if #matches.is_present(#name) { | |
350 | Some(#matches.#value_of(#name).map(#parse)) | |
351 | } else { | |
352 | None | |
353 | } | |
354 | }, | |
355 | ||
356 | Ty::OptionVec => quote_spanned! { ty.span()=> | |
357 | if #matches.is_present(#name) { | |
358 | Some(#matches.#values_of(#name) | |
a2a8927a | 359 | .map_or_else(Vec::new, |v| v.map::<#convert_type, _>(#parse).collect())) |
f20569fa XL |
360 | } else { |
361 | None | |
362 | } | |
363 | }, | |
364 | ||
365 | Ty::Vec => quote_spanned! { ty.span()=> | |
366 | #matches.#values_of(#name) | |
a2a8927a | 367 | .map_or_else(Vec::new, |v| v.map::<#convert_type, _>(#parse).collect()) |
f20569fa XL |
368 | }, |
369 | ||
370 | Ty::Other if occurrences => quote_spanned! { ty.span()=> | |
371 | #parse(#matches.#value_of(#name)) | |
372 | }, | |
373 | ||
374 | Ty::Other if flag => quote_spanned! { ty.span()=> | |
375 | #parse(#matches.is_present(#name)) | |
376 | }, | |
377 | ||
378 | Ty::Other => quote_spanned! { ty.span()=> | |
379 | #matches.#value_of(#name) | |
380 | .map(#parse) | |
381 | .unwrap() | |
382 | }, | |
383 | }; | |
384 | ||
385 | quote_spanned!(field.span()=> #field_name: #field_value ) | |
386 | } | |
387 | } | |
388 | }); | |
389 | ||
390 | quote! {{ | |
391 | #( #fields ),* | |
392 | }} | |
393 | } | |
394 | ||
395 | fn gen_from_clap( | |
396 | struct_name: &Ident, | |
397 | fields: &Punctuated<Field, Comma>, | |
398 | parent_attribute: &Attrs, | |
399 | ) -> TokenStream { | |
400 | let field_block = gen_constructor(fields, parent_attribute); | |
401 | ||
402 | quote! { | |
403 | fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self { | |
404 | #struct_name #field_block | |
405 | } | |
406 | } | |
407 | } | |
408 | ||
409 | fn gen_clap(attrs: &[Attribute]) -> GenOutput { | |
410 | let name = std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default(); | |
411 | ||
412 | let attrs = Attrs::from_struct( | |
413 | Span::call_site(), | |
414 | attrs, | |
415 | Name::Assigned(quote!(#name)), | |
416 | None, | |
417 | Sp::call_site(DEFAULT_CASING), | |
418 | Sp::call_site(DEFAULT_ENV_CASING), | |
a2a8927a | 419 | false, |
f20569fa XL |
420 | ); |
421 | let tokens = { | |
422 | let name = attrs.cased_name(); | |
423 | quote!(::structopt::clap::App::new(#name)) | |
424 | }; | |
425 | ||
426 | GenOutput { tokens, attrs } | |
427 | } | |
428 | ||
429 | fn gen_clap_struct(struct_attrs: &[Attribute]) -> GenOutput { | |
430 | let initial_clap_app_gen = gen_clap(struct_attrs); | |
431 | let clap_tokens = initial_clap_app_gen.tokens; | |
432 | ||
433 | let augmented_tokens = quote! { | |
434 | fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> { | |
435 | let app = #clap_tokens; | |
436 | <Self as ::structopt::StructOptInternal>::augment_clap(app) | |
437 | } | |
438 | }; | |
439 | ||
440 | GenOutput { | |
441 | tokens: augmented_tokens, | |
442 | attrs: initial_clap_app_gen.attrs, | |
443 | } | |
444 | } | |
445 | ||
446 | fn gen_augment_clap(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs) -> TokenStream { | |
447 | let app_var = Ident::new("app", Span::call_site()); | |
448 | let augmentation = gen_augmentation(fields, &app_var, parent_attribute); | |
449 | quote! { | |
450 | fn augment_clap<'a, 'b>( | |
451 | #app_var: ::structopt::clap::App<'a, 'b> | |
452 | ) -> ::structopt::clap::App<'a, 'b> { | |
453 | #augmentation | |
454 | } | |
455 | } | |
456 | } | |
457 | ||
458 | fn gen_clap_enum(enum_attrs: &[Attribute]) -> GenOutput { | |
459 | let initial_clap_app_gen = gen_clap(enum_attrs); | |
460 | let clap_tokens = initial_clap_app_gen.tokens; | |
461 | ||
462 | let tokens = quote! { | |
463 | fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> { | |
464 | let app = #clap_tokens | |
465 | .setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp); | |
466 | <Self as ::structopt::StructOptInternal>::augment_clap(app) | |
467 | } | |
468 | }; | |
469 | ||
470 | GenOutput { | |
471 | tokens, | |
472 | attrs: initial_clap_app_gen.attrs, | |
473 | } | |
474 | } | |
475 | ||
476 | fn gen_augment_clap_enum( | |
477 | variants: &Punctuated<Variant, Comma>, | |
478 | parent_attribute: &Attrs, | |
479 | ) -> TokenStream { | |
480 | use syn::Fields::*; | |
481 | ||
a2a8927a | 482 | let subcommands = variants.iter().filter_map(|variant| { |
f20569fa XL |
483 | let attrs = Attrs::from_struct( |
484 | variant.span(), | |
485 | &variant.attrs, | |
486 | Name::Derived(variant.ident.clone()), | |
487 | Some(parent_attribute), | |
488 | parent_attribute.casing(), | |
489 | parent_attribute.env_casing(), | |
a2a8927a | 490 | true, |
f20569fa XL |
491 | ); |
492 | ||
493 | let kind = attrs.kind(); | |
494 | match &*kind { | |
a2a8927a XL |
495 | Kind::Skip(_) => None, |
496 | ||
f20569fa | 497 | Kind::ExternalSubcommand => { |
a2a8927a XL |
498 | let app_var = Ident::new("app", Span::call_site()); |
499 | Some(quote_spanned! { attrs.kind().span()=> | |
500 | let #app_var = #app_var.setting( | |
f20569fa XL |
501 | ::structopt::clap::AppSettings::AllowExternalSubcommands |
502 | ); | |
a2a8927a | 503 | }) |
f20569fa XL |
504 | }, |
505 | ||
506 | Kind::Flatten => { | |
507 | match variant.fields { | |
508 | Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { | |
509 | let ty = &unnamed[0]; | |
a2a8927a | 510 | Some(quote! { |
f20569fa | 511 | let app = <#ty as ::structopt::StructOptInternal>::augment_clap(app); |
a2a8927a | 512 | }) |
f20569fa XL |
513 | }, |
514 | _ => abort!( | |
515 | variant, | |
516 | "`flatten` is usable only with single-typed tuple variants" | |
517 | ), | |
518 | } | |
519 | }, | |
520 | ||
521 | _ => { | |
522 | let app_var = Ident::new("subcommand", Span::call_site()); | |
a2a8927a XL |
523 | let from_attrs = attrs.top_level_methods(); |
524 | let version = attrs.version(); | |
525 | ||
f20569fa | 526 | let arg_block = match variant.fields { |
a2a8927a XL |
527 | // If the variant is named, then gen_augmentation already generates the |
528 | // top level methods (#from_attrs) and version. | |
f20569fa | 529 | Named(ref fields) => gen_augmentation(&fields.named, &app_var, &attrs), |
a2a8927a | 530 | Unit => quote!( #app_var#from_attrs#version ), |
f20569fa XL |
531 | Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { |
532 | let ty = &unnamed[0]; | |
533 | quote_spanned! { ty.span()=> | |
534 | { | |
535 | let #app_var = <#ty as ::structopt::StructOptInternal>::augment_clap( | |
536 | #app_var | |
537 | ); | |
538 | if <#ty as ::structopt::StructOptInternal>::is_subcommand() { | |
539 | #app_var.setting( | |
540 | ::structopt::clap::AppSettings::SubcommandRequiredElseHelp | |
541 | ) | |
542 | } else { | |
543 | #app_var | |
a2a8927a | 544 | }#from_attrs#version |
f20569fa XL |
545 | } |
546 | } | |
547 | } | |
548 | Unnamed(..) => abort!(variant, "non single-typed tuple enums are not supported"), | |
549 | }; | |
550 | ||
551 | let name = attrs.cased_name(); | |
a2a8927a | 552 | Some(quote! { |
f20569fa XL |
553 | let app = app.subcommand({ |
554 | let #app_var = ::structopt::clap::SubCommand::with_name(#name); | |
a2a8927a | 555 | #arg_block |
f20569fa | 556 | }); |
a2a8927a | 557 | }) |
f20569fa XL |
558 | }, |
559 | } | |
560 | }); | |
561 | ||
562 | let app_methods = parent_attribute.top_level_methods(); | |
563 | let version = parent_attribute.version(); | |
564 | quote! { | |
565 | fn augment_clap<'a, 'b>( | |
566 | app: ::structopt::clap::App<'a, 'b> | |
567 | ) -> ::structopt::clap::App<'a, 'b> { | |
568 | let app = app #app_methods; | |
569 | #( #subcommands )*; | |
570 | app #version | |
571 | } | |
572 | } | |
573 | } | |
574 | ||
a2a8927a | 575 | fn gen_from_clap_enum() -> TokenStream { |
f20569fa XL |
576 | quote! { |
577 | fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self { | |
a2a8927a | 578 | <Self as ::structopt::StructOptInternal>::from_subcommand(matches.subcommand()) |
f20569fa XL |
579 | .expect("structopt misuse: You likely tried to #[flatten] a struct \ |
580 | that contains #[subcommand]. This is forbidden.") | |
581 | } | |
582 | } | |
583 | } | |
584 | ||
585 | fn gen_from_subcommand( | |
586 | name: &Ident, | |
587 | variants: &Punctuated<Variant, Comma>, | |
588 | parent_attribute: &Attrs, | |
589 | ) -> TokenStream { | |
590 | use syn::Fields::*; | |
591 | ||
592 | let mut ext_subcmd = None; | |
593 | ||
594 | let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants | |
595 | .iter() | |
596 | .filter_map(|variant| { | |
597 | let attrs = Attrs::from_struct( | |
598 | variant.span(), | |
599 | &variant.attrs, | |
600 | Name::Derived(variant.ident.clone()), | |
601 | Some(parent_attribute), | |
602 | parent_attribute.casing(), | |
603 | parent_attribute.env_casing(), | |
a2a8927a | 604 | true, |
f20569fa XL |
605 | ); |
606 | ||
607 | let variant_name = &variant.ident; | |
608 | ||
a2a8927a XL |
609 | match *attrs.kind() { |
610 | Kind::ExternalSubcommand => { | |
611 | if ext_subcmd.is_some() { | |
612 | abort!( | |
613 | attrs.kind().span(), | |
614 | "Only one variant can be marked with `external_subcommand`, \ | |
f20569fa | 615 | this is the second" |
a2a8927a XL |
616 | ); |
617 | } | |
f20569fa | 618 | |
a2a8927a XL |
619 | let ty = match variant.fields { |
620 | Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, | |
f20569fa | 621 | |
a2a8927a XL |
622 | _ => abort!( |
623 | variant, | |
624 | "The enum variant marked with `external_attribute` must be \ | |
f20569fa XL |
625 | a single-typed tuple, and the type must be either `Vec<String>` \ |
626 | or `Vec<OsString>`." | |
a2a8927a XL |
627 | ), |
628 | }; | |
f20569fa | 629 | |
a2a8927a XL |
630 | let (span, str_ty, values_of) = match subty_if_name(ty, "Vec") { |
631 | Some(subty) => { | |
632 | if is_simple_ty(subty, "String") { | |
633 | ( | |
634 | subty.span(), | |
635 | quote!(::std::string::String), | |
636 | quote!(values_of), | |
637 | ) | |
638 | } else { | |
639 | ( | |
640 | subty.span(), | |
641 | quote!(::std::ffi::OsString), | |
642 | quote!(values_of_os), | |
643 | ) | |
644 | } | |
f20569fa | 645 | } |
f20569fa | 646 | |
a2a8927a XL |
647 | None => abort!( |
648 | ty, | |
649 | "The type must be either `Vec<String>` or `Vec<OsString>` \ | |
f20569fa | 650 | to be used with `external_subcommand`." |
a2a8927a XL |
651 | ), |
652 | }; | |
f20569fa | 653 | |
a2a8927a XL |
654 | ext_subcmd = Some((span, variant_name, str_ty, values_of)); |
655 | None | |
656 | } | |
657 | Kind::Skip(_) => None, | |
658 | _ => Some((variant, attrs)), | |
f20569fa XL |
659 | } |
660 | }) | |
661 | .partition(|(_, attrs)| match &*attrs.kind() { | |
662 | Kind::Flatten => true, | |
663 | _ => false, | |
664 | }); | |
665 | ||
a2a8927a XL |
666 | let other = format_ident!("other"); |
667 | let matches = format_ident!("matches"); | |
668 | ||
f20569fa XL |
669 | let external = match ext_subcmd { |
670 | Some((span, var_name, str_ty, values_of)) => quote_spanned! { span=> | |
a2a8927a | 671 | match #other { |
f20569fa XL |
672 | ("", ::std::option::Option::None) => None, |
673 | ||
a2a8927a | 674 | (external, Some(#matches)) => { |
f20569fa XL |
675 | ::std::option::Option::Some(#name::#var_name( |
676 | ::std::iter::once(#str_ty::from(external)) | |
677 | .chain( | |
a2a8927a | 678 | #matches.#values_of("").into_iter().flatten().map(#str_ty::from) |
f20569fa XL |
679 | ) |
680 | .collect::<::std::vec::Vec<_>>() | |
681 | )) | |
682 | } | |
683 | ||
684 | (external, None) => { | |
685 | ::std::option::Option::Some(#name::#var_name( | |
686 | ::std::iter::once(#str_ty::from(external)) | |
687 | .collect::<::std::vec::Vec<_>>() | |
688 | )) | |
689 | } | |
690 | } | |
691 | }, | |
692 | ||
693 | None => quote!(None), | |
694 | }; | |
695 | ||
696 | let match_arms = variants.iter().map(|(variant, attrs)| { | |
697 | let sub_name = attrs.cased_name(); | |
698 | let variant_name = &variant.ident; | |
699 | let constructor_block = match variant.fields { | |
700 | Named(ref fields) => gen_constructor(&fields.named, &attrs), | |
701 | Unit => quote!(), | |
702 | Unnamed(ref fields) if fields.unnamed.len() == 1 => { | |
703 | let ty = &fields.unnamed[0]; | |
a2a8927a | 704 | quote!( ( <#ty as ::structopt::StructOpt>::from_clap(#matches) ) ) |
f20569fa XL |
705 | } |
706 | Unnamed(..) => abort!( | |
707 | variant.ident, | |
708 | "non single-typed tuple enums are not supported" | |
709 | ), | |
710 | }; | |
711 | ||
712 | quote! { | |
a2a8927a | 713 | (#sub_name, Some(#matches)) => { |
f20569fa XL |
714 | Some(#name :: #variant_name #constructor_block) |
715 | } | |
716 | } | |
717 | }); | |
718 | ||
719 | let child_subcommands = flatten_variants.iter().map(|(variant, _attrs)| { | |
720 | let variant_name = &variant.ident; | |
721 | match variant.fields { | |
722 | Unnamed(ref fields) if fields.unnamed.len() == 1 => { | |
723 | let ty = &fields.unnamed[0]; | |
724 | quote! { | |
725 | if let Some(res) = | |
a2a8927a | 726 | <#ty as ::structopt::StructOptInternal>::from_subcommand(#other) |
f20569fa XL |
727 | { |
728 | return Some(#name :: #variant_name (res)); | |
729 | } | |
730 | } | |
731 | } | |
732 | _ => abort!( | |
733 | variant, | |
734 | "`flatten` is usable only with single-typed tuple variants" | |
735 | ), | |
736 | } | |
737 | }); | |
738 | ||
739 | quote! { | |
740 | fn from_subcommand<'a, 'b>( | |
741 | sub: (&'b str, Option<&'b ::structopt::clap::ArgMatches<'a>>) | |
742 | ) -> Option<Self> { | |
743 | match sub { | |
744 | #( #match_arms, )* | |
a2a8927a | 745 | #other => { |
f20569fa XL |
746 | #( #child_subcommands )else*; |
747 | #external | |
748 | } | |
749 | } | |
750 | } | |
751 | } | |
752 | } | |
753 | ||
754 | #[cfg(feature = "paw")] | |
a2a8927a XL |
755 | fn gen_paw_impl( |
756 | impl_generics: &ImplGenerics, | |
757 | name: &Ident, | |
758 | ty_generics: &TypeGenerics, | |
759 | where_clause: &TokenStream, | |
760 | ) -> TokenStream { | |
f20569fa | 761 | quote! { |
a2a8927a | 762 | impl #impl_generics ::structopt::paw::ParseArgs for #name #ty_generics #where_clause { |
f20569fa XL |
763 | type Error = std::io::Error; |
764 | ||
765 | fn parse_args() -> std::result::Result<Self, Self::Error> { | |
766 | Ok(<#name as ::structopt::StructOpt>::from_args()) | |
767 | } | |
768 | } | |
769 | } | |
770 | } | |
771 | #[cfg(not(feature = "paw"))] | |
a2a8927a | 772 | fn gen_paw_impl(_: &ImplGenerics, _: &Ident, _: &TypeGenerics, _: &TokenStream) -> TokenStream { |
f20569fa XL |
773 | TokenStream::new() |
774 | } | |
775 | ||
a2a8927a XL |
776 | fn split_structopt_generics_for_impl( |
777 | generics: &Generics, | |
778 | ) -> (ImplGenerics, TypeGenerics, TokenStream) { | |
779 | use syn::{token::Add, TypeParamBound::Trait}; | |
780 | ||
781 | fn path_ends_with(path: &Path, ident: &str) -> bool { | |
782 | path.segments.last().unwrap().ident == ident | |
783 | } | |
784 | ||
785 | fn type_param_bounds_contains(bounds: &Punctuated<TypeParamBound, Add>, ident: &str) -> bool { | |
786 | for bound in bounds { | |
787 | if let Trait(bound) = bound { | |
788 | if path_ends_with(&bound.path, ident) { | |
789 | return true; | |
790 | } | |
791 | } | |
792 | } | |
793 | return false; | |
794 | } | |
795 | ||
796 | struct TraitBoundAmendments { | |
797 | tokens: TokenStream, | |
798 | need_where: bool, | |
799 | need_comma: bool, | |
800 | } | |
801 | ||
802 | impl TraitBoundAmendments { | |
803 | fn new(where_clause: Option<&WhereClause>) -> Self { | |
804 | let tokens = TokenStream::new(); | |
805 | let (need_where, need_comma) = if let Some(where_clause) = where_clause { | |
806 | if where_clause.predicates.trailing_punct() { | |
807 | (false, false) | |
808 | } else { | |
809 | (false, true) | |
810 | } | |
811 | } else { | |
812 | (true, false) | |
813 | }; | |
814 | Self { | |
815 | tokens, | |
816 | need_where, | |
817 | need_comma, | |
818 | } | |
819 | } | |
820 | ||
821 | fn add(&mut self, amendment: TokenStream) { | |
822 | if self.need_where { | |
823 | self.tokens.extend(quote! { where }); | |
824 | self.need_where = false; | |
825 | } | |
826 | if self.need_comma { | |
827 | self.tokens.extend(quote! { , }); | |
828 | } | |
829 | self.tokens.extend(amendment); | |
830 | self.need_comma = true; | |
831 | } | |
832 | ||
833 | fn into_tokens(self) -> TokenStream { | |
834 | self.tokens | |
835 | } | |
836 | } | |
837 | ||
838 | let mut trait_bound_amendments = TraitBoundAmendments::new(generics.where_clause.as_ref()); | |
839 | ||
840 | for param in &generics.params { | |
841 | if let GenericParam::Type(param) = param { | |
842 | let param_ident = ¶m.ident; | |
843 | if type_param_bounds_contains(¶m.bounds, "StructOpt") { | |
844 | trait_bound_amendments | |
845 | .add(quote! { #param_ident : ::structopt::StructOptInternal }); | |
846 | } | |
847 | } | |
848 | } | |
849 | ||
850 | if let Some(where_clause) = &generics.where_clause { | |
851 | for predicate in &where_clause.predicates { | |
852 | if let WherePredicate::Type(predicate) = predicate { | |
853 | let predicate_bounded_ty = &predicate.bounded_ty; | |
854 | if type_param_bounds_contains(&predicate.bounds, "StructOpt") { | |
855 | trait_bound_amendments | |
856 | .add(quote! { #predicate_bounded_ty : ::structopt::StructOptInternal }); | |
857 | } | |
858 | } | |
859 | } | |
860 | } | |
861 | ||
862 | let trait_bound_amendments = trait_bound_amendments.into_tokens(); | |
863 | ||
864 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); | |
865 | ||
866 | let where_clause = quote! { #where_clause #trait_bound_amendments }; | |
867 | ||
868 | (impl_generics, ty_generics, where_clause) | |
869 | } | |
870 | ||
f20569fa XL |
871 | fn impl_structopt_for_struct( |
872 | name: &Ident, | |
873 | fields: &Punctuated<Field, Comma>, | |
874 | attrs: &[Attribute], | |
a2a8927a | 875 | generics: &Generics, |
f20569fa | 876 | ) -> TokenStream { |
a2a8927a XL |
877 | let (impl_generics, ty_generics, where_clause) = split_structopt_generics_for_impl(&generics); |
878 | ||
f20569fa XL |
879 | let basic_clap_app_gen = gen_clap_struct(attrs); |
880 | let augment_clap = gen_augment_clap(fields, &basic_clap_app_gen.attrs); | |
881 | let from_clap = gen_from_clap(name, fields, &basic_clap_app_gen.attrs); | |
a2a8927a | 882 | let paw_impl = gen_paw_impl(&impl_generics, name, &ty_generics, &where_clause); |
f20569fa XL |
883 | |
884 | let clap_tokens = basic_clap_app_gen.tokens; | |
885 | quote! { | |
886 | #[allow(unused_variables)] | |
887 | #[allow(unknown_lints)] | |
888 | #[allow( | |
889 | clippy::style, | |
890 | clippy::complexity, | |
891 | clippy::pedantic, | |
892 | clippy::restriction, | |
893 | clippy::perf, | |
894 | clippy::deprecated, | |
895 | clippy::nursery, | |
896 | clippy::cargo | |
897 | )] | |
898 | #[deny(clippy::correctness)] | |
899 | #[allow(dead_code, unreachable_code)] | |
a2a8927a | 900 | impl #impl_generics ::structopt::StructOpt for #name #ty_generics #where_clause { |
f20569fa XL |
901 | #clap_tokens |
902 | #from_clap | |
903 | } | |
904 | ||
905 | #[allow(unused_variables)] | |
906 | #[allow(unknown_lints)] | |
907 | #[allow( | |
908 | clippy::style, | |
909 | clippy::complexity, | |
910 | clippy::pedantic, | |
911 | clippy::restriction, | |
912 | clippy::perf, | |
913 | clippy::deprecated, | |
914 | clippy::nursery, | |
915 | clippy::cargo | |
916 | )] | |
917 | #[deny(clippy::correctness)] | |
918 | #[allow(dead_code, unreachable_code)] | |
a2a8927a | 919 | impl #impl_generics ::structopt::StructOptInternal for #name #ty_generics #where_clause { |
f20569fa XL |
920 | #augment_clap |
921 | fn is_subcommand() -> bool { false } | |
922 | } | |
923 | ||
924 | #paw_impl | |
925 | } | |
926 | } | |
927 | ||
928 | fn impl_structopt_for_enum( | |
929 | name: &Ident, | |
930 | variants: &Punctuated<Variant, Comma>, | |
931 | attrs: &[Attribute], | |
a2a8927a | 932 | generics: &Generics, |
f20569fa | 933 | ) -> TokenStream { |
a2a8927a XL |
934 | let (impl_generics, ty_generics, where_clause) = split_structopt_generics_for_impl(&generics); |
935 | ||
f20569fa XL |
936 | let basic_clap_app_gen = gen_clap_enum(attrs); |
937 | let clap_tokens = basic_clap_app_gen.tokens; | |
938 | let attrs = basic_clap_app_gen.attrs; | |
939 | ||
940 | let augment_clap = gen_augment_clap_enum(variants, &attrs); | |
a2a8927a | 941 | let from_clap = gen_from_clap_enum(); |
f20569fa | 942 | let from_subcommand = gen_from_subcommand(name, variants, &attrs); |
a2a8927a | 943 | let paw_impl = gen_paw_impl(&impl_generics, name, &ty_generics, &where_clause); |
f20569fa XL |
944 | |
945 | quote! { | |
946 | #[allow(unknown_lints)] | |
947 | #[allow(unused_variables, dead_code, unreachable_code)] | |
948 | #[allow( | |
949 | clippy::style, | |
950 | clippy::complexity, | |
951 | clippy::pedantic, | |
952 | clippy::restriction, | |
953 | clippy::perf, | |
954 | clippy::deprecated, | |
955 | clippy::nursery, | |
956 | clippy::cargo | |
957 | )] | |
958 | #[deny(clippy::correctness)] | |
a2a8927a | 959 | impl #impl_generics ::structopt::StructOpt for #name #ty_generics #where_clause { |
f20569fa XL |
960 | #clap_tokens |
961 | #from_clap | |
962 | } | |
963 | ||
964 | #[allow(unused_variables)] | |
965 | #[allow(unknown_lints)] | |
966 | #[allow( | |
967 | clippy::style, | |
968 | clippy::complexity, | |
969 | clippy::pedantic, | |
970 | clippy::restriction, | |
971 | clippy::perf, | |
972 | clippy::deprecated, | |
973 | clippy::nursery, | |
974 | clippy::cargo | |
975 | )] | |
976 | #[deny(clippy::correctness)] | |
977 | #[allow(dead_code, unreachable_code)] | |
a2a8927a | 978 | impl #impl_generics ::structopt::StructOptInternal for #name #ty_generics #where_clause { |
f20569fa XL |
979 | #augment_clap |
980 | #from_subcommand | |
981 | fn is_subcommand() -> bool { true } | |
982 | } | |
983 | ||
984 | #paw_impl | |
985 | } | |
986 | } | |
987 | ||
988 | fn impl_structopt(input: &DeriveInput) -> TokenStream { | |
989 | use syn::Data::*; | |
990 | ||
991 | let struct_name = &input.ident; | |
992 | ||
993 | set_dummy(quote! { | |
994 | impl ::structopt::StructOpt for #struct_name { | |
995 | fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> { | |
996 | unimplemented!() | |
997 | } | |
998 | fn from_clap(_matches: &::structopt::clap::ArgMatches) -> Self { | |
999 | unimplemented!() | |
1000 | } | |
1001 | } | |
1002 | ||
1003 | impl ::structopt::StructOptInternal for #struct_name {} | |
1004 | }); | |
1005 | ||
1006 | match input.data { | |
1007 | Struct(DataStruct { | |
1008 | fields: syn::Fields::Named(ref fields), | |
1009 | .. | |
a2a8927a XL |
1010 | }) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs, &input.generics), |
1011 | Enum(ref e) => { | |
1012 | impl_structopt_for_enum(struct_name, &e.variants, &input.attrs, &input.generics) | |
1013 | } | |
f20569fa XL |
1014 | _ => abort_call_site!("structopt only supports non-tuple structs and enums"), |
1015 | } | |
1016 | } |