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
11use proc_macro2::TokenStream;
12use quote::quote;
13use quote::quote_spanned;
14use syn::{spanned::Spanned, Data, DeriveInput, Fields, Ident, Variant};
15
16use crate::item::{Item, Kind, Name};
17
18pub 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
37pub 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
82fn 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
106fn 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
116fn 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