vika_cli/templates/
loader.rs

1use crate::error::{FileSystemError, Result};
2use rust_embed::RustEmbed;
3use std::path::Path;
4
5/// Embedded built-in templates.
6#[derive(RustEmbed)]
7#[folder = "builtin/templates"]
8#[include = "*.tera"]
9struct BuiltinTemplates;
10
11/// Loads templates from embedded built-in templates or user directory.
12pub struct TemplateLoader;
13
14impl TemplateLoader {
15    /// Load a template from embedded built-in templates.
16    pub fn load_builtin(template_name: &str) -> Result<String> {
17        let filename = format!("{}.tera", template_name);
18        BuiltinTemplates::get(&filename)
19            .map(|file| {
20                String::from_utf8(file.data.to_vec()).map_err(|e| FileSystemError::ReadFileFailed {
21                    path: filename.clone(),
22                    source: std::io::Error::new(
23                        std::io::ErrorKind::InvalidData,
24                        format!("Invalid UTF-8 in template: {}", e),
25                    ),
26                })
27            })
28            .transpose()?
29            .ok_or_else(|| FileSystemError::FileNotFound {
30                path: format!("builtin/templates/{}", filename),
31            })
32            .map_err(Into::into)
33    }
34
35    /// Load a template from user directory (`.vika/templates/`).
36    pub fn load_user(template_name: &str, project_root: &Path) -> Result<Option<String>> {
37        let user_template_dir = project_root.join(".vika").join("templates");
38        let template_path = user_template_dir.join(format!("{}.tera", template_name));
39
40        if !template_path.exists() {
41            return Ok(None);
42        }
43
44        std::fs::read_to_string(&template_path)
45            .map(Some)
46            .map_err(|e| FileSystemError::ReadFileFailed {
47                path: template_path.to_string_lossy().to_string(),
48                source: e,
49            })
50            .map_err(Into::into)
51    }
52
53    /// List all available built-in templates.
54    pub fn list_builtin() -> Vec<String> {
55        BuiltinTemplates::iter()
56            .filter_map(|path| path.strip_suffix(".tera").map(|s| s.to_string()))
57            .collect()
58    }
59
60    /// List all user templates in the project directory.
61    pub fn list_user(project_root: &Path) -> Result<Vec<String>> {
62        let user_template_dir = project_root.join(".vika").join("templates");
63
64        if !user_template_dir.exists() {
65            return Ok(Vec::new());
66        }
67
68        let mut templates = Vec::new();
69        let entries =
70            std::fs::read_dir(&user_template_dir).map_err(|e| FileSystemError::ReadFileFailed {
71                path: user_template_dir.to_string_lossy().to_string(),
72                source: e,
73            })?;
74
75        for entry in entries {
76            let entry = entry.map_err(|e| FileSystemError::ReadFileFailed {
77                path: user_template_dir.to_string_lossy().to_string(),
78                source: e,
79            })?;
80
81            let path = entry.path();
82            if path.is_file() {
83                if let Some(ext) = path.extension() {
84                    if ext == "tera" {
85                        if let Some(stem) = path.file_stem() {
86                            if let Some(name) = stem.to_str() {
87                                templates.push(name.to_string());
88                            }
89                        }
90                    }
91                }
92            }
93        }
94
95        Ok(templates)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use tempfile::TempDir;
103
104    #[test]
105    fn test_list_builtin() {
106        let templates = TemplateLoader::list_builtin();
107        // Should list all embedded templates
108        assert!(!templates.is_empty());
109    }
110
111    #[test]
112    fn test_list_user_empty() {
113        let temp_dir = TempDir::new().unwrap();
114        let templates = TemplateLoader::list_user(temp_dir.path()).unwrap();
115        assert!(templates.is_empty());
116    }
117
118    #[test]
119    fn test_load_user_nonexistent() {
120        let temp_dir = TempDir::new().unwrap();
121        let result = TemplateLoader::load_user("nonexistent", temp_dir.path()).unwrap();
122        assert!(result.is_none());
123    }
124}