#![feature(proc_macro_span)] extern crate proc_macro; use std::fs; use proc_macro::TokenStream; use quote::quote; #[proc_macro_derive(Model, attributes(model))] pub fn model_derive(input: TokenStream) -> TokenStream { // Construct a representation of Rust code as a syntax tree // that we can manipulate let ast = syn::parse(input).unwrap(); // Build the trait implementation impl_model(&ast) } fn impl_model(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; let gen = quote! { impl #name { pub const META: tosin::db::models::ModelMeta = tosin::db::models::ModelMeta { name: stringify!(#name), }; } }; gen.into() } #[proc_macro] pub fn gather_migrations(_input: TokenStream) -> TokenStream { let call_site = proc_macro::Span::call_site(); let call_site_file = call_site.source_file(); let call_site_path = call_site_file.path(); if !call_site_file.is_real() { panic!("call site does not have a real path"); } let migrations_dir = call_site_path.parent().unwrap(); let migrations: Vec = migrations_dir.read_dir() .unwrap() .map(Result::unwrap) .map(|x| x.path().file_stem().unwrap().to_string_lossy().into_owned()) .filter(|x| x != "mod") .map(|x| syn::parse_str(&x).unwrap()) .collect(); let gen = quote! { #( mod #migrations; )* pub const migrations: &[Migration] = &[ #(#migrations::MIGRATION),* ]; }; gen.into() } #[proc_macro] pub fn gather_models(_input: TokenStream) -> TokenStream { let call_site = proc_macro::Span::call_site(); let call_site_file = call_site.source_file(); let call_site_path = call_site_file.path(); if !call_site_file.is_real() { panic!("call site does not have a real path"); } let call_site_ast = syn::parse_file(&fs::read_to_string(call_site_path).unwrap()).unwrap(); let models = call_site_ast.items.iter() .filter_map(|item| if let syn::Item::Struct(item) = item { Some(item) } else { None }) .filter(|item| item.attrs.iter().any(|attr| { let attr = if let Ok(syn::Meta::List(attr)) = attr.parse_meta() { attr } else { return false; }; if attr.path.get_ident().map_or(false, |hopefully_derive| hopefully_derive == "derive") { let mut derived = attr.nested.iter() .filter_map(|derived| if let syn::NestedMeta::Meta(derived) = derived { Some(derived) } else { None }); derived.any(|derived| derived.path().get_ident().map_or(false, |hopefully_model| hopefully_model == "Model")) } else { false } })) .map(|item| &item.ident); let gen = quote! { pub const ALL: &[tosin::db::models::ModelMeta] = &[ #(#models::META),* ]; }; gen.into() }