webserver_base/templates/
template_registry.rs1use chrono::{DateTime, Utc};
2use handlebars::{Handlebars, handlebars_helper};
3use serde::Serialize;
4use serde_json::Value;
5use std::path::PathBuf;
6use std::{env, fs};
7use tracing::instrument;
8
9use super::error::TemplateRegistryError;
10
11handlebars_helper!(
13 join: |list: Vec<String>| list.join(",")
14);
15
16handlebars_helper!(
18 pretty_date: |date: DateTime<Utc>| date.format("%B %e, %Y").to_string()
19);
20
21handlebars_helper!(has_key: |obj: Value, key: str| {
23 obj.as_object()
24 .is_some_and(|o| o.contains_key(key))
25});
26
27#[derive(Clone)]
28pub struct TemplateRegistry<'a> {
29 handlebars: Handlebars<'a>,
30}
31
32impl Default for TemplateRegistry<'_> {
33 fn default() -> Self {
34 let mut html_path: PathBuf = env::current_dir().expect("failed to get current directory");
35 html_path.push("html");
36
37 let template_files: Vec<PathBuf> =
38 TemplateRegistry::find_all_template_files(&mut html_path)
39 .expect("failed to retrieve template files");
40
41 Self::new(template_files).expect("failed to create TemplateRegistry")
42 }
43}
44
45impl TemplateRegistry<'_> {
46 #[instrument(skip_all)]
54 pub fn new(template_files: Vec<PathBuf>) -> Result<Self, TemplateRegistryError> {
55 let mut handlebars = Handlebars::new();
57
58 handlebars.register_helper("join", Box::new(join));
60 handlebars.register_helper("pretty_date", Box::new(pretty_date));
61 handlebars.register_helper("has_key", Box::new(has_key));
62
63 handlebars.set_strict_mode(true);
65
66 for template_file in template_files {
68 let file_name = template_file
69 .file_stem()
70 .expect("failed to extract stem (non-file-extension) part of template file name")
71 .to_string_lossy();
72 handlebars.register_template_file(file_name.as_ref(), &template_file)?;
73 }
74
75 Ok(Self { handlebars })
76 }
77
78 #[instrument(skip_all)]
79 fn find_all_template_files(
80 html_path: &mut PathBuf,
81 ) -> Result<Vec<PathBuf>, TemplateRegistryError> {
82 let mut template_files: Vec<PathBuf> = vec![];
83
84 for directory in &["layouts", "pages", "partials"] {
85 html_path.push(directory);
86 template_files.extend(TemplateRegistry::get_all_files(html_path)?);
87 html_path.pop();
88 }
89
90 Ok(template_files)
91 }
92
93 #[instrument(skip_all)]
94 fn get_all_files(current_dir: &PathBuf) -> Result<Vec<PathBuf>, TemplateRegistryError> {
95 let mut files: Vec<PathBuf> = vec![];
96
97 for entry in fs::read_dir(current_dir)? {
98 let entry = entry?;
99 let path = entry.path();
100 files.push(path);
101 }
102
103 Ok(files)
104 }
105
106 #[instrument(skip_all)]
110 pub fn render<T>(&self, name: &str, data: &T) -> Result<String, TemplateRegistryError>
111 where
112 T: Serialize,
113 {
114 let rendered_template: String = self.handlebars.render(name, data)?;
115 Ok(rendered_template)
116 }
117}