simple_tables_derive/
lib.rs

1extern crate proc_macro;
2extern crate proc_macro2;
3// Parsing rust code
4extern crate syn;
5// Creating rust code
6#[macro_use]
7extern crate quote;
8
9use proc_macro::TokenStream;
10use syn::{ItemStruct, parse_macro_input};
11use syn::parse::Parser;
12use proc_macro2::{Ident as Ident2, TokenStream as TokenStream2};
13use quote::ToTokens;
14
15/// Initialises a struct to be used as a TableRow so it can be used as an entry inside of a
16/// [Table](simple_tables_core::Table)
17#[proc_macro_attribute]
18pub fn table_row(_attrs: TokenStream, input: TokenStream) -> TokenStream {
19    let item_struct = parse_macro_input!(input as ItemStruct);
20    
21    let struct_name = &item_struct.ident;
22    
23    let fields: Vec<(String, syn::Type)>;
24    let mut ident_fields: Vec<(Ident2, syn::Type)> = Vec::new();
25    if let syn::Fields::Named(ref _fields) = item_struct.fields {
26        let _fields = &_fields.named;
27        fields = _fields.iter().map(|field| {
28            if let Some(ident) = &field.ident {
29                let field_name: String = ident.to_string();
30                let field_type = &field.ty;
31                let entry = (field_name, field_type.clone());
32                
33                ident_fields.push((ident.clone(), field_type.clone()));
34                
35                entry
36            } else {
37                panic!("Only named fields are supported.")
38                // syn::Error::into_compile_error("Only named fields are supported."); // TODO
39            }
40        }).collect();
41    } else {
42        panic!("The row struct has no fields.");
43    }
44    
45    let mut field_names: Vec<String> = Vec::new();
46    let mut field_types: Vec<syn::Type> = Vec::new();
47    
48    // (field_names, field_types) = fields.iter().unzip(); // TODO
49    fields.iter()
50        .for_each(|field| {
51        let (_name, _type) = field;
52        field_names.push(_name.to_owned());
53        field_types.push(_type.to_owned());
54    });
55    let field_types_strings: Vec<String> = field_types.iter().map(|v| v.to_token_stream().to_string()).collect();
56    
57    let field_len = fields.iter().count();
58    let mut get_field_str_elements: Vec<proc_macro2::TokenStream> = Vec::new();
59    for ident_field in ident_fields {
60        let ident = ident_field.0;
61        let field = quote!( self.#ident );
62        get_field_str_elements.push(field);
63    }
64    let get_field_str = quote!(
65        fn get_field_str(&self) -> Vec<String> {
66            vec![ #(#get_field_str_elements.to_string(),)* ]
67        }
68    );
69    TokenStream::from (
70        quote! (
71            use simple_tables::core::TableRow as TableRowTrait;
72            
73            #[derive(Debug, Clone)]
74            #item_struct
75            
76            impl #struct_name {
77                const FIELDS: [&'static str; #field_len] = [#(#field_names),*];
78                // const TYPES: [FieldType; #field_len] = [#(#field_types),*];
79                const TYPES: [&'static str; #field_len] = [#(#field_types_strings),*];
80                
81                #get_field_str
82            }
83            
84            impl TableRowTrait for #struct_name {
85                fn get_fields() -> Vec<&'static str> {
86                    Self::FIELDS.to_vec()
87                }
88                fn get_field_types() -> Vec<&'static str> {
89                    Self::TYPES.to_vec()
90                }
91                fn field_count() -> usize {
92                    return #field_len;
93                }
94            }
95        )
96    )
97}
98
99/// Initialises a struct to be a Table that holds information about a [table row](macro@crate::table_row).
100///
101/// # Examples
102/// ```rust
103/// #[table_row]
104/// struct TableRow {
105///     id: i32,
106///     name: String,
107///     email: String
108/// }
109///
110/// #[table(rows = TableRow)]
111/// struct Table {}
112/// ```
113#[proc_macro_attribute]
114pub fn table(attrs: TokenStream, input: TokenStream) -> TokenStream {
115    let mut item_struct = parse_macro_input!(input as ItemStruct);
116    
117    // parse the attributes
118    // # Attributes:
119    // - rows: Ident — '=': Punct — TableRowStruct: Ident
120    // - uid: Ident — '=': Punct — "FieldName": Literal (kind: Str)
121    let mut current_attr: Option<&str> = None;
122    let mut table_row_struct: Option<Ident2> = None;
123    let mut uid_field_name: Option<String> = None;
124    attrs.into_iter().for_each(|token| {
125        match token {
126            // https://doc.rust-lang.org/proc_macro/enum.TokenTree.html
127            proc_macro::TokenTree::Group(group) => panic!("Unexpected attribute: {}", group),
128            proc_macro::TokenTree::Ident(ident) => {
129                match &ident.to_string().as_str() {
130                    &"rows" =>  current_attr = Some("rows"),
131                    &"uid" => current_attr = Some("uid"),
132                    val => {
133                        if current_attr == Some("rows") {
134                            table_row_struct = Some(Ident2::new(val, proc_macro2::Span::call_site()));
135                        }  else {
136                            panic!("Unexpected token: {}", val);
137                        }
138                    }
139                }
140            },
141            proc_macro::TokenTree::Punct(punct) => {
142                if punct.as_char() == '=' && current_attr.is_some() {
143                    // ignored (TODO: enforce syntax)
144                } else if punct.as_char() == ',' {
145                    if current_attr == None {
146                        panic!("Unexpected character: {}", punct.as_char());
147                    } else {
148                        current_attr = None;
149                    }
150                } else {
151                    panic!("Unkown character: {}", punct);
152                }
153            },
154            proc_macro::TokenTree::Literal(literal) => {
155                if current_attr == Some("uid") {
156                    // Remove "\"" at begin and end using trim_matches
157                    uid_field_name = Some(literal.to_string().trim_matches(|c| c == '\"').to_string());
158                }
159            }
160        }
161    });
162    
163    if let Some(table_row_struct) = table_row_struct {
164        let field_to_add = quote!(rows: Vec<#table_row_struct>);
165        let struct_name = &item_struct.ident;
166        // add field to struct
167        if let syn::Fields::Named(ref mut fields) = item_struct.fields {
168            fields.named.push(
169                syn::Field::parse_named
170                    .parse2(field_to_add)
171                    .unwrap(),
172            );
173        }
174        
175        let uid_code: TokenStream2;
176        if let Some(uid) = uid_field_name {
177            uid_code = quote!(const UID: &'static str = #uid;);
178        } else {
179            uid_code = quote!();
180        }
181        
182        let to_string = quote!(
183            // The names of the fields
184                    let field_names = dev_simple_tables_core_table_row_type::get_fields();
185                    // All cells
186                    let mut row_values: Vec<Vec<String>> = Vec::new();
187                    for row in &self.rows {
188                        row_values.push(row.get_field_str());
189                    }
190                    // The sizes of the columns
191                    let mut column_sizes: Vec<usize> = vec![0; field_names.len()];
192                    row_values.iter().for_each(|(row_val)| {
193                        row_val.iter().enumerate().for_each(|(col, col_val)| {
194                            let len = col_val.to_string().chars().count();
195                            if column_sizes[col] < len {
196                                column_sizes[col] = len;
197                            }
198                        });
199                    });
200                    
201                    let mut top_line: String = String::from("+-");
202                    let mut headers: String = String::from("| ");
203                    let mut bottom_line: String = String::from("+=");
204                    let mut actual_column_sizes: Vec<usize> = column_sizes.clone();
205                    let total_columns = column_sizes.len();
206                    column_sizes.into_iter().enumerate().for_each(|(col, col_size)| {
207                        let mut local_col_size = col_size.clone();
208                        let field_name = field_names[col];
209                        let field_len = field_name.chars().count();
210                        // Hanlde case when cells are smaller than the title
211                        let left_over = if field_len > local_col_size {
212                            local_col_size = field_len;
213                            actual_column_sizes[col] = field_len;
214                            0
215                        } else {
216                            local_col_size - field_len
217                        };
218                        top_line.push_str(format!("{}-+", "-".repeat(local_col_size)).as_str());
219                        headers.push_str(format!("{}{} |", field_name, " ".repeat(left_over)).as_str());
220                        bottom_line.push_str(format!("{}=+", "=".repeat(local_col_size)).as_str());
221                        if col != total_columns - 1 {
222                            top_line.push_str("-");
223                            headers.push_str(" ");
224                            bottom_line.push_str("=");
225                        }
226                    });
227                    
228                    // Adding the cells to the formatted table
229                    let mut cells: String = String::from("| ");
230                    row_values.into_iter().enumerate().for_each(|(row, row_val)| {
231                        if row != 0 {
232                            cells.push_str("\n| ");
233                        }
234                        row_val.into_iter().enumerate().for_each(|(col, cell_val)| {
235                            let left_over = actual_column_sizes[col] - cell_val.chars().count();
236                            cells.push_str(format!("{}{} |", cell_val, " ".repeat(left_over)).as_str());
237                            if col != total_columns - 1 {
238                            cells.push_str(" ");
239                        }
240                        });
241                        // Add horizontal line to bottom
242                        cells.push_str(format!("\n{}", top_line).as_str());
243                    });
244        );
245        let impl_to_string = quote!(
246            impl ToString for #struct_name {
247                fn to_string(&self) -> String {
248                    #to_string
249                    format!("{}\n{}\n{}\n{}", top_line, headers, bottom_line, cells)
250                }
251            }
252        );
253        
254        let output = quote! (
255            #[automatically_derived]
256            #item_struct
257            
258            impl #struct_name {
259                #uid_code
260            }
261            
262            type dev_simple_tables_core_table_row_type = #table_row_struct; // TODO: append the name of the struct to this type to avoid collisions
263            
264            impl simple_tables::core::Table<#table_row_struct> for #struct_name {
265                fn new() -> #struct_name {
266                    #struct_name { rows: Vec::new() }
267                }
268                
269                fn from_vec(vec: &Vec<#table_row_struct>) -> #struct_name {
270                    #struct_name { rows: vec.to_vec() }
271                }
272                
273                fn get_rows(&self) -> &Vec<#table_row_struct> {
274                    &self.rows
275                }
276                
277                fn get_rows_mut(&mut self) -> &mut Vec<#table_row_struct> {
278                    &mut self.rows
279                }
280                
281                fn push(&mut self, row: #table_row_struct) {
282                    self.rows.push(row);
283                }
284                
285                fn insert_top(&mut self, row: #table_row_struct) {
286                    self.rows.insert(0, row);
287                }
288                
289                fn insert(&mut self, i: usize, row: #table_row_struct) {
290                    self.rows.insert(i, row);
291                }
292            }
293            
294            #impl_to_string
295            
296            impl std::fmt::Debug for #struct_name {
297                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298                    #to_string
299                    let output = format!("{}\n{}\n{}\n{}", top_line, headers, bottom_line, cells);
300                    write!(f, "{}", output)
301                }
302            }
303        );
304    
305        TokenStream::from(output)
306    } else {
307        panic!("Please specify a struct to use as the data type for the table rows. \
308        e.g. `#[table(rows = TableRowStruct)]`. Refer to the `table` macro documentation for more info.")
309    }
310}
311
312// https://mbuffett.com/posts/incomplete-macro-walkthrough/