1extern crate proc_macro;
2
3use proc_macro2::TokenStream;
4use serde_json::Value;
5use syn::LitStr;
6use syn::parse::{Parse, ParseStream, Result};
7use tera::{Context, Tera};
8use unicode_segmentation::UnicodeSegmentation;
9
10struct TeraMacroInput {
11 context: LitStr,
12 rust_code: TokenStream,
13}
14
15impl Parse for TeraMacroInput {
16 fn parse(input: ParseStream) -> Result<Self> {
17 let json: LitStr = input.parse()?;
18 input.parse::<syn::token::Comma>()?;
19 let rust_code = input.parse()?;
20
21 Ok(TeraMacroInput { context: json, rust_code })
22 }
23}
24
25#[proc_macro]
26pub fn tera(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
27 let input = syn::parse_macro_input!(tokens as TeraMacroInput);
28 let template = remove_space_added_by_parsing(&input.rust_code.to_string());
29 println!("template: {:?}", template);
30 let mut tera = Tera::default();
31 tera.add_raw_template("tera", &template).expect("The template was not valid.");
32 let json_string: String = input.context.value();
33 let value: Value = serde_json::from_str(&json_string).expect("The Context was not valid json.");
34 println!("value: {:?}", value);
35 let context = Context::from_value(value).expect("Tera failed to create Context from json value.");
36 let output: String = tera.render("tera", &context).expect("Could not render the template");
37 println!("output: {:?}", output);
38 let token_stream: TokenStream = syn::parse_str(&output).expect("Could not converted the rendered output into a \
39 valid token stream");
40 proc_macro::TokenStream::from(token_stream)
41}
42
43
44fn remove_space_added_by_parsing(input: &str) -> String {
45 let graphemes = UnicodeSegmentation::graphemes(input, true);
46 let mut graphemes_it = graphemes.into_iter();
47 let Some(mut prev) = graphemes_it.next() else {
48 return String::new();
49 };
50 let Some(mut this) = graphemes_it.next() else {
51 return prev.into();
52 };
53 let mut result: Vec<&str> = Vec::with_capacity(input.len());
54 result.push(prev);
55
56 while let Some(next) = graphemes_it.next() {
57 match (prev, this, next) {
58 ("{", " ", "{") => (),
59 ("{", " ", "%") => (),
60 ("{", " ", "#") => (),
61 ("}", " ", "}") => (),
62 ("%", " ", "}") => (),
63 ("#", " ", "}") => (),
64 _ => result.push(this)
65 }
66 prev = this;
67 this = next;
68 }
69 result.push(this);
70 result.join("")
71}