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