vika_cli/templates/
loader.rs1use crate::error::{FileSystemError, Result};
2use rust_embed::RustEmbed;
3use std::path::Path;
4
5#[derive(RustEmbed)]
7#[folder = "builtin/templates"]
8#[include = "*.tera"]
9struct BuiltinTemplates;
10
11pub struct TemplateLoader;
13
14impl TemplateLoader {
15 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 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 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 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 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}