macron_impl_display/
lib.rs

1//! See the documentation here [macron documentation](https://docs.rs/macron)
2
3use proc_macro::TokenStream;
4use quote::quote;
5
6/// The implementation of trait Display
7#[proc_macro_derive(Display, attributes(display))]
8pub fn impl_display(input: TokenStream) -> TokenStream {
9    let syn::DeriveInput { ident, data, attrs, .. } = syn::parse_macro_input!(input as syn::DeriveInput);
10    
11    match data {
12        // impl Struct:
13        syn::Data::Struct(st) => {
14            // is has attr display:
15            let output = if let Some(fmt) = read_attr_value(&attrs) {
16                let args = st.fields
17                    .iter()
18                    .map(|field| field.ident.clone().unwrap())
19                    .filter(|ident| {
20                        let name = ident.to_string();
21                        fmt.contains(&format!("{{{name}}}")) || fmt.contains(&format!("{{{name}:"))
22                    });
23
24                quote! { write!(f, #fmt, #(#args = &self.#args,)*) }
25            }
26            // no attr display:
27            else {
28                quote! { write!(f, stringify!(#ident)) }
29            };
30
31            quote! {
32                impl ::std::fmt::Display for #ident {
33                    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
34                        #output
35                    }
36                }
37            }.into()
38        },
39
40        // impl Enum:
41        syn::Data::Enum(en) => {
42            if en.variants.is_empty() {
43                return quote! {
44                    impl ::std::fmt::Display for #ident {
45                        fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
46                            write!(f, "")
47                        }
48                    }
49                }.into();
50            }
51            
52            let vars = en.variants
53                .iter()
54                .map(|syn::Variant { ident: var_ident, fields, attrs, .. }| {
55                    // is has attr display:
56                    if let Some(mut fmt) = read_attr_value(&attrs) {
57                        match fields {
58                            // Named { ..:.., }
59                            syn::Fields::Named(_) => {
60                                let args = fields
61                                    .iter()
62                                    .map(|field| field.ident.clone().unwrap())
63                                    .filter(|ident| {
64                                        let name = ident.to_string();
65    
66                                        fmt.contains(&format!("{{{name}}}"))
67                                        || fmt.contains(&format!("{{{name}:"))
68                                    });
69    
70                                quote! { Self::#var_ident { #(#args,)* .. } => write!(f, #fmt) }
71                            },
72    
73                            // Unnamed(.., .., )
74                            syn::Fields::Unnamed(_) => {
75                                let args = (0..fields.len())
76                                    .into_iter()
77                                    .map(|i| {
78                                        let arg = if fmt.contains(&format!("{{{i}}}"))
79                                        || fmt.contains(&format!("{{{i}:")) {
80                                            format!("arg{i}")
81                                        } else {
82                                            fmt.push_str(&format!("{{{i}}}"));
83                                            format!("_")
84                                        };
85                                        
86                                        proc_macro2::Ident::new(&arg, proc_macro2::Span::call_site())
87                                    })
88                                    .collect::<Vec<_>>();
89    
90                                let vals = args.clone()
91                                    .into_iter()
92                                    .map(|arg| 
93                                        if arg != "_" {
94                                            quote! { #arg }
95                                        } else {
96                                            quote! { "" }
97                                        }
98                                    );
99    
100                                quote! { Self::#var_ident(#(#args,)*) => write!(f, #fmt, #(#vals,)*) }
101                            },
102    
103                            _ => quote! { Self::#var_ident => write!(f, #fmt) }
104                        }
105                    }
106                    // no attr display:
107                    else {
108                        quote! { Self::#var_ident => write!(f, stringify!(#var_ident)) }
109                    }
110                });
111            
112            quote! {
113                impl ::std::fmt::Display for #ident {
114                    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
115                        match &self {
116                            #(
117                                #vars,
118                            )*
119                        }
120                    }
121                }
122            }.into()
123        },
124
125        _ => panic!("Expected a 'struct' or 'enum'")
126    }
127}
128
129// Reads an attribute value
130fn read_attr_value(attrs: &[syn::Attribute]) -> Option<String>{
131    attrs
132        .iter()
133        .find(|attr| attr.path().is_ident("display"))
134        .map(|attr| 
135            match &attr.meta {
136                syn::Meta::NameValue(meta) => {
137                    if let syn::Expr::Lit(lit) = &meta.value {
138                        if let syn::Lit::Str(s) = &lit.lit {
139                            return s.value();
140                        }
141                    }
142
143                    panic!("Expected the attribute format like this '#[display = \"a text..\"]'");
144                },
145
146                _ => panic!("Expected the attribute format like this '#[display = \"a text..\"]'")
147            }
148        )
149}