1use crate::parser::UnifiedRule;
4
5#[derive(Debug, Default)]
7pub struct RuleDiffer;
8
9impl RuleDiffer {
10 pub fn new() -> Self {
12 Self
13 }
14
15 pub fn diff(&self, old: &[UnifiedRule], new: &[UnifiedRule]) -> RuleDiff {
17 let mut diff = RuleDiff::default();
18
19 for new_rule in new {
21 if !self.contains_similar(old, new_rule) {
22 diff.added.push(new_rule.clone());
23 }
24 }
25
26 for old_rule in old {
27 if !self.contains_similar(new, old_rule) {
28 diff.removed.push(old_rule.clone());
29 }
30 }
31
32 diff
33 }
34
35 fn contains_similar(&self, rules: &[UnifiedRule], target: &UnifiedRule) -> bool {
36 rules.iter().any(|r| self.rules_similar(r, target))
37 }
38
39 fn rules_similar(&self, a: &UnifiedRule, b: &UnifiedRule) -> bool {
40 match (a, b) {
41 (
42 UnifiedRule::Standard {
43 description: desc_a,
44 ..
45 },
46 UnifiedRule::Standard {
47 description: desc_b,
48 ..
49 },
50 ) => desc_a == desc_b,
51 (
52 UnifiedRule::Persona { name: name_a, .. },
53 UnifiedRule::Persona { name: name_b, .. },
54 ) => name_a == name_b,
55 (UnifiedRule::Raw { content: a }, UnifiedRule::Raw { content: b }) => a == b,
56 _ => false,
57 }
58 }
59}
60
61#[derive(Debug, Default, Clone)]
63pub struct RuleDiff {
64 pub added: Vec<UnifiedRule>,
66 pub removed: Vec<UnifiedRule>,
68}
69
70impl RuleDiff {
71 pub fn has_changes(&self) -> bool {
73 !self.added.is_empty() || !self.removed.is_empty()
74 }
75
76 pub fn change_count(&self) -> usize {
78 self.added.len() + self.removed.len()
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use crate::format::RuleCategory;
86
87 #[test]
88 fn test_diff_identical() {
89 let differ = RuleDiffer::new();
90 let rules = vec![UnifiedRule::Standard {
91 category: RuleCategory::Style,
92 priority: 0,
93 description: "Test rule".to_string(),
94 pattern: None,
95 }];
96
97 let diff = differ.diff(&rules, &rules);
98 assert!(!diff.has_changes());
99 }
100
101 #[test]
102 fn test_diff_added() {
103 let differ = RuleDiffer::new();
104 let old = vec![];
105 let new = vec![UnifiedRule::Standard {
106 category: RuleCategory::Style,
107 priority: 0,
108 description: "New rule".to_string(),
109 pattern: None,
110 }];
111
112 let diff = differ.diff(&old, &new);
113 assert!(diff.has_changes());
114 assert_eq!(diff.added.len(), 1);
115 assert_eq!(diff.removed.len(), 0);
116 }
117
118 #[test]
119 fn test_diff_removed() {
120 let differ = RuleDiffer::new();
121 let old = vec![UnifiedRule::Standard {
122 category: RuleCategory::Style,
123 priority: 0,
124 description: "Old rule".to_string(),
125 pattern: None,
126 }];
127 let new = vec![];
128
129 let diff = differ.diff(&old, &new);
130 assert!(diff.has_changes());
131 assert_eq!(diff.added.len(), 0);
132 assert_eq!(diff.removed.len(), 1);
133 }
134}