easy_csv_derive/
lib.rs

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    // Construct a string representation of the type definition
12    let s = input.to_string();
13
14    // Parse the string representation
15    let ast = syn::parse_macro_input(&s).unwrap();
16
17    // Build the impl
18    let gen = impl_easy_csv(&ast);
19
20    // Return the generated impl
21    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}