use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, LitStr};
#[cfg(feature = "template")]
use handlebars::Handlebars;
#[cfg(feature = "template")]
mod template;
fn read_to_string_relative(rel_path: &Path) -> String {
let crate_root = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let path = PathBuf::from(crate_root).join(rel_path);
if !path.exists() {
panic!("'{}' does not exist", path.display());
}
std::fs::read_to_string(path).expect("could not read file")
}
#[proc_macro]
pub fn include_js(item: TokenStream) -> TokenStream {
let input_path = parse_macro_input!(item as LitStr).value();
let content = read_to_string_relative(Path::new(&input_path));
let _ = boa::parse(&content, false).expect("syntax error");
TokenStream::from(quote! {
unsafe { JSStr::new_unchecked(#content) }
})
}
#[cfg(feature = "template")]
#[proc_macro_derive(JSTemplate, attributes(include_js))]
pub fn derive_js_template(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let template_path = {
let template_attr = template::get_attr(&input);
let template = template_attr.parse_args::<template::TemplatePathInput>().unwrap();
template.path.value()
};
let struct_name = &input.ident;
let content = read_to_string_relative(Path::new(&template_path));
let data: HashMap<String, [(); 0]> = {
let field_names = match &input.data {
Data::Struct(ds) => template::struct_field_names(&ds),
_ => panic!("only structs supported"),
};
field_names.into_iter().zip(std::iter::repeat([])).collect()
};
let expanded = {
let mut h = Handlebars::new();
h.set_strict_mode(true);
h.render_template(&content, &data)
.expect("error rendering template")
};
let _ = boa::parse(&expanded, false).expect("syntax error");
TokenStream::from(quote! {
impl JSTemplate for #struct_name {
fn render_template(&self) -> ::include_js::JSString {
let mut h = ::include_js::TemplateEngine::new();
h.set_strict_mode(true);
let s = h.render_template(#content, self).unwrap();
unsafe {
::include_js::JSString::new_unchecked(s)
}
}
}
})
}