structs_from_excel/
lib.rs

1#![allow(unused_must_use)]
2use std::collections::HashMap;
3use std::fmt::Write;
4
5use calamine::{open_workbook_auto, Reader, DataType};
6
7use proc_macro::{TokenStream};
8
9#[proc_macro_attribute]
10pub fn sheet(args: TokenStream, _input: TokenStream) -> TokenStream {
11    println!("Parsing the provided Excel sheet");
12    // i am aware of syn and i'm also aware that i didn't want to make
13    // my code look worse in the name of making it "more proper"
14    // (syn looks awful)
15    let mut s: String = String::new();
16    let mut structs: Vec<String> = Vec::new();
17    for arg in args.into_iter() {
18        match arg {
19            // only continue if given a string as an argument
20            proc_macro::TokenTree::Literal(a) => {
21                let mut n = 0;
22                let path = &a.to_string().replace("\"","");
23                let workbook = open_workbook_auto(&path).unwrap();
24                // this package puts every sheet type into different enums but the value given by the worksheets method is always the same.
25                let workbooks = match workbook {
26                    calamine::Sheets::Xls(mut a) => { 
27                        a.worksheets()
28                    },
29                    calamine::Sheets::Xlsx(mut a) => {
30                        a.worksheets()
31                    },
32                    calamine::Sheets::Xlsb(mut a) => {
33                        a.worksheets()
34                    },
35                    calamine::Sheets::Ods(mut a) => {
36                        a.worksheets()
37                    },
38                };
39                
40                for workbook in workbooks {
41                    let mut field_names: HashMap<i32, String> = HashMap::new();
42                    let mut first_row = true;
43                    let a = workbook.1;
44                    for row in a.rows().by_ref() {
45                        println!("ROW: {:?}",row);
46                        let len = row.len();
47                        let row = [row, &[calamine::DataType::String("dummy".to_string())]].concat();
48                        // the first row is alwayse used to designate the names of the fields.
49                        if first_row {
50                            let mut i = 0;
51                            let mut first_row_first_cell = true;
52                            for cell in row {
53                                match cell {
54                                    calamine::DataType::String(a) => {
55                                        if first_row_first_cell {
56                                            if a.to_lowercase() != "name" {
57                                                panic!("The first cell of the first row is reserved for names, and must be titled 'name' (case insensitive)")
58                                            }
59                                            first_row_first_cell = false;
60                                        }
61                                        field_names.insert(i, a.to_lowercase().replace(" ","_"));
62                                    },
63                                    calamine::DataType::Empty => break,
64                                    calamine::DataType::Error(a) => panic!("The provided sheet has an error: {:?}",a),
65                                    _ => panic!("All of the cells in the first row must be strings.")
66                                };
67                                i += 1;
68                            }
69                            first_row = false;
70                        } else {
71                            let mut struct_name = String::from("");
72                            let mut fields: HashMap<String, DataType> = HashMap::new();
73                            // go through it once to create the structs.
74                            for i in 0..row.len() {
75                                let cell = row.get(i).unwrap();
76                                // first cell is for the name of the struct
77                                if struct_name == "" {
78                                    match cell {
79                                        calamine::DataType::Int(a) => {
80                                            panic!("The first cell in row {} is an int ('{}') and not a string.",n,a);
81                                        },
82                                        calamine::DataType::Float(a) => {
83                                            panic!("The first cell in row {} is a float ('{}') and not a string.",n,a);
84                                        },
85                                        calamine::DataType::String(a) => {
86                                            struct_name = a.clone();
87                                        },
88                                        calamine::DataType::Bool(a) => {
89                                            panic!("The first cell in row {} is a boolean ('{}') and not a string.",n,a);
90                                        },
91                                        calamine::DataType::Error(a) => {
92                                            panic!("The first cell in row {} is an error ('{:?}') and not a string.",n,a);
93                                        },
94                                        calamine::DataType::DateTime(a) => {
95                                            panic!("The first cell in row {} is a datetime ('{:?}') and not a string.",n,a);
96                                        }
97                                        calamine::DataType::Empty => {
98                                            break;
99                                        },
100                                    };
101                                // others are for the values
102                                } else {
103                                    let na = field_names.get_key_value(&(i as i32)).unwrap_or_else(|| {
104                                        panic!("oh");
105                                    });
106                                    fields.insert(na.1.clone(), cell.clone());
107                                    if i < (len-1) {
108                                        continue;
109                                    }
110                                    
111
112                                    // Create the struct first.
113                                    s.write_str(format!(
114                                        "pub struct {} {{",
115                                        struct_name
116                                    ).as_str());
117                                    for (name, field) in fields.clone() {
118                                        s.write_str(format!(
119                                            "pub {}: ",
120                                            name
121                                        ).as_str());
122                                        match field {
123                                            calamine::DataType::Int(_) => s.write_str("i32"),
124                                            calamine::DataType::Float(_) => s.write_str("f32"),
125                                            calamine::DataType::String(_) => s.write_str("String"),
126                                            calamine::DataType::Bool(_) => s.write_str("bool"),
127                                            calamine::DataType::Error(a) => {
128                                                panic!("Error at row {} cell {}: {:?}",n,i,a);
129                                            }
130                                            calamine::DataType::DateTime(_) => s.write_str("f64"),
131                                            calamine::DataType::Empty => s.write_str("u128"),
132                                        };
133                                        s.write_str(",");
134                                    }
135                                    s.write_str("}");
136                                    // Create the Default impl second.
137                                    s.write_str(format!(
138                                        "impl Default for {} {{
139                                            fn default() -> Self {{
140                                                Self {{
141                                            ",
142                                        struct_name
143                                    ).as_str());
144                                    for (name, field) in fields.clone() {
145                                        match field {
146                                            calamine::DataType::Int(a) => {s.write_str(format!("{}: {},",name,a).as_str());}
147                                            calamine::DataType::Float(a) => {s.write_str(format!("{}: {},",name,a).as_str());}
148                                            calamine::DataType::String(a) => {s.write_str(format!("{}: String::from(\"{}\"),",name,a).as_str());}
149                                            calamine::DataType::Bool(a) => {s.write_str(format!("{}: {},",name,a).as_str());}
150                                            calamine::DataType::Error(a) => {
151                                                panic!("Error at row {} cell {}: {:?}",n,i,a);
152                                            }
153                                            calamine::DataType::DateTime(a) => {s.write_str(format!("{}: {},",name,a).as_str());}
154                                            calamine::DataType::Empty => {s.write_str(format!("{}: 0,",name).as_str());},
155
156                                        }
157                                    }
158                                    s.write_str("}}}");
159                                    // finally, create the new constructor, which just uses the default one.
160                                    s.write_str(format!(
161                                        "impl {} {{
162                                            pub fn new() -> Self {{
163                                                return Default::default();
164                                            }}
165                                        }}
166                                            ",
167                                        struct_name
168                                    ).as_str());
169                                    structs.push(struct_name);
170                                    break;
171                                }
172                            }
173                        }
174                    }
175                } 
176                n += 1;
177            },
178            _ => {
179                panic!("must pass a string");
180                //Diagnostic::new(Warning, "only literals are considered").emit();
181            },
182        }
183    };
184    s.write_str(format!("
185        pub enum StructsFromExcel {{
186    ").as_str());
187    for struct_name in structs {
188        s.write_str(format!("{s}({s}),",s=struct_name).as_str());
189    }
190    s.write_str(format!("
191        }}
192    ").as_str());
193    println!("Finished parsing the excel sheet.");
194    s.parse().expect("Generated invalid tokens")
195}