| 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 | |