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