codeSentry / codesentry-backend /tools /diff_generator.py
YashashviAlva's picture
Initial commit for HF Spaces deploy
7b4f5dd
"""
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)