106 lines
3.7 KiB
Python
106 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate line-by-line diff between two markdown files."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import difflib
|
|
from pathlib import Path
|
|
|
|
|
|
def read_lines(path: Path) -> list[str]:
|
|
text = path.read_text(encoding="utf-8-sig")
|
|
return text.splitlines()
|
|
|
|
|
|
def generate_line_diff(src: Path, dst: Path) -> str:
|
|
src_lines = read_lines(src)
|
|
dst_lines = read_lines(dst)
|
|
src_name = src.as_posix()
|
|
dst_name = dst.as_posix()
|
|
diff = difflib.unified_diff(
|
|
src_lines,
|
|
dst_lines,
|
|
fromfile=src_name,
|
|
tofile=dst_name,
|
|
lineterm="",
|
|
)
|
|
return "\n".join(diff)
|
|
|
|
|
|
def generate_compact_line_diff(src: Path, dst: Path, context: int = 2) -> str:
|
|
"""Generate a compact diff showing only changed lines with a little context."""
|
|
src_lines = read_lines(src)
|
|
dst_lines = read_lines(dst)
|
|
src_name = src.as_posix()
|
|
dst_name = dst.as_posix()
|
|
|
|
sm = difflib.SequenceMatcher(None, src_lines, dst_lines)
|
|
output = [f"--- {src_name}", f"+++ {dst_name}", ""]
|
|
|
|
for tag, i1, i2, j1, j2 in sm.get_opcodes():
|
|
if tag == "equal":
|
|
# Show only a few context lines around changes
|
|
if context > 0:
|
|
ctx = src_lines[i1:i2]
|
|
if len(ctx) <= context * 2 + 1:
|
|
for line in ctx:
|
|
output.append(f" {line}")
|
|
else:
|
|
for line in ctx[:context]:
|
|
output.append(f" {line}")
|
|
output.append(" ...")
|
|
for line in ctx[-context:]:
|
|
output.append(f" {line}")
|
|
elif tag == "replace":
|
|
output.append(f"@@ -{i1 + 1},{i2 - i1} +{j1 + 1},{j2 - j1} @@")
|
|
for line in src_lines[i1:i2]:
|
|
output.append(f"-{line}")
|
|
for line in dst_lines[j1:j2]:
|
|
output.append(f"+{line}")
|
|
elif tag == "delete":
|
|
output.append(f"@@ -{i1 + 1},{i2 - i1} +{j1 + 1},0 @@")
|
|
for line in src_lines[i1:i2]:
|
|
output.append(f"-{line}")
|
|
elif tag == "insert":
|
|
output.append(f"@@ -{i1 + 1},0 +{j1 + 1},{j2 - j1} @@")
|
|
for line in dst_lines[j1:j2]:
|
|
output.append(f"+{line}")
|
|
|
|
return "\n".join(output)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Generate line-by-line diff for markdown files.")
|
|
parser.add_argument("--input-dir", type=Path, required=True, help="Directory containing source files.")
|
|
parser.add_argument("--output-dir", type=Path, required=True, help="Directory containing polished files.")
|
|
parser.add_argument("--out-diff-dir", type=Path, required=True, help="Directory to write diff files.")
|
|
parser.add_argument("--compact", action="store_true", help="Show only changed lines with context.")
|
|
parser.add_argument("--context", type=int, default=2, help="Number of context lines around changes (compact mode).")
|
|
args = parser.parse_args()
|
|
|
|
args.out_diff_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
for src in sorted(args.input_dir.glob("*.md")):
|
|
dst = args.output_dir / src.name
|
|
if not dst.exists():
|
|
print(f"Skip {src.name}: no matching polished file.")
|
|
continue
|
|
|
|
if args.compact:
|
|
diff_text = generate_compact_line_diff(src, dst, context=args.context)
|
|
suffix = "-compact.diff"
|
|
else:
|
|
diff_text = generate_line_diff(src, dst)
|
|
suffix = "-line.diff"
|
|
|
|
out_path = args.out_diff_dir / (src.stem + suffix)
|
|
out_path.write_text(diff_text, encoding="utf-8")
|
|
print(f"Wrote {out_path}")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|