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