1 from difflib import SequenceMatcher
2 from enum import Enum, auto
3 from typing import Optional
5 from termcolor import cprint # python3-termcolor
8 def _format_range_unified(start: int, stop: int) -> str:
9 """Copied from difflib._format_range_unified"""
10 beginning = start + 1 # lines start numbering with one
15 beginning -= 1 # empty ranges begin at line just before the range
16 return f'{beginning},{length}'
19 def unified_diff(a: str, b: str, context: int = 3):
20 a_lines = a.splitlines()
21 b_lines = b.splitlines()
22 for group in SequenceMatcher(None, a_lines, b_lines).get_grouped_opcodes(context):
23 first, last = group[0], group[-1]
24 file1_range = _format_range_unified(first[1], last[2])
25 file2_range = _format_range_unified(first[3], last[4])
26 cprint(f'@@ -{file1_range} +{file2_range} @@', 'magenta')
28 for tag, i1, i2, j1, j2 in group:
30 for line in a_lines[i1:i2]:
33 if tag in {'replace', 'delete'}:
34 for line in a_lines[i1:i2]:
35 cprint(f'- {line}', 'red')
36 if tag in {'replace', 'insert'}:
37 for line in b_lines[j1:j2]:
38 cprint(f'+ {line}', 'green')
47 def input_yes_no_quit(text: str, default: Optional[Choice]) -> Choice:
50 if result in ['Y', 'y', 'yes']:
52 elif result in ['N', 'n', 'no']:
54 elif result in ['Q', 'q', 'quit']:
56 elif result == '' and default is not None:
58 cprint(f'Unrecognized input: "{result}"', 'red')