##// END OF EJS Templates
Added differ lib from mercurial.
marcink -
r130:ffddbd80 default
parent child Browse files
Show More
@@ -0,0 +1,209 b''
1 # -*- coding: utf-8 -*-
2 # original copyright: 2007-2008 by Armin Ronacher
3 # licensed under the BSD license.
4
5 import re, difflib
6
7 def render_udiff(udiff, differ='udiff'):
8 """Renders the udiff into multiple chunks of nice looking tables.
9 The return value is a list of those tables.
10 """
11 return DiffProcessor(udiff, differ).prepare()
12
13 class DiffProcessor(object):
14 """Give it a unified diff and it returns a list of the files that were
15 mentioned in the diff together with a dict of meta information that
16 can be used to render it in a HTML template.
17 """
18 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
19
20 def __init__(self, udiff, differ):
21 """
22 :param udiff: a text in udiff format
23 """
24 if isinstance(udiff, basestring):
25 udiff = udiff.splitlines(1)
26
27 self.lines = map(self.escaper, udiff)
28
29 # Select a differ.
30 if differ == 'difflib':
31 self.differ = self._highlight_line_difflib
32 else:
33 self.differ = self._highlight_line_udiff
34
35
36 def escaper(self, string):
37 return string.replace('<', '&lt;').replace('>', '&gt;')
38
39 def _extract_rev(self, line1, line2):
40 """Extract the filename and revision hint from a line."""
41 try:
42 if line1.startswith('--- ') and line2.startswith('+++ '):
43 filename, old_rev = line1[4:].split(None, 1)
44 new_rev = line2[4:].split(None, 1)[1]
45 return filename, 'old', 'new'
46 except (ValueError, IndexError):
47 pass
48 return None, None, None
49
50 def _highlight_line_difflib(self, line, next):
51 """Highlight inline changes in both lines."""
52
53 if line['action'] == 'del':
54 old, new = line, next
55 else:
56 old, new = next, line
57
58 oldwords = re.split(r'(\W)', old['line'])
59 newwords = re.split(r'(\W)', new['line'])
60
61 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
62
63 oldfragments, newfragments = [], []
64 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
65 oldfrag = ''.join(oldwords[i1:i2])
66 newfrag = ''.join(newwords[j1:j2])
67 if tag != 'equal':
68 if oldfrag:
69 oldfrag = '<del>%s</del>' % oldfrag
70 if newfrag:
71 newfrag = '<ins>%s</ins>' % newfrag
72 oldfragments.append(oldfrag)
73 newfragments.append(newfrag)
74
75 old['line'] = "".join(oldfragments)
76 new['line'] = "".join(newfragments)
77
78 def _highlight_line_udiff(self, line, next):
79 """Highlight inline changes in both lines."""
80 start = 0
81 limit = min(len(line['line']), len(next['line']))
82 while start < limit and line['line'][start] == next['line'][start]:
83 start += 1
84 end = -1
85 limit -= start
86 while - end <= limit and line['line'][end] == next['line'][end]:
87 end -= 1
88 end += 1
89 if start or end:
90 def do(l):
91 last = end + len(l['line'])
92 if l['action'] == 'add':
93 tag = 'ins'
94 else:
95 tag = 'del'
96 l['line'] = '%s<%s>%s</%s>%s' % (
97 l['line'][:start],
98 tag,
99 l['line'][start:last],
100 tag,
101 l['line'][last:]
102 )
103 do(line)
104 do(next)
105
106 def _parse_udiff(self):
107 """Parse the diff an return data for the template."""
108 lineiter = iter(self.lines)
109 files = []
110 try:
111 line = lineiter.next()
112 while 1:
113 # continue until we found the old file
114 if not line.startswith('--- '):
115 line = lineiter.next()
116 continue
117
118 chunks = []
119 filename, old_rev, new_rev = \
120 self._extract_rev(line, lineiter.next())
121 files.append({
122 'filename': filename,
123 'old_revision': old_rev,
124 'new_revision': new_rev,
125 'chunks': chunks
126 })
127
128 line = lineiter.next()
129 while line:
130 match = self._chunk_re.match(line)
131 if not match:
132 break
133
134 lines = []
135 chunks.append(lines)
136
137 old_line, old_end, new_line, new_end = \
138 [int(x or 1) for x in match.groups()[:-1]]
139 old_line -= 1
140 new_line -= 1
141 context = match.groups()[-1]
142 old_end += old_line
143 new_end += new_line
144
145 if context:
146 lines.append({
147 'old_lineno': None,
148 'new_lineno': None,
149 'action': 'context',
150 'line': line,
151 })
152
153 line = lineiter.next()
154
155 while old_line < old_end or new_line < new_end:
156 if line:
157 command, line = line[0], line[1:]
158 else:
159 command = ' '
160 affects_old = affects_new = False
161
162 # ignore those if we don't expect them
163 if command in '#@':
164 continue
165 elif command == '+':
166 affects_new = True
167 action = 'add'
168 elif command == '-':
169 affects_old = True
170 action = 'del'
171 else:
172 affects_old = affects_new = True
173 action = 'unmod'
174
175 old_line += affects_old
176 new_line += affects_new
177 lines.append({
178 'old_lineno': affects_old and old_line or '',
179 'new_lineno': affects_new and new_line or '',
180 'action': action,
181 'line': line
182 })
183 line = lineiter.next()
184
185 except StopIteration:
186 pass
187
188 # highlight inline changes
189 for file in files:
190 for chunk in chunks:
191 lineiter = iter(chunk)
192 first = True
193 try:
194 while 1:
195 line = lineiter.next()
196 if line['action'] != 'unmod':
197 nextline = lineiter.next()
198 if nextline['action'] == 'unmod' or \
199 nextline['action'] == line['action']:
200 continue
201 self.differ(line, nextline)
202 except StopIteration:
203 pass
204
205 return files
206
207 def prepare(self):
208 """Prepare the passed udiff for HTML rendering."""
209 return self._parse_udiff()
@@ -41,6 +41,11 b' class FilesController(BaseController):'
41 from difflib import unified_diff
41 from difflib import unified_diff
42 d = unified_diff(c.file_1.splitlines(1), c.file_2.splitlines(1))
42 d = unified_diff(c.file_1.splitlines(1), c.file_2.splitlines(1))
43 c.diff = ''.join(d)
43 c.diff = ''.join(d)
44
45 from pylons_app.lib.differ import render_udiff
46 d2 = unified_diff(c.file_1.splitlines(1), c.file_2.splitlines(1))
47 c.diff_2 = render_udiff(udiff=d2)
48
44 return render('files/file_diff.html')
49 return render('files/file_diff.html')
45
50
46 def _get_history(self, repo, node, f_path):
51 def _get_history(self, repo, node, f_path):
@@ -32,4 +32,40 b''
32 ${h.pygmentize(c.diff,linenos=True,anchorlinenos=True,cssclass="code-diff")}
32 ${h.pygmentize(c.diff,linenos=True,anchorlinenos=True,cssclass="code-diff")}
33 </div>
33 </div>
34 </div>
34 </div>
35 <div style="clear:both"></div>
36 <div id="files_data">
37 <div id="body" class="codeblock">
38 <table class='highlighttable'>
39 %for x in c.diff_2[0]['chunks']:
40 %for y in x:
41 <tr class="line ${y['action']}">
42 <td class="lineno_new">
43 <div class="linenodiv">
44 <pre>
45 ${y['new_lineno']}
46 </pre>
47 </div>
48 </td>
49 <td class="lineno_old">
50 <div class="linenodiv">
51 <pre>
52 ${y['old_lineno']}
53 </pre>
54 </div>
55 </td>
56 <td class="code">
57 <div class="code-diff">
58 <pre>
59 <span>
60 ${y}
61 </span>
62 </pre>
63 </div>
64 </td>
65 </tr>
66 %endfor$
67 %endfor
68 </table>
69 </div>
70 </div>
35 </%def> No newline at end of file
71 </%def>
General Comments 0
You need to be logged in to leave comments. Login now