tera_template_macro/
lib.rs1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use proc_macro2::Ident;
5use quote::quote;
6use syn::{parse_macro_input, Attribute, DeriveInput, Expr, ExprLit, Lit, LitStr, Meta};
7
8struct Struct<'a> {
9 name: &'a Ident,
10 path: LitStr,
11}
12
13#[inline]
14#[must_use]
15fn is_template_attr(attr: &&Attribute) -> bool {
16 attr.path().is_ident("template")
17}
18
19fn parse_args(input: &Attribute) -> syn::Result<Meta> {
20 input.parse_args().map_err(|_| {
21 syn::Error::new_spanned(
22 input,
23 "Expected attribute argument `path` in parentheses: \
24 `#[template(path = \"index.html\")]`",
25 )
26 })
27}
28
29fn get_attr(input: &DeriveInput) -> syn::Result<&Attribute> {
30 input.attrs.iter().find(is_template_attr).ok_or_else(|| {
31 syn::Error::new_spanned(input, "The #[template(path = \"...\")] is required.")
32 })
33}
34
35fn get_path_name_value(meta: Meta) -> syn::Result<ExprLit> {
36 let Meta::NameValue(nv) = meta else {
37 return Err(syn::Error::new_spanned(
38 meta,
39 "Expected `path = \"...\"`. E.g. `#[template(path = \"index.html\")]`",
40 ));
41 };
42
43 if !nv.path.is_ident("path") {
44 return Err(syn::Error::new_spanned(&nv.path, "Expected `path`."));
45 }
46
47 match nv.value {
48 Expr::Lit(lit) => Ok(lit),
49 _ => Err(syn::Error::new_spanned(
50 nv,
51 "The assignment to `path` must be a string literal.",
52 )),
53 }
54}
55
56fn get_lit_str(lit: ExprLit) -> syn::Result<LitStr> {
57 match lit.lit {
58 Lit::Str(res) => Ok(res),
59 _ => Err(syn::Error::new_spanned(
60 lit,
61 "The assignment to `path` must be a string literal.",
62 )),
63 }
64}
65
66fn get_path_value(input: &Attribute) -> syn::Result<LitStr> {
67 parse_args(input)
68 .and_then(get_path_name_value)
69 .and_then(get_lit_str)
70}
71
72impl<'a> Struct<'a> {
73 fn from_syn(input: &'a DeriveInput) -> syn::Result<Self> {
74 let name = &input.ident;
75 get_attr(input)
76 .and_then(get_path_value)
77 .map(move |path| Self { name, path })
78 }
79}
80
81fn expand_struct(s: Struct) -> proc_macro2::TokenStream {
82 let Struct { name, path } = s;
83
84 quote! {
85 #[automatically_derived]
86 impl #name {
87 fn render(&self, tera: &tera::Tera) -> String {
88 let context = tera::Context::from_serialize(self).expect("Failed to create context");
89 let rendered = tera
90 .render(#path, &context)
91 .expect("Failed to render template");
92 rendered
93 }
94 }
95 }
96}
97
98#[proc_macro_derive(TeraTemplate, attributes(template))]
124pub fn tera_template_derive(input: TokenStream) -> TokenStream {
125 let input = parse_macro_input!(input as DeriveInput);
126
127 Struct::from_syn(&input)
128 .map_or_else(syn::Error::into_compile_error, expand_struct)
129 .into()
130}