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
#![recursion_limit="500"]
extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;
use proc_macro::TokenStream;
#[proc_macro_derive(CSVParsable)]
pub fn easy_csv(input: TokenStream) -> TokenStream {
let s = input.to_string();
let ast = syn::parse_macro_input(&s).unwrap();
let gen = impl_easy_csv(&ast);
gen.parse().unwrap()
}
fn impl_easy_csv(ast: &syn::MacroInput) -> quote::Tokens {
let name = &ast.ident;
let fields = match ast.body {
syn::Body::Struct(ref data) => data.fields(),
syn::Body::Enum(_) => panic!("#[derive(CSVParsable)] can only be used with structs"),
};
let mut header_parser = Vec::new();
let mut row_parser = Vec::new();
for (idx, field) in fields.iter().enumerate() {
let name = field.ident.clone().expect("Fields must be named");
header_parser.push({
quote! {
let mut index = None;
let headers = try!(reader.headers());
for (i, col) in headers.iter().enumerate() {
if col == stringify!(#name) {
index = Some(i);
}
}
let index = match index {
Some(index) => index,
None => {
return Err(easy_csv::Error::MissingColumnError(
format!("Column \"{}\" not found", stringify!(#name))));
}
};
column_indices.push(index);
}
});
row_parser.push({
let ty = field.ty.clone();
quote! {
#name : {
#[allow(unreachable_patterns)]
match record[col_indices[#idx]].parse::<#ty>() {
Ok(v) => v,
Err(_) => {
return Some(Err(easy_csv::Error::ParseError(
format!("Error parsing column \"{}\" on row {}",
stringify!(#name),
row))));
}
}
}
}
});
}
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
quote! {
impl #impl_generics easy_csv::CSVParsable<#name> for #name #ty_generics #where_clause {
fn parse_header<R: std::io::Read>(
reader: &mut csv::Reader<R>) -> Result<Vec<usize>, easy_csv::Error> {
let mut column_indices = vec![];
#(#header_parser)*
Ok(column_indices)
}
fn parse_row<R: std::io::Read>(
records: &mut std::iter::Enumerate<csv::StringRecords<R>>,
col_indices: &Vec<usize>) -> Option<Result<#name,easy_csv::Error>> {
match records.next() {
Some((row, record)) => {
match record {
Ok(record) => Some(Ok(#name { #(#row_parser),* })),
Err(e) => Some(Err(easy_csv::Error::CSVError(e)))
}
}
None => None
}
}
}
}
}