1#![recursion_limit="500"]
2extern crate proc_macro;
3extern crate syn;
4#[macro_use]
5extern crate quote;
6
7use proc_macro::TokenStream;
8
9#[proc_macro_derive(CSVParsable)]
10pub fn easy_csv(input: TokenStream) -> TokenStream {
11 let s = input.to_string();
13
14 let ast = syn::parse_macro_input(&s).unwrap();
16
17 let gen = impl_easy_csv(&ast);
19
20 gen.parse().unwrap()
22}
23
24fn impl_easy_csv(ast: &syn::MacroInput) -> quote::Tokens {
25 let name = &ast.ident;
26 let fields = match ast.body {
27 syn::Body::Struct(ref data) => data.fields(),
28 syn::Body::Enum(_) => panic!("#[derive(CSVParsable)] can only be used with structs"),
29 };
30
31 let mut header_parser = Vec::new();
32 let mut row_parser = Vec::new();
33 for (idx, field) in fields.iter().enumerate() {
34 let name = field.ident.clone().expect("Fields must be named");
35 header_parser.push({
36 quote! {
37 let mut index = None;
38 let headers = try!(reader.headers());
39 for (i, col) in headers.iter().enumerate() {
40 if col == stringify!(#name) {
41 index = Some(i);
42 }
43 }
44 let index = match index {
45 Some(index) => index,
46 None => {
47 return Err(easy_csv::Error::MissingColumnError(
48 format!("Column \"{}\" not found", stringify!(#name))));
49 }
50 };
51 column_indices.push(index);
52 }
53 });
54
55
56 row_parser.push({
57 let ty = field.ty.clone();
58 quote! {
59 #name : {
60 #[allow(unreachable_patterns)]
61 match record[col_indices[#idx]].parse::<#ty>() {
62 Ok(v) => v,
63 Err(_) => {
64 return Some(Err(easy_csv::Error::ParseError(
65 format!("Error parsing column \"{}\" on row {}",
66 stringify!(#name),
67 row))));
68 }
69 }
70 }
71 }
72 });
73 }
74
75 let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
76
77 quote! {
78 impl #impl_generics easy_csv::CSVParsable<#name> for #name #ty_generics #where_clause {
79 fn parse_header<R: std::io::Read>(
80 reader: &mut csv::Reader<R>) -> Result<Vec<usize>, easy_csv::Error> {
81 let mut column_indices = vec![];
82 #(#header_parser)*
83 Ok(column_indices)
84 }
85
86 fn parse_row<R: std::io::Read>(
87 records: &mut std::iter::Enumerate<csv::StringRecords<R>>,
88 col_indices: &Vec<usize>) -> Option<Result<#name,easy_csv::Error>> {
89 match records.next() {
90 Some((row, record)) => {
91 match record {
92 Ok(record) => Some(Ok(#name { #(#row_parser),* })),
93 Err(e) => Some(Err(easy_csv::Error::CSVError(e)))
94 }
95 }
96 None => None
97 }
98 }
99 }
100 }
101}