169 lines
4.6 KiB
Python
169 lines
4.6 KiB
Python
"""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)
|