macron_impl_display/
lib.rs1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use proc_macro::TokenStream;
6use quote::quote;
7
8#[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 syn::Data::Struct(st) => {
16 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 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 syn::Data::Enum(en) => {
44 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 let vars = en.variants
57 .iter()
58 .map(|syn::Variant { ident: var_ident, fields, attrs, .. }| {
59 match fields {
60 syn::Fields::Named(_) => {
62 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 else {
78 quote! { Self::#var_ident { .. } => write!(f, stringify!(#var_ident)) }
79 }
80 },
81
82 syn::Fields::Unnamed(_) => {
84 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 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
150fn 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}