codegenr_lib/
render.rs

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