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, albums: Vec<(String, Vec)>, } impl Renderer { pub fn new<'a, T>(it: T) -> Result> where T: Iterator, { 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::>(), )?; tera.register_filter( "make_filename", move |value: &Value, attrs: &HashMap| { 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> { 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> { 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) { 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; } }