debug_concisely_derive 0.1.0

Helper crate for debug_concisely
Documentation
use itertools::Itertools;
use proc_macro::TokenStream;
use quote::{ToTokens, format_ident, quote};
use syn::{Data, DeriveInput, Fields, Ident, parse_macro_input};

#[proc_macro_derive(DebugConcisely)]
pub fn derive_debug_concisely(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    let body = match input.data {
        Data::Struct(ref data_struct) => {
            build_struct_or_variant_handler(false, &name, &data_struct.fields)
        }
        Data::Enum(ref data_enum) => {
            let match_arms = data_enum.variants.iter().map(|variant| {
                let variant_name = &variant.ident;
                build_struct_or_variant_handler(true, variant_name, &variant.fields)
            });
            quote! {
                match self {
                    #(#match_arms)*
                }
            }
        }
        Data::Union(_) => panic!("DebugConcisely only supports structs and enums"),
    };

    TokenStream::from(quote! {
        #[automatically_derived]
        impl std::fmt::Debug for #name {
            fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                #body
            }
        }
    })
}

fn build_struct_or_variant_handler(
    is_variant: bool,
    name: &Ident,
    fields: &Fields,
) -> proc_macro2::TokenStream {
    let name_str = name.to_string();

    let copy_alternate = quote! { let alternate = formatter.alternate(); };

    let field_vars = (0..fields.len())
        .map(|i| format_ident!("v{}", i))
        .collect_vec();

    let field_expressions = fields
        .iter()
        .zip(field_vars.iter())
        .map(|(field, var)| {
            let typename = field
                .ty
                .to_token_stream()
                .into_iter()
                .next()
                .unwrap()
                .to_string();
            match typename.as_str() {
                "Option" => quote! {
                    &match #var {
                        Some(value) =>
                            ::debug_concisely::DebugConciselyProxy(
                                if alternate {
                                    format!("Some $ {:#?}", value)
                                } else {
                                    format!("Some $ {:?}", value)
                                }
                            ),
                        None => ::debug_concisely::DebugConciselyProxy("None".to_string()),
                    }
                },
                "Vec" | "Vector" => quote! {
                    &if #var.len() == 1 {
                        if alternate {
                            ::debug_concisely::DebugConciselyProxy(format!("[{:#?}]", #var[0]))
                        } else {
                            ::debug_concisely::DebugConciselyProxy(format!("[{:?}]", #var[0]))
                        }
                    } else {
                        if alternate {
                            ::debug_concisely::DebugConciselyProxy(format!("{:#?}", #var))
                        } else {
                            ::debug_concisely::DebugConciselyProxy(format!("{:?}", #var))
                        }
                    }
                },
                _ => quote! { #var },
            }
        })
        .collect_vec();

    match fields {
        Fields::Unnamed(fields) => {
            let prefix = if is_variant {
                quote! { Self::#name(#(#field_vars),*) => }
            } else {
                quote! { let Self(#(#field_vars),*) = self; }
            };

            if fields.unnamed.len() == 1 {
                quote! {
                    #prefix {
                        #copy_alternate
                        if alternate {
                            write!(formatter, "{} $ {:#?}", #name_str, #(#field_expressions)*)
                        } else {
                            write!(formatter, "{} $ {:?}", #name_str, #(#field_expressions)*)
                        }
                    }
                }
            } else {
                quote! {
                    #prefix {
                        #copy_alternate
                        formatter.debug_tuple(#name_str)
                            #(.field(#field_expressions))*
                            .finish()
                    }
                }
            }
        }
        Fields::Named(fields) => {
            let field_names = fields.named.iter().map(|f| &f.ident).collect_vec();

            let prefix = if is_variant {
                quote! { Self::#name { #(#field_names: #field_vars),* } => }
            } else {
                quote! { let Self { #(#field_names: #field_vars),* } = self; }
            };

            if fields.named.len() == 1 {
                quote! {
                    #prefix {
                        #copy_alternate
                        if alternate {
                            write!(formatter, "{} $ {:#?}", #name_str, #(#field_expressions)*)
                        } else {
                            write!(formatter, "{} $ {:?}", #name_str, #(#field_expressions)*)
                        }
                    }
                }
            } else {
                quote! {
                    #prefix {
                        #copy_alternate
                        formatter.debug_struct(#name_str)
                            #(.field(stringify!(#field_names), #field_expressions))*
                            .finish()
                    }
                }
            }
        }
        Fields::Unit => {
            let prefix = if is_variant {
                quote! { Self::#name => }
            } else {
                quote! {}
            };

            quote! {
                #prefix {
                    #copy_alternate
                    write!(formatter, #name_str)
                }
            }
        }
    }
}