Initial upload with full project structure

This commit is contained in:
2026-02-01 20:49:01 +00:00
parent 58b0c3471a
commit 08dd7ed3ee

157
app/src/confgen/diff.py Normal file
View 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),
}