term_data_table_derive/
lib.rs1use 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}