-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathutils.py
More file actions
100 lines (81 loc) · 3.26 KB
/
Copy pathutils.py
File metadata and controls
100 lines (81 loc) · 3.26 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
100
# source: https://skeptric.com/python-diffs/
from typing import List, Any, Callable, Tuple, Union
from IPython.display import HTML, display
import re
import difflib
Token = str
TokenList = List[Token]
whitespace = re.compile('\s+')
end_sentence = re.compile('[.!?]\s+')
def tokenize(s:str) -> TokenList:
'''Split a string into tokens'''
return whitespace.split(s)
def untokenize(ts:TokenList) -> str:
'''Join a list of tokens into a string'''
return ' '.join(ts)
def sentencize(s:str) -> TokenList:
'''Split a string into a list of sentences'''
return end_sentence.split(s)
def unsentencise(ts:TokenList) -> str:
'''Join a list of sentences into a string'''
return '. '.join(ts)
def html_unsentencise(ts:TokenList) -> str:
'''Joing a list of sentences into HTML for display'''
return ''.join(f'<p>{t}</p>' for t in ts)
def mark_text(text:str) -> str:
return f'<span style="color: red;">{text}</span>'
def mark_span(text:TokenList) -> TokenList:
return [mark_text(token) for token in text]
def mark_span(text:TokenList) -> TokenList:
if len(text) > 0:
text[0] = '<span style="background: #FF6502;">' + text[0]
text[-1] += '</span>'
return text
def markup_diff(a:TokenList, b:TokenList,
mark:Callable[TokenList, TokenList]=mark_span,
default_mark: Callable[TokenList, TokenList] = lambda x: x,
isjunk:Union[None, Callable[[Token], bool]]=None) -> Tuple[TokenList, TokenList]:
"""Returns a and b with any differences processed by mark
Junk is ignored by the differ
"""
seqmatcher = difflib.SequenceMatcher(isjunk=isjunk, a=a, b=b, autojunk=False)
out_a, out_b = [], []
for tag, a0, a1, b0, b1 in seqmatcher.get_opcodes():
markup = default_mark if tag == 'equal' else mark
out_a += markup(a[a0:a1])
out_b += markup(b[b0:b1])
assert len(out_a) == len(a)
assert len(out_b) == len(b)
return out_a, out_b
def align_seqs(a: TokenList, b: TokenList, fill:Token='') -> Tuple[TokenList, TokenList]:
out_a, out_b = [], []
seqmatcher = difflib.SequenceMatcher(a=a, b=b, autojunk=False)
for tag, a0, a1, b0, b1 in seqmatcher.get_opcodes():
delta = (a1 - a0) - (b1 - b0)
out_a += a[a0:a1] + [fill] * max(-delta, 0)
out_b += b[b0:b1] + [fill] * max(delta, 0)
assert len(out_a) == len(out_b)
return out_a, out_b
from itertools import zip_longest
def html_sidebyside(a, b):
# Set the panel display
out = '<div style="display: grid;grid-template-columns: 1fr 1fr;grid-gap: 20px;">'
# There's some CSS in Jupyter notebooks that makes the first pair unalign. This is a workaround
out += '<p></p><p></p>'
for left, right in zip_longest(a, b, fillvalue=''):
out += f'<p>{left}</p>'
out += f'<p>{right}</p>'
out += '</div>'
return out
import html
def html_diffs(a, b):
a = html.escape(a)
b = html.escape(b)
out_a, out_b = [], []
for sent_a, sent_b in zip(*align_seqs(sentencize(a), sentencize(b))):
mark_a, mark_b = markup_diff(tokenize(sent_a), tokenize(sent_b))
out_a.append(untokenize(mark_a))
out_b.append(untokenize(mark_b))
return html_sidebyside(out_a, out_b)
def show_diffs(a, b):
display(HTML(html_diffs(a,b)))