1#![recursion_limit = "128"]
2
3extern crate proc_macro;
4#[macro_use]
5extern crate quote;
6#[macro_use]
7extern crate syn;
8
9use proc_macro::TokenStream;
10use std::convert::TryFrom;
11
12#[proc_macro_derive(Template, attributes(template))]
13pub fn template_derive(input: TokenStream) -> TokenStream {
14 let input = parse_macro_input!(input as syn::DeriveInput);
15 template_derive_inner(input).unwrap()
16}
17
18fn template_derive_inner(input: syn::DeriveInput) -> Result<TokenStream, Box<dyn std::error::Error>> {
19 let name = &input.ident;
20 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
21
22 let mut path = None;
23 let mut type_ = None;
24 let mut size_hint = None;
25
26 for pair in input
27 .attrs
28 .iter()
29 .flat_map(|x| x.parse_meta())
30 .filter(|x| x.name() == "template")
31 .filter_map(|x| match x {
32 syn::Meta::List(ml) => Some(ml),
33 _ => None,
34 })
35 .flat_map(|x| x.nested)
36 .filter_map(|x| match x {
37 syn::NestedMeta::Meta(m) => Some(m),
38 _ => None,
39 })
40 .filter_map(|x| match x {
41 syn::Meta::NameValue(nv) => Some(nv),
42 _ => None,
43 })
44 {
45 if pair.ident == "path" {
46 if let syn::Lit::Str(ref s) = pair.lit {
47 path = Some(s.value());
48 }
49 }
50
51 if pair.ident == "type" {
52 if let syn::Lit::Str(ref s) = pair.lit {
53 type_ = Some(s.value());
54 }
55 }
56
57 if pair.ident == "size_hint" {
58 if let syn::Lit::Int(i) = pair.lit {
59 size_hint = Some(i.value());
60 }
61 }
62 }
63
64 let type_ = type_.as_ref().map(|x| x.as_str()).unwrap_or_else(|| "");
65
66 let size_hint: usize = size_hint.and_then(|x| usize::try_from(x).ok()).unwrap_or(1024);
67
68 let path = path.ok_or_else(|| "No path given")?;
69
70 let full_path = erst_shared::utils::templates_dir()?.join(&path);
71
72 let body = std::fs::read_to_string(&full_path)?;
73
74 let body = parse(&full_path.display().to_string(), &body, type_)?;
75
76 let body = format!("{{ {} }}", body);
77
78 let block = syn::parse_str::<syn::Block>(&body)?;
79
80 let stmts = &block.stmts;
81
82 #[cfg(any(not(feature = "dynamic"), not(debug_assertions)))]
83 let template_marker = {
84 let path_display = full_path.display().to_string();
85 let template_marker = syn::Ident::new(
86 &format!("__ERST_TEMPLATE_MARKER_{}", &name),
87 proc_macro2::Span::call_site(),
88 );
89 quote!(pub const #template_marker: () = { include_str!(#path_display); };)
90 };
91
92 #[cfg(all(feature = "dynamic", debug_assertions))]
93 let template_marker = {
94 let template_marker = syn::Ident::new(
95 &format!("__ERST_TEMPLATE_MARKER_{}", &name),
96 proc_macro2::Span::call_site(),
97 );
98
99 if let Some(path) = erst_shared::dynamic::get_code_cache_path(&full_path) {
100 let path_display = path.display().to_string();
101 quote!(pub const #template_marker: () = { include_str!(#path_display); };)
102 } else {
103 let path_display = full_path.display().to_string();
104 quote!(pub const #template_marker: () = { include_str!(#path_display); };)
105 }
106 };
107
108 let out = quote! {
109
110 impl #impl_generics erst::Template for #name #ty_generics #where_clause {
111 fn render_into(&self, writer: &mut dyn std::fmt::Write) -> std::fmt::Result {
112 #template_marker
113 let __erst_buffer = writer;
114 #(#stmts)*
115 Ok(())
116 }
117
118 fn size_hint() -> usize { #size_hint }
119 }
120
121 impl #impl_generics std::fmt::Display for #name #ty_generics #where_clause {
122 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
123 erst::Template::render_into(self, f)
124 }
125 }
126 };
127
128 Ok(out.into())
129}
130
131#[cfg(any(not(feature = "dynamic"), not(debug_assertions)))]
132fn parse(_: &str, template: &str, type_: &str) -> Result<String, Box<dyn std::error::Error>> {
133 use erst_shared::{
134 exp::Parser as _,
135 parser::{ErstParser, Rule},
136 };
137
138 let pairs = ErstParser::parse(erst_shared::parser::Rule::template, template)?;
139
140 let mut buffer = String::new();
141
142 for pair in pairs {
143 match pair.as_rule() {
144 Rule::code => {
145 let inner = pair.into_inner();
146 buffer.push_str(inner.as_str());
147 }
148 Rule::expr => match type_ {
149 "html" => {
150 buffer.push_str(&format!(
151 "write!(__erst_buffer, \"{{}}\", erst::Html({}))?;",
152 pair.into_inner().as_str()
153 ));
154 }
155 _ => {
156 buffer.push_str(&format!(
157 "write!(__erst_buffer, \"{{}}\", {})?;",
158 pair.into_inner().as_str()
159 ));
160 }
161 },
162 Rule::text => {
163 buffer.push_str(&format!(
164 "__erst_buffer.write_str(r####\"{}\"####)?;",
165 pair.as_str()
166 ));
167 }
168 _ => {}
169 }
170 }
171
172 Ok(buffer)
173}
174
175#[cfg(all(feature = "dynamic", debug_assertions))]
176fn parse(path: &str, template: &str, type_: &str) -> Result<String, Box<dyn std::error::Error>> {
177 use erst_shared::{
178 exp::Parser as _,
179 parser::{ErstParser, Rule},
180 };
181
182 let pairs = ErstParser::parse(erst_shared::parser::Rule::template, template)?;
183
184 let mut buffer = String::new();
185
186 for (idx, pair) in pairs.enumerate() {
187 match pair.as_rule() {
188 Rule::code => {
189 let inner = pair.into_inner();
190 buffer.push_str(inner.as_str());
191 }
192 Rule::expr => match type_ {
193 "html" => {
194 buffer.push_str(&format!(
195 "write!(__erst_buffer, \"{{}}\", erst::Html({}))?;",
196 pair.into_inner().as_str()
197 ));
198 }
199 _ => {
200 buffer.push_str(&format!(
201 "write!(__erst_buffer, \"{{}}\", {})?;",
202 pair.into_inner().as_str()
203 ));
204 }
205 },
206 Rule::text => {
207 buffer.push_str(&format!(
208 "write!(__erst_buffer, \"{{}}\",
209 erst::dynamic::get(\"{}\", {}).unwrap_or_default())?;",
210 path, idx
211 ));
212 }
213 _ => {}
214 }
215 }
216
217 Ok(buffer)
218}