1use handlebars::Handlebars;
2use serde_json::Value;
3use std::collections::HashMap;
4use walkdir::WalkDir;
5
6const PARTIAL_TEMPLATE_PREFIX: &str = "_";
7const HANDLEBARS_TEMPLATE_EXTENSION: &str = ".hbs";
8
9#[derive(Debug, Clone, PartialEq)]
10pub struct TemplateCollection {
11 main: Template,
12 partials: HashMap<String, Template>,
13}
14
15impl TemplateCollection {
16 pub fn from_list(templates: impl IntoIterator<Item = Template>) -> Result<TemplateCollection, anyhow::Error> {
17 let mut main: Option<Template> = None;
18 let mut partials = HashMap::<String, Template>::new();
19
20 for t in templates {
21 match t.template_type() {
22 TemplateType::Main => {
23 if let Some(existing) = main.as_ref() {
24 return Err(anyhow::anyhow!(
25 "2 main templates were found : \n-{}\n-{}\nTheir should be only one in all the template directories",
26 existing.file_path(),
27 t.file_path()
28 ));
29 };
30 main = Some(t);
31 }
32 TemplateType::Partial => {
33 if let Some(existing) = partials.get(t.template_name()) {
34 return Err(anyhow::anyhow!(
35 "2 partial templates are named `{}` : \n-{}\n-{}\nThey should have unique names",
36 existing.template_name(),
37 existing.file_path(),
38 t.file_path()
39 ));
40 };
41 partials.insert(t.template_name().into(), t);
42 }
43 }
44 }
45
46 let main = main.ok_or_else(|| anyhow::anyhow!("No main template has been detected, we don't know what to execute..."))?;
47
48 Ok(Self { main, partials })
49 }
50
51 pub fn render(&self, json: &Value, mut handlebars: Handlebars) -> Result<String, anyhow::Error> {
52 let template_name = self.main.template_name();
53 handlebars.register_template_file(template_name, self.main.file_path())?;
54 for (_, value) in self.partials.iter() {
55 handlebars.register_template_file(value.template_name(), value.file_path())?
56 }
57 Ok(handlebars.render(template_name, json)?)
58 }
59}
60
61#[derive(Debug, Copy, Clone, PartialEq)]
62pub enum TemplateType {
63 Main,
64 Partial,
65}
66
67#[derive(Debug, Clone, PartialEq)]
68pub struct Template {
69 template_type: TemplateType,
70 file_name: String,
71 file_path: String,
72}
73
74impl Template {
75 pub fn new(template_type: TemplateType, file_name: impl Into<String>, file_path: impl Into<String>) -> Self {
76 Self {
77 template_type,
78 file_name: file_name.into(),
79 file_path: file_path.into(),
80 }
81 }
82
83 pub fn file_path(&self) -> &str {
84 &self.file_path
85 }
86
87 pub fn template_name(&self) -> &str {
88 self.file_name.trim_start_matches('_').trim_end_matches(".hbs")
89 }
90
91 pub fn template_type(&self) -> TemplateType {
92 self.template_type
93 }
94}
95
96pub fn get_templates_from_directory(dir_path: &str) -> Result<Vec<Template>, anyhow::Error> {
97 let mut result = vec![];
98 for entry in WalkDir::new(dir_path) {
99 let entry = entry?;
100
101 let file_path = entry.path().to_str();
102 let file_path = match file_path {
103 Some(f) => f,
104 None => continue,
105 };
106
107 let file_name = entry.file_name().to_str();
108 if let Some(file_name) = file_name {
109 if !file_name.ends_with(HANDLEBARS_TEMPLATE_EXTENSION) {
110 continue;
111 }
112 let t = if file_name.starts_with(PARTIAL_TEMPLATE_PREFIX) {
113 TemplateType::Partial
114 } else {
115 TemplateType::Main
116 };
117 result.push(Template::new(t, file_name, file_path));
118 }
119 }
120 Ok(result)
121}
122
123#[cfg(test)]
124mod test {
125 use super::*;
126 use crate::helpers::handlebars_setup;
127 use crate::loader::DocumentPath;
128 use crate::resolver::resolve_refs;
129
130 #[test]
131 fn handlebars_loading_test() -> Result<(), anyhow::Error> {
132 let document = DocumentPath::parse("_samples/resolver/petshop_with_external.yaml")?;
133 let json = resolve_refs(document)?;
134 let list = get_templates_from_directory("_samples/render/test_denis")?;
135 let collection = TemplateCollection::from_list(list)?;
136
137 let mut h = Handlebars::new();
138 handlebars_setup(&mut h, Default::default());
139 let result = collection.render(&json, h)?;
140 dbg!(result);
141
142 Ok(())
143 }
144
145 #[test]
146 fn get_templates_from_directory_test() -> Result<(), anyhow::Error> {
147 let mut templates = get_templates_from_directory("./_samples/render/templates")?;
148 templates.sort_by_key(|t| t.file_path.clone());
149 let expected = vec![
150 Template::new(
151 TemplateType::Partial,
152 "_other_partial.hbs",
153 "./_samples/render/templates/_other_partial.hbs",
154 ),
155 Template::new(TemplateType::Partial, "_partial.hbs", "./_samples/render/templates/_partial.hbs"),
156 Template::new(TemplateType::Partial, "_plop.hbs", "./_samples/render/templates/sub/_plop.hbs"),
157 Template::new(TemplateType::Main, "plop.hbs", "./_samples/render/templates/sub/plop.hbs"),
158 Template::new(TemplateType::Main, "test.hbs", "./_samples/render/templates/test.hbs"),
159 ];
160 assert_eq!(templates, expected);
162
163 let first = templates.get(0).expect("?");
164 assert_eq!(first.template_name(), "other_partial");
165 Ok(())
166 }
167
168 #[test]
169 fn from_list_fails_with_double_main() {
170 let list = vec![
171 Template::new(TemplateType::Main, "plop.hbs", "./_samples/render/templates/sub/plop.hbs"),
172 Template::new(TemplateType::Main, "test.hbs", "./_samples/render/templates/test.hbs"),
173 ];
174
175 let test = TemplateCollection::from_list(list);
176 let err = test.expect_err("Should be an error");
177 assert!(err.to_string().starts_with("2 main templates were found"));
178 }
179
180 #[test]
181 fn from_list_fails_with_same_names_partials() {
182 let list = vec![
183 Template::new(
184 TemplateType::Partial,
185 "_other_partial.hbs",
186 "./_samples/render/templates/_other_partial.hbs",
187 ),
188 Template::new(TemplateType::Partial, "_partial.hbs", "./_samples/render/templates/_partial.hbs"),
189 Template::new(TemplateType::Partial, "_plop.hbs", "./_samples/render/templates/sub/_plop.hbs"),
190 Template::new(TemplateType::Partial, "_plop.hbs", "./_samples/render/templates/sub2/_plop.hbs"),
191 ];
192
193 let test = TemplateCollection::from_list(list);
194 let err = test.expect_err("Should be an error");
195 assert!(err.to_string().starts_with("2 partial templates are named `plop`"));
196 }
197
198 #[test]
199 fn from_list_fails_with_no_main_found() {
200 let list = vec![
201 Template::new(
202 TemplateType::Partial,
203 "_other_partial.hbs",
204 "./_samples/render/templates/_other_partial.hbs",
205 ),
206 Template::new(TemplateType::Partial, "_partial.hbs", "./_samples/render/templates/_partial.hbs"),
207 Template::new(TemplateType::Partial, "_plop.hbs", "./_samples/render/templates/sub/_plop.hbs"),
208 ];
209
210 let test = TemplateCollection::from_list(list);
211 let err = test.expect_err("Should be an error");
212 assert!(err
213 .to_string()
214 .starts_with("No main template has been detected, we don't know what to execute..."));
215 }
216
217 #[test]
218 fn from_list_success() {
219 let list = vec![
220 Template::new(TemplateType::Main, "plop.hbs", "./_samples/render/templates/sub/plop.hbs"),
221 Template::new(TemplateType::Partial, "_partial.hbs", "./_samples/render/templates/_partial.hbs"),
222 ];
223 let test = TemplateCollection::from_list(list).expect("?");
224 let mut map = HashMap::new();
225 map.insert(
226 "partial".into(),
227 Template::new(TemplateType::Partial, "_partial.hbs", "./_samples/render/templates/_partial.hbs"),
228 );
229 let expected = TemplateCollection {
230 main: Template::new(TemplateType::Main, "plop.hbs", "./_samples/render/templates/sub/plop.hbs"),
231 partials: map,
232 };
233
234 assert_eq!(test, expected);
235 }
236}