Coverage for src/diff_engine.py: 98%
49 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-08 05:40 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-08 05:40 +0000
1#!/usr/bin/env python3
3import difflib
4from typing import List, Dict, Any
5from dataclasses import dataclass
7@dataclass
8class DiffLine:
9 line_type: str # 'added', 'removed', 'unchanged'
10 content: str
11 line_number: int
13class DiffEngine:
14 """Simple diff engine for content changes"""
16 def __init__(self):
17 self.previous_content = {}
19 def compare_content(self, section_id: str, old_content: str, new_content: str) -> Dict[str, Any]:
20 """Compare two versions of content and return diff"""
22 old_lines = old_content.splitlines() if old_content else []
23 new_lines = new_content.splitlines() if new_content else []
25 diff_lines = []
27 # Use difflib for line-by-line comparison
28 differ = difflib.unified_diff(
29 old_lines,
30 new_lines,
31 fromfile='old',
32 tofile='new',
33 lineterm=''
34 )
36 changes = {
37 'added_lines': 0,
38 'removed_lines': 0,
39 'changed_lines': 0
40 }
42 line_num = 0
43 for line in differ:
44 line_num += 1
45 if line.startswith('+') and not line.startswith('+++'):
46 diff_lines.append(DiffLine('added', line[1:], line_num))
47 changes['added_lines'] += 1
48 elif line.startswith('-') and not line.startswith('---'):
49 diff_lines.append(DiffLine('removed', line[1:], line_num))
50 changes['removed_lines'] += 1
51 elif not line.startswith('@@') and not line.startswith('+++') and not line.startswith('---'):
52 diff_lines.append(DiffLine('unchanged', line[1:] if line.startswith(' ') else line, line_num))
54 # Calculate change percentage
55 total_lines = max(len(old_lines), len(new_lines))
56 change_percentage = ((changes['added_lines'] + changes['removed_lines']) / total_lines * 100) if total_lines > 0 else 0
58 return {
59 'section_id': section_id,
60 'changes': changes,
61 'change_percentage': round(change_percentage, 2),
62 'diff_lines': [{'type': d.line_type, 'content': d.content, 'line_number': d.line_number} for d in diff_lines],
63 'has_changes': changes['added_lines'] > 0 or changes['removed_lines'] > 0
64 }
66 def track_change(self, section_id: str, content: str) -> Dict[str, Any]:
67 """Track a change and return diff from previous version"""
68 old_content = self.previous_content.get(section_id, '')
69 diff_result = self.compare_content(section_id, old_content, content)
71 # Store new content for next comparison
72 self.previous_content[section_id] = content
74 return diff_result
76 def get_html_diff(self, section_id: str, old_content: str, new_content: str) -> str:
77 """Generate HTML diff visualization"""
78 diff_result = self.compare_content(section_id, old_content, new_content)
80 html_lines = []
81 html_lines.append('<div class="diff-container">')
82 html_lines.append(f'<h4>Changes in {section_id}</h4>')
83 html_lines.append(f'<p>Added: {diff_result["changes"]["added_lines"]} lines, '
84 f'Removed: {diff_result["changes"]["removed_lines"]} lines, '
85 f'Change: {diff_result["change_percentage"]}%</p>')
87 for diff_line in diff_result['diff_lines']:
88 css_class = {
89 'added': 'diff-added',
90 'removed': 'diff-removed',
91 'unchanged': 'diff-unchanged'
92 }.get(diff_line['type'], '')
94 html_lines.append(f'<div class="{css_class}">{diff_line["content"]}</div>')
96 html_lines.append('</div>')
97 return '\n'.join(html_lines)
99 def get_summary(self) -> Dict[str, Any]:
100 """Get summary of all tracked changes"""
101 return {
102 'tracked_sections': len(self.previous_content),
103 'sections': list(self.previous_content.keys())
104 }