Spaces:
Running
Running
| """ | |
| Unified diff generator for producing git-compatible patch output. | |
| Used by the Fix Agent to generate per-file diffs. | |
| """ | |
| from __future__ import annotations | |
| import difflib | |
| from typing import List, Tuple | |
| def generate_unified_diff( | |
| original: str, | |
| fixed: str, | |
| filename: str = "file.py", | |
| context_lines: int = 3, | |
| ) -> str: | |
| """ | |
| Generate a unified diff string between *original* and *fixed* code. | |
| Compatible with `git apply` and standard patch utilities. | |
| """ | |
| original_lines = original.splitlines(keepends=True) | |
| fixed_lines = fixed.splitlines(keepends=True) | |
| diff_lines = list( | |
| difflib.unified_diff( | |
| original_lines, | |
| fixed_lines, | |
| fromfile=f"a/{filename}", | |
| tofile=f"b/{filename}", | |
| n=context_lines, | |
| ) | |
| ) | |
| if not diff_lines: | |
| return "" # No changes | |
| return "".join(diff_lines) | |
| def generate_inline_diff(original: str, fixed: str) -> List[Tuple[str, str]]: | |
| """ | |
| Return a list of (tag, line) tuples using difflib opcodes. | |
| Tags: 'equal', 'replace', 'delete', 'insert' | |
| Useful for rich HTML/JSON diff rendering. | |
| """ | |
| matcher = difflib.SequenceMatcher(None, original.splitlines(), fixed.splitlines()) | |
| result: List[Tuple[str, str]] = [] | |
| for tag, i1, i2, j1, j2 in matcher.get_opcodes(): | |
| if tag == "equal": | |
| for line in original.splitlines()[i1:i2]: | |
| result.append(("equal", line)) | |
| elif tag in ("replace", "delete"): | |
| for line in original.splitlines()[i1:i2]: | |
| result.append(("delete", f"- {line}")) | |
| if tag == "replace": | |
| for line in fixed.splitlines()[j1:j2]: | |
| result.append(("insert", f"+ {line}")) | |
| elif tag == "insert": | |
| for line in fixed.splitlines()[j1:j2]: | |
| result.append(("insert", f"+ {line}")) | |
| return result | |
| def apply_line_fix( | |
| original: str, | |
| line_number: int, | |
| replacement_line: str, | |
| ) -> str: | |
| """ | |
| Replace a single line (1-indexed) in *original* with *replacement_line*. | |
| Returns the modified code string. | |
| """ | |
| lines = original.splitlines(keepends=True) | |
| if 1 <= line_number <= len(lines): | |
| # Preserve original line ending | |
| ending = "\n" | |
| if lines[line_number - 1].endswith("\r\n"): | |
| ending = "\r\n" | |
| lines[line_number - 1] = replacement_line.rstrip("\r\n") + ending | |
| return "".join(lines) | |
| def insert_before_line( | |
| original: str, | |
| line_number: int, | |
| new_lines: str, | |
| ) -> str: | |
| """ | |
| Insert *new_lines* before the given 1-indexed *line_number*. | |
| """ | |
| lines = original.splitlines(keepends=True) | |
| insert_text = new_lines if new_lines.endswith("\n") else new_lines + "\n" | |
| idx = max(0, line_number - 1) | |
| lines.insert(idx, insert_text) | |
| return "".join(lines) | |
| def count_diff_stats(diff_text: str) -> dict: | |
| """Return additions, deletions, and net change counts from a unified diff.""" | |
| additions = sum(1 for line in diff_text.splitlines() if line.startswith("+") and not line.startswith("+++")) | |
| deletions = sum(1 for line in diff_text.splitlines() if line.startswith("-") and not line.startswith("---")) | |
| return { | |
| "additions": additions, | |
| "deletions": deletions, | |
| "net_change": additions - deletions, | |
| } | |
| def format_pr_diff_block(diffs: List[Tuple[str, str]]) -> str: | |
| """ | |
| Format a list of (filename, diff) tuples as a markdown code block | |
| suitable for GitHub PR descriptions. | |
| """ | |
| blocks: List[str] = [] | |
| for filename, diff in diffs: | |
| if diff: | |
| blocks.append(f"**`{filename}`**\n```diff\n{diff}\n```") | |
| return "\n\n".join(blocks) | |