use proc_macro::TokenStream;
use quote::{quote, quote_spanned};
use syn::{
parse_macro_input, parse_quote, spanned::Spanned, Data, DeriveInput, GenericParam, Generics,
Index, LitStr,
};
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 {
let input = parse_macro_input!(input as DeriveInput);
let Data::Struct(struct_data) = input.data else {
panic!("Can only derive DisplayTableRow on structs.");
};
if struct_data.fields.is_empty() {
panic!("Cannot derive DisplayTableRow on an empty struct.");
}
let num_columns = struct_data.fields.len();
let generics = add_trait_bounds(input.generics);
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
let name = input.ident;
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!(),
};
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
}
}
};
TokenStream::from(expanded)
}
#[proc_macro_derive(DefaultTableStyle)]
pub fn default_table_style_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let Data::Struct(struct_data) = input.data else {
panic!("Can only derive DefaultTableStyle on structs.");
};
if struct_data.fields.is_empty() {
panic!("Cannot derive DefaultTableStyle on an empty struct.");
}
let num_columns = struct_data.fields.len();
let generics = add_trait_bounds(input.generics);
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
let name = input.ident;
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!(),
};
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
}
}
};
TokenStream::from(expanded)
}