Files
versioning/src/versiond/diff.py
T

73 lines
2.4 KiB
Python
Raw Normal View History

"""Text diffs between stored versions or a version and the working copy."""
from __future__ import annotations
import difflib
import re
from typing import Any
_WS = re.compile(r"\s+")
def _lines(content: bytes) -> list[str]:
return content.decode("utf-8", errors="replace").splitlines(keepends=True)
def _normalize(line: str, ignore_whitespace: bool, ignore_eol: bool) -> str:
if ignore_whitespace:
return _WS.sub(" ", line).strip()
if ignore_eol:
return line.rstrip("\r\n")
return line
def hunks(
a: bytes,
b: bytes,
context: int = 3,
ignore_whitespace: bool = False,
ignore_eol: bool = False,
) -> list[dict[str, Any]]:
a_lines, b_lines = _lines(a), _lines(b)
a_norm = [_normalize(x, ignore_whitespace, ignore_eol) for x in a_lines]
b_norm = [_normalize(x, ignore_whitespace, ignore_eol) for x in b_lines]
matcher = difflib.SequenceMatcher(None, a_norm, b_norm, autojunk=False)
result = []
for group in matcher.get_grouped_opcodes(context):
first, last = group[0], group[-1]
lines = []
for tag, i1, i2, j1, j2 in group:
if tag == "equal":
lines += [{"op": " ", "text": x} for x in a_lines[i1:i2]]
continue
if tag in ("replace", "delete"):
lines += [{"op": "-", "text": x} for x in a_lines[i1:i2]]
if tag in ("replace", "insert"):
lines += [{"op": "+", "text": x} for x in b_lines[j1:j2]]
result.append({
"old_start": first[1] + 1,
"old_lines": last[2] - first[1],
"new_start": first[3] + 1,
"new_lines": last[4] - first[3],
"lines": lines,
})
return result
def unified(hunk_list: list[dict[str, Any]], from_label: str, to_label: str) -> str:
if not hunk_list:
return ""
out = [f"--- {from_label}\n", f"+++ {to_label}\n"]
for h in hunk_list:
out.append(f"@@ -{h['old_start']},{h['old_lines']} +{h['new_start']},{h['new_lines']} @@\n")
for line in h["lines"]:
text = line["text"]
out.append(line["op"] + text + ("" if text.endswith("\n") else "\n\\ No newline at end of file\n"))
return "".join(out)
def side_by_side_html(a: bytes, b: bytes, from_label: str, to_label: str, context: int = 3) -> str:
return difflib.HtmlDiff(wrapcolumn=100).make_file(
_lines(a), _lines(b), from_label, to_label, context=True, numlines=context
)