Skip to main content

driven/templates/
composer.rs

1//! Template composition engine
2
3use super::TemplateRegistry;
4use crate::{Result, parser::UnifiedRule};
5
6/// Composes multiple templates into a unified rule set
7#[derive(Debug)]
8pub struct TemplateComposer<'a> {
9    registry: &'a TemplateRegistry,
10}
11
12impl<'a> TemplateComposer<'a> {
13    /// Create a new composer with a template registry
14    pub fn new(registry: &'a TemplateRegistry) -> Self {
15        Self { registry }
16    }
17
18    /// Compose multiple templates by name into a unified rule set
19    pub fn compose(&self, template_names: &[&str]) -> Result<Vec<UnifiedRule>> {
20        let mut rules = Vec::new();
21
22        for name in template_names {
23            if let Some(template) = self.registry.get(name) {
24                rules.extend(template.expand()?);
25            }
26        }
27
28        // Deduplicate rules
29        self.deduplicate(&mut rules);
30
31        Ok(rules)
32    }
33
34    /// Compose templates with conflict resolution
35    pub fn compose_with_priority(&self, template_names: &[&str]) -> Result<Vec<UnifiedRule>> {
36        let mut all_rules: Vec<(usize, UnifiedRule)> = Vec::new();
37
38        // Later templates have higher priority
39        for (priority, name) in template_names.iter().enumerate() {
40            if let Some(template) = self.registry.get(name) {
41                for rule in template.expand()? {
42                    all_rules.push((priority, rule));
43                }
44            }
45        }
46
47        // Sort by priority (higher priority = later in list)
48        all_rules.sort_by_key(|(p, _)| std::cmp::Reverse(*p));
49
50        let rules: Vec<UnifiedRule> = all_rules.into_iter().map(|(_, r)| r).collect();
51        Ok(rules)
52    }
53
54    /// Remove duplicate rules
55    fn deduplicate(&self, rules: &mut Vec<UnifiedRule>) {
56        let mut seen_descriptions = std::collections::HashSet::new();
57        rules.retain(|rule| {
58            if let UnifiedRule::Standard { description, .. } = rule {
59                seen_descriptions.insert(description.clone())
60            } else {
61                true
62            }
63        });
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_compose_empty() {
73        let registry = TemplateRegistry::new();
74        let composer = TemplateComposer::new(&registry);
75
76        let rules = composer.compose(&[]).unwrap();
77        assert!(rules.is_empty());
78    }
79
80    #[test]
81    fn test_compose_single() {
82        let registry = TemplateRegistry::new();
83        let composer = TemplateComposer::new(&registry);
84
85        let rules = composer.compose(&["architect"]).unwrap();
86        assert!(!rules.is_empty());
87    }
88
89    #[test]
90    fn test_compose_multiple() {
91        let registry = TemplateRegistry::new();
92        let composer = TemplateComposer::new(&registry);
93
94        let rules = composer
95            .compose(&["architect", "rust-idioms", "testing"])
96            .unwrap();
97        assert!(!rules.is_empty());
98    }
99}