1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4 punctuated::Punctuated, spanned::Spanned, DataStruct, DeriveInput, Field, FieldsNamed, Ident,
5 Token,
6};
7
8#[proc_macro_derive(IOExcel, attributes(column))]
9pub fn excel_io_derive(input: TokenStream) -> TokenStream {
10 let st = syn::parse_macro_input!(input as DeriveInput);
11 match do_expand(&st) {
12 Ok(tokens) => tokens.into(),
13 Err(err) => err.to_compile_error().into(),
14 }
15}
16
17fn do_expand(st: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
18 let struct_ident = &st.ident;
19 let excel_struct_ident = get_excel_struct_ident(struct_ident);
20
21 let struct_fields = get_struct_fields(st)?;
22
23 let excel_fields = parse_excel_io_stream(struct_fields)?;
24
25 let from_fields = parse_from_excel_to_record_stream(struct_fields)?;
26
27 let write_token_stream = parse_write_fields_stream(struct_fields)?;
28
29 let token_stream = quote! {
30 use calamine::Reader;
31 #[derive(Debug, serde::Deserialize)]
32 pub struct #excel_struct_ident{
33 #(#excel_fields),*
34 }
35
36 impl #struct_ident {
37 pub fn read_excel(file_path: &str,sheet: &str) -> std::result::Result<std::vec::Vec<#struct_ident>, std::boxed::Box<dyn std::error::Error>> {
38 let mut workbook = calamine::open_workbook_auto(file_path)?;
39
40 let range = workbook.worksheet_range(sheet).unwrap();
41
42 let mut result_list = std::vec::Vec::new();
43
44 let deserializer = calamine::RangeDeserializerBuilder::new().from_range(&range)?;
45 for result in deserializer {
46 let record: #excel_struct_ident = result?;
47 let record: #struct_ident = record.into();
48 result_list.push(record);
49 }
50 Ok(result_list)
51 }
52
53 pub fn write_excel(file_path: &str,sheet: &str,record_list:&[#struct_ident]) -> std::result::Result<(), std::boxed::Box<dyn std::error::Error>> {
54 let mut workbook = rust_xlsxwriter::Workbook::new();
55 let worksheet = workbook.add_worksheet();
56 worksheet.set_name(sheet)?;
57 #write_token_stream
58 workbook.save(file_path).unwrap();
59 Ok(())
60 }
61 }
62
63 impl From<#excel_struct_ident> for #struct_ident{
64 fn from(record:#excel_struct_ident) -> Self {
65 Self{
66 #(#from_fields),*
67 }
68 }
69 }
70 };
71 Ok(token_stream)
72}
73
74fn parse_excel_io_stream(
75 struct_fields: &StructFields,
76) -> syn::Result<Vec<proc_macro2::TokenStream>> {
77 let mut token_stream_list = Vec::new();
78 for field in struct_fields {
79 let excel_field = get_io_excel_field(field)?;
80 let ident = excel_field.ident;
81 let ty = excel_field.ty;
82 let column = excel_field.column_name.unwrap();
83 let token = quote! {
84 #[serde(rename = #column)]
85 pub #ident: #ty
86 };
87 token_stream_list.push(token);
88 }
89 Ok(token_stream_list)
90}
91
92fn parse_from_excel_to_record_stream(
93 struct_fields: &StructFields,
94) -> syn::Result<Vec<proc_macro2::TokenStream>> {
95 let mut token_stream_list = Vec::new();
96 for field in struct_fields {
97 let excel_field = get_io_excel_field(field)?;
98 let ident = excel_field.ident;
99 let token = quote! {
100 #ident:record.#ident
101 };
102 token_stream_list.push(token);
103 }
104 Ok(token_stream_list)
105}
106
107fn parse_write_fields_stream(
108 struct_fields: &StructFields,
109) -> syn::Result<proc_macro2::TokenStream> {
110 let mut header_token_stream = proc_macro2::TokenStream::new();
111
112 let mut row_token_stream = proc_macro2::TokenStream::new();
113
114 for (row, field) in struct_fields.iter().enumerate() {
115 let excel_field = get_io_excel_field(field)?;
116 let ident = excel_field.ident;
117 let header = excel_field.column_name.unwrap();
118
119 let header_token = quote! {
120 worksheet.write_string(0, #row as u16, #header)?;
121 };
122
123 header_token_stream.extend(header_token);
124
125 if excel_field.is_option {
126 let row_token = quote! {
127 let val_str = record.#ident.as_ref().map(|n| format!("{}", n)).unwrap_or_default();
128 worksheet.write_string(row_idx as u32 + 1, #row as u16, &val_str)?;
129 };
130 row_token_stream.extend(row_token);
131 } else {
132 let row_token = quote! {
133 let val = format!("{}",record.#ident);
134 worksheet.write_string(row_idx as u32 + 1, #row as u16, &val)?;
135 };
136 row_token_stream.extend(row_token);
137 }
138 }
139 let token_stream = quote! {
140 #header_token_stream
141 for (row_idx, record) in record_list.iter().enumerate() {
142 #row_token_stream
143 }
144 };
145 Ok(token_stream)
146}
147
148fn get_excel_struct_ident(struct_ident: &Ident) -> syn::Ident {
149 let struct_literal = struct_ident.to_string();
150 let excel_struct_literal = format!("{}IOExcel", struct_literal);
151 syn::Ident::new(&excel_struct_literal, struct_ident.span())
152}
153
154type StructFields = Punctuated<Field, Token![,]>;
155fn get_struct_fields(st: &DeriveInput) -> syn::Result<&StructFields> {
156 if let syn::Data::Struct(DataStruct {
157 fields:
158 syn::Fields::Named(FieldsNamed {
159 named: ref struct_fields,
160 ..
161 }),
162 ..
163 }) = &st.data
164 {
165 Ok(struct_fields)
166 } else {
167 Err(syn::Error::new_spanned(
168 st,
169 "IOExcel can only use to struct not enum or other !",
170 ))
171 }
172}
173
174fn get_inner_type_by_target(field: &Field, target: &str) -> Option<syn::Type> {
175 if let Field {
176 ty:
177 syn::Type::Path(syn::TypePath {
178 path: syn::Path { ref segments, .. },
179 ..
180 }),
181 ..
182 } = field
183 {
184 if let Some(syn::PathSegment { ref ident, .. }) = segments.first() {
185 let ident_letial = ident.to_string();
186 if ident_letial == target {
187 if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
188 ref args,
189 ..
190 }) = segments.first().unwrap().arguments
191 {
192 if let Some(syn::GenericArgument::Type(ty)) = args.first() {
193 return Some(ty.clone());
194 }
195 }
196 }
197 }
198 }
199 None
200}
201
202struct IOExcelField {
203 ident: Ident,
204 ty: syn::Type,
205 column_name: Option<String>,
206 is_option: bool,
207}
208
209fn get_io_excel_field(field: &Field) -> syn::Result<IOExcelField> {
210 let ident = &field.ident;
211 let ident_letial = &ident.as_ref().unwrap().to_string();
212 let excel_ident = Ident::new(ident_letial, ident.span());
213 let type_ = field.ty.clone();
214 let mut column_name = None;
215 let is_option = get_inner_type_by_target(field, "Option").is_some();
216 for attr in &field.attrs {
217 if let Ok(syn::Meta::List(list)) = attr.parse_meta() {
218 if list.path.is_ident("column") {
219 for arg in list.nested {
220 if let syn::NestedMeta::Meta(syn::Meta::NameValue(name_value)) = arg {
221 if name_value.path.is_ident("name") {
222 if let syn::Lit::Str(lit_str) = name_value.lit {
223 column_name = Some(lit_str.value());
224 return Ok(IOExcelField {
225 ident: excel_ident,
226 ty: type_,
227 column_name,
228 is_option,
229 });
230 }
231 }
232 }
233 }
234 }
235 }
236 }
237 Ok(IOExcelField {
238 ident: excel_ident,
239 ty: type_,
240 column_name,
241 is_option,
242 })
243}