extern crate proc_macro;
extern crate proc_macro2;
mod derive_table;
mod parse_arguments;
use parse_arguments::parse_arguments;
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 {
match derive_table::derive_table(item) {
Ok(tts) => tts.into(),
Err(e) => e.to_compile_error().into(),
}
}
#[derive(Debug, PartialEq)]
pub(crate) enum Argument {
Switch { name: String },
Flag { key: String, value: String },
Function { name: String, args: Vec<Argument> },
}
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 arguments;
match parse_arguments(
proc_macro2::TokenStream::from(attr),
proc_macro2::Span::call_site(),
) {
Ok(arg) => arguments = arg,
Err(e) => return syn::Error::from(e).to_compile_error().into(),
}
let mut name = "".to_string();
let mut adaptor = "rusqlite".to_string();
let mut with_id = false;
let mut with_created_at = false;
let mut with_updated_at = false;
for arg in arguments {
match arg {
Argument::Flag { key, value } => {
if key == "name" {
name = value;
} else if key == "adaptor" {
adaptor = value;
} else {
return_error_with_msg!(format!("Unrecognized flag with key {}.", key));
}
}
Argument::Switch { name } => {
return_error_with_msg!(format!("Unrecognized switch {}.", name));
}
Argument::Function { name, args } => {
if name == "with_columns" {
for arg in args {
if let Argument::Switch { name } = arg {
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 {
return_error_with_msg!(format!("Unrecognized switch {}.", name));
}
} else {
return_error_with_msg!(format!(
"Unrecognized function argument {:?}.",
arg
));
}
}
} else {
return_error_with_msg!(format!("Unrecognized function {}.", name));
}
}
}
}
if name.is_empty() {
return_error_with_msg!("Table name must be specified and non-empty.")
}
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;
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<chrono::Utc> });
}
if with_updated_at {
named_fields
.named
.push(build_field! { #[column] updated_at: chrono::DateTime<chrono::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()
}
#[macro_use]
extern crate doc_comment;
doc_comment!(include_str!("../README.md"));