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())
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    /// Load a template from user directory (`.vika/templates/`).
37    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    /// List all available built-in templates.
55    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    /// List all user templates in the project directory.
65    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        // Should list all embedded templates
113        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