# -*- coding: iso8859-1 -*- # # Copyright (C) 2005 Edgewall Software # Copyright (C) 2005 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://projects.edgewall.com/trac/. # # Author: Christopher Lenz # Ludvig Strigeus from trac.core import * from trac.mimeview.api import IHTMLPreviewRenderer from trac.util import escape from trac.web.chrome import add_stylesheet __all__ = ['PatchRenderer'] class PatchRenderer(Component): """Structured display of patches in unified diff format, similar to the layout provided by the changeset view. """ implements(IHTMLPreviewRenderer) diff_cs = """
  •  
     
""" # diff_cs # IHTMLPreviewRenderer methods def get_quality_ratio(self, mimetype): if mimetype == 'text/x-diff': return 8 return 0 def render(self, req, mimetype, content, filename=None, rev=None): from trac.web.clearsilver import HDFWrapper tabwidth = int(self.config.get('diff', 'tab_width', self.config.get('mimeviewer', 'tab_width'))) d = self._diff_to_hdf(content.splitlines(), tabwidth) if not d: raise TracError, 'Invalid unified diff content' hdf = HDFWrapper(loadpaths=[self.env.get_templates_dir(), self.config.get('trac', 'templates_dir')]) hdf['diff.files'] = d add_stylesheet(req, 'common/css/diff.css') return hdf.render(hdf.parse(self.diff_cs)) # Internal methods # FIXME: This function should probably share more code with the # trac.versioncontrol.diff module def _diff_to_hdf(self, difflines, tabwidth): """ Translate a diff file into something suitable for inclusion in HDF. The result is [(filename, revname_old, revname_new, changes)], where changes has the same format as the result of `trac.versioncontrol.diff.hdf_diff`. If the diff cannot be parsed, this method returns None. """ def _markup_intraline_change(fromlines, tolines): from trac.versioncontrol.diff import _get_change_extent for i in xrange(len(fromlines)): fr, to = fromlines[i], tolines[i] (start, end) = _get_change_extent(fr, to) if start != 0 and end != 0: fromlines[i] = fr[:start] + '\0' + fr[start:end+len(fr)] + \ '\1' + fr[end:] tolines[i] = to[:start] + '\0' + to[start:end+len(to)] + \ '\1' + to[end:] import re space_re = re.compile(' ( +)|^ ') def htmlify(match): div, mod = divmod(len(match.group(0)), 2) return div * '  ' + mod * ' ' output = [] filename, groups = None, None for line in difflines: if line.startswith('--- '): # Base filename/version words = line.split(None, 2) filename, fromrev = words[1], 'old' groups, blocks = None, None continue if line.startswith('+++ '): # Changed filename/version words = line.split(None, 2) if len(words[1]) < len(filename): # Always use the shortest filename for display filename = words[1] groups = [] output.append({'filename' : filename, 'oldrev' : fromrev, 'newrev' : 'new', 'diff' : groups}) continue # Lines to ignore if line.startswith('Index: ') or line.startswith('======') or line == '': continue if groups == None: return None # @@ -333,10 +329,8 @@ if line.startswith('@@ '): r = re.match(r'@@ -(\d+),\d+ \+(\d+),\d+ @@', line) if not r: return None blocks = [] groups.append(blocks) fromline,toline = map(int, r.groups()) last_type = None continue if blocks == None: return None # First character is the command command,line = line[0],line[1:] # Make a new block? if (command == ' ') != last_type: last_type = command == ' ' blocks.append({'type': last_type and 'unmod' or 'mod', 'base.offset': fromline - 1, 'base.lines': [], 'changed.offset': toline - 1, 'changed.lines': []}) if command == ' ': blocks[-1]['changed.lines'].append(line) blocks[-1]['base.lines'].append(line) fromline += 1 toline += 1 elif command == '+': blocks[-1]['changed.lines'].append(line) toline += 1 elif command == '-': blocks[-1]['base.lines'].append(line) fromline += 1 else: return None # Go through all groups/blocks and mark up intraline changes, and # convert to html for o in output: for group in o['diff']: for b in group: f, t = b['base.lines'], b['changed.lines'] if b['type'] == 'mod': if len(f) == 0: b['type'] = 'add' elif len(t) == 0: b['type'] = 'rem' elif len(f) == len(t): _markup_intraline_change(f, t) for i in xrange(len(f)): line = f[i].expandtabs(tabwidth) line = escape(line).replace('\0', '') \ .replace('\1', '') f[i] = space_re.sub(htmlify, line) for i in xrange(len(t)): line = t[i].expandtabs(tabwidth) line = escape(line).replace('\0', '') \ .replace('\1', '') t[i] = space_re.sub(htmlify, line) return output