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::TokenStream; |
16 | use quote::quote; |
17 | use syn::Ident; |
18 | use syn::Variant; |
19 | use syn::{ |
20 | self, punctuated::Punctuated, token::Comma, Data, DataStruct, DeriveInput, Field, Fields, |
21 | Generics, |
22 | }; |
23 | |
24 | use crate::derives::args::collect_args_fields; |
25 | use crate::derives::{args, into_app, subcommand}; |
26 | use crate::item::Item; |
27 | use crate::item::Name; |
28 | |
29 | pub fn derive_parser(input: &DeriveInput) -> Result<TokenStream, syn::Error> { |
30 | let ident = &input.ident; |
31 | let pkg_name = std::env::var("CARGO_PKG_NAME" ).ok().unwrap_or_default(); |
32 | |
33 | match input.data { |
34 | Data::Struct(DataStruct { |
35 | fields: Fields::Named(ref fields), |
36 | .. |
37 | }) => { |
38 | let name = Name::Assigned(quote!(#pkg_name)); |
39 | let item = Item::from_args_struct(input, name)?; |
40 | let fields = collect_args_fields(&item, fields)?; |
41 | gen_for_struct(&item, ident, &input.generics, &fields) |
42 | } |
43 | Data::Struct(DataStruct { |
44 | fields: Fields::Unit, |
45 | .. |
46 | }) => { |
47 | let name = Name::Assigned(quote!(#pkg_name)); |
48 | let item = Item::from_args_struct(input, name)?; |
49 | let fields = Punctuated::<Field, Comma>::new(); |
50 | let fields = fields |
51 | .iter() |
52 | .map(|field| { |
53 | let item = Item::from_args_field(field, item.casing(), item.env_casing())?; |
54 | Ok((field, item)) |
55 | }) |
56 | .collect::<Result<Vec<_>, syn::Error>>()?; |
57 | gen_for_struct(&item, ident, &input.generics, &fields) |
58 | } |
59 | Data::Enum(ref e) => { |
60 | let name = Name::Assigned(quote!(#pkg_name)); |
61 | let item = Item::from_subcommand_enum(input, name)?; |
62 | let variants = e |
63 | .variants |
64 | .iter() |
65 | .map(|variant| { |
66 | let item = |
67 | Item::from_subcommand_variant(variant, item.casing(), item.env_casing())?; |
68 | Ok((variant, item)) |
69 | }) |
70 | .collect::<Result<Vec<_>, syn::Error>>()?; |
71 | gen_for_enum(&item, ident, &input.generics, &variants) |
72 | } |
73 | _ => abort_call_site!("`#[derive(Parser)]` only supports non-tuple structs and enums" ), |
74 | } |
75 | } |
76 | |
77 | fn gen_for_struct( |
78 | item: &Item, |
79 | item_name: &Ident, |
80 | generics: &Generics, |
81 | fields: &[(&Field, Item)], |
82 | ) -> Result<TokenStream, syn::Error> { |
83 | let (impl_generics: ImplGenerics<'_>, ty_generics: TypeGenerics<'_>, where_clause: Option<&WhereClause>) = generics.split_for_impl(); |
84 | |
85 | let into_app: TokenStream = into_app::gen_for_struct(item, item_name, generics)?; |
86 | let args: TokenStream = args::gen_for_struct(item, item_name, generics, fields)?; |
87 | |
88 | Ok(quote! { |
89 | #[automatically_derived] |
90 | #[allow( |
91 | unused_qualifications, |
92 | clippy::redundant_locals, |
93 | )] |
94 | impl #impl_generics clap::Parser for #item_name #ty_generics #where_clause {} |
95 | |
96 | #into_app |
97 | #args |
98 | }) |
99 | } |
100 | |
101 | fn gen_for_enum( |
102 | item: &Item, |
103 | item_name: &Ident, |
104 | generics: &Generics, |
105 | variants: &[(&Variant, Item)], |
106 | ) -> Result<TokenStream, syn::Error> { |
107 | let (impl_generics: ImplGenerics<'_>, ty_generics: TypeGenerics<'_>, where_clause: Option<&WhereClause>) = generics.split_for_impl(); |
108 | |
109 | let into_app: TokenStream = into_app::gen_for_enum(item, item_name, generics)?; |
110 | let subcommand: TokenStream = subcommand::gen_for_enum(item, item_name, generics, variants)?; |
111 | |
112 | Ok(quote! { |
113 | #[automatically_derived] |
114 | impl #impl_generics clap::Parser for #item_name #ty_generics #where_clause {} |
115 | |
116 | #into_app |
117 | #subcommand |
118 | }) |
119 | } |
120 | |