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 | 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 | |
18 | pub fn derive_value_enum(input: &DeriveInput) -> Result<TokenStream, syn::Error> { |
19 | let ident: &Ident = &input.ident; |
20 | |
21 | match input.data { |
22 | Data::Enum(ref e: &DataEnum) => { |
23 | let name: Name = Name::Derived(ident.clone()); |
24 | let item: Item = Item::from_value_enum(input, name)?; |
25 | let mut variants: Vec<(&Variant, Item)> = Vec::new(); |
26 | for variant: &Variant in &e.variants { |
27 | let item: Item = |
28 | Item::from_value_enum_variant(variant, argument_casing:item.casing(), item.env_casing())?; |
29 | variants.push((variant, item)); |
30 | } |
31 | gen_for_enum(&item, item_name:ident, &variants) |
32 | } |
33 | _ => abort_call_site!("`#[derive(ValueEnum)]` only supports enums" ), |
34 | } |
35 | } |
36 | |
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 | |
82 | fn lits(variants: &[(&Variant, Item)]) -> Result<Vec<(TokenStream, Ident)>, syn::Error> { |
83 | let mut genned: Vec<(TokenStream, Ident)> = Vec::new(); |
84 | for (variant: &&Variant, item: &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: TokenStream = item.field_methods(); |
92 | let deprecations: TokenStream = item.deprecations(); |
93 | let name: TokenStream = 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 | |
106 | fn gen_value_variants(lits: &[(TokenStream, Ident)]) -> TokenStream { |
107 | let lit: Vec<&Ident> = lits.iter().map(|l: &(TokenStream, Ident)| &l.1).collect::<Vec<_>>(); |
108 | |
109 | quote! { |
110 | fn value_variants<'a>() -> &'a [Self]{ |
111 | &[#(Self::#lit),*] |
112 | } |
113 | } |
114 | } |
115 | |
116 | fn gen_to_possible_value(item: &Item, lits: &[(TokenStream, Ident)]) -> TokenStream { |
117 | let (lit: Vec, variant: Vec): (Vec<TokenStream>, Vec<Ident>) = lits.iter().cloned().unzip(); |
118 | |
119 | let deprecations: TokenStream = 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 | |