summaryrefslogtreecommitdiff
path: root/src/render.rs
blob: befc78095b1c0e4cd19fc1a2aafc2b0a142a4063 (plain)
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;
    }
}