Skip to main content

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}