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::ext::IdentExt;
18 use syn::{
19 punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DataStruct, DeriveInput, Field,
20 Fields, Generics,
21 };
22
23 use crate::item::{Item, Kind, Name};
24 use crate::utils::{inner_type, sub_type, Sp, Ty};
25
derive_args(input: &DeriveInput) -> Result<TokenStream, syn::Error>26 pub fn derive_args(input: &DeriveInput) -> Result<TokenStream, syn::Error> {
27 let ident = &input.ident;
28
29 match input.data {
30 Data::Struct(DataStruct {
31 fields: Fields::Named(ref fields),
32 ..
33 }) => {
34 let name = Name::Derived(ident.clone());
35 let item = Item::from_args_struct(input, name)?;
36 let fields = fields
37 .named
38 .iter()
39 .map(|field| {
40 let item = Item::from_args_field(field, item.casing(), item.env_casing())?;
41 Ok((field, item))
42 })
43 .collect::<Result<Vec<_>, syn::Error>>()?;
44 gen_for_struct(&item, ident, &input.generics, &fields)
45 }
46 Data::Struct(DataStruct {
47 fields: Fields::Unit,
48 ..
49 }) => {
50 let name = Name::Derived(ident.clone());
51 let item = Item::from_args_struct(input, name)?;
52 let fields = Punctuated::<Field, Comma>::new();
53 let fields = fields
54 .iter()
55 .map(|field| {
56 let item = Item::from_args_field(field, item.casing(), item.env_casing())?;
57 Ok((field, item))
58 })
59 .collect::<Result<Vec<_>, syn::Error>>()?;
60 gen_for_struct(&item, ident, &input.generics, &fields)
61 }
62 _ => abort_call_site!("`#[derive(Args)]` only supports non-tuple structs"),
63 }
64 }
65
gen_for_struct( item: &Item, item_name: &Ident, generics: &Generics, fields: &[(&Field, Item)], ) -> Result<TokenStream, syn::Error>66 pub fn gen_for_struct(
67 item: &Item,
68 item_name: &Ident,
69 generics: &Generics,
70 fields: &[(&Field, Item)],
71 ) -> Result<TokenStream, syn::Error> {
72 if !matches!(&*item.kind(), Kind::Command(_)) {
73 abort! { item.kind().span(),
74 "`{}` cannot be used with `command`",
75 item.kind().name(),
76 }
77 }
78
79 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
80
81 let constructor = gen_constructor(fields)?;
82 let updater = gen_updater(fields, true)?;
83 let raw_deprecated = raw_deprecated();
84
85 let app_var = Ident::new("__clap_app", Span::call_site());
86 let augmentation = gen_augment(fields, &app_var, item, false)?;
87 let augmentation_update = gen_augment(fields, &app_var, item, true)?;
88
89 let group_id = if item.skip_group() {
90 quote!(None)
91 } else {
92 let group_id = item.ident().unraw().to_string();
93 quote!(Some(clap::Id::from(#group_id)))
94 };
95
96 Ok(quote! {
97 #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
98 #[allow(
99 clippy::style,
100 clippy::complexity,
101 clippy::pedantic,
102 clippy::restriction,
103 clippy::perf,
104 clippy::deprecated,
105 clippy::nursery,
106 clippy::cargo,
107 clippy::suspicious_else_formatting,
108 clippy::almost_swapped,
109 )]
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(dead_code, unreachable_code, unused_variables, unused_braces)]
133 #[allow(
134 clippy::style,
135 clippy::complexity,
136 clippy::pedantic,
137 clippy::restriction,
138 clippy::perf,
139 clippy::deprecated,
140 clippy::nursery,
141 clippy::cargo,
142 clippy::suspicious_else_formatting,
143 clippy::almost_swapped,
144 )]
145 impl #impl_generics clap::Args for #item_name #ty_generics #where_clause {
146 fn group_id() -> Option<clap::Id> {
147 #group_id
148 }
149 fn augment_args<'b>(#app_var: clap::Command) -> clap::Command {
150 #augmentation
151 }
152 fn augment_args_for_update<'b>(#app_var: clap::Command) -> clap::Command {
153 #augmentation_update
154 }
155 }
156 })
157 }
158
159 /// Generate a block of code to add arguments/subcommands corresponding to
160 /// the `fields` to an cmd.
gen_augment( fields: &[(&Field, Item)], app_var: &Ident, parent_item: &Item, override_required: bool, ) -> Result<TokenStream, syn::Error>161 pub fn gen_augment(
162 fields: &[(&Field, Item)],
163 app_var: &Ident,
164 parent_item: &Item,
165 override_required: bool,
166 ) -> Result<TokenStream, syn::Error> {
167 let mut subcommand_specified = false;
168 let mut args = Vec::new();
169 for (field, item) in fields {
170 let kind = item.kind();
171 let genned = match &*kind {
172 Kind::Command(_)
173 | Kind::Value
174 | Kind::Skip(_, _)
175 | Kind::FromGlobal(_)
176 | Kind::ExternalSubcommand => None,
177 Kind::Subcommand(ty) => {
178 if subcommand_specified {
179 abort!(
180 field.span(),
181 "`#[command(subcommand)]` can only be used once per container"
182 );
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 args.push(genned);
359 }
360
361 let deprecations = if !override_required {
362 parent_item.deprecations()
363 } else {
364 quote!()
365 };
366 let initial_app_methods = parent_item.initial_top_level_methods();
367 let final_app_methods = parent_item.final_top_level_methods();
368 let group_app_methods = if parent_item.skip_group() {
369 quote!()
370 } else {
371 let group_id = parent_item.ident().unraw().to_string();
372 let literal_group_members = fields
373 .iter()
374 .filter_map(|(_field, item)| {
375 let kind = item.kind();
376 if matches!(*kind, Kind::Arg(_)) {
377 Some(item.id())
378 } else {
379 None
380 }
381 })
382 .collect::<Vec<_>>();
383 let literal_group_members_len = literal_group_members.len();
384 let mut literal_group_members = quote! {{
385 let members: [clap::Id; #literal_group_members_len] = [#( clap::Id::from(#literal_group_members) ),* ];
386 members
387 }};
388 // HACK: Validation isn't ready yet for nested arg groups, so just don't populate the group in
389 // that situation
390 let possible_group_members_len = fields
391 .iter()
392 .filter(|(_field, item)| {
393 let kind = item.kind();
394 matches!(*kind, Kind::Flatten(_))
395 })
396 .count();
397 if 0 < possible_group_members_len {
398 literal_group_members = quote! {{
399 let members: [clap::Id; 0] = [];
400 members
401 }};
402 }
403
404 quote!(
405 .group(
406 clap::ArgGroup::new(#group_id)
407 .multiple(true)
408 .args(#literal_group_members)
409 )
410 )
411 };
412 Ok(quote! {{
413 #deprecations
414 let #app_var = #app_var
415 #initial_app_methods
416 #group_app_methods
417 ;
418 #( #args )*
419 #app_var #final_app_methods
420 }})
421 }
422
gen_constructor(fields: &[(&Field, Item)]) -> Result<TokenStream, syn::Error>423 pub fn gen_constructor(fields: &[(&Field, Item)]) -> Result<TokenStream, syn::Error> {
424 let fields = fields.iter().map(|(field, item)| {
425 let field_name = field.ident.as_ref().unwrap();
426 let kind = item.kind();
427 let arg_matches = format_ident!("__clap_arg_matches");
428 let genned = match &*kind {
429 Kind::Command(_)
430 | Kind::Value
431 | Kind::ExternalSubcommand => {
432 abort! { kind.span(),
433 "`{}` cannot be used with `arg`",
434 kind.name(),
435 }
436 }
437 Kind::Subcommand(ty) => {
438 let subcmd_type = match (**ty, sub_type(&field.ty)) {
439 (Ty::Option, Some(sub_type)) => sub_type,
440 _ => &field.ty,
441 };
442 match **ty {
443 Ty::Option => {
444 quote_spanned! { kind.span()=>
445 #field_name: {
446 if #arg_matches.subcommand_name().map(<#subcmd_type as clap::Subcommand>::has_subcommand).unwrap_or(false) {
447 Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?)
448 } else {
449 None
450 }
451 }
452 }
453 },
454 Ty::Other => {
455 quote_spanned! { kind.span()=>
456 #field_name: {
457 <#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
458 }
459 }
460 },
461 Ty::Unit |
462 Ty::Vec |
463 Ty::OptionOption |
464 Ty::OptionVec |
465 Ty::VecVec |
466 Ty::OptionVecVec => {
467 abort!(
468 ty.span(),
469 "{} types are not supported for subcommand",
470 ty.as_str()
471 );
472 }
473 }
474 }
475
476 Kind::Flatten(ty) => {
477 let inner_type = match (**ty, sub_type(&field.ty)) {
478 (Ty::Option, Some(sub_type)) => sub_type,
479 _ => &field.ty,
480 };
481 match **ty {
482 Ty::Other => {
483 quote_spanned! { kind.span()=>
484 #field_name: <#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
485 }
486 },
487 Ty::Option => {
488 quote_spanned! { kind.span()=>
489 #field_name: {
490 let group_id = <#inner_type as clap::Args>::group_id()
491 .expect("`#[arg(flatten)]`ed field type implements `Args::group_id`");
492 if #arg_matches.contains_id(group_id.as_str()) {
493 Some(
494 <#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
495 )
496 } else {
497 None
498 }
499 }
500 }
501 },
502 Ty::Unit |
503 Ty::Vec |
504 Ty::OptionOption |
505 Ty::OptionVec |
506 Ty::VecVec |
507 Ty::OptionVecVec => {
508 abort!(
509 ty.span(),
510 "{} types are not supported for flatten",
511 ty.as_str()
512 );
513 }
514 }
515 },
516
517 Kind::Skip(val, _) => match val {
518 None => quote_spanned!(kind.span()=> #field_name: Default::default()),
519 Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()),
520 },
521
522 Kind::Arg(ty) | Kind::FromGlobal(ty) => {
523 gen_parsers(item, ty, field_name, field, None)?
524 }
525 };
526 Ok(genned)
527 }).collect::<Result<Vec<_>, syn::Error>>()?;
528
529 Ok(quote! {{
530 #( #fields ),*
531 }})
532 }
533
gen_updater(fields: &[(&Field, Item)], use_self: bool) -> Result<TokenStream, syn::Error>534 pub fn gen_updater(fields: &[(&Field, Item)], use_self: bool) -> Result<TokenStream, syn::Error> {
535 let mut genned_fields = Vec::new();
536 for (field, item) in fields {
537 let field_name = field.ident.as_ref().unwrap();
538 let kind = item.kind();
539
540 let access = if use_self {
541 quote! {
542 #[allow(non_snake_case)]
543 let #field_name = &mut self.#field_name;
544 }
545 } else {
546 quote!()
547 };
548 let arg_matches = format_ident!("__clap_arg_matches");
549
550 let genned = match &*kind {
551 Kind::Command(_) | Kind::Value | Kind::ExternalSubcommand => {
552 abort! { kind.span(),
553 "`{}` cannot be used with `arg`",
554 kind.name(),
555 }
556 }
557 Kind::Subcommand(ty) => {
558 let subcmd_type = match (**ty, sub_type(&field.ty)) {
559 (Ty::Option, Some(sub_type)) => sub_type,
560 _ => &field.ty,
561 };
562
563 let updater = quote_spanned! { ty.span()=>
564 <#subcmd_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?;
565 };
566
567 let updater = match **ty {
568 Ty::Option => quote_spanned! { kind.span()=>
569 if let Some(#field_name) = #field_name.as_mut() {
570 #updater
571 } else {
572 *#field_name = Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(
573 #arg_matches
574 )?);
575 }
576 },
577 _ => quote_spanned! { kind.span()=>
578 #updater
579 },
580 };
581
582 quote_spanned! { kind.span()=>
583 {
584 #access
585 #updater
586 }
587 }
588 }
589
590 Kind::Flatten(ty) => {
591 let inner_type = match (**ty, sub_type(&field.ty)) {
592 (Ty::Option, Some(sub_type)) => sub_type,
593 _ => &field.ty,
594 };
595
596 let updater = quote_spanned! { ty.span()=>
597 <#inner_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?;
598 };
599
600 let updater = match **ty {
601 Ty::Option => quote_spanned! { kind.span()=>
602 if let Some(#field_name) = #field_name.as_mut() {
603 #updater
604 } else {
605 *#field_name = Some(<#inner_type as clap::FromArgMatches>::from_arg_matches_mut(
606 #arg_matches
607 )?);
608 }
609 },
610 _ => quote_spanned! { kind.span()=>
611 #updater
612 },
613 };
614
615 quote_spanned! { kind.span()=>
616 {
617 #access
618 #updater
619 }
620 }
621 }
622
623 Kind::Skip(_, _) => quote!(),
624
625 Kind::Arg(ty) | Kind::FromGlobal(ty) => {
626 gen_parsers(item, ty, field_name, field, Some(&access))?
627 }
628 };
629 genned_fields.push(genned);
630 }
631
632 Ok(quote! {
633 #( #genned_fields )*
634 })
635 }
636
gen_parsers( item: &Item, ty: &Sp<Ty>, field_name: &Ident, field: &Field, update: Option<&TokenStream>, ) -> Result<TokenStream, syn::Error>637 fn gen_parsers(
638 item: &Item,
639 ty: &Sp<Ty>,
640 field_name: &Ident,
641 field: &Field,
642 update: Option<&TokenStream>,
643 ) -> Result<TokenStream, syn::Error> {
644 let span = ty.span();
645 let convert_type = inner_type(&field.ty);
646 let id = item.id();
647 let get_one = quote_spanned!(span=> remove_one::<#convert_type>);
648 let get_many = quote_spanned!(span=> remove_many::<#convert_type>);
649 let get_occurrences = quote_spanned!(span=> remove_occurrences::<#convert_type>);
650
651 // Give this identifier the same hygiene
652 // as the `arg_matches` parameter definition. This
653 // allows us to refer to `arg_matches` within a `quote_spanned` block
654 let arg_matches = format_ident!("__clap_arg_matches");
655
656 let field_value = match **ty {
657 Ty::Unit => {
658 quote_spanned! { ty.span()=>
659 ()
660 }
661 }
662
663 Ty::Option => {
664 quote_spanned! { ty.span()=>
665 #arg_matches.#get_one(#id)
666 }
667 }
668
669 Ty::OptionOption => quote_spanned! { ty.span()=>
670 if #arg_matches.contains_id(#id) {
671 Some(
672 #arg_matches.#get_one(#id)
673 )
674 } else {
675 None
676 }
677 },
678
679 Ty::OptionVec => quote_spanned! { ty.span()=>
680 if #arg_matches.contains_id(#id) {
681 Some(#arg_matches.#get_many(#id)
682 .map(|v| v.collect::<Vec<_>>())
683 .unwrap_or_else(Vec::new))
684 } else {
685 None
686 }
687 },
688
689 Ty::Vec => {
690 quote_spanned! { ty.span()=>
691 #arg_matches.#get_many(#id)
692 .map(|v| v.collect::<Vec<_>>())
693 .unwrap_or_else(Vec::new)
694 }
695 }
696
697 Ty::VecVec => quote_spanned! { ty.span()=>
698 #arg_matches.#get_occurrences(#id)
699 .map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>())
700 .unwrap_or_else(Vec::new)
701 },
702
703 Ty::OptionVecVec => quote_spanned! { ty.span()=>
704 #arg_matches.#get_occurrences(#id)
705 .map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>())
706 },
707
708 Ty::Other => {
709 quote_spanned! { ty.span()=>
710 #arg_matches.#get_one(#id)
711 .ok_or_else(|| clap::Error::raw(clap::error::ErrorKind::MissingRequiredArgument, format!("The following required argument was not provided: {}", #id)))?
712 }
713 }
714 };
715
716 let genned = if let Some(access) = update {
717 quote_spanned! { field.span()=>
718 if #arg_matches.contains_id(#id) {
719 #access
720 *#field_name = #field_value
721 }
722 }
723 } else {
724 quote_spanned!(field.span()=> #field_name: #field_value )
725 };
726 Ok(genned)
727 }
728
729 #[cfg(feature = "raw-deprecated")]
raw_deprecated() -> TokenStream730 pub fn raw_deprecated() -> TokenStream {
731 quote! {}
732 }
733
734 #[cfg(not(feature = "raw-deprecated"))]
raw_deprecated() -> TokenStream735 pub fn raw_deprecated() -> TokenStream {
736 quote! {
737 #![allow(deprecated)] // Assuming any deprecation in here will be related to a deprecation in `Args`
738
739 }
740 }
741