1 // Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
2 // Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
3 // Andrew Hobden (@hoverbear) <andrew@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 use crate::{
12     attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING},
13     dummies,
14     utils::Sp,
15 };
16 
17 use proc_macro2::{Span, TokenStream};
18 use proc_macro_error::abort_call_site;
19 use quote::quote;
20 use syn::{
21     punctuated::Punctuated, token::Comma, Attribute, Data, DataEnum, DeriveInput, Fields, Ident,
22     Variant,
23 };
24 
derive_arg_enum(input: &DeriveInput) -> TokenStream25 pub fn derive_arg_enum(input: &DeriveInput) -> TokenStream {
26     let ident = &input.ident;
27 
28     dummies::arg_enum(ident);
29 
30     match input.data {
31         Data::Enum(ref e) => gen_for_enum(ident, &input.attrs, e),
32         _ => abort_call_site!("`#[derive(ArgEnum)]` only supports enums"),
33     }
34 }
35 
gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream36 pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream {
37     if !e.variants.iter().all(|v| matches!(v.fields, Fields::Unit)) {
38         return quote!();
39     };
40 
41     let attrs = Attrs::from_struct(
42         Span::call_site(),
43         attrs,
44         Name::Derived(name.clone()),
45         Sp::call_site(DEFAULT_CASING),
46         Sp::call_site(DEFAULT_ENV_CASING),
47     );
48 
49     let lits = lits(&e.variants, &attrs);
50     let arg_values = gen_arg_values(&lits);
51     let to_arg_value = gen_to_arg_value(&lits);
52 
53     quote! {
54         #[allow(dead_code, unreachable_code, unused_variables)]
55         #[allow(
56             clippy::style,
57             clippy::complexity,
58             clippy::pedantic,
59             clippy::restriction,
60             clippy::perf,
61             clippy::deprecated,
62             clippy::nursery,
63             clippy::cargo
64         )]
65         #[deny(clippy::correctness)]
66         impl clap::ArgEnum for #name {
67             #arg_values
68             #to_arg_value
69         }
70     }
71 }
72 
lits( variants: &Punctuated<Variant, Comma>, parent_attribute: &Attrs, ) -> Vec<(TokenStream, Ident)>73 fn lits(
74     variants: &Punctuated<Variant, Comma>,
75     parent_attribute: &Attrs,
76 ) -> Vec<(TokenStream, Ident)> {
77     variants
78         .iter()
79         .filter_map(|variant| {
80             let attrs = Attrs::from_arg_enum_variant(
81                 variant,
82                 parent_attribute.casing(),
83                 parent_attribute.env_casing(),
84             );
85             if let Kind::Skip(_) = &*attrs.kind() {
86                 None
87             } else {
88                 let fields = attrs.field_methods();
89                 let name = attrs.cased_name();
90                 Some((
91                     quote! {
92                         clap::ArgValue::new(#name)
93                         #fields
94                     },
95                     variant.ident.clone(),
96                 ))
97             }
98         })
99         .collect::<Vec<_>>()
100 }
101 
gen_arg_values(lits: &[(TokenStream, Ident)]) -> TokenStream102 fn gen_arg_values(lits: &[(TokenStream, Ident)]) -> TokenStream {
103     let lit = lits.iter().map(|l| &l.1).collect::<Vec<_>>();
104 
105     quote! {
106         fn value_variants<'a>() -> &'a [Self]{
107             &[#(Self::#lit),*]
108         }
109     }
110 }
111 
gen_to_arg_value(lits: &[(TokenStream, Ident)]) -> TokenStream112 fn gen_to_arg_value(lits: &[(TokenStream, Ident)]) -> TokenStream {
113     let (lit, variant): (Vec<TokenStream>, Vec<Ident>) = lits.iter().cloned().unzip();
114 
115     quote! {
116         fn to_arg_value<'a>(&self) -> Option<clap::ArgValue<'a>> {
117             match self {
118                 #(Self::#variant => Some(#lit),)*
119                 _ => None
120             }
121         }
122     }
123 }
124