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::TokenStream;
16use quote::quote;
17use syn::Ident;
18use syn::Variant;
19use syn::{
20 self, punctuated::Punctuated, token::Comma, Data, DataStruct, DeriveInput, Field, Fields,
21 Generics,
22};
23
24use crate::derives::args::collect_args_fields;
25use crate::derives::{args, into_app, subcommand};
26use crate::item::Item;
27use crate::item::Name;
28
29pub 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
77fn 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
101fn 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