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())
21 .map_err(|e| FileSystemError::ReadFileFailed {
22 path: filename.clone(),
23 source: std::io::Error::new(
24 std::io::ErrorKind::InvalidData,
25 format!("Invalid UTF-8 in template: {}", e),
26 ),
27 })
28 })
29 .transpose()?
30 .ok_or_else(|| FileSystemError::FileNotFound {
31 path: format!("builtin/templates/{}", filename),
32 })
33 .map_err(Into::into)
34 }
35
36 pub fn load_user(template_name: &str, project_root: &Path) -> Result<Option<String>> {
38 let user_template_dir = project_root.join(".vika").join("templates");
39 let template_path = user_template_dir.join(format!("{}.tera", template_name));
40
41 if !template_path.exists() {
42 return Ok(None);
43 }
44
45 std::fs::read_to_string(&template_path)
46 .map(Some)
47 .map_err(|e| FileSystemError::ReadFileFailed {
48 path: template_path.to_string_lossy().to_string(),
49 source: e,
50 })
51 .map_err(Into::into)
52 }
53
54 pub fn list_builtin() -> Vec<String> {
56 BuiltinTemplates::iter()
57 .filter_map(|path| {
58 path.strip_suffix(".tera")
59 .map(|s| s.to_string())
60 })
61 .collect()
62 }
63
64 pub fn list_user(project_root: &Path) -> Result<Vec<String>> {
66 let user_template_dir = project_root.join(".vika").join("templates");
67
68 if !user_template_dir.exists() {
69 return Ok(Vec::new());
70 }
71
72 let mut templates = Vec::new();
73 let entries = std::fs::read_dir(&user_template_dir).map_err(|e| {
74 FileSystemError::ReadFileFailed {
75 path: user_template_dir.to_string_lossy().to_string(),
76 source: e,
77 }
78 })?;
79
80 for entry in entries {
81 let entry = entry.map_err(|e| FileSystemError::ReadFileFailed {
82 path: user_template_dir.to_string_lossy().to_string(),
83 source: e,
84 })?;
85
86 let path = entry.path();
87 if path.is_file() {
88 if let Some(ext) = path.extension() {
89 if ext == "tera" {
90 if let Some(stem) = path.file_stem() {
91 if let Some(name) = stem.to_str() {
92 templates.push(name.to_string());
93 }
94 }
95 }
96 }
97 }
98 }
99
100 Ok(templates)
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use tempfile::TempDir;
108
109 #[test]
110 fn test_list_builtin() {
111 let templates = TemplateLoader::list_builtin();
112 assert!(!templates.is_empty());
114 }
115
116 #[test]
117 fn test_list_user_empty() {
118 let temp_dir = TempDir::new().unwrap();
119 let templates = TemplateLoader::list_user(temp_dir.path()).unwrap();
120 assert!(templates.is_empty());
121 }
122
123 #[test]
124 fn test_load_user_nonexistent() {
125 let temp_dir = TempDir::new().unwrap();
126 let result = TemplateLoader::load_user("nonexistent", temp_dir.path()).unwrap();
127 assert!(result.is_none());
128 }
129}
130