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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// 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()
}