macron_impl_display/
lib.rs

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