#!/usr/bin/env python3 import re import sys def filter_change_block( change_lines: list[str], search: str, replace: str ) -> list[str]: removed_lines = [] added_lines = [] phase = "minus" for line in change_lines: if line.startswith("-"): if phase != "minus": raise ValueError( "Non-consecutive '-' and '+' lines in change block." ) removed_lines.append(line[1:]) continue if line.startswith("+"): phase = "plus" added_lines.append(line[1:]) continue raise ValueError("Unexpected non-change line in change block.") transformed_removed = [ re.sub(search, replace, line) for line in removed_lines ] if transformed_removed == added_lines: return change_lines if len(removed_lines) != len(added_lines): return [] kept_lines = [] for removed, added in zip(removed_lines, added_lines): if re.sub(search, replace, removed) == added: kept_lines.append(f"-{removed}") kept_lines.append(f"+{added}") else: kept_lines.append(f" {removed}") if not kept_lines: return [] return kept_lines def filter_hunk( hunk_lines: list[str], search: str, replace: str ) -> list[str]: if not hunk_lines: return [] header = hunk_lines[0] body = hunk_lines[1:] output_body: list[str] = [] i = 0 while i < len(body): line = body[i] if line.startswith(("+", "-")): block = [] while i < len(body) and body[i].startswith(("+", "-")): block.append(body[i]) i += 1 kept_block = filter_change_block(block, search, replace) if kept_block: output_body.extend(kept_block) continue output_body.append(line) i += 1 if not any(line.startswith(("+", "-")) for line in output_body): return [] return [header] + output_body def main() -> None: if len(sys.argv) != 3: raise SystemExit("Usage: mechanicaldiff ") search = sys.argv[1] replace = sys.argv[2] input_data = sys.stdin.read() lines = input_data.splitlines(keepends=True) preamble_lines = [] sections = [] current = None for line in lines: if line.startswith("diff --git "): if current is not None: sections.append(current) current = {"header": [line], "hunks": []} continue if current is None: preamble_lines.append(line) continue if line.startswith("@@ "): current["hunks"].append([line]) else: if current["hunks"]: current["hunks"][-1].append(line) else: current["header"].append(line) if current is not None: sections.append(current) output_lines = [] output_lines.extend(preamble_lines) for section in sections: kept_hunks = [] for hunk_lines in section["hunks"]: filtered = filter_hunk(hunk_lines, search, replace) if filtered: kept_hunks.append(filtered) if not kept_hunks: continue output_lines.extend(section["header"]) for hunk_lines in kept_hunks: output_lines.extend(hunk_lines) output_data = "".join(output_lines) sys.stdout.write(output_data) if output_data != input_data: raise SystemExit(1) if __name__ == "__main__": main()