erst_derive/
lib.rs

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}