1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#![deny(warnings)]
extern crate proc_macro;
extern crate proc_macro_helper;
#[macro_use]
extern crate quote;
extern crate syn;

mod utils;

use proc_macro::TokenStream;
use proc_macro_helper::prelude::*;
use syn::{DeriveInput, Ident};
use utils::*;

#[proc_macro_derive(EnumToStr, attributes(ETS))]
pub fn enum_to_str(input: TokenStream) -> TokenStream {
    let derive_input = syn::parse::<DeriveInput>(input).unwrap();
    let target_enum = Enum::parse(&derive_input);
    let enum_name = syn::parse_str::<Ident>(target_enum.name.as_ref()).unwrap();
    let match_content = target_enum
        .variants
        .iter()
        .map(|x| each_variant(x, &enum_name));

    quote!(
        impl #enum_name {
            pub fn enum_to_str(&self) -> &'static str {
                match *self {
                    #(#match_content),*
                }
            }
        }
    ).into()
}

fn each_variant(source: &Variant, enum_name: &Ident) -> impl quote::ToTokens {
    let variant_name: Ident = syn::parse_str(source.name.as_ref()).unwrap();
    let variant_content = if source.fields.is_empty() {
        quote!()
    } else {
        let temp = vec![quote!(_); source.fields.len()];
        quote!((#(#temp),*))
    };
    let variant_value = variant_value(source);
    quote!( #enum_name::#variant_name#variant_content => #variant_value)
}

fn variant_value(source: &Variant) -> impl quote::ToTokens {
    if let Some(value_attribute) = target_attributes(source).find(|x| x.name == "value") {
        let value = lit_str_value(
            value_attribute
                .values
                .first()
                .expect("string value must be set"),
        ).expect("value must be string");
        return quote!(#value);
    }

    let value = source.name.clone();
    return quote!(#value);
}