Add diff and utils modules
Some checks failed
CI / test (3.11) (push) Has been cancelled
CI / test (3.10) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-02-02 17:22:46 +00:00
parent 9a7fe4ee49
commit 8bacceea68

View File

@@ -0,0 +1,168 @@
"""Diff generation for i18n fixes."""
import difflib
from dataclasses import dataclass, field
from pathlib import Path
from typing import List, Optional
from rich.console import Console
from rich.syntax import Syntax
@dataclass
class DiffChange:
"""Represents a single change in a diff."""
file_path: Path
original_line: int
original_text: str
new_text: str
new_line: Optional[int] = None
@dataclass
class DiffResult:
"""Result of a diff operation."""
changes: List[DiffChange] = field(default_factory=list)
files_modified: int = 0
total_changes: int = 0
class DiffGenerator:
"""Generate diffs for i18n fixes."""
def __init__(self, color: bool = True) -> None:
self.color = color
self.console = Console()
def generate_diff(
self,
file_path: Path,
old_content: str,
new_content: str,
) -> str:
"""Generate a unified diff between old and new content."""
diff = difflib.unified_diff(
old_content.splitlines(keepends=True),
new_content.splitlines(keepends=True),
fromfile=str(file_path),
tofile=str(file_path),
n=3,
)
return "".join(diff)
def preview_diff(self, diff: str) -> None:
"""Preview a diff using Rich."""
if self.color:
self.console.print(Syntax(diff, "diff", line_numbers=True))
else:
self.console.print(diff)
def apply_diff(
self,
file_path: Path,
diff: str,
backup: bool = True,
) -> bool:
"""Apply a diff to a file."""
try:
if backup:
backup_path = file_path.with_suffix(".bak")
backup_path.write_text(file_path.read_text(encoding="utf-8"), encoding="utf-8")
lines = file_path.read_text(encoding="utf-8").splitlines(keepends=True)
diff_lines = diff.splitlines(keepends=True)
patched_lines = self._apply_diff_lines(lines, diff_lines)
new_content = "".join(patched_lines)
file_path.write_text(new_content, encoding="utf-8")
return True
except (OSError, UnicodeDecodeError) as e:
print(f"Error applying diff: {e}")
return False
def _apply_diff_lines(
self,
lines: List[str],
diff_lines: List[str],
) -> List[str]:
"""Apply diff lines to document."""
result = []
i = 0
j = 0
while j < len(diff_lines):
diff_line = diff_lines[j]
if diff_line.startswith("---") or diff_line.startswith("+++"):
j += 1
continue
if diff_line.startswith("@@"):
j += 1
continue
if diff_line.startswith("-"):
original_line = diff_line[1:]
added_content = ""
if "+" in original_line:
parts = original_line.split("+", 1)
original_line = parts[0]
added_content = parts[1] if len(parts) > 1 else ""
while i < len(lines) and lines[i].rstrip("\n") != original_line.rstrip("\n"):
result.append(lines[i])
i += 1
if i < len(lines):
i += 1
if added_content:
result.append(added_content)
j += 1
continue
if diff_line.startswith("+"):
result.append(diff_line[1:])
j += 1
continue
if diff_line.startswith(" "):
result.append(lines[i] if i < len(lines) else diff_line[1:])
i += 1
j += 1
continue
j += 1
while i < len(lines):
result.append(lines[i])
i += 1
return result
def create_replacement(
self,
original_text: str,
replacement: str,
i18n_function: str = "t",
) -> str:
"""Create a replacement string with i18n function call."""
escaped = original_text.replace("\\", "\\\\").replace('"', '\\"')
return f'{i18n_function}("{escaped}")'
def preview_change(
self,
file_path: Path,
original_text: str,
replacement: str,
line_number: int,
) -> None:
"""Preview a single change."""
diff = self.generate_diff(
file_path,
original_text,
original_text.replace(original_text, replacement, 1),
)
self.preview_diff(diff)