kitty_table_proc_macro 1.0.0

Proc macro crate for Kitty Table.
Documentation
//! See: https://github.com/dtolnay/syn/blob/master/examples/heapsize/heapsize_derive/src/lib.rs
use proc_macro::TokenStream;
use quote::{quote, quote_spanned};
use syn::{
    parse_macro_input, parse_quote, spanned::Spanned, Data, DeriveInput, GenericParam, Generics,
    Index, LitStr,
};

/// Add a bound `T: Debug` to every type parameter T.
fn add_trait_bounds(mut generics: Generics) -> Generics {
    for param in &mut generics.params {
        if let GenericParam::Type(ref mut type_param) = *param {
            type_param.bounds.push(parse_quote!(std::fmt::Debug));
        }
    }
    generics
}

#[proc_macro_derive(DebugTableRow)]
pub fn debug_table_row_derive(input: TokenStream) -> TokenStream {
    // Parse the input tokens into a syntax tree
    let input = parse_macro_input!(input as DeriveInput);

    // Panic if the annotated item isn't a struct.
    let Data::Struct(struct_data) = input.data else {
        panic!("Can only derive DisplayTableRow on structs.");
    };

    // Panic if the struct has no fields.
    if struct_data.fields.is_empty() {
        panic!("Cannot derive DisplayTableRow on an empty struct.");
    }

    // The number of columns that will be needed.
    let num_columns = struct_data.fields.len();

    // Get generics and make them require Debug to be implemented.
    let generics = add_trait_bounds(input.generics);
    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();

    // The name of the thing we're deriving.
    let name = input.ident;

    // Generate the inner code for the impl later
    let inner = match struct_data.fields {
        syn::Fields::Named(ref fields) => {
            let iter = fields.named.iter().map(|f| {
                let name = &f.ident;

                quote_spanned! { f.span() =>
                    std::format!("{:?}", self.#name)
                }
            });

            quote!([#(#iter ,)*])
        }
        syn::Fields::Unnamed(ref fields) => {
            let iter = fields.unnamed.iter().enumerate().map(|(i, f)| {
                let index = Index::from(i);

                quote_spanned! { f.span() =>
                    std::format!("{:?}", self.#index)
                }
            });

            quote!([#(#iter ,)*])
        }
        syn::Fields::Unit => unreachable!(),
    };

    // Build the output
    let expanded = quote! {
        impl #impl_generics kitty_table::DebugTableRow<#num_columns> for #name #type_generics #where_clause {
            fn into_debug_table_row(self) -> [String; #num_columns] {
                #inner
            }
        }
    };

    // Hand the output tokens back to the compiler
    TokenStream::from(expanded)
}

#[proc_macro_derive(DefaultTableStyle)]
pub fn default_table_style_derive(input: TokenStream) -> TokenStream {
    // Parse the input tokens into a syntax tree
    let input = parse_macro_input!(input as DeriveInput);

    // Panic if the annotated item isn't a struct.
    let Data::Struct(struct_data) = input.data else {
        panic!("Can only derive DefaultTableStyle on structs.");
    };

    // Panic if the struct has no fields.
    if struct_data.fields.is_empty() {
        panic!("Cannot derive DefaultTableStyle on an empty struct.");
    }

    // The number of columns that will be needed.
    let num_columns = struct_data.fields.len();

    // Get generics and make them require Debug to be implemented.
    let generics = add_trait_bounds(input.generics);
    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();

    // The name of the thing we're deriving.
    let name = input.ident;

    // Generate the inner code for the impl later
    let inner = match struct_data.fields {
        syn::Fields::Named(ref fields) => {
            let iter = fields.named.iter().map(|f| {
                let name = LitStr::new(f.ident.as_ref().unwrap().to_string().as_str(), f.span());

                quote_spanned! { f.span() =>
                    kitty_table::Column::new(
                        core::option::Option::Some((#name, kitty_table::ColumnAlign::Centered)),
                        kitty_table::ColumnSize::Contain,
                        kitty_table::ColumnAlign::Left,
                        1
                    )
                }
            });

            quote!([#(#iter ,)*])
        }
        syn::Fields::Unnamed(ref fields) => {
            let iter = fields.unnamed.iter().map(|f| {
                quote_spanned! { f.span() =>
                    kitty_table::Column::new(
                        core::option::Option::None,
                        kitty_table::ColumnSize::Contain,
                        kitty_table::ColumnAlign::Left,
                        1
                    )
                }
            });

            quote!([#(#iter ,)*])
        }
        syn::Fields::Unit => unreachable!(),
    };

    // Build the output
    let expanded = quote! {
        impl #impl_generics kitty_table::DefaultTableStyle<#num_columns> for #name #type_generics #where_clause {
            fn default_table_style() -> [kitty_table::Column<'static>; #num_columns] {
                #inner
            }
        }
    };

    // Hand the output tokens back to the compiler
    TokenStream::from(expanded)
}