Skip to main content

driven/templates/
registry.rs

1//! Template registry for discovery and loading
2
3use super::{Template, TemplateCategory, builtin};
4use crate::{DrivenError, Result};
5use std::collections::HashMap;
6use std::path::Path;
7
8/// Information about a template
9#[derive(Debug, Clone)]
10pub struct TemplateInfo {
11    /// Template name
12    pub name: String,
13    /// Template description
14    pub description: String,
15    /// Template category
16    pub category: TemplateCategory,
17}
18
19/// Registry for discovering and loading templates
20#[derive(Default)]
21pub struct TemplateRegistry {
22    /// Templates indexed by name
23    templates: HashMap<String, Box<dyn Template>>,
24    /// Custom template paths
25    custom_paths: Vec<std::path::PathBuf>,
26}
27
28impl TemplateRegistry {
29    /// Create a new registry with built-in templates
30    pub fn new() -> Self {
31        let mut registry = Self::default();
32        registry.load_builtins();
33        registry
34    }
35
36    /// Create an empty registry (no built-ins)
37    pub fn empty() -> Self {
38        Self::default()
39    }
40
41    /// Load all built-in templates
42    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    /// Add a custom template path
49    pub fn add_path(&mut self, path: impl AsRef<Path>) {
50        self.custom_paths.push(path.as_ref().to_path_buf());
51    }
52
53    /// Register a template
54    pub fn register(&mut self, template: Box<dyn Template>) {
55        self.templates.insert(template.name().to_string(), template);
56    }
57
58    /// Get a template by name
59    pub fn get(&self, name: &str) -> Option<&dyn Template> {
60        self.templates.get(name).map(|t| t.as_ref())
61    }
62
63    /// List all templates with info
64    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    /// Search templates by name or description
76    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    /// List templates by category
93    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    /// Search templates by tag
102    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    /// Load template from file
116    pub fn load_file(&mut self, path: &Path) -> Result<()> {
117        // For now, just check if it's a .drv file
118        if path.extension().is_some_and(|ext| ext == "drv") {
119            // TODO: Load binary template
120            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    /// Get template count
133    pub fn len(&self) -> usize {
134        self.templates.len()
135    }
136
137    /// Check if registry is empty
138    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}