1use proc_macro::TokenStream;
4use proc_macro_crate::{FoundCrate, crate_name};
5use quote::quote;
6use syn::{Data, DeriveInput, parse_macro_input, parse_quote};
7
8#[proc_macro_derive(TableRow, attributes(table))]
10pub fn derive_table_row(input: TokenStream) -> TokenStream {
11 let input = parse_macro_input!(input as DeriveInput);
12 expand_table_row(input)
13 .unwrap_or_else(syn::Error::into_compile_error)
14 .into()
15}
16
17fn expand_table_row(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
18 let name = input.ident;
19 let mut generics = input.generics;
20 let fields = match input.data {
21 Data::Struct(data) => data.fields,
22 _ => {
23 return Err(syn::Error::new_spanned(
24 name,
25 "TableRow can only be derived for structs",
26 ));
27 }
28 };
29
30 let mut writes = Vec::new();
31 let mut displayed_types = Vec::new();
32 for (index, field) in fields.iter().enumerate() {
33 let mut skip = false;
34 for attribute in &field.attrs {
35 if !attribute.path().is_ident("table") {
36 continue;
37 }
38 attribute.parse_nested_meta(|meta| {
39 if meta.path.is_ident("skip") {
40 skip = true;
41 Ok(())
42 } else {
43 Err(meta.error("only #[table(skip)] is supported"))
44 }
45 })?;
46 }
47 if skip {
48 continue;
49 }
50 let member = field
51 .ident
52 .clone()
53 .map(syn::Member::Named)
54 .unwrap_or_else(|| syn::Member::Unnamed(index.into()));
55 displayed_types.push(field.ty.clone());
56 writes.push(quote! { cells.display(&self.#member); });
57 }
58
59 for ty in displayed_types {
60 generics
61 .make_where_clause()
62 .predicates
63 .push(parse_quote!(#ty: ::core::fmt::Display));
64 }
65 let urushi = match crate_name("urushi") {
66 Ok(FoundCrate::Itself) => quote!(crate),
67 Ok(FoundCrate::Name(name)) => {
68 let name = syn::Ident::new(&name, proc_macro2::Span::call_site());
69 quote!(::#name)
70 }
71 Err(error) => {
72 return Err(syn::Error::new(
73 proc_macro2::Span::call_site(),
74 format!("could not locate the urushi crate: {error}"),
75 ));
76 }
77 };
78 let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
79 Ok(quote! {
80 impl #impl_generics #urushi::TableRow for #name #type_generics #where_clause {
81 fn write_cells(&self, cells: &mut #urushi::TableRowCells) {
82 #(#writes)*
83 }
84 }
85 })
86}