raw
1mod func_link_graph;
2mod funcs;
3mod markdown;
4
5use sable_core::config::Config;
6use sable_vault::SharedVault;
7use tera::{Context, Tera};
8
9static DEFAULT_TEMPLATE_NAME: &str = "--sable-default.html";
10static DEFAULT_TEMPLATE: &str = include_str!("default.html");
11
12#[derive(Debug, miette::Diagnostic, thiserror::Error)]
13pub enum TemplatesError {
14 #[error("failed to convert path as its not valid utf-8")]
15 PathNotUtf8,
16 #[error("failed to load tera templates")]
17 TeraInit(#[source] tera::Error),
18 #[error("failed to render template")]
19 RenderTemplate(#[source] tera::Error),
20}
21
22#[derive(Debug)]
24pub struct Templates {
25 tera: Tera,
26
27 default_template: Option<String>,
28}
29
30impl Templates {
31 pub fn load(config: &Config, vault: SharedVault) -> Result<Self, TemplatesError> {
32 let dir_str = config
33 .templates
34 .to_str()
35 .ok_or(TemplatesError::PathNotUtf8)?;
36 let dir_pattern = format!("{dir_str}/**/*");
37
38 let mut tera = Tera::new(&dir_pattern).map_err(TemplatesError::TeraInit)?;
39
40 tera.add_raw_template(DEFAULT_TEMPLATE_NAME, DEFAULT_TEMPLATE)
41 .expect("failed to add default fallback template");
42
43 tera.register_filter("markdown", markdown::Markdown::new(vault.clone()));
44
45 tera.register_function("link_graph", func_link_graph::LinkGraph::new(vault));
46
47 for template in tera.get_template_names() {
48 tracing::debug!(template=%template, "loaded tera template");
49 }
50
51 Ok(Self {
52 tera,
53
54 default_template: config.default_template.clone(),
55 })
56 }
57
58
62 pub fn get_template_names(&self) -> impl Iterator<Item = &str> {
64 self.tera.get_template_names()
65 }
66
67 #[must_use]
69 pub fn has_template(&self, name: &str) -> bool {
70 self.get_template_names().any(|tn| tn == name)
71 }
72
73 pub fn render(&self, template_name: &str, context: &Context) -> Result<String, TemplatesError> {
74 let name = if self.has_template(template_name) {
75 template_name
76 } else if let Some(default_template) = &self.default_template {
77 default_template
78 } else {
79 tracing::warn!(template=%template_name, "requested template does not exist, using fallback");
80
81 DEFAULT_TEMPLATE_NAME
82 };
83
84 self.tera
85 .render(name, context)
86 .map_err(TemplatesError::RenderTemplate)
87 }
88}
89
90#[derive(Debug)]
91struct FilterError {
92 msg: String,
93}
94
95impl FilterError {
96 fn new<M: ToString>(msg: M) -> Self {
97 Self {
98 msg: msg.to_string(),
99 }
100 }
101}
102
103impl std::fmt::Display for FilterError {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 write!(f, "{}", self.msg)
106 }
107}
108
109impl std::error::Error for FilterError {}
110