##// END OF EJS Templates
adapt codes to latest vcs
marcink -
r1894:0cbc3fb5 beta
parent child Browse files
Show More
@@ -1,515 +1,517 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.diffs
3 rhodecode.lib.diffs
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Set of diffing helpers, previously part of vcs
6 Set of diffing helpers, previously part of vcs
7
7
8
8
9 :created_on: Dec 4, 2011
9 :created_on: Dec 4, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :original copyright: 2007-2008 by Armin Ronacher
12 :original copyright: 2007-2008 by Armin Ronacher
13 :license: GPLv3, see COPYING for more details.
13 :license: GPLv3, see COPYING for more details.
14 """
14 """
15 # This program is free software: you can redistribute it and/or modify
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
18 # (at your option) any later version.
19 #
19 #
20 # This program is distributed in the hope that it will be useful,
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
23 # GNU General Public License for more details.
24 #
24 #
25 # You should have received a copy of the GNU General Public License
25 # You should have received a copy of the GNU General Public License
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27
27
28 import re
28 import re
29 import difflib
29 import difflib
30 import markupsafe
30 import markupsafe
31 from itertools import tee, imap
31 from itertools import tee, imap
32
32
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34
34
35 from vcs.exceptions import VCSError
35 from vcs.exceptions import VCSError
36 from vcs.nodes import FileNode
36 from vcs.nodes import FileNode
37
37
38 from rhodecode.lib.utils import EmptyChangeset
38 from rhodecode.lib.utils import EmptyChangeset
39
39
40
40
41 def wrap_to_table(str_):
41 def wrap_to_table(str_):
42 return '''<table class="code-difftable">
42 return '''<table class="code-difftable">
43 <tr class="line no-comment">
43 <tr class="line no-comment">
44 <td class="lineno new"></td>
44 <td class="lineno new"></td>
45 <td class="code no-comment"><pre>%s</pre></td>
45 <td class="code no-comment"><pre>%s</pre></td>
46 </tr>
46 </tr>
47 </table>''' % str_
47 </table>''' % str_
48
48
49
49
50 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
50 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
51 ignore_whitespace=True, line_context=3,
51 ignore_whitespace=True, line_context=3,
52 enable_comments=False):
52 enable_comments=False):
53 """
53 """
54 returns a wrapped diff into a table, checks for cut_off_limit and presents
54 returns a wrapped diff into a table, checks for cut_off_limit and presents
55 proper message
55 proper message
56 """
56 """
57
57
58 if filenode_old is None:
58 if filenode_old is None:
59 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
59 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
60
60
61 if filenode_old.is_binary or filenode_new.is_binary:
61 if filenode_old.is_binary or filenode_new.is_binary:
62 diff = wrap_to_table(_('binary file'))
62 diff = wrap_to_table(_('binary file'))
63 stats = (0, 0)
63 stats = (0, 0)
64 size = 0
64 size = 0
65
65
66 elif cut_off_limit != -1 and (cut_off_limit is None or
66 elif cut_off_limit != -1 and (cut_off_limit is None or
67 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
67 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
68
68
69 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
69 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
70 ignore_whitespace=ignore_whitespace,
70 ignore_whitespace=ignore_whitespace,
71 context=line_context)
71 context=line_context)
72 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
72 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
73
73
74 diff = diff_processor.as_html(enable_comments=enable_comments)
74 diff = diff_processor.as_html(enable_comments=enable_comments)
75 stats = diff_processor.stat()
75 stats = diff_processor.stat()
76 size = len(diff or '')
76 size = len(diff or '')
77 else:
77 else:
78 diff = wrap_to_table(_('Changeset was to big and was cut off, use '
78 diff = wrap_to_table(_('Changeset was to big and was cut off, use '
79 'diff menu to display this diff'))
79 'diff menu to display this diff'))
80 stats = (0, 0)
80 stats = (0, 0)
81 size = 0
81 size = 0
82
82
83 if not diff:
83 if not diff:
84 diff = wrap_to_table(_('No changes detected'))
84 diff = wrap_to_table(_('No changes detected'))
85
85
86 cs1 = filenode_old.last_changeset.raw_id
86 cs1 = filenode_old.last_changeset.raw_id
87 cs2 = filenode_new.last_changeset.raw_id
87 cs2 = filenode_new.last_changeset.raw_id
88
88
89 return size, cs1, cs2, diff, stats
89 return size, cs1, cs2, diff, stats
90
90
91
91
92 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
92 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
93 """
93 """
94 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
94 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
95
95
96 :param ignore_whitespace: ignore whitespaces in diff
96 :param ignore_whitespace: ignore whitespaces in diff
97 """
97 """
98 # make sure we pass in default context
99 context = context or 3
98
100
99 for filenode in (filenode_old, filenode_new):
101 for filenode in (filenode_old, filenode_new):
100 if not isinstance(filenode, FileNode):
102 if not isinstance(filenode, FileNode):
101 raise VCSError("Given object should be FileNode object, not %s"
103 raise VCSError("Given object should be FileNode object, not %s"
102 % filenode.__class__)
104 % filenode.__class__)
103
105
104 old_raw_id = getattr(filenode_old.changeset, 'raw_id', '0' * 40)
106 repo = filenode_new.changeset.repository
105 new_raw_id = getattr(filenode_new.changeset, 'raw_id', '0' * 40)
107 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
108 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
106
109
107 repo = filenode_new.changeset.repository
108 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
110 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
109 ignore_whitespace, context)
111 ignore_whitespace, context)
110
112
111 return vcs_gitdiff
113 return vcs_gitdiff
112
114
113
115
114 class DiffProcessor(object):
116 class DiffProcessor(object):
115 """
117 """
116 Give it a unified diff and it returns a list of the files that were
118 Give it a unified diff and it returns a list of the files that were
117 mentioned in the diff together with a dict of meta information that
119 mentioned in the diff together with a dict of meta information that
118 can be used to render it in a HTML template.
120 can be used to render it in a HTML template.
119 """
121 """
120 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
122 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
121
123
122 def __init__(self, diff, differ='diff', format='udiff'):
124 def __init__(self, diff, differ='diff', format='udiff'):
123 """
125 """
124 :param diff: a text in diff format or generator
126 :param diff: a text in diff format or generator
125 :param format: format of diff passed, `udiff` or `gitdiff`
127 :param format: format of diff passed, `udiff` or `gitdiff`
126 """
128 """
127 if isinstance(diff, basestring):
129 if isinstance(diff, basestring):
128 diff = [diff]
130 diff = [diff]
129
131
130 self.__udiff = diff
132 self.__udiff = diff
131 self.__format = format
133 self.__format = format
132 self.adds = 0
134 self.adds = 0
133 self.removes = 0
135 self.removes = 0
134
136
135 if isinstance(self.__udiff, basestring):
137 if isinstance(self.__udiff, basestring):
136 self.lines = iter(self.__udiff.splitlines(1))
138 self.lines = iter(self.__udiff.splitlines(1))
137
139
138 elif self.__format == 'gitdiff':
140 elif self.__format == 'gitdiff':
139 udiff_copy = self.copy_iterator()
141 udiff_copy = self.copy_iterator()
140 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
142 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
141 else:
143 else:
142 udiff_copy = self.copy_iterator()
144 udiff_copy = self.copy_iterator()
143 self.lines = imap(self.escaper, udiff_copy)
145 self.lines = imap(self.escaper, udiff_copy)
144
146
145 # Select a differ.
147 # Select a differ.
146 if differ == 'difflib':
148 if differ == 'difflib':
147 self.differ = self._highlight_line_difflib
149 self.differ = self._highlight_line_difflib
148 else:
150 else:
149 self.differ = self._highlight_line_udiff
151 self.differ = self._highlight_line_udiff
150
152
151 def escaper(self, string):
153 def escaper(self, string):
152 return markupsafe.escape(string)
154 return markupsafe.escape(string)
153
155
154 def copy_iterator(self):
156 def copy_iterator(self):
155 """
157 """
156 make a fresh copy of generator, we should not iterate thru
158 make a fresh copy of generator, we should not iterate thru
157 an original as it's needed for repeating operations on
159 an original as it's needed for repeating operations on
158 this instance of DiffProcessor
160 this instance of DiffProcessor
159 """
161 """
160 self.__udiff, iterator_copy = tee(self.__udiff)
162 self.__udiff, iterator_copy = tee(self.__udiff)
161 return iterator_copy
163 return iterator_copy
162
164
163 def _extract_rev(self, line1, line2):
165 def _extract_rev(self, line1, line2):
164 """
166 """
165 Extract the filename and revision hint from a line.
167 Extract the filename and revision hint from a line.
166 """
168 """
167
169
168 try:
170 try:
169 if line1.startswith('--- ') and line2.startswith('+++ '):
171 if line1.startswith('--- ') and line2.startswith('+++ '):
170 l1 = line1[4:].split(None, 1)
172 l1 = line1[4:].split(None, 1)
171 old_filename = (l1[0].replace('a/', '', 1)
173 old_filename = (l1[0].replace('a/', '', 1)
172 if len(l1) >= 1 else None)
174 if len(l1) >= 1 else None)
173 old_rev = l1[1] if len(l1) == 2 else 'old'
175 old_rev = l1[1] if len(l1) == 2 else 'old'
174
176
175 l2 = line2[4:].split(None, 1)
177 l2 = line2[4:].split(None, 1)
176 new_filename = (l2[0].replace('b/', '', 1)
178 new_filename = (l2[0].replace('b/', '', 1)
177 if len(l1) >= 1 else None)
179 if len(l1) >= 1 else None)
178 new_rev = l2[1] if len(l2) == 2 else 'new'
180 new_rev = l2[1] if len(l2) == 2 else 'new'
179
181
180 filename = (old_filename
182 filename = (old_filename
181 if old_filename != '/dev/null' else new_filename)
183 if old_filename != '/dev/null' else new_filename)
182
184
183 return filename, new_rev, old_rev
185 return filename, new_rev, old_rev
184 except (ValueError, IndexError):
186 except (ValueError, IndexError):
185 pass
187 pass
186
188
187 return None, None, None
189 return None, None, None
188
190
189 def _parse_gitdiff(self, diffiterator):
191 def _parse_gitdiff(self, diffiterator):
190 def line_decoder(l):
192 def line_decoder(l):
191 if l.startswith('+') and not l.startswith('+++'):
193 if l.startswith('+') and not l.startswith('+++'):
192 self.adds += 1
194 self.adds += 1
193 elif l.startswith('-') and not l.startswith('---'):
195 elif l.startswith('-') and not l.startswith('---'):
194 self.removes += 1
196 self.removes += 1
195 return l.decode('utf8', 'replace')
197 return l.decode('utf8', 'replace')
196
198
197 output = list(diffiterator)
199 output = list(diffiterator)
198 size = len(output)
200 size = len(output)
199
201
200 if size == 2:
202 if size == 2:
201 l = []
203 l = []
202 l.extend([output[0]])
204 l.extend([output[0]])
203 l.extend(output[1].splitlines(1))
205 l.extend(output[1].splitlines(1))
204 return map(line_decoder, l)
206 return map(line_decoder, l)
205 elif size == 1:
207 elif size == 1:
206 return map(line_decoder, output[0].splitlines(1))
208 return map(line_decoder, output[0].splitlines(1))
207 elif size == 0:
209 elif size == 0:
208 return []
210 return []
209
211
210 raise Exception('wrong size of diff %s' % size)
212 raise Exception('wrong size of diff %s' % size)
211
213
212 def _highlight_line_difflib(self, line, next_):
214 def _highlight_line_difflib(self, line, next_):
213 """
215 """
214 Highlight inline changes in both lines.
216 Highlight inline changes in both lines.
215 """
217 """
216
218
217 if line['action'] == 'del':
219 if line['action'] == 'del':
218 old, new = line, next_
220 old, new = line, next_
219 else:
221 else:
220 old, new = next_, line
222 old, new = next_, line
221
223
222 oldwords = re.split(r'(\W)', old['line'])
224 oldwords = re.split(r'(\W)', old['line'])
223 newwords = re.split(r'(\W)', new['line'])
225 newwords = re.split(r'(\W)', new['line'])
224
226
225 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
227 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
226
228
227 oldfragments, newfragments = [], []
229 oldfragments, newfragments = [], []
228 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
230 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
229 oldfrag = ''.join(oldwords[i1:i2])
231 oldfrag = ''.join(oldwords[i1:i2])
230 newfrag = ''.join(newwords[j1:j2])
232 newfrag = ''.join(newwords[j1:j2])
231 if tag != 'equal':
233 if tag != 'equal':
232 if oldfrag:
234 if oldfrag:
233 oldfrag = '<del>%s</del>' % oldfrag
235 oldfrag = '<del>%s</del>' % oldfrag
234 if newfrag:
236 if newfrag:
235 newfrag = '<ins>%s</ins>' % newfrag
237 newfrag = '<ins>%s</ins>' % newfrag
236 oldfragments.append(oldfrag)
238 oldfragments.append(oldfrag)
237 newfragments.append(newfrag)
239 newfragments.append(newfrag)
238
240
239 old['line'] = "".join(oldfragments)
241 old['line'] = "".join(oldfragments)
240 new['line'] = "".join(newfragments)
242 new['line'] = "".join(newfragments)
241
243
242 def _highlight_line_udiff(self, line, next_):
244 def _highlight_line_udiff(self, line, next_):
243 """
245 """
244 Highlight inline changes in both lines.
246 Highlight inline changes in both lines.
245 """
247 """
246 start = 0
248 start = 0
247 limit = min(len(line['line']), len(next_['line']))
249 limit = min(len(line['line']), len(next_['line']))
248 while start < limit and line['line'][start] == next_['line'][start]:
250 while start < limit and line['line'][start] == next_['line'][start]:
249 start += 1
251 start += 1
250 end = -1
252 end = -1
251 limit -= start
253 limit -= start
252 while -end <= limit and line['line'][end] == next_['line'][end]:
254 while -end <= limit and line['line'][end] == next_['line'][end]:
253 end -= 1
255 end -= 1
254 end += 1
256 end += 1
255 if start or end:
257 if start or end:
256 def do(l):
258 def do(l):
257 last = end + len(l['line'])
259 last = end + len(l['line'])
258 if l['action'] == 'add':
260 if l['action'] == 'add':
259 tag = 'ins'
261 tag = 'ins'
260 else:
262 else:
261 tag = 'del'
263 tag = 'del'
262 l['line'] = '%s<%s>%s</%s>%s' % (
264 l['line'] = '%s<%s>%s</%s>%s' % (
263 l['line'][:start],
265 l['line'][:start],
264 tag,
266 tag,
265 l['line'][start:last],
267 l['line'][start:last],
266 tag,
268 tag,
267 l['line'][last:]
269 l['line'][last:]
268 )
270 )
269 do(line)
271 do(line)
270 do(next_)
272 do(next_)
271
273
272 def _parse_udiff(self):
274 def _parse_udiff(self):
273 """
275 """
274 Parse the diff an return data for the template.
276 Parse the diff an return data for the template.
275 """
277 """
276 lineiter = self.lines
278 lineiter = self.lines
277 files = []
279 files = []
278 try:
280 try:
279 line = lineiter.next()
281 line = lineiter.next()
280 # skip first context
282 # skip first context
281 skipfirst = True
283 skipfirst = True
282 while 1:
284 while 1:
283 # continue until we found the old file
285 # continue until we found the old file
284 if not line.startswith('--- '):
286 if not line.startswith('--- '):
285 line = lineiter.next()
287 line = lineiter.next()
286 continue
288 continue
287
289
288 chunks = []
290 chunks = []
289 filename, old_rev, new_rev = \
291 filename, old_rev, new_rev = \
290 self._extract_rev(line, lineiter.next())
292 self._extract_rev(line, lineiter.next())
291 files.append({
293 files.append({
292 'filename': filename,
294 'filename': filename,
293 'old_revision': old_rev,
295 'old_revision': old_rev,
294 'new_revision': new_rev,
296 'new_revision': new_rev,
295 'chunks': chunks
297 'chunks': chunks
296 })
298 })
297
299
298 line = lineiter.next()
300 line = lineiter.next()
299 while line:
301 while line:
300 match = self._chunk_re.match(line)
302 match = self._chunk_re.match(line)
301 if not match:
303 if not match:
302 break
304 break
303
305
304 lines = []
306 lines = []
305 chunks.append(lines)
307 chunks.append(lines)
306
308
307 old_line, old_end, new_line, new_end = \
309 old_line, old_end, new_line, new_end = \
308 [int(x or 1) for x in match.groups()[:-1]]
310 [int(x or 1) for x in match.groups()[:-1]]
309 old_line -= 1
311 old_line -= 1
310 new_line -= 1
312 new_line -= 1
311 context = len(match.groups()) == 5
313 context = len(match.groups()) == 5
312 old_end += old_line
314 old_end += old_line
313 new_end += new_line
315 new_end += new_line
314
316
315 if context:
317 if context:
316 if not skipfirst:
318 if not skipfirst:
317 lines.append({
319 lines.append({
318 'old_lineno': '...',
320 'old_lineno': '...',
319 'new_lineno': '...',
321 'new_lineno': '...',
320 'action': 'context',
322 'action': 'context',
321 'line': line,
323 'line': line,
322 })
324 })
323 else:
325 else:
324 skipfirst = False
326 skipfirst = False
325
327
326 line = lineiter.next()
328 line = lineiter.next()
327 while old_line < old_end or new_line < new_end:
329 while old_line < old_end or new_line < new_end:
328 if line:
330 if line:
329 command, line = line[0], line[1:]
331 command, line = line[0], line[1:]
330 else:
332 else:
331 command = ' '
333 command = ' '
332 affects_old = affects_new = False
334 affects_old = affects_new = False
333
335
334 # ignore those if we don't expect them
336 # ignore those if we don't expect them
335 if command in '#@':
337 if command in '#@':
336 continue
338 continue
337 elif command == '+':
339 elif command == '+':
338 affects_new = True
340 affects_new = True
339 action = 'add'
341 action = 'add'
340 elif command == '-':
342 elif command == '-':
341 affects_old = True
343 affects_old = True
342 action = 'del'
344 action = 'del'
343 else:
345 else:
344 affects_old = affects_new = True
346 affects_old = affects_new = True
345 action = 'unmod'
347 action = 'unmod'
346
348
347 old_line += affects_old
349 old_line += affects_old
348 new_line += affects_new
350 new_line += affects_new
349 lines.append({
351 lines.append({
350 'old_lineno': affects_old and old_line or '',
352 'old_lineno': affects_old and old_line or '',
351 'new_lineno': affects_new and new_line or '',
353 'new_lineno': affects_new and new_line or '',
352 'action': action,
354 'action': action,
353 'line': line
355 'line': line
354 })
356 })
355 line = lineiter.next()
357 line = lineiter.next()
356
358
357 except StopIteration:
359 except StopIteration:
358 pass
360 pass
359
361
360 # highlight inline changes
362 # highlight inline changes
361 for _ in files:
363 for _ in files:
362 for chunk in chunks:
364 for chunk in chunks:
363 lineiter = iter(chunk)
365 lineiter = iter(chunk)
364 #first = True
366 #first = True
365 try:
367 try:
366 while 1:
368 while 1:
367 line = lineiter.next()
369 line = lineiter.next()
368 if line['action'] != 'unmod':
370 if line['action'] != 'unmod':
369 nextline = lineiter.next()
371 nextline = lineiter.next()
370 if nextline['action'] == 'unmod' or \
372 if nextline['action'] == 'unmod' or \
371 nextline['action'] == line['action']:
373 nextline['action'] == line['action']:
372 continue
374 continue
373 self.differ(line, nextline)
375 self.differ(line, nextline)
374 except StopIteration:
376 except StopIteration:
375 pass
377 pass
376
378
377 return files
379 return files
378
380
379 def prepare(self):
381 def prepare(self):
380 """
382 """
381 Prepare the passed udiff for HTML rendering. It'l return a list
383 Prepare the passed udiff for HTML rendering. It'l return a list
382 of dicts
384 of dicts
383 """
385 """
384 return self._parse_udiff()
386 return self._parse_udiff()
385
387
386 def _safe_id(self, idstring):
388 def _safe_id(self, idstring):
387 """Make a string safe for including in an id attribute.
389 """Make a string safe for including in an id attribute.
388
390
389 The HTML spec says that id attributes 'must begin with
391 The HTML spec says that id attributes 'must begin with
390 a letter ([A-Za-z]) and may be followed by any number
392 a letter ([A-Za-z]) and may be followed by any number
391 of letters, digits ([0-9]), hyphens ("-"), underscores
393 of letters, digits ([0-9]), hyphens ("-"), underscores
392 ("_"), colons (":"), and periods (".")'. These regexps
394 ("_"), colons (":"), and periods (".")'. These regexps
393 are slightly over-zealous, in that they remove colons
395 are slightly over-zealous, in that they remove colons
394 and periods unnecessarily.
396 and periods unnecessarily.
395
397
396 Whitespace is transformed into underscores, and then
398 Whitespace is transformed into underscores, and then
397 anything which is not a hyphen or a character that
399 anything which is not a hyphen or a character that
398 matches \w (alphanumerics and underscore) is removed.
400 matches \w (alphanumerics and underscore) is removed.
399
401
400 """
402 """
401 # Transform all whitespace to underscore
403 # Transform all whitespace to underscore
402 idstring = re.sub(r'\s', "_", '%s' % idstring)
404 idstring = re.sub(r'\s', "_", '%s' % idstring)
403 # Remove everything that is not a hyphen or a member of \w
405 # Remove everything that is not a hyphen or a member of \w
404 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
406 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
405 return idstring
407 return idstring
406
408
407 def raw_diff(self):
409 def raw_diff(self):
408 """
410 """
409 Returns raw string as udiff
411 Returns raw string as udiff
410 """
412 """
411 udiff_copy = self.copy_iterator()
413 udiff_copy = self.copy_iterator()
412 if self.__format == 'gitdiff':
414 if self.__format == 'gitdiff':
413 udiff_copy = self._parse_gitdiff(udiff_copy)
415 udiff_copy = self._parse_gitdiff(udiff_copy)
414 return u''.join(udiff_copy)
416 return u''.join(udiff_copy)
415
417
416 def as_html(self, table_class='code-difftable', line_class='line',
418 def as_html(self, table_class='code-difftable', line_class='line',
417 new_lineno_class='lineno old', old_lineno_class='lineno new',
419 new_lineno_class='lineno old', old_lineno_class='lineno new',
418 code_class='code', enable_comments=False):
420 code_class='code', enable_comments=False):
419 """
421 """
420 Return udiff as html table with customized css classes
422 Return udiff as html table with customized css classes
421 """
423 """
422 def _link_to_if(condition, label, url):
424 def _link_to_if(condition, label, url):
423 """
425 """
424 Generates a link if condition is meet or just the label if not.
426 Generates a link if condition is meet or just the label if not.
425 """
427 """
426
428
427 if condition:
429 if condition:
428 return '''<a href="%(url)s">%(label)s</a>''' % {
430 return '''<a href="%(url)s">%(label)s</a>''' % {
429 'url': url,
431 'url': url,
430 'label': label
432 'label': label
431 }
433 }
432 else:
434 else:
433 return label
435 return label
434 diff_lines = self.prepare()
436 diff_lines = self.prepare()
435 _html_empty = True
437 _html_empty = True
436 _html = []
438 _html = []
437 _html.append('''<table class="%(table_class)s">\n''' % {
439 _html.append('''<table class="%(table_class)s">\n''' % {
438 'table_class': table_class
440 'table_class': table_class
439 })
441 })
440 for diff in diff_lines:
442 for diff in diff_lines:
441 for line in diff['chunks']:
443 for line in diff['chunks']:
442 _html_empty = False
444 _html_empty = False
443 for change in line:
445 for change in line:
444 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
446 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
445 'lc': line_class,
447 'lc': line_class,
446 'action': change['action']
448 'action': change['action']
447 })
449 })
448 anchor_old_id = ''
450 anchor_old_id = ''
449 anchor_new_id = ''
451 anchor_new_id = ''
450 anchor_old = "%(filename)s_o%(oldline_no)s" % {
452 anchor_old = "%(filename)s_o%(oldline_no)s" % {
451 'filename': self._safe_id(diff['filename']),
453 'filename': self._safe_id(diff['filename']),
452 'oldline_no': change['old_lineno']
454 'oldline_no': change['old_lineno']
453 }
455 }
454 anchor_new = "%(filename)s_n%(oldline_no)s" % {
456 anchor_new = "%(filename)s_n%(oldline_no)s" % {
455 'filename': self._safe_id(diff['filename']),
457 'filename': self._safe_id(diff['filename']),
456 'oldline_no': change['new_lineno']
458 'oldline_no': change['new_lineno']
457 }
459 }
458 cond_old = (change['old_lineno'] != '...' and
460 cond_old = (change['old_lineno'] != '...' and
459 change['old_lineno'])
461 change['old_lineno'])
460 cond_new = (change['new_lineno'] != '...' and
462 cond_new = (change['new_lineno'] != '...' and
461 change['new_lineno'])
463 change['new_lineno'])
462 if cond_old:
464 if cond_old:
463 anchor_old_id = 'id="%s"' % anchor_old
465 anchor_old_id = 'id="%s"' % anchor_old
464 if cond_new:
466 if cond_new:
465 anchor_new_id = 'id="%s"' % anchor_new
467 anchor_new_id = 'id="%s"' % anchor_new
466 ###########################################################
468 ###########################################################
467 # OLD LINE NUMBER
469 # OLD LINE NUMBER
468 ###########################################################
470 ###########################################################
469 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
471 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
470 'a_id': anchor_old_id,
472 'a_id': anchor_old_id,
471 'olc': old_lineno_class
473 'olc': old_lineno_class
472 })
474 })
473
475
474 _html.append('''%(link)s''' % {
476 _html.append('''%(link)s''' % {
475 'link': _link_to_if(True, change['old_lineno'],
477 'link': _link_to_if(True, change['old_lineno'],
476 '#%s' % anchor_old)
478 '#%s' % anchor_old)
477 })
479 })
478 _html.append('''</td>\n''')
480 _html.append('''</td>\n''')
479 ###########################################################
481 ###########################################################
480 # NEW LINE NUMBER
482 # NEW LINE NUMBER
481 ###########################################################
483 ###########################################################
482
484
483 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
485 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
484 'a_id': anchor_new_id,
486 'a_id': anchor_new_id,
485 'nlc': new_lineno_class
487 'nlc': new_lineno_class
486 })
488 })
487
489
488 _html.append('''%(link)s''' % {
490 _html.append('''%(link)s''' % {
489 'link': _link_to_if(True, change['new_lineno'],
491 'link': _link_to_if(True, change['new_lineno'],
490 '#%s' % anchor_new)
492 '#%s' % anchor_new)
491 })
493 })
492 _html.append('''</td>\n''')
494 _html.append('''</td>\n''')
493 ###########################################################
495 ###########################################################
494 # CODE
496 # CODE
495 ###########################################################
497 ###########################################################
496 comments = '' if enable_comments else 'no-comment'
498 comments = '' if enable_comments else 'no-comment'
497 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
499 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
498 'cc': code_class,
500 'cc': code_class,
499 'inc': comments
501 'inc': comments
500 })
502 })
501 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
503 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
502 'code': change['line']
504 'code': change['line']
503 })
505 })
504 _html.append('''\t</td>''')
506 _html.append('''\t</td>''')
505 _html.append('''\n</tr>\n''')
507 _html.append('''\n</tr>\n''')
506 _html.append('''</table>''')
508 _html.append('''</table>''')
507 if _html_empty:
509 if _html_empty:
508 return None
510 return None
509 return ''.join(_html)
511 return ''.join(_html)
510
512
511 def stat(self):
513 def stat(self):
512 """
514 """
513 Returns tuple of added, and removed lines for this instance
515 Returns tuple of added, and removed lines for this instance
514 """
516 """
515 return self.adds, self.removes
517 return self.adds, self.removes
General Comments 0
You need to be logged in to leave comments. Login now