diff options
Diffstat (limited to 'src/render.rs')
-rw-r--r-- | src/render.rs | 116 |
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; + } +} |