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
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(dead_code, unreachable_code, unused_variables, unused_braces)]
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 clippy::suspicious_else_formatting,
65 clippy::almost_swapped,
66 )]
67 impl clap::ValueEnum for #item_name {
68 #value_variants
69 #to_possible_value
70 }
71 })
72 }
73
lits(variants: &[(&Variant, Item)]) -> Result<Vec<(TokenStream, Ident)>, syn::Error>74 fn lits(variants: &[(&Variant, Item)]) -> Result<Vec<(TokenStream, Ident)>, syn::Error> {
75 let mut genned = Vec::new();
76 for (variant, item) in variants {
77 if let Kind::Skip(_, _) = &*item.kind() {
78 continue;
79 }
80 if !matches!(variant.fields, Fields::Unit) {
81 abort!(variant.span(), "`#[derive(ValueEnum)]` only supports unit variants. Non-unit variants must be skipped");
82 }
83 let fields = item.field_methods();
84 let deprecations = item.deprecations();
85 let name = item.cased_name();
86 genned.push((
87 quote_spanned! { variant.span()=> {
88 #deprecations
89 clap::builder::PossibleValue::new(#name)
90 #fields
91 }},
92 variant.ident.clone(),
93 ));
94 }
95 Ok(genned)
96 }
97
gen_value_variants(lits: &[(TokenStream, Ident)]) -> TokenStream98 fn gen_value_variants(lits: &[(TokenStream, Ident)]) -> TokenStream {
99 let lit = lits.iter().map(|l| &l.1).collect::<Vec<_>>();
100
101 quote! {
102 fn value_variants<'a>() -> &'a [Self]{
103 &[#(Self::#lit),*]
104 }
105 }
106 }
107
gen_to_possible_value(item: &Item, lits: &[(TokenStream, Ident)]) -> TokenStream108 fn gen_to_possible_value(item: &Item, lits: &[(TokenStream, Ident)]) -> TokenStream {
109 let (lit, variant): (Vec<TokenStream>, Vec<Ident>) = lits.iter().cloned().unzip();
110
111 let deprecations = item.deprecations();
112
113 quote! {
114 fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::builder::PossibleValue> {
115 #deprecations
116 match self {
117 #(Self::#variant => Some(#lit),)*
118 _ => None
119 }
120 }
121 }
122 }
123