term_data_table_derive/
lib.rs

1use proc_macro2::TokenStream;
2use quote::{quote, ToTokens};
3use syn::{parse_macro_input, Data, DeriveInput, Fields, Generics, Ident};
4
5macro_rules! bail {
6    ($span:expr, $fmt:expr $(,$args:expr)*) => {
7        return Err(::syn::Error::new(
8            $span, format!($fmt $(,$args)*)
9        ))
10    }
11}
12
13#[proc_macro_derive(IntoRow)]
14pub fn my_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
15    let input = parse_macro_input!(input as DeriveInput);
16    let input = match IntoRowInput::from_derive(input) {
17        Ok(v) => v,
18        Err(e) => return e.into_compile_error().into(),
19    };
20
21    input.to_token_stream().into()
22}
23
24struct IntoRowInput {
25    name: Ident,
26    generics: Generics,
27    fields: IntoRowFields,
28}
29
30impl IntoRowInput {
31    fn from_derive(input: DeriveInput) -> Result<Self, syn::Error> {
32        let struct_ = match input.data {
33            Data::Struct(v) => v,
34            Data::Enum(e) => {
35                return Err(syn::Error::new(
36                    e.enum_token.span,
37                    "can only derive this trait on structs",
38                ))
39            }
40            Data::Union(u) => {
41                return Err(syn::Error::new(
42                    u.union_token.span,
43                    "can only derive this trait on structs",
44                ))
45            }
46        };
47
48        let fields = match struct_.fields {
49            Fields::Named(fields) => {
50                if fields.named.is_empty() {
51                    bail!(
52                        struct_.struct_token.span,
53                        "no data to display for zero-sized types"
54                    );
55                }
56                IntoRowFields::Named(
57                    fields
58                        .named
59                        .into_iter()
60                        .map(|field| (field.ident.unwrap()))
61                        .collect(),
62                )
63            }
64            Fields::Unnamed(fields) => {
65                if fields.unnamed.is_empty() {
66                    bail!(
67                        struct_.struct_token.span,
68                        "no data to display for zero-sized types"
69                    );
70                }
71                IntoRowFields::Unnamed(fields.unnamed.len())
72            }
73            Fields::Unit => bail!(
74                struct_.struct_token.span,
75                "no data to display for zero-sized types"
76            ),
77        };
78
79        Ok(Self {
80            name: input.ident,
81            fields,
82            generics: input.generics,
83        })
84    }
85}
86
87impl ToTokens for IntoRowInput {
88    fn to_tokens(&self, tokens: &mut TokenStream) {
89        let name = &self.name;
90        let (g_impl, g_type, g_where) = self.generics.split_for_impl();
91        let headers = self.fields.headers();
92        let into_row = self.fields.into_row();
93
94        tokens.extend(quote! {
95            impl #g_impl ::term_data_table::IntoRow for #name #g_type #g_where {
96                fn headers(&self) -> ::term_data_table::Row {
97                    #headers
98                }
99
100                fn into_row(&self) -> ::term_data_table::Row {
101                    #into_row
102                }
103            }
104        })
105    }
106}
107
108enum IntoRowFields {
109    Named(Vec<Ident>),
110    Unnamed(usize),
111}
112
113impl IntoRowFields {
114    fn headers(&self) -> TokenStream {
115        match self {
116            Self::Named(idents) => {
117                let idents = idents.into_iter().map(|ident| ident.to_string());
118                quote! {
119                    ::term_data_table::Row::new()
120                    #(
121                        .with_cell(::term_data_table::Cell::from(#idents))
122                    )*
123                }
124            }
125            Self::Unnamed(count) => {
126                let idents = (0..*count).map(|idx| idx.to_string());
127                quote! {
128                    ::term_data_table::Row::new()
129                    #(
130                        .with_cell(::term_data_table::Cell::from(#idents))
131                    )*
132                }
133            }
134        }
135    }
136
137    fn into_row(&self) -> TokenStream {
138        match self {
139            Self::Named(idents) => {
140                let idents = idents.into_iter();
141                quote! {
142                    ::term_data_table::Row::new()
143                    #(
144                        .with_cell(::term_data_table::Cell::from(self.#idents.to_string()))
145                    )*
146                }
147            }
148            Self::Unnamed(count) => {
149                let idents = 0..*count;
150                quote! {
151                    ::term_data_table::Row::new()
152                    #(
153                        .with_cell(::term_data_table::Cell::from(self.#idents.to_string()))
154                    )*
155                }
156            }
157        }
158    }
159}