1use std::fs::File;
11use std::io;
12use std::path::Path;
13
14use serde::{Serialize, Deserialize};
15
16use crate::encoding::{EscapingIOEncoder, SqlEncoder};
17use crate::{Partials, hash_name};
18use crate::{Content, TemplateError};
19
20mod parse;
21mod section;
22
23pub use section::Section;
24pub use parse::Tag;
25
26#[derive(Debug)]
27#[derive(Serialize, Deserialize)]
28pub struct Template {
31    blocks: Vec<Block>,
33
34    capacity_hint: usize,
36
37    source: String,
39}
40
41impl Template {
42    pub fn new(source: &str) -> Result<Self, TemplateError>
47    {
48        Template::load(source, &mut NoPartials)
49    }
50
51    pub(crate) fn load(source: &str, partials: &mut impl Partials) -> Result<Self, TemplateError>
52    {
53        let source = source.to_owned();
54
55        let unsafe_source: &str = unsafe { &*(&*source as *const str) };
58
59        let mut tpl = Template {
60            blocks: Vec::with_capacity(16),
61            capacity_hint: 0,
62            source,
63        };
64        let last = tpl.parse(unsafe_source, partials)?;
65        let tail = &unsafe_source[last..].trim_end();
66        tpl.blocks.push(Block::nameless(tail, Tag::Tail));
67        tpl.capacity_hint += tail.len();
68
69        Ok(tpl)
70    }
71
72    pub fn capacity_hint(&self) -> usize {
74        self.capacity_hint
75    }
76
77    pub fn render<C>(&self, content: &C) -> String 
79    where
80        for<'section> C: Content
81    {
82        let mut capacity = content.capacity_hint(self);
83
84        capacity += capacity / 4;
86
87        let mut buf = String::with_capacity(capacity);
88
89        let rst = Section::new(&self.blocks)
91            .with(content);
92        
93        let _ = rst.render(&mut buf, Option::<&()>::None);
94
95        buf
96    }
97
98    pub fn render_sql<C>(&self, content: &C) -> String 
100    where
101        for<'section> C: Content
102    {
103        let mut capacity = content.capacity_hint(self);
104
105        capacity += capacity / 4;
107
108        let mut buf = SqlEncoder::with_capacity(capacity);
110
111        let rst = Section::new(&self.blocks)
113            .with(content);
114        
115        let _ = rst.render(&mut buf, Option::<&()>::None);
116
117        buf.trim()
118    }
119
120    pub fn render_to_writer<W, C>(&self, writer: &mut W, content: &C) -> io::Result<()>
122    where
123        W: io::Write,
124        for<'section> C: Content,
125    {
126        let mut encoder = EscapingIOEncoder::new(writer);
127        Section::new(&self.blocks)
128            .with(content)
129            .render(&mut encoder, Option::<&()>::None)
130    }
131
132    pub fn render_to_file<P, C>(&self, path: P, content: &C) -> io::Result<()>
134    where
135        P: AsRef<Path>,
136        for<'section> C: Content,
137    {
138        use io::BufWriter;
139
140        let writer = BufWriter::new(File::create(path)?);
141        let mut encoder = EscapingIOEncoder::new(writer);
142
143        Section::new(&self.blocks)
144            .with(content)
145            .render(&mut encoder, Option::<&()>::None)
146    }
147
148    pub fn source(&self) -> &str {
150        &self.source
151    }
152
153    pub fn serialize(&self) -> Vec<u8> {
155        bincode::serialize(&self).expect("error: serialize template failed")
156    }
157
158    pub fn deserialize(bin_content: &[u8]) -> Self {
160        bincode::deserialize(bin_content).expect("error: deserialize template failed")
161    }
162}
163
164#[derive(Debug, Clone, PartialEq, Eq)]
165#[derive(Serialize, Deserialize)]
166pub(crate) struct Block {
167    html: String,
168    name: String,
169    hash: u64,
170    tag: Tag,
171    children: u32,
172}
173
174impl Block {
175    #[inline]
176    fn new(html: &str, name: &str, tag: Tag) -> Self {
177        Block {
178            html: html.to_owned(),
179            name: name.to_owned(),
180            hash: hash_name(name),
181            tag,
182            children: 0,
183        }
184    }
185
186    #[inline]
188    fn nameless(html: &str, tag: Tag) -> Self {
189        Block {
190            html: html.to_owned(),
191            name: "".to_owned(),
192            hash: 0,
193            tag,
194            children: 0,
195        }
196    }
197}
198
199struct NoPartials;
200
201impl Partials for NoPartials {
202    fn get_partial(&mut self, _name: &str) -> Result<&Template, TemplateError> {
203        Err(TemplateError::PartialsDisabled)
204    }
205}
206
207#[cfg(test)]
208mod test {
209    use super::*;
210    use pretty_assertions::assert_eq;
211
212    impl Block {
213        fn children(self, children: u32) -> Self {
214            Block { children, ..self }
215        }
216    }
217
218    #[test]
219    fn template_from_string_is_static() {
220        let tpl: Template = Template::new(&String::from("Ramhorns")).unwrap();
221
222        assert_eq!(tpl.source(), "Ramhorns");
223    }
224
225    #[test]
226    fn block_hashes_correctly() {
227        assert_eq!(
228            Block::new("", "test", Tag::Escaped),
229            Block {
230                html: "".to_owned(),
231                name: "test".to_owned(),
232                hash: 2271575940368597870,
233                tag: Tag::Escaped,
234                children: 0,
235            }
236        );
237    }
238
239    #[test]
240    fn constructs_blocks_correctly() {
241        let source = "<title>{{title}}</title><h1>{{title}}</h1><div>{{{body}}}</div>";
242        let tpl = Template::new(source).unwrap();
243
244        assert_eq!(
245            &tpl.blocks,
246            &[
247                Block::new("<title>", "title", Tag::Escaped),
248                Block::new("</title><h1>", "title", Tag::Escaped),
249                Block::new("</h1><div>", "body", Tag::Unescaped),
250                Block::nameless("</div>", Tag::Tail),
251            ]
252        );
253    }
254
255    #[test]
256    fn constructs_nested_sections_correctly() {
257        let source = "<body><h1>{{title}}</h1>{{#posts}}<article>{{name}}</article>{{/posts}}{{^posts}}<p>Nothing here :(</p>{{/posts}}</body>";
258        let tpl = Template::new(source).unwrap();
259
260        assert_eq!(
261            &tpl.blocks,
262            &[
263                Block::new("<body><h1>", "title", Tag::Escaped),
264                Block::new("</h1>", "posts", Tag::Section).children(2),
265                Block::new("<article>", "name", Tag::Escaped),
266                Block::nameless("</article>", Tag::Closing),
267                Block::new("", "posts", Tag::Inverse).children(1),
268                Block::nameless("<p>Nothing here :(</p>", Tag::Closing),
269                Block::nameless("</body>", Tag::Tail),
270            ]
271        );
272    }
273
274    #[test]
275    fn constructs_nested_sections_with_dot_correctly() {
276        let source = "<body><h1>{{site title}}</h1>{{^archive posts}}<article>{{name}}</article>{{/archive posts}}</body>";
277        let tpl = Template::new(source).unwrap();
278
279        assert_eq!(
280            &tpl.blocks,
281            &[
282                Block::new("<body><h1>", "site", Tag::Section).children(1),
283                Block::new("", "title", Tag::Escaped),
284                Block::new("</h1>", "archive", Tag::Section).children(3),
285                Block::new("", "posts", Tag::Inverse).children(2),
286                Block::new("<article>", "name", Tag::Escaped),
287                Block::nameless("</article>", Tag::Closing),
288                Block::nameless("</body>", Tag::Tail),
289            ]
290        );
291    }
292}