git_function_history_proc_macro/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::{quote_spanned, ToTokens};
4use syn::{parse_macro_input, DeriveInput};
5
6/// Allows the type that derives this macro, to have a method from_str
7/// that takes a list of strings and returns the type.
8///
9/// use `[enumstuff(skip)]` attribute on a variant or field to
10/// make it not able to be parsed by `from_str`.
11#[proc_macro_derive(enumstuff, attributes(enumstuff))]
12pub fn enum_stuff(input: TokenStream) -> TokenStream {
13    let ast = parse_macro_input!(input as DeriveInput);
14    let span = Span::call_site();
15    let name = &ast.ident;
16    let vis = &ast.vis;
17    let data = match ast.data {
18        syn::Data::Enum(data) => data,
19        _ => panic!("Only enums are supported"),
20    };
21
22    let variants = data.variants.iter().map(|var| {
23        let name = var.ident.to_string();
24        quote_spanned!(span=>#name=>true,)
25    });
26
27    let types = data
28        .variants
29        .iter()
30        .filter_map(|v| {
31            // filter out the variants that have the enum attribute #[enum_stuff(skip)]
32            for attr in &v.attrs {
33                // #[enumstuff(skip)]
34                if attr.path().is_ident("enumstuff") {
35                    let p = attr.parse_args::<syn::Ident>().unwrap();
36                    if p == "skip" {
37                        return None;
38                    }
39                }
40            }
41
42            let name_tok = v.ident.to_token_stream();
43            let name = name_tok.to_string();
44            Some(match v.fields.clone() {
45                syn::Fields::Named(_) => {
46                    quote_spanned!(span=>#name => None,)
47                }
48                syn::Fields::Unnamed(v) => {
49                    let values = v.unnamed;
50                    let v = values
51                        .iter()
52                        .map(|v| {
53                            let rest = quote_spanned!(span=>rest);
54                            // todo: more base cases
55                            if v.ty.to_token_stream().to_string() == "String" {
56                                quote_spanned!(span=>#rest.join(" "))
57                            } else {
58                                let ty = v.ty.to_token_stream();
59                                quote_spanned!(span=>#ty::from_str(#rest)?)
60                            }
61                        })
62                        .collect::<Vec<_>>();
63                    quote_spanned!(span=>#name => Some(Self::#name_tok(#(#v),*)),)
64                }
65                syn::Fields::Unit => {
66                    let ident = v.ident.to_token_stream();
67                    quote_spanned!(span=>#name => Some(Self::#ident),)
68                }
69            })
70        })
71        .collect::<Vec<_>>();
72
73    let gen = quote_spanned! {
74        // TODO: make a function that can recurse through the enum and get the types of the variants and types of the variants variants and so on to a specified depth
75        // amd also make a function that can does the same but for the variant names
76        span=>
77        impl #name {
78            // we nned to have a list of the types the variant is constructed of so we can create the variant
79            #vis fn from_str(makeup: &[&str]) -> Option<Self> {
80                let (variant, rest) = makeup.split_first()?;
81                match *variant {
82                    #(#types)*
83                    _ => None,
84                }
85            }
86
87            #vis fn has_variant(variant: &str) -> bool {
88                match variant {
89                    #(#variants)*
90                    _ => false,
91                }
92            }
93        }
94    };
95    gen.into()
96}