use crate::error::{FileSystemError, Result};
use rust_embed::RustEmbed;
use std::path::Path;
#[derive(RustEmbed)]
#[folder = "builtin/templates"]
#[include = "*.tera"]
struct BuiltinTemplates;
pub struct TemplateLoader;
impl TemplateLoader {
pub fn load_builtin(template_name: &str) -> Result<String> {
let filename = format!("{}.tera", template_name);
BuiltinTemplates::get(&filename)
.map(|file| {
String::from_utf8(file.data.to_vec()).map_err(|e| FileSystemError::ReadFileFailed {
path: filename.clone(),
source: std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid UTF-8 in template: {}", e),
),
})
})
.transpose()?
.ok_or_else(|| FileSystemError::FileNotFound {
path: format!("builtin/templates/{}", filename),
})
.map_err(Into::into)
}
pub fn load_user(template_name: &str, project_root: &Path) -> Result<Option<String>> {
let user_template_dir = project_root.join(".vika").join("templates");
let template_path = user_template_dir.join(format!("{}.tera", template_name));
if !template_path.exists() {
return Ok(None);
}
std::fs::read_to_string(&template_path)
.map(Some)
.map_err(|e| FileSystemError::ReadFileFailed {
path: template_path.to_string_lossy().to_string(),
source: e,
})
.map_err(Into::into)
}
pub fn list_builtin() -> Vec<String> {
BuiltinTemplates::iter()
.filter_map(|path| path.strip_suffix(".tera").map(|s| s.to_string()))
.collect()
}
pub fn list_user(project_root: &Path) -> Result<Vec<String>> {
let user_template_dir = project_root.join(".vika").join("templates");
if !user_template_dir.exists() {
return Ok(Vec::new());
}
let mut templates = Vec::new();
let entries =
std::fs::read_dir(&user_template_dir).map_err(|e| FileSystemError::ReadFileFailed {
path: user_template_dir.to_string_lossy().to_string(),
source: e,
})?;
for entry in entries {
let entry = entry.map_err(|e| FileSystemError::ReadFileFailed {
path: user_template_dir.to_string_lossy().to_string(),
source: e,
})?;
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension() {
if ext == "tera" {
if let Some(stem) = path.file_stem() {
if let Some(name) = stem.to_str() {
templates.push(name.to_string());
}
}
}
}
}
}
Ok(templates)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_list_builtin() {
let templates = TemplateLoader::list_builtin();
assert!(!templates.is_empty());
}
#[test]
fn test_list_user_empty() {
let temp_dir = TempDir::new().unwrap();
let templates = TemplateLoader::list_user(temp_dir.path()).unwrap();
assert!(templates.is_empty());
}
#[test]
fn test_load_user_nonexistent() {
let temp_dir = TempDir::new().unwrap();
let result = TemplateLoader::load_user("nonexistent", temp_dir.path()).unwrap();
assert!(result.is_none());
}
}