Initial upload with full project structure
This commit is contained in:
157
app/src/confgen/diff.py
Normal file
157
app/src/confgen/diff.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
"""Diff display for configuration changes using Rich."""
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigDiff:
|
||||||
|
"""Diff viewer for configuration changes."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def show_diff(
|
||||||
|
self,
|
||||||
|
old_content: str,
|
||||||
|
new_content: str,
|
||||||
|
old_label: str = "Current",
|
||||||
|
new_label: str = "Generated",
|
||||||
|
) -> None:
|
||||||
|
"""Show a unified diff between two configurations."""
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.panel import Panel
|
||||||
|
from rich.text import Text
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
if old_content == new_content:
|
||||||
|
console.print(Panel("[yellow]No changes detected.[/yellow]"))
|
||||||
|
return
|
||||||
|
|
||||||
|
diff_lines = self._generate_diff(old_content, new_content)
|
||||||
|
|
||||||
|
diff_text = Text()
|
||||||
|
for line in diff_lines:
|
||||||
|
if line.startswith("+") and not line.startswith("+++"):
|
||||||
|
diff_text.append(line + "\n", style="green")
|
||||||
|
elif line.startswith("-") and not line.startswith("---"):
|
||||||
|
diff_text.append(line + "\n", style="red")
|
||||||
|
elif line.startswith("@@"):
|
||||||
|
diff_text.append(line + "\n", style="dim")
|
||||||
|
else:
|
||||||
|
diff_text.append(line + "\n", style="white")
|
||||||
|
|
||||||
|
console.print(
|
||||||
|
Panel(
|
||||||
|
diff_text,
|
||||||
|
title=f"[bold]Diff: {old_label} → {new_label}[/bold]",
|
||||||
|
expand=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _generate_diff(
|
||||||
|
self,
|
||||||
|
old_content: str,
|
||||||
|
new_content: str,
|
||||||
|
context: int = 3,
|
||||||
|
old_label: str = "Current",
|
||||||
|
new_label: str = "Generated",
|
||||||
|
) -> list[str]:
|
||||||
|
"""Generate unified diff lines."""
|
||||||
|
old_lines = old_content.splitlines(keepends=True)
|
||||||
|
new_lines = new_content.splitlines(keepends=True)
|
||||||
|
|
||||||
|
diff = []
|
||||||
|
diff.append(f"--- {old_label}")
|
||||||
|
diff.append(f"+++ {new_label}")
|
||||||
|
|
||||||
|
i, j = 0, 0
|
||||||
|
while i < len(old_lines) or j < len(new_lines):
|
||||||
|
if (
|
||||||
|
i < len(old_lines)
|
||||||
|
and j < len(new_lines)
|
||||||
|
and old_lines[i] == new_lines[j]
|
||||||
|
):
|
||||||
|
diff.append(" " + old_lines[i].rstrip())
|
||||||
|
i += 1
|
||||||
|
j += 1
|
||||||
|
else:
|
||||||
|
start_i, start_j = i, j
|
||||||
|
|
||||||
|
while i < len(old_lines) and (
|
||||||
|
j >= len(new_lines) or old_lines[i] != new_lines[j]
|
||||||
|
):
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
while j < len(new_lines) and (
|
||||||
|
i >= len(old_lines) or old_lines[i - 1] != new_lines[j]
|
||||||
|
):
|
||||||
|
j += 1
|
||||||
|
|
||||||
|
before = max(0, start_i - context)
|
||||||
|
after = min(len(old_lines), i + context)
|
||||||
|
|
||||||
|
if before < start_i:
|
||||||
|
diff.append("...")
|
||||||
|
|
||||||
|
for k in range(before, start_i):
|
||||||
|
diff.append(" " + old_lines[k].rstrip())
|
||||||
|
|
||||||
|
for k in range(start_i, i):
|
||||||
|
diff.append("-" + old_lines[k].rstrip())
|
||||||
|
|
||||||
|
for k in range(start_j, j):
|
||||||
|
diff.append("+" + new_lines[k].rstrip())
|
||||||
|
|
||||||
|
if after > i:
|
||||||
|
diff.append("...")
|
||||||
|
|
||||||
|
return diff
|
||||||
|
|
||||||
|
def show_json_diff(
|
||||||
|
self,
|
||||||
|
old_data: dict,
|
||||||
|
new_data: dict,
|
||||||
|
old_label: str = "Current",
|
||||||
|
new_label: str = "Generated",
|
||||||
|
) -> None:
|
||||||
|
"""Show diff between two JSON configurations."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
old_content = json.dumps(old_data, indent=2, sort_keys=True)
|
||||||
|
new_content = json.dumps(new_data, indent=2, sort_keys=True)
|
||||||
|
|
||||||
|
self.show_diff(old_content, new_content, old_label, new_label)
|
||||||
|
|
||||||
|
def show_yaml_diff(
|
||||||
|
self,
|
||||||
|
old_data: dict,
|
||||||
|
new_data: dict,
|
||||||
|
old_label: str = "Current",
|
||||||
|
new_label: str = "Generated",
|
||||||
|
) -> None:
|
||||||
|
"""Show diff between two YAML configurations."""
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
old_content = yaml.dump(old_data, default_flow_style=False, sort_keys=True)
|
||||||
|
new_content = yaml.dump(new_data, default_flow_style=False, sort_keys=True)
|
||||||
|
|
||||||
|
self.show_diff(old_content, new_content, old_label, new_label)
|
||||||
|
|
||||||
|
def format_change_summary(
|
||||||
|
self,
|
||||||
|
old_content: str,
|
||||||
|
new_content: str,
|
||||||
|
) -> dict[str, int]:
|
||||||
|
"""Get a summary of changes."""
|
||||||
|
old_lines = set(old_content.splitlines())
|
||||||
|
new_lines = set(new_content.splitlines())
|
||||||
|
|
||||||
|
added = len(new_lines - old_lines)
|
||||||
|
removed = len(old_lines - new_lines)
|
||||||
|
unchanged = len(old_lines & new_lines)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"added": added,
|
||||||
|
"removed": removed,
|
||||||
|
"unchanged": unchanged,
|
||||||
|
"total_old": len(old_lines),
|
||||||
|
"total_new": len(new_lines),
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user