blob: 0838f50458d59f5edb2ce6155668724796a0159f [file] [log] [blame]
Matthew Maurerdc6194e2020-06-02 11:15:18 -07001// 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)]
Haibo Huangfef3b762020-10-28 22:33:57 -070014// FIXME: remove when and if our MSRV hits 1.42
15#![allow(clippy::match_like_matches_macro)]
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -070016#![forbid(unsafe_code)]
Matthew Maurerdc6194e2020-06-02 11:15:18 -070017
18extern crate proc_macro;
19
20mod attrs;
21mod doc_comments;
22mod parse;
23mod spanned;
24mod ty;
25
26use crate::{
27 attrs::{Attrs, CasingStyle, Kind, Name, ParserKind},
28 spanned::Sp,
29 ty::{is_simple_ty, sub_type, subty_if_name, Ty},
30};
31
32use proc_macro2::{Span, TokenStream};
33use proc_macro_error::{abort, abort_call_site, proc_macro_error, set_dummy};
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -070034use quote::{format_ident, quote, quote_spanned};
Matthew Maurerdc6194e2020-06-02 11:15:18 -070035use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, *};
36
37/// Default casing style for generated arguments.
38const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab;
39
40/// Default casing style for environment variables
41const 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.
48struct 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]
56pub 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.
64fn 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
244fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs) -> TokenStream {
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700245 // 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
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700255 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(
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700281 #matches.subcommand())
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700282 #unwrapper
283 }
284 }
285
286 Kind::Flatten => quote_spanned! { kind.span()=>
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700287 #field_name: ::structopt::StructOpt::from_clap(#matches)
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700288 },
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();
Joel Galenson564f03e2021-09-22 11:22:52 -0700333 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 };
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700340 let field_value = match **ty {
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700341 Ty::Bool => quote_spanned!(ty.span()=> #matches.is_present(#name)),
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700342
343 Ty::Option => quote_spanned! { ty.span()=>
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700344 #matches.#value_of(#name)
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700345 .map(#parse)
346 },
347
348 Ty::OptionOption => quote_spanned! { ty.span()=>
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700349 if #matches.is_present(#name) {
350 Some(#matches.#value_of(#name).map(#parse))
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700351 } else {
352 None
353 }
354 },
355
356 Ty::OptionVec => quote_spanned! { ty.span()=>
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700357 if #matches.is_present(#name) {
358 Some(#matches.#values_of(#name)
Joel Galenson564f03e2021-09-22 11:22:52 -0700359 .map_or_else(Vec::new, |v| v.map::<#convert_type, _>(#parse).collect()))
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700360 } else {
361 None
362 }
363 },
364
365 Ty::Vec => quote_spanned! { ty.span()=>
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700366 #matches.#values_of(#name)
Joel Galenson564f03e2021-09-22 11:22:52 -0700367 .map_or_else(Vec::new, |v| v.map::<#convert_type, _>(#parse).collect())
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700368 },
369
370 Ty::Other if occurrences => quote_spanned! { ty.span()=>
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700371 #parse(#matches.#value_of(#name))
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700372 },
373
374 Ty::Other if flag => quote_spanned! { ty.span()=>
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700375 #parse(#matches.is_present(#name))
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700376 },
377
378 Ty::Other => quote_spanned! { ty.span()=>
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700379 #matches.#value_of(#name)
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700380 .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
395fn 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
409fn 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),
Joel Galenson564f03e2021-09-22 11:22:52 -0700419 false,
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700420 );
421 let tokens = {
422 let name = attrs.cased_name();
423 quote!(::structopt::clap::App::new(#name))
424 };
425
426 GenOutput { tokens, attrs }
427}
428
429fn 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
446fn 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
458fn 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
476fn gen_augment_clap_enum(
477 variants: &Punctuated<Variant, Comma>,
478 parent_attribute: &Attrs,
479) -> TokenStream {
480 use syn::Fields::*;
481
Joel Galenson564f03e2021-09-22 11:22:52 -0700482 let subcommands = variants.iter().filter_map(|variant| {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700483 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(),
Joel Galenson564f03e2021-09-22 11:22:52 -0700490 true,
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700491 );
492
493 let kind = attrs.kind();
494 match &*kind {
Joel Galenson564f03e2021-09-22 11:22:52 -0700495 Kind::Skip(_) => None,
496
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700497 Kind::ExternalSubcommand => {
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700498 let app_var = Ident::new("app", Span::call_site());
Joel Galenson564f03e2021-09-22 11:22:52 -0700499 Some(quote_spanned! { attrs.kind().span()=>
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700500 let #app_var = #app_var.setting(
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700501 ::structopt::clap::AppSettings::AllowExternalSubcommands
502 );
Joel Galenson564f03e2021-09-22 11:22:52 -0700503 })
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700504 },
505
506 Kind::Flatten => {
507 match variant.fields {
508 Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
509 let ty = &unnamed[0];
Joel Galenson564f03e2021-09-22 11:22:52 -0700510 Some(quote! {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700511 let app = <#ty as ::structopt::StructOptInternal>::augment_clap(app);
Joel Galenson564f03e2021-09-22 11:22:52 -0700512 })
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700513 },
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());
David LeGareb66219a2022-03-02 16:21:26 +0000523 let from_attrs = attrs.top_level_methods();
524 let version = attrs.version();
525
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700526 let arg_block = match variant.fields {
David LeGareb66219a2022-03-02 16:21:26 +0000527 // If the variant is named, then gen_augmentation already generates the
528 // top level methods (#from_attrs) and version.
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700529 Named(ref fields) => gen_augmentation(&fields.named, &app_var, &attrs),
David LeGareb66219a2022-03-02 16:21:26 +0000530 Unit => quote!( #app_var#from_attrs#version ),
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700531 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
David LeGareb66219a2022-03-02 16:21:26 +0000544 }#from_attrs#version
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700545 }
546 }
547 }
548 Unnamed(..) => abort!(variant, "non single-typed tuple enums are not supported"),
549 };
550
551 let name = attrs.cased_name();
Joel Galenson564f03e2021-09-22 11:22:52 -0700552 Some(quote! {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700553 let app = app.subcommand({
554 let #app_var = ::structopt::clap::SubCommand::with_name(#name);
David LeGareb66219a2022-03-02 16:21:26 +0000555 #arg_block
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700556 });
Joel Galenson564f03e2021-09-22 11:22:52 -0700557 })
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700558 },
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
Joel Galenson7081b4a2021-08-09 10:45:24 -0700575fn gen_from_clap_enum() -> TokenStream {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700576 quote! {
577 fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self {
Joel Galenson7081b4a2021-08-09 10:45:24 -0700578 <Self as ::structopt::StructOptInternal>::from_subcommand(matches.subcommand())
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700579 .expect("structopt misuse: You likely tried to #[flatten] a struct \
580 that contains #[subcommand]. This is forbidden.")
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700581 }
582 }
583}
584
585fn 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(),
Joel Galenson564f03e2021-09-22 11:22:52 -0700604 true,
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700605 );
606
607 let variant_name = &variant.ident;
608
Joel Galenson564f03e2021-09-22 11:22:52 -0700609 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`, \
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700615 this is the second"
Joel Galenson564f03e2021-09-22 11:22:52 -0700616 );
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700617 }
618
Joel Galenson564f03e2021-09-22 11:22:52 -0700619 let ty = match variant.fields {
620 Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty,
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700621
Joel Galenson564f03e2021-09-22 11:22:52 -0700622 _ => abort!(
623 variant,
624 "The enum variant marked with `external_attribute` must be \
625 a single-typed tuple, and the type must be either `Vec<String>` \
626 or `Vec<OsString>`."
627 ),
628 };
629
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 }
645 }
646
647 None => abort!(
648 ty,
649 "The type must be either `Vec<String>` or `Vec<OsString>` \
650 to be used with `external_subcommand`."
651 ),
652 };
653
654 ext_subcmd = Some((span, variant_name, str_ty, values_of));
655 None
656 }
657 Kind::Skip(_) => None,
658 _ => Some((variant, attrs)),
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700659 }
660 })
661 .partition(|(_, attrs)| match &*attrs.kind() {
662 Kind::Flatten => true,
663 _ => false,
664 });
665
Haibo Huang70c1ca62020-11-30 18:20:15 -0800666 let other = format_ident!("other");
667 let matches = format_ident!("matches");
668
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700669 let external = match ext_subcmd {
670 Some((span, var_name, str_ty, values_of)) => quote_spanned! { span=>
Haibo Huang70c1ca62020-11-30 18:20:15 -0800671 match #other {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700672 ("", ::std::option::Option::None) => None,
673
Haibo Huang70c1ca62020-11-30 18:20:15 -0800674 (external, Some(#matches)) => {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700675 ::std::option::Option::Some(#name::#var_name(
676 ::std::iter::once(#str_ty::from(external))
677 .chain(
Haibo Huang70c1ca62020-11-30 18:20:15 -0800678 #matches.#values_of("").into_iter().flatten().map(#str_ty::from)
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700679 )
680 .collect::<::std::vec::Vec<_>>()
681 ))
682 }
683
684 (external, None) => {
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700685 ::std::option::Option::Some(#name::#var_name(
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700686 ::std::iter::once(#str_ty::from(external))
687 .collect::<::std::vec::Vec<_>>()
Chih-Hung Hsiehad4a5aa2020-10-06 15:19:28 -0700688 ))
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700689 }
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];
Haibo Huang70c1ca62020-11-30 18:20:15 -0800704 quote!( ( <#ty as ::structopt::StructOpt>::from_clap(#matches) ) )
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700705 }
706 Unnamed(..) => abort!(
707 variant.ident,
708 "non single-typed tuple enums are not supported"
709 ),
710 };
711
712 quote! {
Haibo Huang70c1ca62020-11-30 18:20:15 -0800713 (#sub_name, Some(#matches)) => {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700714 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) =
Haibo Huang70c1ca62020-11-30 18:20:15 -0800726 <#ty as ::structopt::StructOptInternal>::from_subcommand(#other)
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700727 {
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, )*
Haibo Huang70c1ca62020-11-30 18:20:15 -0800745 #other => {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700746 #( #child_subcommands )else*;
747 #external
748 }
749 }
750 }
751 }
752}
753
754#[cfg(feature = "paw")]
Joel Galenson564f03e2021-09-22 11:22:52 -0700755fn gen_paw_impl(
756 impl_generics: &ImplGenerics,
757 name: &Ident,
758 ty_generics: &TypeGenerics,
759 where_clause: &TokenStream,
760) -> TokenStream {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700761 quote! {
Joel Galenson7081b4a2021-08-09 10:45:24 -0700762 impl #impl_generics ::structopt::paw::ParseArgs for #name #ty_generics #where_clause {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700763 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"))]
Joel Galenson7081b4a2021-08-09 10:45:24 -0700772fn gen_paw_impl(_: &ImplGenerics, _: &Ident, _: &TypeGenerics, _: &TokenStream) -> TokenStream {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700773 TokenStream::new()
774}
775
Joel Galenson564f03e2021-09-22 11:22:52 -0700776fn split_structopt_generics_for_impl(
777 generics: &Generics,
778) -> (ImplGenerics, TypeGenerics, TokenStream) {
779 use syn::{token::Add, TypeParamBound::Trait};
Joel Galenson7081b4a2021-08-09 10:45:24 -0700780
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
Joel Galenson564f03e2021-09-22 11:22:52 -0700796 struct TraitBoundAmendments {
Joel Galenson7081b4a2021-08-09 10:45:24 -0700797 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();
Joel Galenson564f03e2021-09-22 11:22:52 -0700805 let (need_where, need_comma) = if let Some(where_clause) = where_clause {
Joel Galenson7081b4a2021-08-09 10:45:24 -0700806 if where_clause.predicates.trailing_punct() {
807 (false, false)
808 } else {
809 (false, true)
810 }
811 } else {
812 (true, false)
813 };
Joel Galenson564f03e2021-09-22 11:22:52 -0700814 Self {
815 tokens,
816 need_where,
817 need_comma,
818 }
Joel Galenson7081b4a2021-08-09 10:45:24 -0700819 }
820
821 fn add(&mut self, amendment: TokenStream) {
822 if self.need_where {
Joel Galenson564f03e2021-09-22 11:22:52 -0700823 self.tokens.extend(quote! { where });
Joel Galenson7081b4a2021-08-09 10:45:24 -0700824 self.need_where = false;
825 }
826 if self.need_comma {
Joel Galenson564f03e2021-09-22 11:22:52 -0700827 self.tokens.extend(quote! { , });
Joel Galenson7081b4a2021-08-09 10:45:24 -0700828 }
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 = &param.ident;
843 if type_param_bounds_contains(&param.bounds, "StructOpt") {
Joel Galenson564f03e2021-09-22 11:22:52 -0700844 trait_bound_amendments
845 .add(quote! { #param_ident : ::structopt::StructOptInternal });
Joel Galenson7081b4a2021-08-09 10:45:24 -0700846 }
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") {
Joel Galenson564f03e2021-09-22 11:22:52 -0700855 trait_bound_amendments
856 .add(quote! { #predicate_bounded_ty : ::structopt::StructOptInternal });
Joel Galenson7081b4a2021-08-09 10:45:24 -0700857 }
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
Joel Galenson564f03e2021-09-22 11:22:52 -0700866 let where_clause = quote! { #where_clause #trait_bound_amendments };
Joel Galenson7081b4a2021-08-09 10:45:24 -0700867
868 (impl_generics, ty_generics, where_clause)
869}
870
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700871fn impl_structopt_for_struct(
872 name: &Ident,
873 fields: &Punctuated<Field, Comma>,
874 attrs: &[Attribute],
Joel Galenson7081b4a2021-08-09 10:45:24 -0700875 generics: &Generics,
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700876) -> TokenStream {
Joel Galenson7081b4a2021-08-09 10:45:24 -0700877 let (impl_generics, ty_generics, where_clause) = split_structopt_generics_for_impl(&generics);
878
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700879 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);
Joel Galenson7081b4a2021-08-09 10:45:24 -0700882 let paw_impl = gen_paw_impl(&impl_generics, name, &ty_generics, &where_clause);
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700883
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)]
Joel Galenson7081b4a2021-08-09 10:45:24 -0700900 impl #impl_generics ::structopt::StructOpt for #name #ty_generics #where_clause {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700901 #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)]
Joel Galenson7081b4a2021-08-09 10:45:24 -0700919 impl #impl_generics ::structopt::StructOptInternal for #name #ty_generics #where_clause {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700920 #augment_clap
921 fn is_subcommand() -> bool { false }
922 }
923
924 #paw_impl
925 }
926}
927
928fn impl_structopt_for_enum(
929 name: &Ident,
930 variants: &Punctuated<Variant, Comma>,
931 attrs: &[Attribute],
Joel Galenson7081b4a2021-08-09 10:45:24 -0700932 generics: &Generics,
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700933) -> TokenStream {
Joel Galenson7081b4a2021-08-09 10:45:24 -0700934 let (impl_generics, ty_generics, where_clause) = split_structopt_generics_for_impl(&generics);
935
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700936 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);
Joel Galenson7081b4a2021-08-09 10:45:24 -0700941 let from_clap = gen_from_clap_enum();
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700942 let from_subcommand = gen_from_subcommand(name, variants, &attrs);
Joel Galenson7081b4a2021-08-09 10:45:24 -0700943 let paw_impl = gen_paw_impl(&impl_generics, name, &ty_generics, &where_clause);
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700944
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)]
Joel Galenson7081b4a2021-08-09 10:45:24 -0700959 impl #impl_generics ::structopt::StructOpt for #name #ty_generics #where_clause {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700960 #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)]
Joel Galenson7081b4a2021-08-09 10:45:24 -0700978 impl #impl_generics ::structopt::StructOptInternal for #name #ty_generics #where_clause {
Matthew Maurerdc6194e2020-06-02 11:15:18 -0700979 #augment_clap
980 #from_subcommand
981 fn is_subcommand() -> bool { true }
982 }
983
984 #paw_impl
985 }
986}
987
988fn 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 ..
Joel Galenson7081b4a2021-08-09 10:45:24 -07001010 }) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs, &input.generics),
Joel Galenson564f03e2021-09-22 11:22:52 -07001011 Enum(ref e) => {
1012 impl_structopt_for_enum(struct_name, &e.variants, &input.attrs, &input.generics)
1013 }
Matthew Maurerdc6194e2020-06-02 11:15:18 -07001014 _ => abort_call_site!("structopt only supports non-tuple structs and enums"),
1015 }
1016}