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