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 generate_fmt_body(fmt_args: &FmtArgs) -> proc_macro2::TokenStream {
55 let format_str = &fmt_args.format_str;
56 let args: Vec<_> = fmt_args.args.iter().collect();
57
58 quote! {
59 write!(f, #format_str #(, #args)*)
60 }
61}
62
63#[proc_macro_derive(DisplayAttr, attributes(fmt, fmt_display))]
82pub fn derive_display(input: TokenStream) -> TokenStream {
83 let input = syn::parse_macro_input!(input as DeriveInput);
84
85 let name = &input.ident;
86 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
87
88 let fmt_args = match parse_display_attr(&input.attrs) {
89 Some(args) => args,
90 None => {
91 return syn::Error::new_spanned(
92 &input,
93 "DisplayAttr requires a #[fmt(...)] or #[fmt_display(...)] attribute",
94 )
95 .to_compile_error()
96 .into();
97 }
98 };
99
100 let fmt_body = generate_fmt_body(&fmt_args);
101
102 let expanded = quote! {
103 impl #impl_generics std::fmt::Display for #name #ty_generics #where_clause {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 #fmt_body
106 }
107 }
108 };
109
110 TokenStream::from(expanded)
111}
112
113#[proc_macro_derive(DebugAttr, attributes(fmt, fmt_debug))]
132pub fn derive_debug(input: TokenStream) -> TokenStream {
133 let input = syn::parse_macro_input!(input as DeriveInput);
134
135 let name = &input.ident;
136 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
137
138 let fmt_args = match parse_debug_attr(&input.attrs) {
139 Some(args) => args,
140 None => {
141 return syn::Error::new_spanned(
142 &input,
143 "DebugAttr requires a #[fmt(...)] or #[fmt_debug(...)] attribute",
144 )
145 .to_compile_error()
146 .into();
147 }
148 };
149
150 let fmt_body = generate_fmt_body(&fmt_args);
151
152 let expanded = quote! {
153 impl #impl_generics std::fmt::Debug for #name #ty_generics #where_clause {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 #fmt_body
156 }
157 }
158 };
159
160 TokenStream::from(expanded)
161}
162
163#[proc_macro_derive(DisplayAsDebug)]
181pub fn derive_display_as_debug(input: TokenStream) -> TokenStream {
182 let input = syn::parse_macro_input!(input as DeriveInput);
183
184 let name = &input.ident;
185 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
186
187 let expanded = quote! {
188 impl #impl_generics std::fmt::Display for #name #ty_generics #where_clause {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 std::fmt::Debug::fmt(self, f)
191 }
192 }
193 };
194
195 TokenStream::from(expanded)
196}