impl_table 0.1.1

Generate table binding and utils for rust-postgres and rusqlite.
Documentation
// Sample usage
//
// #[impl_table(name = "books", adaptor = rusqlite, with_columns(id, timestamps))]
// #[derive(Table)]
// // Optionally generate an id column and two timestamp columns: created_at and
// // updated_at.
// struct Book {
//     #[column] name: String,
//     #[column] pub published_at: DateTime<Utc>,
//     #[column(rename = 'author_id')] author: i64,
// }
//
// #[derive(Table)]
// struct Person {
//     // If id is not generated, specify the primary key.
//     #[primary_key] name: String,
//     #[column] date_of_birth: DateTime<Utc>,
// }
//
extern crate proc_macro;
extern crate proc_macro2;

mod parse_parameters;

use parse_parameters::parse_parameters;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(Table, attributes(column, primary_key))]
pub fn derive_table(_item: TokenStream) -> TokenStream {
    TokenStream::new()
}

#[derive(Debug, PartialEq)]
pub(crate) enum Parameter {
    Switch {
        name: String,
    },
    Flag {
        key: String,
        value: String,
    },
    Function {
        name: String,
        params: Vec<Parameter>,
    },
}

macro_rules! build_field {
    ($($body:tt)*) => {
        {
            let mut outer_fields: syn::FieldsNamed = syn::parse_quote! {
                {
                    $($body)*
                }
            };
            outer_fields.named.pop().unwrap().into_value()
        }
    }
}

macro_rules! return_error_with_msg {
    ($msg:expr) => {
        return syn::Error::new(proc_macro2::Span::call_site(), $msg)
            .to_compile_error()
            .into()
    }
}

#[proc_macro_attribute]
pub fn impl_table(attr: TokenStream, item: TokenStream) -> TokenStream {
    let parameters;
    match parse_parameters(
        proc_macro2::TokenStream::from(attr),
        proc_macro2::Span::call_site(),
    ) {
        Ok(param) => parameters = param,
        Err(e) => return syn::Error::from(e).to_compile_error().into(),
    }

    let mut name = String::from("");
    let mut adaptor = String::from("rusqlite");
    let mut with_id = false;
    let mut with_created_at = false;
    let mut with_updated_at = false;
    for para in parameters {
        match para {
            Parameter::Flag { key, value } => {
                if key == "name" {
                    name = value;
                } else if key == "adaptor" {
                    adaptor = value;
                } else {
                    // error.
                }
            }
            Parameter::Switch { name: _ } => (), // error.
            Parameter::Function { name, params } => {
                if name == "with_columns" {
                    for para in params {
                        if let Parameter::Switch { name } = para {
                            if name == "id" {
                                with_id = true;
                            } else if name == "created_at" {
                                with_created_at = true;
                            } else if name == "updated_at" {
                                with_updated_at = true;
                            } else if name == "timestamps" {
                                with_created_at = true;
                                with_updated_at = true;
                            } else {
                                // error.
                            }
                        } else {
                            // error.
                        }
                    }
                } else {
                    // error.
                }
            }
        }
    }
    if name.is_empty() {
        return_error_with_msg!("Table name must be specified and non-empty.")
    }

    // Now we start to parse the struct
    let mut struct_def = parse_macro_input!(item as DeriveInput);
    let data = &mut struct_def.data;
    if let syn::Data::Struct(data_struct) = data {
        let fields = &mut data_struct.fields;

        // struct can only have named fields.
        if let syn::Fields::Named(named_fields) = fields {
            if with_id {
                named_fields.named.insert(0usize, build_field!{ #[primary_key] id: i64 });
            }
            if with_created_at {
                named_fields.named.push(
                    build_field!{ #[column] created_at: chrono::DateTime<Utc> });
            }
            if with_updated_at {
                named_fields.named.push(
                    build_field!{ #[column] updated_at: chrono::DateTime<Utc> });
            }
        } else {
            panic!("Expecting named fields within a struct.");
        }
    } else {
        return_error_with_msg!("impl_table can only be applied to structs.")
    }

    let struct_name = &struct_def.ident;
    let expr = quote! {
        #struct_def

        impl #struct_name {
            pub const TABLE_NAME: &'static str = #name;
            pub const ADAPTOR_NAME: &'static str = #adaptor;
        }
    };
    expr.into()
}