-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhighlight.py
134 lines (117 loc) · 4.68 KB
/
highlight.py
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import difflib
import itertools
import pygments
import pygments.lexers
import pygments.formatters
class Line:
def __init__(self, is_diff=True, tag=None,
line_before=None, line_after=None, contents='', comments=()):
self.is_diff = is_diff
self.tag = tag
self.line_before = line_before
self.line_after = line_after
self.contents = contents
self.comments = comments
def highlight(filename, source):
"""Highlights an input string into a list of HTML strings, one per line."""
try:
highlighted = pygments.highlight(source,
pygments.lexers.guess_lexer_for_filename(filename, source, stripnl=False),
pygments.formatters.HtmlFormatter(nowrap=True))
except pygments.util.ClassNotFound:
highlighted = source
return highlighted.splitlines(keepends=True)
def highlight_file(filename, source):
"""Given a source file, generate a sequence of (line index, HTML) pairs."""
for i, contents in zip(itertools.count(1), highlight(filename, source)):
yield Line(is_diff=False, tag='equal', line_after=i, contents=contents)
def highlight_diff(filename, a, b, diff_type='short'):
"""Given two input strings, generate a sequence of 4-tuples. The elements
of each tuple are:
* 'delete', 'insert', 'equal', or 'header' (the tag)
* the line number of the first file (or None)
* the line number of the second file (or None)
* the rendered HTML string of the line
DIFF_TYPE is either 'short' (3 lines of context) or
'full' (all context lines).
"""
highlighted_a = highlight(filename, a)
highlighted_b = highlight(filename, b)
def delete(i1, i2):
for i in range(i1, i2):
yield Line(
tag='delete',
line_before=i + 1,
contents='-' + highlighted_a[i])
def insert(i1, i2):
for j in range(j1, j2):
yield Line(
tag='insert',
line_after=j + 1,
contents='+' + highlighted_b[j])
def equal(i1, i2, j1, j2):
for i, j in zip(range(i1, i2), range(j1, j2)):
yield Line(
tag='equal',
line_before=i + 1,
line_after=j + 1,
contents=' ' + highlighted_b[j])
def format_range_unified(start, stop):
"""Convert range to the "ed" format. From difflib.py"""
# Per the diff spec at http://www.unix.org/single_unix_specification/
beginning = start + 1 # lines start numbering with one
length = stop - start
if length == 1:
return '{}'.format(beginning)
if not length:
beginning -= 1 # empty ranges begin at line just before the range
return '{},{}'.format(beginning, length)
matcher = difflib.SequenceMatcher(None, a.splitlines(), b.splitlines())
if diff_type == 'short':
groups = matcher.get_grouped_opcodes()
elif diff_type == 'full':
groups = [matcher.get_opcodes()]
else:
raise ValueError('Unknown diff type {}'.format(diff_type))
for group in groups:
first, last = group[0], group[-1]
header = '@@ -{} +{} @@\n'.format(
format_range_unified(first[1], last[2]),
format_range_unified(first[3], last[4]))
yield Line(tag='header', contents=header)
for tag, i1, i2, j1, j2 in group:
if tag == 'replace':
yield from delete(i1, i2)
yield from insert(j1, j2)
elif tag == 'delete':
yield from delete(i1, i2)
elif tag == 'insert':
yield from insert(j1, j2)
elif tag == 'equal':
yield from equal(i1, i2, j1, j2)
def diff_files(files_before, files_after, diff_type, inputoutputID):
if diff_type:
files = {}
for filename in files_before.keys() | files_after.keys():
lines = highlight_diff(
filename,
files_before.get(filename, ''),
files_after.get(filename, ''),
diff_type)
files[filename] = (list(lines), inputoutputID)
else:
files = {filename: list(highlight_file(filename, source))
for filename, source in files_after.items()}
return files
import json
def diff_file(filename, file_before, file_after, diff_type):
if diff_type:
lines = highlight_diff(
filename,
file_before,
file_after,
diff_type
)
else:
lines = []
return list(lines)