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::ext::IdentExt;
18use syn::{
19    punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DataStruct, DeriveInput, Field,
20    Fields, Generics,
21};
22
23use crate::item::{Item, Kind, Name};
24use crate::utils::{inner_type, sub_type, Sp, Ty};
25
26pub 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
66pub 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.
161pub 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
423pub 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
534pub 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
637fn 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")]
730pub fn raw_deprecated() -> TokenStream {
731    quote! {}
732}
733
734#[cfg(not(feature = "raw-deprecated"))]
735pub 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