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>(
41        &self,
42        template_id: TemplateId,
43        context: &T,
44    ) -> Result<String> {
45        let template_name = template_id.filename();
46
47        let json_value = serde_json::to_value(context)
48            .map_err(|e| {
49                crate::error::GenerationError::Template(TemplateError::RenderFailed {
50                    name: template_name.to_string(),
51                    message: format!("Failed to serialize context: {}", e),
52                })
53            })?;
54        let tera_context = Context::from_serialize(&json_value)
55            .map_err(|e| {
56                crate::error::GenerationError::Template(TemplateError::RenderFailed {
57                    name: template_name.to_string(),
58                    message: format!("Failed to create Tera context: {}", e),
59                })
60            })?;
61
62        self.tera
63            .render(&template_name, &tera_context)
64            .map_err(|e| {
65                crate::error::GenerationError::Template(TemplateError::RenderFailed {
66                    name: template_name.to_string(),
67                    message: e.to_string(),
68                })
69            })
70            .map_err(Into::into)
71    }
72
73    /// Check if a template is overridden by user.
74    pub fn is_overridden(&self, template_id: TemplateId) -> bool {
75        self.resolver.is_overridden(template_id)
76    }
77
78    /// List all templates with their override status.
79    pub fn list_templates(&self) -> Result<Vec<(String, bool)>> {
80        self.resolver.list_templates()
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use crate::templates::context::TypeContext;
88    use tempfile::TempDir;
89
90    #[test]
91    fn test_template_engine_new() {
92        let engine = TemplateEngine::new(None);
93        assert!(engine.is_ok());
94    }
95
96    #[test]
97    fn test_template_engine_render_enum() {
98        let engine = TemplateEngine::new(None).unwrap();
99        let context = TypeContext::enum_type("TestEnum".to_string(), vec!["A".to_string(), "B".to_string()]);
100        let result = engine.render(TemplateId::TypeEnum, &context);
101        assert!(result.is_ok());
102        let output = result.unwrap();
103        assert!(output.contains("TestEnum"));
104        assert!(output.contains("A"));
105        assert!(output.contains("B"));
106    }
107}
108