1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![cfg_attr(docsrs, allow(unused_attributes))]
3#![doc = include_str!("../README.md")]
4
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::{parse::Parse, punctuated::Punctuated, DeriveInput, Expr, LitStr, Token};
8
9struct FmtArgs {
11 format_str: LitStr,
12 _comma: Option<Token![,]>,
13 args: Punctuated<Expr, Token![,]>,
14}
15
16impl Parse for FmtArgs {
17 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
18 let format_str: LitStr = input.parse()?;
19 let _comma: Option<Token![,]> = input.parse()?;
20 let args: Punctuated<Expr, Token![,]> =
21 input.parse_terminated(Expr::parse, Token![,])?;
22
23 Ok(FmtArgs {
24 format_str,
25 _comma,
26 args,
27 })
28 }
29}
30
31fn parse_specific_attr(attrs: &[syn::Attribute], name: &str) -> Option<FmtArgs> {
33 for attr in attrs {
34 if attr.path().is_ident(name) {
35 return attr.parse_args().ok();
36 }
37 }
38 None
39}
40
41fn parse_display_attr(attrs: &[syn::Attribute]) -> Option<FmtArgs> {
43 parse_specific_attr(attrs, "fmt_display")
44 .or_else(|| parse_specific_attr(attrs, "fmt"))
45}
46
47fn parse_debug_attr(attrs: &[syn::Attribute]) -> Option<FmtArgs> {
49 parse_specific_attr(attrs, "fmt_debug")
50 .or_else(|| parse_specific_attr(attrs, "fmt"))
51}
52
53fn is_simple_field(expr: &Expr) -> bool {
56 matches!(expr, Expr::Path(expr_path) if expr_path.path.get_ident().is_some())
57}
58
59fn extract_field_from_format(spec: &str) -> Option<&str> {
63 let content = spec.strip_prefix('{')?.strip_suffix('}')?;
65
66 let field_part = content.split(':').next()?;
68
69 if field_part.is_empty() {
71 return None;
72 }
73
74 let first_char = field_part.chars().next()?;
75 if !first_char.is_ascii_alphabetic() && first_char != '_' {
76 return None;
77 }
78
79 if !field_part.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
80 return None;
81 }
82
83 Some(field_part)
84}
85
86fn transform_format_string(input: &str) -> (String, Vec<String>) {
89 let mut result = String::with_capacity(input.len());
90 let mut fields = Vec::new();
91 let mut chars = input.chars().peekable();
92
93 while let Some(ch) = chars.next() {
94 if ch == '{' {
95 if chars.peek() == Some(&'{') {
97 chars.next(); result.push_str("{{");
99 continue;
100 }
101
102 let mut brace_content = String::new();
104 let mut found_closing = false;
105
106 for inner_ch in chars.by_ref() {
107 if inner_ch == '}' {
108 found_closing = true;
109 break;
110 }
111 brace_content.push(inner_ch);
112 }
113
114 if !found_closing {
115 result.push('{');
117 result.push_str(&brace_content);
118 continue;
119 }
120
121 if let Some(field_name) = extract_field_from_format(&format!("{{{}}}", brace_content)) {
123 fields.push(field_name.to_string());
125
126 let format_spec = &brace_content[field_name.len()..];
128 result.push('{');
129 result.push_str(format_spec);
130 result.push('}');
131 } else {
132 result.push('{');
134 result.push_str(&brace_content);
135 result.push('}');
136 }
137 } else if ch == '}' {
138 if chars.peek() == Some(&'}') {
140 chars.next(); result.push_str("}}");
142 } else {
143 result.push('}');
144 }
145 } else {
146 result.push(ch);
147 }
148 }
149
150 (result, fields)
151}
152
153fn generate_fmt_body(fmt_args: &FmtArgs) -> proc_macro2::TokenStream {
156 let original_format_str = fmt_args.format_str.value();
157
158 let (transformed_format, extracted_fields) = transform_format_string(&original_format_str);
160
161 let format_str = LitStr::new(&transformed_format, fmt_args.format_str.span());
163
164 let mut all_args: Vec<proc_macro2::TokenStream> = Vec::new();
166
167 for field in &extracted_fields {
169 let field_ident = syn::Ident::new(field, proc_macro2::Span::call_site());
170 all_args.push(quote! { self.#field_ident });
171 }
172
173 for arg in &fmt_args.args {
175 if is_simple_field(arg) {
176 all_args.push(quote! { self.#arg });
178 } else {
179 all_args.push(quote! { #arg });
181 }
182 }
183
184 quote! {
185 write!(f, #format_str #(, #all_args)*)
186 }
187}
188
189#[proc_macro_derive(DisplayAttr, attributes(fmt, fmt_display))]
208pub fn derive_display(input: TokenStream) -> TokenStream {
209 let input = syn::parse_macro_input!(input as DeriveInput);
210
211 let name = &input.ident;
212 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
213
214 let fmt_args = match parse_display_attr(&input.attrs) {
215 Some(args) => args,
216 None => {
217 return syn::Error::new_spanned(
218 &input,
219 "DisplayAttr requires a #[fmt(...)] or #[fmt_display(...)] attribute",
220 )
221 .to_compile_error()
222 .into();
223 }
224 };
225
226 let fmt_body = generate_fmt_body(&fmt_args);
227
228 let expanded = quote! {
229 impl #impl_generics std::fmt::Display for #name #ty_generics #where_clause {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 #fmt_body
232 }
233 }
234 };
235
236 TokenStream::from(expanded)
237}
238
239#[proc_macro_derive(DebugAttr, attributes(fmt, fmt_debug))]
258pub fn derive_debug(input: TokenStream) -> TokenStream {
259 let input = syn::parse_macro_input!(input as DeriveInput);
260
261 let name = &input.ident;
262 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
263
264 let fmt_args = match parse_debug_attr(&input.attrs) {
265 Some(args) => args,
266 None => {
267 return syn::Error::new_spanned(
268 &input,
269 "DebugAttr requires a #[fmt(...)] or #[fmt_debug(...)] attribute",
270 )
271 .to_compile_error()
272 .into();
273 }
274 };
275
276 let fmt_body = generate_fmt_body(&fmt_args);
277
278 let expanded = quote! {
279 impl #impl_generics std::fmt::Debug for #name #ty_generics #where_clause {
280 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281 #fmt_body
282 }
283 }
284 };
285
286 TokenStream::from(expanded)
287}
288
289#[proc_macro_derive(DisplayAsDebug)]
307pub fn derive_display_as_debug(input: TokenStream) -> TokenStream {
308 let input = syn::parse_macro_input!(input as DeriveInput);
309
310 let name = &input.ident;
311 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
312
313 let expanded = quote! {
314 impl #impl_generics std::fmt::Display for #name #ty_generics #where_clause {
315 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316 std::fmt::Debug::fmt(self, f)
317 }
318 }
319 };
320
321 TokenStream::from(expanded)
322}