description_macro/
lib.rs

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
extern crate proc_macro;
use proc_macro::TokenStream;
use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Error, Expr, Ident, Result};

#[proc_macro_derive(Description, attributes(description))]
pub fn derive_description(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    
    match try_expand(&input) {
        Ok(expanded) => expanded,
        Err(error) => error.to_compile_error().into(),
    }
}

fn try_expand(input: &DeriveInput) -> Result<TokenStream> {
    match &input.data {
        Data::Enum(e) => Ok(impl_enum(&input.ident, e)?),
        _ => Err(Error::new_spanned(input, "Description cannot be implemented on structs or unions"))
    }
}

fn impl_enum(ident: &Ident, input: &DataEnum) -> Result<TokenStream> {
    let mut vec = Vec::with_capacity(input.variants.len());

    for variant in &input.variants {
        let attr = variant.attrs.iter()
        .filter(|x| x.path().is_ident("description"))
        .nth(0).ok_or(syn::Error::new_spanned(variant, "Missing 'description' attribute"))?;

        let segment: Expr = attr.parse_args().unwrap();

        match segment {
            Expr::Lit(l) => {
                let ident = &variant.ident;
                vec.push(quote::quote! {
                    Self::#ident => #l,
                });
            },
            _ => return Err(syn::Error::new_spanned(segment, "Expected literal, provided expression"))
        }
    }

    Ok(quote::quote! {
        impl ::description::Description for #ident {
            fn description(&self) -> &'static str {
                match self {
                    #(#vec)*
                }
            }
        }
    }.into())
}

#[proc_macro_derive(OptionalDescription, attributes(description))]
pub fn derive_optional_description(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    
    match try_expand_optional(&input) {
        Ok(expanded) => expanded,
        Err(error) => error.to_compile_error().into(),
    }
}

fn try_expand_optional(input: &DeriveInput) -> Result<TokenStream> {
    match &input.data {
        Data::Enum(e) => Ok(impl_enum_optional(&input.ident, e)?),
        _ => Err(Error::new_spanned(input, "Description cannot be implemented on structs or unions"))
    }
}

fn impl_enum_optional(ident: &Ident, input: &DataEnum) -> Result<TokenStream> {
    let mut vec = Vec::with_capacity(input.variants.len());

    for variant in &input.variants {
        let attr = variant.attrs.iter()
        .filter(|x| x.path().is_ident("description"))
        .nth(0);

        let segment: Option<Expr> = attr.map(|x| x.parse_args().unwrap());

        let ident = &variant.ident;
        match segment {
            Some(Expr::Lit(l)) => {
                vec.push(quote::quote! {
                    Self::#ident => Some(#l),
                });
            },
            None => {
                vec.push(quote::quote! {
                    Self::#ident => None,
                });
            }
            _ => return Err(syn::Error::new_spanned(segment, "Expected literal, provided expression"))
        }
    }

    Ok(quote::quote! {
        impl ::description::OptionalDescription for #ident {
            fn description(&self) -> Option<&'static str> {
                match self {
                    #(#vec)*
                }
            }
        }
    }.into())
}