cliask_derive/
lib.rs

1use quote::quote;
2
3fn do_action(input: syn::DeriveInput) -> Result<proc_macro::TokenStream, syn::Error> {
4    assert_eq!(input.generics.params.len(), 0);
5
6    let syn::Data::Enum(enumdata) = input.data else {
7        return Err(syn::Error::new(input.ident.span(), "must be enum"));
8    };
9
10    // confirm all variants are units
11    for var in enumdata.variants.iter() {
12        assert!(var.fields.is_empty());
13    }
14
15    let mut keys = vec![];
16
17    for var in enumdata.variants.iter() {
18        let name = var.ident.to_string();
19
20        let Some(key) = name.chars().filter(|v| v.is_uppercase() && !keys.contains(v)).next() else {
21            return Err(syn::Error::new(var.ident.span(), "no unused key for action entry"))
22        };
23        keys.push(key);
24    }
25
26    let enum_ident = input.ident;
27    let idents = enumdata.variants.iter().map(|v| v.ident.clone()).collect::<Vec<_>>();
28    let labels = enumdata.variants.iter().map(|v| v.ident.to_string()).collect::<Vec<_>>();
29
30    let indicies = 0..(enumdata.variants.len());
31
32    Ok(quote! {
33        impl ::cliask::ActionEnum for #enum_ident {
34            const LABELS : &'static [&'static str] = &[ #(#labels),* ];
35            const KEYS : &'static [char] = &[ #(#keys),* ];
36
37            fn try_parse(from: char) -> Option<#enum_ident> {
38                match from.to_uppercase().next().unwrap() {
39                    #(
40                        #keys => Some ( Self :: #idents ),
41                    )*
42                    _ => None
43                }
44            }
45
46            fn action_index(&self) -> usize {
47                match self {
48                    #( Self :: #idents => #indicies ),*
49                }
50            }
51        }
52    }.into())
53}
54
55#[proc_macro_derive(ActionEnum)]
56pub fn action(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
57    let input = syn::parse_macro_input!(input as syn::DeriveInput);
58
59    match do_action(input) {
60        Ok(ts) => ts,
61        Err(err) => err.to_compile_error().into()
62    }
63}