vika_cli/templates/
engine.rs

1use crate::error::{Result, TemplateError};
2use crate::templates::registry::TemplateId;
3use crate::templates::resolver::TemplateResolver;
4use serde::Serialize;
5use std::path::Path;
6use tera::{Context, Tera};
7
8/// Template engine wrapper around Tera.
9pub struct TemplateEngine {
10    tera: Tera,
11    resolver: TemplateResolver,
12}
13
14impl TemplateEngine {
15    /// Create a new template engine.
16    ///
17    /// Loads all templates (built-in and user overrides) and compiles them.
18    pub fn new(project_root: Option<&Path>) -> Result<Self> {
19        let resolver = TemplateResolver::new(project_root);
20        let mut tera = Tera::default();
21
22        // Load all templates into Tera
23        for template_id in TemplateId::all() {
24            let template_content = resolver.resolve(template_id)?;
25            let template_name = template_id.filename();
26
27            tera.add_raw_template(&template_name, &template_content)
28                .map_err(|e| {
29                    crate::error::GenerationError::Template(TemplateError::InvalidSyntax {
30                        name: template_name.to_string(),
31                        message: e.to_string(),
32                    })
33                })?;
34        }
35
36        Ok(Self { tera, resolver })
37    }
38
39    /// Render a template with the given context.
40    pub fn render<T: Serialize>(&self, template_id: TemplateId, context: &T) -> Result<String> {
41        let template_name = template_id.filename();
42
43        let json_value = serde_json::to_value(context).map_err(|e| {
44            crate::error::GenerationError::Template(TemplateError::RenderFailed {
45                name: template_name.to_string(),
46                message: format!("Failed to serialize context: {}", e),
47            })
48        })?;
49        let tera_context = Context::from_serialize(&json_value).map_err(|e| {
50            crate::error::GenerationError::Template(TemplateError::RenderFailed {
51                name: template_name.to_string(),
52                message: format!("Failed to create Tera context: {}", e),
53            })
54        })?;
55
56        self.tera
57            .render(&template_name, &tera_context)
58            .map_err(|e| {
59                crate::error::GenerationError::Template(TemplateError::RenderFailed {
60                    name: template_name.to_string(),
61                    message: e.to_string(),
62                })
63            })
64            .map_err(Into::into)
65    }
66
67    /// Check if a template is overridden by user.
68    pub fn is_overridden(&self, template_id: TemplateId) -> bool {
69        self.resolver.is_overridden(template_id)
70    }
71
72    /// List all templates with their override status.
73    pub fn list_templates(&self) -> Result<Vec<(String, bool)>> {
74        self.resolver.list_templates()
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use crate::templates::context::TypeContext;
82
83    #[test]
84    fn test_template_engine_new() {
85        let engine = TemplateEngine::new(None);
86        assert!(engine.is_ok());
87    }
88
89    #[test]
90    fn test_template_engine_render_enum() {
91        let engine = TemplateEngine::new(None).unwrap();
92        let context = TypeContext::enum_type(
93            "TestEnum".to_string(),
94            vec!["A".to_string(), "B".to_string()],
95            None,
96        );
97        let result = engine.render(TemplateId::TypeEnum, &context);
98        assert!(result.is_ok());
99        let output = result.unwrap();
100        assert!(output.contains("TestEnum"));
101        assert!(output.contains("A"));
102        assert!(output.contains("B"));
103    }
104}