-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchar_diff.py
More file actions
99 lines (87 loc) · 4.21 KB
/
char_diff.py
File metadata and controls
99 lines (87 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env python3
# char_diff.py
# Посимвольное сравнение двух текстов с цветной подсветкой (терминал) и опцией HTML-вывода.
import argparse
import sys
from diff_match_patch import diff_match_patch
from colorama import init, Fore, Style
init(autoreset=False) # we'll вставлять Reset сами
def colored_terminal_diff(text1: str, text2: str) -> str:
"""
Возвращает строку с ANSI-цветами: удалённое - красным, добавленное - зелёным, неизменное - без цвета.
Делает посимвольный diff (checklines=False).
"""
dmp = diff_match_patch()
dmp.Diff_Timeout = 0 # без таймаута
# checklines=False -> finer-grained (чаще всего посимвольный)
diffs = dmp.diff_main(text1, text2, False)
# почистим немного, чтобы сделать вывод более читаемым
dmp.diff_cleanupSemantic(diffs)
out_parts = []
for (op, data) in diffs:
if op == 0: # equals
out_parts.append(data)
elif op == -1: # delete
# покраска каждого символа отдельно — чтобы не ломать формат/новые строки
part = []
for ch in data:
# красный текст для удалённых
part.append(Fore.RED + ch + Style.RESET_ALL)
out_parts.append(''.join(part))
elif op == 1: # insert
part = []
for ch in data:
# зелёный текст для добавленных
part.append(Fore.GREEN + ch + Style.RESET_ALL)
out_parts.append(''.join(part))
return ''.join(out_parts)
def html_diff(text1: str, text2: str) -> str:
"""
Возвращает HTML, где удалённое — span с class="del", добавленное — span class="ins".
Это даёт более аккуратный визуальный просмотр в браузере.
"""
dmp = diff_match_patch()
dmp.Diff_Timeout = 0
diffs = dmp.diff_main(text1, text2, False)
dmp.diff_cleanupSemantic(diffs)
html_parts = ['<html><meta charset="utf-8"><style>',
'.del {background:#ffd6d6;color:#900;text-decoration:line-through;}',
'.ins {background:#d6ffd6;color:#060;}',
'pre {white-space:pre-wrap;word-wrap:break-word;font-family:monospace;}',
'</style><body><pre>']
for op, data in diffs:
esc = (data.replace('&', '&')
.replace('<', '<')
.replace('>', '>'))
if op == 0:
html_parts.append(esc)
elif op == -1:
html_parts.append(f'<span class="del">{esc}</span>')
elif op == 1:
html_parts.append(f'<span class="ins">{esc}</span>')
html_parts.append('</pre></body></html>')
return ''.join(html_parts)
def read_text_from_path(path: str) -> str:
if path == '-':
return sys.stdin.read()
with open(path, 'r', encoding='utf-8') as f:
return f.read()
def main():
p = argparse.ArgumentParser(description="Посимвольный diff с подсветкой (diff-match-patch)")
p.add_argument('first', help="путь к первому файлу (или '-' для stdin)")
p.add_argument('second', help="путь ко второму файлу (или '-' для stdin)")
p.add_argument('--html', '-o', help="записать HTML-версию в файл (опционально)")
args = p.parse_args()
a = read_text_from_path(args.first)
b = read_text_from_path(args.second)
colored = colored_terminal_diff(a, b)
# печатаем в терминал (stdout)
# убедимся, что ANSI-коды сброшены по концу — Style.RESET_ALL уже добавляется
print(colored)
if args.html:
html = html_diff(a, b)
with open(args.html, 'w', encoding='utf-8') as f:
f.write(html)
print(f"\nHTML сохранён в: {args.html}")
if __name__ == '__main__':
main()