1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
extern crate proc_macro; use darling::FromMeta; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::quote; use syn::{self, DeriveInput, Fields, Data, DataStruct, Meta, MetaList, MetaNameValue}; type GenericError = Box<dyn std::error::Error + Send + Sync>; type GenericResult<T> = Result<T, GenericError>; struct Column { field: String, name: String, } macro_rules! Err { ($($arg:tt)*) => (::std::result::Result::Err(format!($($arg)*).into())) } #[proc_macro_derive(XlsTableRow, attributes(column))] pub fn xls_table_row_derive(input: TokenStream) -> TokenStream { match xls_table_row_derive_impl(input) { Ok(output) => output, Err(err) => panic!("{}", err), } } fn xls_table_row_derive_impl(input: TokenStream) -> GenericResult<TokenStream> { let ast: DeriveInput = syn::parse(input)?; let span = Span::call_site(); let columns = get_table_columns(&ast)?; let mod_ident = quote!(crate::xls); let row_ident = &ast.ident; let column_names_code = columns.iter().map(|column| { let name = &column.name; quote!(#name) }); let columns_parse_code = columns.iter().enumerate().map(|(id, column)| { let field = Ident::new(&column.field, span); let name = &column.name; quote! { #field: #mod_ident::CellType::parse(row[#id]).map_err(|e| format!( "Column {:?}: {}", #name, e))? } }); Ok(quote! { impl #mod_ident::TableRow for #row_ident { fn columns() -> Vec<&'static str> { vec![#(#column_names_code,)*] } fn parse(row: &[&#mod_ident::Cell]) -> crate::core::GenericResult<#row_ident> { Ok(#row_ident { #(#columns_parse_code,)* }) } } }.into()) } fn get_table_columns(ast: &DeriveInput) -> GenericResult<Vec<Column>> { #[derive(FromMeta)] struct ColumnParams { name: String, } let column_attr_name = "column"; let mut columns = Vec::new(); let fields = match ast.data { Data::Struct(DataStruct{fields: Fields::Named(ref fields), ..}) => &fields.named, _ => return Err!("A struct with named fields is expected"), }; for field in fields { let field_name = field.ident.as_ref() .ok_or_else(|| "A struct with named fields is expected")?.to_string(); let mut field_params = None; for attr in &field.attrs { let meta = attr.parse_meta().map_err(|e| format!( "Failed to parse `{:#?}` on {:?} field: {}", attr, field_name, e))?; if !match_attribute_name(&meta, column_attr_name) { continue; } let params = ColumnParams::from_meta(&meta).map_err(|e| format!( "{:?} attribute on {:?} field validation error: {}", column_attr_name, field_name, e))?; if field_params.replace(params).is_some() { return Err!("Duplicated {:?} attribute on {:?} field", column_attr_name, field_name); } } let column_params = field_params.ok_or_else(|| format!( "Column name is not specified for {:?} field", field_name ))?; columns.push(Column { field: field_name, name: column_params.name, }) } Ok(columns) } fn match_attribute_name(meta: &Meta, name: &str) -> bool { let path = match meta { Meta::Path(path) => path, Meta::List(MetaList{path, ..}) => path, Meta::NameValue(MetaNameValue{path, ..}) => path, }; path.segments.len() == 1 && path.segments.first().unwrap().ident == name }