1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
use std::{
collections::{BTreeMap, HashMap},
error::Error,
};
use serde::{Deserialize, Serialize};
use tera::{Context, Error as TeraError, Tera, Value};
use crate::{Entry, Template};
/// Page attributes available to the template.
#[derive(Serialize, Deserialize, Debug)]
struct Page<'a> {
/// The path portion of the URL to this page.
path: &'a str,
}
/// Renderer combines entries with a template to produce output content.
pub struct Renderer {
tera: Tera,
entries: Vec<Entry>,
albums: Vec<(String, Vec<Entry>)>,
}
impl Renderer {
pub fn new<'a, T>(it: T) -> Result<Self, Box<dyn Error>>
where
T: Iterator<Item = &'a Template>,
{
let templates: BTreeMap<_, _> = it.map(|t| (t.name.to_string(), t.clone())).collect();
let mut tera = Tera::default();
tera.add_raw_templates(
templates
.values()
.map(|t| (t.name.to_string(), t.template.to_string()))
.collect::<Vec<_>>(),
)?;
tera.register_filter(
"make_filename",
move |value: &Value, attrs: &HashMap<String, Value>| {
let name = attrs
.get("template")
.and_then(|v| v.as_str())
.ok_or(TeraError::msg("Missing attribute template"))?;
let template = templates
.get(name)
.ok_or(TeraError::msg("Template doesn't exist"))?;
let entry = serde_json::from_value(value.clone())
.map_err(|_| TeraError::msg("Input to filter is not an entry"))?;
Ok(Value::from(template.make_filename(&entry)))
},
);
Ok(Self {
tera,
entries: Vec::new(),
albums: Vec::new(),
})
}
/// Render a template that represents a collection of entries.
pub fn render_index(&self, template: &str, path: &str) -> Result<String, Box<dyn Error>> {
let page = Page { path };
let context = self.make_context(&page);
Ok(self.tera.render(template, &context)?)
}
/// Render a template for a single entry.
pub fn render_entry(
&self,
template: &str,
path: &str,
entry: &Entry,
) -> Result<String, Box<dyn Error>> {
let page = Page { path };
let mut context = self.make_context(&page);
context.insert("entry", entry);
Ok(self.tera.render(template, &context)?)
}
/// Prepare the Context object for the template.
fn make_context(&self, page: &Page) -> Context {
let mut context = Context::new();
context.insert("page", &page);
context.insert("entries", &self.entries);
context.insert("albums", &self.albums);
context
}
pub fn set_entries(&mut self, entries: Vec<Entry>) {
let mut albums: Vec<(_, _)> = entries
.iter()
.fold(BTreeMap::new(), |mut a: BTreeMap<_, Vec<_>>, b| {
let k = b
.album
.clone()
.or_else(|| b.title.clone())
.unwrap_or_else(|| b.filename.clone());
a.entry(k).or_default().push(b.to_owned());
a
})
.into_iter()
.collect();
albums.sort_by(|a, b| {
let da = a.1.iter().map(|v| &v.date).max();
let db = b.1.iter().map(|v| &v.date).max();
db.cmp(&da)
});
self.albums = albums;
self.entries = entries;
}
}
|