driven/templates/
registry.rs1use super::{Template, TemplateCategory, builtin};
4use crate::{DrivenError, Result};
5use std::collections::HashMap;
6use std::path::Path;
7
8#[derive(Debug, Clone)]
10pub struct TemplateInfo {
11 pub name: String,
13 pub description: String,
15 pub category: TemplateCategory,
17}
18
19#[derive(Default)]
21pub struct TemplateRegistry {
22 templates: HashMap<String, Box<dyn Template>>,
24 custom_paths: Vec<std::path::PathBuf>,
26}
27
28impl TemplateRegistry {
29 pub fn new() -> Self {
31 let mut registry = Self::default();
32 registry.load_builtins();
33 registry
34 }
35
36 pub fn empty() -> Self {
38 Self::default()
39 }
40
41 pub fn load_builtins(&mut self) {
43 for template in builtin::all() {
44 self.templates.insert(template.name().to_string(), template);
45 }
46 }
47
48 pub fn add_path(&mut self, path: impl AsRef<Path>) {
50 self.custom_paths.push(path.as_ref().to_path_buf());
51 }
52
53 pub fn register(&mut self, template: Box<dyn Template>) {
55 self.templates.insert(template.name().to_string(), template);
56 }
57
58 pub fn get(&self, name: &str) -> Option<&dyn Template> {
60 self.templates.get(name).map(|t| t.as_ref())
61 }
62
63 pub fn list(&self) -> Vec<TemplateInfo> {
65 self.templates
66 .values()
67 .map(|t| TemplateInfo {
68 name: t.name().to_string(),
69 description: t.description().to_string(),
70 category: t.category(),
71 })
72 .collect()
73 }
74
75 pub fn search(&self, query: &str) -> Vec<TemplateInfo> {
77 let query_lower = query.to_lowercase();
78 self.templates
79 .values()
80 .filter(|t| {
81 t.name().to_lowercase().contains(&query_lower)
82 || t.description().to_lowercase().contains(&query_lower)
83 })
84 .map(|t| TemplateInfo {
85 name: t.name().to_string(),
86 description: t.description().to_string(),
87 category: t.category(),
88 })
89 .collect()
90 }
91
92 pub fn list_by_category(&self, category: TemplateCategory) -> Vec<&dyn Template> {
94 self.templates
95 .values()
96 .filter(|t| t.category() == category)
97 .map(|t| t.as_ref())
98 .collect()
99 }
100
101 pub fn search_by_tag(&self, tag: &str) -> Vec<&dyn Template> {
103 let tag_lower = tag.to_lowercase();
104 self.templates
105 .values()
106 .filter(|t| {
107 t.tags()
108 .iter()
109 .any(|t| t.to_lowercase().contains(&tag_lower))
110 })
111 .map(|t| t.as_ref())
112 .collect()
113 }
114
115 pub fn load_file(&mut self, path: &Path) -> Result<()> {
117 if path.extension().is_some_and(|ext| ext == "drv") {
119 return Err(DrivenError::TemplateNotFound(format!(
121 "Binary template loading not yet implemented: {}",
122 path.display()
123 )));
124 }
125
126 Err(DrivenError::TemplateNotFound(format!(
127 "Unsupported template format: {}",
128 path.display()
129 )))
130 }
131
132 pub fn len(&self) -> usize {
134 self.templates.len()
135 }
136
137 pub fn is_empty(&self) -> bool {
139 self.templates.is_empty()
140 }
141}
142
143impl std::fmt::Debug for TemplateRegistry {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 f.debug_struct("TemplateRegistry")
146 .field("template_count", &self.templates.len())
147 .field("custom_paths", &self.custom_paths)
148 .finish()
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_new_registry() {
158 let registry = TemplateRegistry::new();
159 assert!(!registry.is_empty());
160 }
161
162 #[test]
163 fn test_empty_registry() {
164 let registry = TemplateRegistry::empty();
165 assert!(registry.is_empty());
166 }
167
168 #[test]
169 fn test_list_templates() {
170 let registry = TemplateRegistry::new();
171 let names = registry.list();
172 assert!(!names.is_empty());
173 }
174
175 #[test]
176 fn test_get_template() {
177 let registry = TemplateRegistry::new();
178 let template = registry.get("architect");
179 assert!(template.is_some());
180 }
181
182 #[test]
183 fn test_list_by_category() {
184 let registry = TemplateRegistry::new();
185 let personas = registry.list_by_category(TemplateCategory::Persona);
186 assert!(!personas.is_empty());
187 }
188}