summaryrefslogtreecommitdiff
path: root/src/render.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/render.rs')
-rw-r--r--src/render.rs116
1 files changed, 116 insertions, 0 deletions
diff --git a/src/render.rs b/src/render.rs
new file mode 100644
index 0000000..befc780
--- /dev/null
+++ b/src/render.rs
@@ -0,0 +1,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;
+ }
+}