use serde::{Serialize, Deserialize};
use pathbufd::PathBufD;
use std::sync::LazyLock;
use std::fs::{read_to_string, File};
mod generator;
#[derive(Serialize, Deserialize, Clone)]
struct Config {
pub root_dir: PathBufD,
}
static CONFIG: LazyLock<Config> = LazyLock::new(|| {
serde_json::from_str::<Config>(
&read_to_string(PathBufD::current().join("crml.json"))
.expect("failed to read configuration"),
)
.expect("failed to deserialize configuration")
});
use syn::{parse_macro_input, LitStr, ItemStruct};
use quote::{quote, ToTokens};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
pub(crate) fn get_file(file: &str) -> File {
File::open(PathBufD::current().extend(&[CONFIG.root_dir.to_string(), format!("{}.crml", file)]))
.expect("failed to read included file")
}
#[proc_macro_attribute]
pub fn template(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as LitStr);
let file_name = args.value();
let input = parse_macro_input!(input as ItemStruct);
let struct_ident = input.ident.clone();
let mut struct_tokens = TokenStream2::new();
input.to_tokens(&mut struct_tokens);
let generated = generator::Generator::from_file(get_file(&file_name)).consume();
let generated_tokens: TokenStream2 = match generated.parse() {
Ok(t) => t,
Err(e) => {
if std::fs::exists("crml_dbg").expect("failed to check for debug dir") == true {
println!(
"Debug directory found. Check \"crml_dbg/{}.rs\" to debug template.",
file_name
);
std::fs::write(
format!("crml_dbg/{file_name}.rs"),
format!("fn debug() {{\n{generated}\n}}"),
)
.expect("failed to write debug file")
}
panic!("{}", e.to_string())
}
};
let expanded = quote! {
#struct_tokens
impl crml::Template for #struct_ident {
fn render(self) -> String {
#generated_tokens
}
}
};
if std::fs::exists("crml_dbg").expect("failed to check for debug dir") == true {
let file_name = file_name.replace("/", "_");
std::fs::write(format!("crml_dbg/{file_name}.rs"), expanded.to_string())
.expect("failed to write debug file")
}
expanded.into()
}