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