| 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(crate) 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(crate) 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 |  | 
|---|