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}