punfetch_derive/
lib.rs

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/// Derive a Render implementation for a struct.
10#[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            // build the field name
40            // first char uppercase, rest lowercase with _ replaced with space
41            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}