"""Word-level diff of one CI Server manual between two releases (e.g. R1.03 and R1.05). Catches changes the revision table does not mention (for example R1.04 edits listed only as "Added contact information"). Needs `pdftotext` (Poppler / Git for Windows) on PATH. Usage: python tools/manual_diff.py IM36K10L40-01EN [more IMs] [-o out_dir] Prints one block per change: [replace]/[insert]/[delete] with old (-) and new (+) words. Page footers, edition lines and dates are removed first, so only text changes remain. Writes nothing into the repo; with -o it saves .diff.txt files in out_dir. """ import difflib import os import re import subprocess import sys import tempfile NOISE = re.compile( r'IM ?36K\w+-01EN( \d+\w\w Edition)?( : [A-Za-z]+\.? ?\d+, ?\d+-00)?' r'|\d+(st|nd|rd|th) Edition|Toc-\d+|Rev-\d+|App\.\d+-\d+|\b\d+-\d+\b') def words(pdf): with tempfile.TemporaryDirectory() as d: out = os.path.join(d, 'm.txt') subprocess.run(['pdftotext', pdf, out], check=True) text = open(out, encoding='utf-8', errors='replace').read() return NOISE.sub(' ', text).split() def diff(old_pdf, new_pdf): a, b = words(old_pdf), words(new_pdf) sm = difflib.SequenceMatcher(None, a, b, autojunk=False) lines = [] for op, a1, a2, b1, b2 in sm.get_opcodes(): if op == 'equal': continue lines.append('[%s] at word %d\n - %s\n + %s' % ( op, a1, ' '.join(a[a1:a2]), ' '.join(b[b1:b2]))) return lines def main(argv): out_dir = None if '-o' in argv: i = argv.index('-o') out_dir = argv[i + 1] del argv[i:i + 2] if len(argv) < 3: sys.exit(__doc__) old_dir, new_dir, ims = argv[0], argv[1], argv[2:] for im in ims: old_pdf = os.path.join(old_dir, im + '.pdf') new_pdf = os.path.join(new_dir, im + '.pdf') if not os.path.exists(old_pdf): print('=====', im, ': new manual (no old edition)') continue lines = diff(old_pdf, new_pdf) report = '\n'.join(lines) print('=====', im, ':', len(lines), 'changes') if out_dir: os.makedirs(out_dir, exist_ok=True) open(os.path.join(out_dir, im + '.diff.txt'), 'w', encoding='utf-8').write(report) else: print(report) if __name__ == '__main__': main(sys.argv[1:])