1use proc_macro::TokenStream;
2
3use proc_macro2::{Span, TokenStream as TokenStream2};
4use quote::{quote, quote_spanned};
5use syn::{
6 parse_macro_input, Data, DataStruct, DeriveInput, Error, Fields, LitStr, Result, Type, TypePath,
7};
8
9#[proc_macro_derive(Render)]
11pub fn rule_system_derive(input: TokenStream) -> TokenStream {
12 let ast = parse_macro_input!(input as _);
13 TokenStream::from(match impl_render(ast) {
14 Ok(it) => it,
15 Err(err) => err.to_compile_error(),
16 })
17}
18
19fn impl_render(ast: DeriveInput) -> Result<TokenStream2> {
20 Ok({
21 let name = ast.ident;
22 let fields = match ast.data {
23 Data::Struct(DataStruct {
24 fields: Fields::Named(it),
25 ..
26 }) => it,
27 _ => {
28 return Err(Error::new(
29 Span::call_site(),
30 "Expected a `struct` with named fields",
31 ));
32 }
33 };
34
35 let data_expanded_members = fields.named.into_iter().map(|field| {
36 let field_name = field.ident.expect("Unreachable");
37 let span = field_name.span();
38
39 let name = field_name.to_string();
42 let display_name = LitStr::new(&name.chars().enumerate().map(|(i, c)| {
43 if i == 0 {
44 c.to_uppercase().to_string()
45 } else if c == '_' {
46 " ".to_string()
47 } else {
48 c.to_lowercase().to_string()
49 }
50 }).collect::<String>(), span);
51
52 match field.ty {
53 Type::Path(TypePath { path, .. }) => {
54 match path {
55 path if path.is_ident("String") => {
56 quote_spanned! {span=>
57 buf.push(format!("{}: {}", #display_name.bold().color(color), self.#field_name));
58 }
59 }
60 path if path.segments.last().unwrap().ident == "Option" => {
61 quote_spanned! {span=>
62 if let Some(inner) = self.#field_name.as_ref() {
63 buf.push(format!("{}: {}", #display_name.bold().color(color), inner));
64 }
65 }
66 }
67 _ => quote!(),
68 }
69 }
70 _ => unimplemented!(),
71 }
72 });
73 quote! {
74 impl Render for #name {
75 fn render(self: &'_ Self, color: DynColors) -> Vec<String> {
76 let mut buf = Vec::new();
77 #(#data_expanded_members)*
78 buf
79 }
80 }
81 }
82 })
83}