1// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
2// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
3// Ana Hobden (@hoverbear) <operator@hoverbear.org>
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10//
11// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
12// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
13// MIT/Apache 2.0 license.
14
15use proc_macro2::{Ident, Span, TokenStream};
16use quote::{format_ident, quote, quote_spanned};
17use syn::{
18 punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DataStruct, DeriveInput, Field,
19 Fields, FieldsNamed, Generics,
20};
21
22use crate::item::{Item, Kind, Name};
23use crate::utils::{inner_type, sub_type, Sp, Ty};
24
25pub fn derive_args(input: &DeriveInput) -> Result<TokenStream, syn::Error> {
26 let ident = &input.ident;
27
28 match input.data {
29 Data::Struct(DataStruct {
30 fields: Fields::Named(ref fields),
31 ..
32 }) => {
33 let name = Name::Derived(ident.clone());
34 let item = Item::from_args_struct(input, name)?;
35 let fields = collect_args_fields(&item, fields)?;
36 gen_for_struct(&item, ident, &input.generics, &fields)
37 }
38 Data::Struct(DataStruct {
39 fields: Fields::Unit,
40 ..
41 }) => {
42 let name = Name::Derived(ident.clone());
43 let item = Item::from_args_struct(input, name)?;
44 let fields = Punctuated::<Field, Comma>::new();
45 let fields = fields
46 .iter()
47 .map(|field| {
48 let item = Item::from_args_field(field, item.casing(), item.env_casing())?;
49 Ok((field, item))
50 })
51 .collect::<Result<Vec<_>, syn::Error>>()?;
52 gen_for_struct(&item, ident, &input.generics, &fields)
53 }
54 _ => abort_call_site!("`#[derive(Args)]` only supports non-tuple structs"),
55 }
56}
57
58pub fn gen_for_struct(
59 item: &Item,
60 item_name: &Ident,
61 generics: &Generics,
62 fields: &[(&Field, Item)],
63) -> Result<TokenStream, syn::Error> {
64 if !matches!(&*item.kind(), Kind::Command(_)) {
65 abort! { item.kind().span(),
66 "`{}` cannot be used with `command`",
67 item.kind().name(),
68 }
69 }
70
71 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
72
73 let constructor = gen_constructor(fields)?;
74 let updater = gen_updater(fields, true)?;
75 let raw_deprecated = raw_deprecated();
76
77 let app_var = Ident::new("__clap_app", Span::call_site());
78 let augmentation = gen_augment(fields, &app_var, item, false)?;
79 let augmentation_update = gen_augment(fields, &app_var, item, true)?;
80
81 let group_id = if item.skip_group() {
82 quote!(None)
83 } else {
84 let group_id = item.group_id();
85 quote!(Some(clap::Id::from(#group_id)))
86 };
87
88 Ok(quote! {
89 #[allow(
90 dead_code,
91 unreachable_code,
92 unused_variables,
93 unused_braces,
94 unused_qualifications,
95 )]
96 #[allow(
97 clippy::style,
98 clippy::complexity,
99 clippy::pedantic,
100 clippy::restriction,
101 clippy::perf,
102 clippy::deprecated,
103 clippy::nursery,
104 clippy::cargo,
105 clippy::suspicious_else_formatting,
106 clippy::almost_swapped,
107 clippy::redundant_locals,
108 )]
109 #[automatically_derived]
110 impl #impl_generics clap::FromArgMatches for #item_name #ty_generics #where_clause {
111 fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
112 Self::from_arg_matches_mut(&mut __clap_arg_matches.clone())
113 }
114
115 fn from_arg_matches_mut(__clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
116 #raw_deprecated
117 let v = #item_name #constructor;
118 ::std::result::Result::Ok(v)
119 }
120
121 fn update_from_arg_matches(&mut self, __clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<(), clap::Error> {
122 self.update_from_arg_matches_mut(&mut __clap_arg_matches.clone())
123 }
124
125 fn update_from_arg_matches_mut(&mut self, __clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<(), clap::Error> {
126 #raw_deprecated
127 #updater
128 ::std::result::Result::Ok(())
129 }
130 }
131
132 #[allow(
133 dead_code,
134 unreachable_code,
135 unused_variables,
136 unused_braces,
137 unused_qualifications,
138 )]
139 #[allow(
140 clippy::style,
141 clippy::complexity,
142 clippy::pedantic,
143 clippy::restriction,
144 clippy::perf,
145 clippy::deprecated,
146 clippy::nursery,
147 clippy::cargo,
148 clippy::suspicious_else_formatting,
149 clippy::almost_swapped,
150 clippy::redundant_locals,
151 )]
152 #[automatically_derived]
153 impl #impl_generics clap::Args for #item_name #ty_generics #where_clause {
154 fn group_id() -> Option<clap::Id> {
155 #group_id
156 }
157 fn augment_args<'b>(#app_var: clap::Command) -> clap::Command {
158 #augmentation
159 }
160 fn augment_args_for_update<'b>(#app_var: clap::Command) -> clap::Command {
161 #augmentation_update
162 }
163 }
164 })
165}
166
167/// Generate a block of code to add arguments/subcommands corresponding to
168/// the `fields` to an cmd.
169pub fn gen_augment(
170 fields: &[(&Field, Item)],
171 app_var: &Ident,
172 parent_item: &Item,
173 override_required: bool,
174) -> Result<TokenStream, syn::Error> {
175 let mut subcommand_specified = false;
176 let mut args = Vec::new();
177 for (field, item) in fields {
178 let kind = item.kind();
179 let genned = match &*kind {
180 Kind::Command(_)
181 | Kind::Value
182 | Kind::Skip(_, _)
183 | Kind::FromGlobal(_)
184 | Kind::ExternalSubcommand => None,
185 Kind::Subcommand(ty) => {
186 if subcommand_specified {
187 abort!(
188 field.span(),
189 "`#[command(subcommand)]` can only be used once per container"
190 );
191 }
192 subcommand_specified = true;
193
194 let subcmd_type = match (**ty, sub_type(&field.ty)) {
195 (Ty::Option, Some(sub_type)) => sub_type,
196 _ => &field.ty,
197 };
198 let implicit_methods = if **ty == Ty::Option {
199 quote!()
200 } else {
201 quote_spanned! { kind.span()=>
202 .subcommand_required(true)
203 .arg_required_else_help(true)
204 }
205 };
206
207 let override_methods = if override_required {
208 quote_spanned! { kind.span()=>
209 .subcommand_required(false)
210 .arg_required_else_help(false)
211 }
212 } else {
213 quote!()
214 };
215
216 Some(quote! {
217 let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands( #app_var );
218 let #app_var = #app_var
219 #implicit_methods
220 #override_methods;
221 })
222 }
223 Kind::Flatten(ty) => {
224 let inner_type = match (**ty, sub_type(&field.ty)) {
225 (Ty::Option, Some(sub_type)) => sub_type,
226 _ => &field.ty,
227 };
228
229 let next_help_heading = item.next_help_heading();
230 let next_display_order = item.next_display_order();
231 if override_required {
232 Some(quote_spanned! { kind.span()=>
233 let #app_var = #app_var
234 #next_help_heading
235 #next_display_order;
236 let #app_var = <#inner_type as clap::Args>::augment_args_for_update(#app_var);
237 })
238 } else {
239 Some(quote_spanned! { kind.span()=>
240 let #app_var = #app_var
241 #next_help_heading
242 #next_display_order;
243 let #app_var = <#inner_type as clap::Args>::augment_args(#app_var);
244 })
245 }
246 }
247 Kind::Arg(ty) => {
248 let value_parser = item.value_parser(&field.ty);
249 let action = item.action(&field.ty);
250 let value_name = item.value_name();
251
252 let implicit_methods = match **ty {
253 Ty::Unit => {
254 // Leaving out `value_parser` as it will always fail
255 quote_spanned! { ty.span()=>
256 .value_name(#value_name)
257 #action
258 }
259 }
260 Ty::Option => {
261 quote_spanned! { ty.span()=>
262 .value_name(#value_name)
263 #value_parser
264 #action
265 }
266 }
267
268 Ty::OptionOption => quote_spanned! { ty.span()=>
269 .value_name(#value_name)
270 .num_args(0..=1)
271 #value_parser
272 #action
273 },
274
275 Ty::OptionVec => {
276 if item.is_positional() {
277 quote_spanned! { ty.span()=>
278 .value_name(#value_name)
279 .num_args(1..) // action won't be sufficient for getting multiple
280 #value_parser
281 #action
282 }
283 } else {
284 quote_spanned! { ty.span()=>
285 .value_name(#value_name)
286 #value_parser
287 #action
288 }
289 }
290 }
291
292 Ty::Vec => {
293 if item.is_positional() {
294 quote_spanned! { ty.span()=>
295 .value_name(#value_name)
296 .num_args(1..) // action won't be sufficient for getting multiple
297 #value_parser
298 #action
299 }
300 } else {
301 quote_spanned! { ty.span()=>
302 .value_name(#value_name)
303 #value_parser
304 #action
305 }
306 }
307 }
308
309 Ty::VecVec | Ty::OptionVecVec => {
310 quote_spanned! { ty.span() =>
311 .value_name(#value_name)
312 #value_parser
313 #action
314 }
315 }
316
317 Ty::Other => {
318 let required = item.find_default_method().is_none();
319 // `ArgAction::takes_values` is assuming `ArgAction::default_value` will be
320 // set though that won't always be true but this should be good enough,
321 // otherwise we'll report an "arg required" error when unwrapping.
322 let action_value = action.args();
323 quote_spanned! { ty.span()=>
324 .value_name(#value_name)
325 .required(#required && #action_value.takes_values())
326 #value_parser
327 #action
328 }
329 }
330 };
331
332 let id = item.id();
333 let explicit_methods = item.field_methods();
334 let deprecations = if !override_required {
335 item.deprecations()
336 } else {
337 quote!()
338 };
339 let override_methods = if override_required {
340 quote_spanned! { kind.span()=>
341 .required(false)
342 }
343 } else {
344 quote!()
345 };
346
347 Some(quote_spanned! { field.span()=>
348 let #app_var = #app_var.arg({
349 #deprecations
350
351 #[allow(deprecated)]
352 let arg = clap::Arg::new(#id)
353 #implicit_methods;
354
355 let arg = arg
356 #explicit_methods;
357
358 let arg = arg
359 #override_methods;
360
361 arg
362 });
363 })
364 }
365 };
366 args.push(genned);
367 }
368
369 let deprecations = if !override_required {
370 parent_item.deprecations()
371 } else {
372 quote!()
373 };
374 let initial_app_methods = parent_item.initial_top_level_methods();
375 let final_app_methods = parent_item.final_top_level_methods();
376 let group_app_methods = if parent_item.skip_group() {
377 quote!()
378 } else {
379 let group_id = parent_item.group_id();
380 let literal_group_members = fields
381 .iter()
382 .filter_map(|(_field, item)| {
383 let kind = item.kind();
384 if matches!(*kind, Kind::Arg(_)) {
385 Some(item.id())
386 } else {
387 None
388 }
389 })
390 .collect::<Vec<_>>();
391 let literal_group_members_len = literal_group_members.len();
392 let mut literal_group_members = quote! {{
393 let members: [clap::Id; #literal_group_members_len] = [#( clap::Id::from(#literal_group_members) ),* ];
394 members
395 }};
396 // HACK: Validation isn't ready yet for nested arg groups, so just don't populate the group in
397 // that situation
398 let possible_group_members_len = fields
399 .iter()
400 .filter(|(_field, item)| {
401 let kind = item.kind();
402 matches!(*kind, Kind::Flatten(_))
403 })
404 .count();
405 if 0 < possible_group_members_len {
406 literal_group_members = quote! {{
407 let members: [clap::Id; 0] = [];
408 members
409 }};
410 }
411
412 let group_methods = parent_item.group_methods();
413
414 quote!(
415 .group(
416 clap::ArgGroup::new(#group_id)
417 .multiple(true)
418 #group_methods
419 .args(#literal_group_members)
420 )
421 )
422 };
423 Ok(quote! {{
424 #deprecations
425 let #app_var = #app_var
426 #initial_app_methods
427 #group_app_methods
428 ;
429 #( #args )*
430 #app_var #final_app_methods
431 }})
432}
433
434pub fn gen_constructor(fields: &[(&Field, Item)]) -> Result<TokenStream, syn::Error> {
435 let fields = fields.iter().map(|(field, item)| {
436 let field_name = field.ident.as_ref().unwrap();
437 let kind = item.kind();
438 let arg_matches = format_ident!("__clap_arg_matches");
439 let genned = match &*kind {
440 Kind::Command(_)
441 | Kind::Value
442 | Kind::ExternalSubcommand => {
443 abort! { kind.span(),
444 "`{}` cannot be used with `arg`",
445 kind.name(),
446 }
447 }
448 Kind::Subcommand(ty) => {
449 let subcmd_type = match (**ty, sub_type(&field.ty)) {
450 (Ty::Option, Some(sub_type)) => sub_type,
451 _ => &field.ty,
452 };
453 match **ty {
454 Ty::Option => {
455 quote_spanned! { kind.span()=>
456 #field_name: {
457 if #arg_matches.subcommand_name().map(<#subcmd_type as clap::Subcommand>::has_subcommand).unwrap_or(false) {
458 Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?)
459 } else {
460 None
461 }
462 }
463 }
464 },
465 Ty::Other => {
466 quote_spanned! { kind.span()=>
467 #field_name: {
468 <#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
469 }
470 }
471 },
472 Ty::Unit |
473 Ty::Vec |
474 Ty::OptionOption |
475 Ty::OptionVec |
476 Ty::VecVec |
477 Ty::OptionVecVec => {
478 abort!(
479 ty.span(),
480 "{} types are not supported for subcommand",
481 ty.as_str()
482 );
483 }
484 }
485 }
486
487 Kind::Flatten(ty) => {
488 let inner_type = match (**ty, sub_type(&field.ty)) {
489 (Ty::Option, Some(sub_type)) => sub_type,
490 _ => &field.ty,
491 };
492 match **ty {
493 Ty::Other => {
494 quote_spanned! { kind.span()=>
495 #field_name: <#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
496 }
497 },
498 Ty::Option => {
499 quote_spanned! { kind.span()=>
500 #field_name: {
501 let group_id = <#inner_type as clap::Args>::group_id()
502 .expect("`#[arg(flatten)]`ed field type implements `Args::group_id`");
503 if #arg_matches.contains_id(group_id.as_str()) {
504 Some(
505 <#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
506 )
507 } else {
508 None
509 }
510 }
511 }
512 },
513 Ty::Unit |
514 Ty::Vec |
515 Ty::OptionOption |
516 Ty::OptionVec |
517 Ty::VecVec |
518 Ty::OptionVecVec => {
519 abort!(
520 ty.span(),
521 "{} types are not supported for flatten",
522 ty.as_str()
523 );
524 }
525 }
526 },
527
528 Kind::Skip(val, _) => match val {
529 None => quote_spanned!(kind.span()=> #field_name: Default::default()),
530 Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()),
531 },
532
533 Kind::Arg(ty) | Kind::FromGlobal(ty) => {
534 gen_parsers(item, ty, field_name, field, None)?
535 }
536 };
537 Ok(genned)
538 }).collect::<Result<Vec<_>, syn::Error>>()?;
539
540 Ok(quote! {{
541 #( #fields ),*
542 }})
543}
544
545pub fn gen_updater(fields: &[(&Field, Item)], use_self: bool) -> Result<TokenStream, syn::Error> {
546 let mut genned_fields = Vec::new();
547 for (field, item) in fields {
548 let field_name = field.ident.as_ref().unwrap();
549 let kind = item.kind();
550
551 let access = if use_self {
552 quote! {
553 #[allow(non_snake_case)]
554 let #field_name = &mut self.#field_name;
555 }
556 } else {
557 quote!()
558 };
559 let arg_matches = format_ident!("__clap_arg_matches");
560
561 let genned = match &*kind {
562 Kind::Command(_) | Kind::Value | Kind::ExternalSubcommand => {
563 abort! { kind.span(),
564 "`{}` cannot be used with `arg`",
565 kind.name(),
566 }
567 }
568 Kind::Subcommand(ty) => {
569 let subcmd_type = match (**ty, sub_type(&field.ty)) {
570 (Ty::Option, Some(sub_type)) => sub_type,
571 _ => &field.ty,
572 };
573
574 let updater = quote_spanned! { ty.span()=>
575 <#subcmd_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?;
576 };
577
578 let updater = match **ty {
579 Ty::Option => quote_spanned! { kind.span()=>
580 if let Some(#field_name) = #field_name.as_mut() {
581 #updater
582 } else {
583 *#field_name = Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(
584 #arg_matches
585 )?);
586 }
587 },
588 _ => quote_spanned! { kind.span()=>
589 #updater
590 },
591 };
592
593 quote_spanned! { kind.span()=>
594 {
595 #access
596 #updater
597 }
598 }
599 }
600
601 Kind::Flatten(ty) => {
602 let inner_type = match (**ty, sub_type(&field.ty)) {
603 (Ty::Option, Some(sub_type)) => sub_type,
604 _ => &field.ty,
605 };
606
607 let updater = quote_spanned! { ty.span()=>
608 <#inner_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?;
609 };
610
611 let updater = match **ty {
612 Ty::Option => quote_spanned! { kind.span()=>
613 if let Some(#field_name) = #field_name.as_mut() {
614 #updater
615 } else {
616 *#field_name = Some(<#inner_type as clap::FromArgMatches>::from_arg_matches_mut(
617 #arg_matches
618 )?);
619 }
620 },
621 _ => quote_spanned! { kind.span()=>
622 #updater
623 },
624 };
625
626 quote_spanned! { kind.span()=>
627 {
628 #access
629 #updater
630 }
631 }
632 }
633
634 Kind::Skip(_, _) => quote!(),
635
636 Kind::Arg(ty) | Kind::FromGlobal(ty) => {
637 gen_parsers(item, ty, field_name, field, Some(&access))?
638 }
639 };
640 genned_fields.push(genned);
641 }
642
643 Ok(quote! {
644 #( #genned_fields )*
645 })
646}
647
648fn gen_parsers(
649 item: &Item,
650 ty: &Sp<Ty>,
651 field_name: &Ident,
652 field: &Field,
653 update: Option<&TokenStream>,
654) -> Result<TokenStream, syn::Error> {
655 let span = ty.span();
656 let convert_type = inner_type(&field.ty);
657 let id = item.id();
658 let get_one = quote_spanned!(span=> remove_one::<#convert_type>);
659 let get_many = quote_spanned!(span=> remove_many::<#convert_type>);
660 let get_occurrences = quote_spanned!(span=> remove_occurrences::<#convert_type>);
661
662 // Give this identifier the same hygiene
663 // as the `arg_matches` parameter definition. This
664 // allows us to refer to `arg_matches` within a `quote_spanned` block
665 let arg_matches = format_ident!("__clap_arg_matches");
666
667 let field_value = match **ty {
668 Ty::Unit => {
669 quote_spanned! { ty.span()=>
670 ()
671 }
672 }
673
674 Ty::Option => {
675 quote_spanned! { ty.span()=>
676 #arg_matches.#get_one(#id)
677 }
678 }
679
680 Ty::OptionOption => quote_spanned! { ty.span()=>
681 if #arg_matches.contains_id(#id) {
682 Some(
683 #arg_matches.#get_one(#id)
684 )
685 } else {
686 None
687 }
688 },
689
690 Ty::OptionVec => quote_spanned! { ty.span()=>
691 if #arg_matches.contains_id(#id) {
692 Some(#arg_matches.#get_many(#id)
693 .map(|v| v.collect::<Vec<_>>())
694 .unwrap_or_else(Vec::new))
695 } else {
696 None
697 }
698 },
699
700 Ty::Vec => {
701 quote_spanned! { ty.span()=>
702 #arg_matches.#get_many(#id)
703 .map(|v| v.collect::<Vec<_>>())
704 .unwrap_or_else(Vec::new)
705 }
706 }
707
708 Ty::VecVec => quote_spanned! { ty.span()=>
709 #arg_matches.#get_occurrences(#id)
710 .map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>())
711 .unwrap_or_else(Vec::new)
712 },
713
714 Ty::OptionVecVec => quote_spanned! { ty.span()=>
715 #arg_matches.#get_occurrences(#id)
716 .map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>())
717 },
718
719 Ty::Other => {
720 quote_spanned! { ty.span()=>
721 #arg_matches.#get_one(#id)
722 .ok_or_else(|| clap::Error::raw(clap::error::ErrorKind::MissingRequiredArgument, concat!("The following required argument was not provided: ", #id)))?
723 }
724 }
725 };
726
727 let genned = if let Some(access) = update {
728 quote_spanned! { field.span()=>
729 if #arg_matches.contains_id(#id) {
730 #access
731 *#field_name = #field_value
732 }
733 }
734 } else {
735 quote_spanned!(field.span()=> #field_name: #field_value )
736 };
737 Ok(genned)
738}
739
740#[cfg(feature = "raw-deprecated")]
741pub fn raw_deprecated() -> TokenStream {
742 quote! {}
743}
744
745#[cfg(not(feature = "raw-deprecated"))]
746pub fn raw_deprecated() -> TokenStream {
747 quote! {
748 #![allow(deprecated)] // Assuming any deprecation in here will be related to a deprecation in `Args`
749
750 }
751}
752
753pub fn collect_args_fields<'a>(
754 item: &'a Item,
755 fields: &'a FieldsNamed,
756) -> Result<Vec<(&'a Field, Item)>, syn::Error> {
757 fieldsimpl Iterator>
758 .named
759 .iter()
760 .map(|field: &Field| {
761 let item: Item = Item::from_args_field(field, struct_casing:item.casing(), item.env_casing())?;
762 Ok((field, item))
763 })
764 .collect()
765}
766