quickstart_lib/template/
engine.rs

1//! Template engine implementation
2//!
3//! This module provides the core template rendering functionality
4//! using Handlebars as the template engine.
5
6use handlebars::Handlebars;
7
8use super::{Result, TemplateError, TemplateVariables};
9use crate::tools::{KebabCaseHelper, LowercaseHelper, SnakeCaseHelper, UppercaseHelper};
10
11/// Template engine for rendering project templates
12pub struct TemplateEngine {
13    /// Handlebars registry
14    handlebars: Handlebars<'static>,
15    /// Template variables
16    variables: TemplateVariables,
17}
18
19impl TemplateEngine {
20    /// Create a new template engine with the given variables
21    pub fn new(variables: TemplateVariables) -> Self {
22        let mut handlebars = Handlebars::new();
23
24        // Configure handlebars
25        handlebars.set_strict_mode(true);
26
27        // Register helpers
28        handlebars.register_helper("lowercase", Box::new(LowercaseHelper));
29        handlebars.register_helper("uppercase", Box::new(UppercaseHelper));
30        handlebars.register_helper("snake_case", Box::new(SnakeCaseHelper));
31        handlebars.register_helper("kebab_case", Box::new(KebabCaseHelper));
32
33        Self {
34            handlebars,
35            variables,
36        }
37    }
38
39    /// Register a template from a string
40    pub fn register_template(&mut self, name: &str, content: &str) -> Result<()> {
41        self.handlebars
42            .register_template_string(name, content)
43            .map_err(|e| TemplateError::RenderError {
44                name: name.to_string(),
45                source: e.into(),
46            })
47    }
48
49    /// Render a template with the stored variables
50    pub fn render(&self, template_name: &str) -> Result<String> {
51        self.handlebars
52            .render(template_name, &self.variables)
53            .map_err(|e| TemplateError::RenderError {
54                name: template_name.to_string(),
55                source: e,
56            })
57    }
58
59    /// Render a template string directly
60    pub fn render_template(&self, template_content: &str) -> Result<String> {
61        self.handlebars
62            .render_template(template_content, &self.variables)
63            .map_err(|e| TemplateError::RenderError {
64                name: "string_template".to_string(),
65                source: e,
66            })
67    }
68
69    /// Get a reference to the template variables
70    pub fn variables(&self) -> &TemplateVariables {
71        &self.variables
72    }
73
74    /// Get a mutable reference to the template variables
75    pub fn variables_mut(&mut self) -> &mut TemplateVariables {
76        &mut self.variables
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use crate::template::variables::TemplateVariables;
84    use pretty_assertions::assert_eq;
85
86    #[test]
87    fn test_render_template() {
88        let variables = TemplateVariables::default_test_variables();
89        let engine = TemplateEngine::new(variables);
90
91        let template =
92            "# {{name}}\n\n{{#if description}}{{description}}{{/if}}\n\nLicense: {{license}}";
93        let expected = "# test-project\n\nA test project\n\nLicense: MIT";
94
95        let result = engine.render_template(template).unwrap();
96        assert_eq!(result, expected);
97    }
98
99    #[test]
100    fn test_conditional_sections() {
101        let mut variables = TemplateVariables::default_test_variables();
102        variables.description = None;
103
104        let engine = TemplateEngine::new(variables);
105
106        let template = "# {{name}}\n\n{{#if description}}{{description}}{{else}}No description{{/if}}\n\nLicense: {{license}}";
107        let expected = "# test-project\n\nNo description\n\nLicense: MIT";
108
109        let result = engine.render_template(template).unwrap();
110        assert_eq!(result, expected);
111    }
112
113    #[test]
114    fn test_project_type_conditions() {
115        let variables = TemplateVariables::default_test_variables();
116        let engine = TemplateEngine::new(variables);
117
118        let template = "{{#if project.is_binary}}Binary{{else}}Library{{/if}}";
119        let expected = "Binary";
120
121        let result = engine.render_template(template).unwrap();
122        assert_eq!(result, expected);
123    }
124
125    #[test]
126    fn test_register_template_invalid_syntax() {
127        let variables = TemplateVariables::default_test_variables();
128        let mut engine = TemplateEngine::new(variables);
129        // Invalid Handlebars syntax
130        let result = engine.register_template("bad", "{{#if}");
131        assert!(result.is_err());
132    }
133
134    #[test]
135    fn test_render_unregistered_template() {
136        let variables = TemplateVariables::default_test_variables();
137        let engine = TemplateEngine::new(variables);
138        // Try to render a template that was never registered
139        let result = engine.render("not_registered");
140        assert!(result.is_err());
141    }
142
143    #[test]
144    fn test_render_template_invalid_content() {
145        let variables = TemplateVariables::default_test_variables();
146        let engine = TemplateEngine::new(variables);
147        // Invalid Handlebars syntax in direct render
148        let result = engine.render_template("{{#if}");
149        assert!(result.is_err());
150    }
151
152    #[test]
153    fn test_variables_accessors() {
154        let variables = TemplateVariables::default_test_variables();
155        let mut engine = TemplateEngine::new(variables.clone());
156        assert_eq!(engine.variables().name, variables.name);
157        engine.variables_mut().name = "changed".to_string();
158        assert_eq!(engine.variables().name, "changed");
159    }
160}