anvil_minijinja_derive/lib.rs
1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput, Error, Expr, ExprLit, Lit};
4
5/// A derive macro for implementing the `Shrine` trait from the `anvil-minijinja` crate.
6///
7/// # Example
8///
9/// ```no_run
10/// // These imports would be needed in a real application
11/// use serde::Serialize;
12/// use anvil_minijinja_derive::Template;
13/// use anvil_minijinja::Shrine;
14/// use std::io::Write;
15///
16/// #[derive(Serialize, Template)]
17/// #[template("my_template.txt")]
18/// struct MyTemplate {
19/// name: String,
20/// }
21/// ```
22///
23/// This expands to code equivalent to:
24///
25/// ```no_run
26/// // Note: This is pseudocode showing what the macro generates
27/// use serde::Serialize;
28/// use anvil_minijinja::Shrine;
29/// use std::io::Write;
30/// use minijinja;
31///
32/// #[derive(Serialize)]
33/// struct MyTemplate {
34/// name: String,
35/// }
36///
37/// impl Shrine for MyTemplate {
38/// fn minijinja(&self, writer: &mut dyn Write) -> Result<(), minijinja::Error> {
39/// let mut env = minijinja::Environment::new();
40/// minijinja_embed::load_templates!(&mut env);
41/// let tmpl = env.get_template("my_template.txt")?;
42/// tmpl.render_to_write(self, writer)?;
43/// Ok(())
44/// }
45/// }
46/// ```
47#[proc_macro_derive(Template, attributes(template))]
48pub fn derive_template(input: TokenStream) -> TokenStream {
49 let input = parse_macro_input!(input as DeriveInput);
50
51 // Extract the name of the struct
52 let name = &input.ident;
53
54 // Find the template attribute
55 let template_name = match extract_template_name(&input) {
56 Ok(name) => name,
57 Err(err) => return err.to_compile_error().into(),
58 };
59
60 // TODO: Ability to add custom template loader fn / macro.
61 // which would allow for custom overriding for dev / runtime
62
63 // Generate the implementation
64 let expanded = quote! {
65 impl ::anvil_minijinja::Shrine for #name {
66 fn minijinja(&self, writer: &mut dyn ::std::io::Write) -> ::std::result::Result<(), ::minijinja::Error> {
67 let mut env = ::minijinja::Environment::new();
68 ::minijinja_embed::load_templates!(&mut env);
69 let tmpl = env.get_template(#template_name)?;
70 tmpl.render_to_write(self, writer)?;
71 Ok(())
72 }
73 }
74 };
75
76 TokenStream::from(expanded)
77}
78
79/// Extracts the template name from the attributes of a struct.
80fn extract_template_name(input: &DeriveInput) -> Result<String, Error> {
81 for attr in &input.attrs {
82 if attr.path().is_ident("template") {
83 let expr = attr.parse_args::<Expr>()?;
84
85 if let Expr::Lit(ExprLit {
86 lit: Lit::Str(lit_str),
87 ..
88 }) = expr
89 {
90 return Ok(lit_str.value());
91 } else {
92 let err_msg = "Expected template attribute to be in the form #[template(\"template_name.ext\")]";
93 return Err(Error::new_spanned(attr, err_msg));
94 }
95 }
96 }
97
98 Err(Error::new_spanned(
99 input,
100 "Missing #[template(\"template_name.ext\")] attribute",
101 ))
102}