1 // Copyright 2018 Guillaume Pinot (@TeXitoi) <[email protected]>,
2 // Kevin Knapp (@kbknapp) <[email protected]>, and
3 // Ana Hobden (@hoverbear) <[email protected]>
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 proc_macro2::TokenStream;
12 use quote::quote;
13 use quote::quote_spanned;
14 use syn::{spanned::Spanned, Data, DeriveInput, Fields, Ident, Variant};
15 
16 use crate::item::{Item, Kind, Name};
17 
derive_value_enum(input: &DeriveInput) -> Result<TokenStream, syn::Error>18 pub fn derive_value_enum(input: &DeriveInput) -> Result<TokenStream, syn::Error> {
19     let ident = &input.ident;
20 
21     match input.data {
22         Data::Enum(ref e) => {
23             let name = Name::Derived(ident.clone());
24             let item = Item::from_value_enum(input, name)?;
25             let mut variants = Vec::new();
26             for variant in &e.variants {
27                 let item =
28                     Item::from_value_enum_variant(variant, item.casing(), item.env_casing())?;
29                 variants.push((variant, item));
30             }
31             gen_for_enum(&item, ident, &variants)
32         }
33         _ => abort_call_site!("`#[derive(ValueEnum)]` only supports enums"),
34     }
35 }
36 
gen_for_enum( item: &Item, item_name: &Ident, variants: &[(&Variant, Item)], ) -> Result<TokenStream, syn::Error>37 pub fn gen_for_enum(
38     item: &Item,
39     item_name: &Ident,
40     variants: &[(&Variant, Item)],
41 ) -> Result<TokenStream, syn::Error> {
42     if !matches!(&*item.kind(), Kind::Value) {
43         abort! { item.kind().span(),
44             "`{}` cannot be used with `value`",
45             item.kind().name(),
46         }
47     }
48 
49     let lits = lits(variants)?;
50     let value_variants = gen_value_variants(&lits);
51     let to_possible_value = gen_to_possible_value(item, &lits);
52 
53     Ok(quote! {
54         #[allow(
55             dead_code,
56             unreachable_code,
57             unused_variables,
58             unused_braces,
59             unused_qualifications,
60         )]
61         #[allow(
62             clippy::style,
63             clippy::complexity,
64             clippy::pedantic,
65             clippy::restriction,
66             clippy::perf,
67             clippy::deprecated,
68             clippy::nursery,
69             clippy::cargo,
70             clippy::suspicious_else_formatting,
71             clippy::almost_swapped,
72             clippy::redundant_locals,
73         )]
74         #[automatically_derived]
75         impl clap::ValueEnum for #item_name {
76             #value_variants
77             #to_possible_value
78         }
79     })
80 }
81 
lits(variants: &[(&Variant, Item)]) -> Result<Vec<(TokenStream, Ident)>, syn::Error>82 fn lits(variants: &[(&Variant, Item)]) -> Result<Vec<(TokenStream, Ident)>, syn::Error> {
83     let mut genned = Vec::new();
84     for (variant, item) in variants {
85         if let Kind::Skip(_, _) = &*item.kind() {
86             continue;
87         }
88         if !matches!(variant.fields, Fields::Unit) {
89             abort!(variant.span(), "`#[derive(ValueEnum)]` only supports unit variants. Non-unit variants must be skipped");
90         }
91         let fields = item.field_methods();
92         let deprecations = item.deprecations();
93         let name = item.cased_name();
94         genned.push((
95             quote_spanned! { variant.span()=> {
96                 #deprecations
97                 clap::builder::PossibleValue::new(#name)
98                 #fields
99             }},
100             variant.ident.clone(),
101         ));
102     }
103     Ok(genned)
104 }
105 
gen_value_variants(lits: &[(TokenStream, Ident)]) -> TokenStream106 fn gen_value_variants(lits: &[(TokenStream, Ident)]) -> TokenStream {
107     let lit = lits.iter().map(|l| &l.1).collect::<Vec<_>>();
108 
109     quote! {
110         fn value_variants<'a>() -> &'a [Self]{
111             &[#(Self::#lit),*]
112         }
113     }
114 }
115 
gen_to_possible_value(item: &Item, lits: &[(TokenStream, Ident)]) -> TokenStream116 fn gen_to_possible_value(item: &Item, lits: &[(TokenStream, Ident)]) -> TokenStream {
117     let (lit, variant): (Vec<TokenStream>, Vec<Ident>) = lits.iter().cloned().unzip();
118 
119     let deprecations = item.deprecations();
120 
121     quote! {
122         fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::builder::PossibleValue> {
123             #deprecations
124             match self {
125                 #(Self::#variant => Some(#lit),)*
126                 _ => None
127             }
128         }
129     }
130 }
131