Server : Apache System : Linux iad1-shared-b8-43 6.6.49-grsec-jammy+ #10 SMP Thu Sep 12 23:23:08 UTC 2024 x86_64 User : dh_edsupp ( 6597262) PHP Version : 8.2.26 Disable Function : NONE Directory : /lib/python3/dist-packages/trac/mimeview/ |
Upload File : |
# -*- coding: utf-8 -*- # # Copyright (C) 2005-2021 Edgewall Software # Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de> # Copyright (C) 2006 Christian Boos <cboos@edgewall.org> # 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 https://trac.edgewall.org/wiki/TracLicense. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at https://trac.edgewall.org/log/. # # Author: Christopher Lenz <cmlenz@gmx.de> # Ludvig Strigeus import os.path import re from trac.core import * from trac.mimeview.api import content_to_unicode, IHTMLPreviewRenderer, \ Mimeview from trac.util.html import Markup, escape from trac.util.text import expandtabs from trac.util.translation import _ from trac.web.chrome import Chrome, add_script, add_stylesheet __all__ = ['PatchRenderer'] class PatchRenderer(Component): """HTML renderer for patches in unified diff format. This uses the same layout as in the wiki diff view or the changeset view. """ implements(IHTMLPreviewRenderer) # IHTMLPreviewRenderer methods def get_quality_ratio(self, mimetype): if mimetype in ('text/x-diff', 'text/x-patch'): return 8 return 0 def render(self, context, mimetype, content, filename=None, rev=None): req = context.req content = content_to_unicode(self.env, content, mimetype) changes = self._diff_to_hdf(content.splitlines(), Mimeview(self.env).tab_width) if not changes or not any(c['diffs'] for c in changes): self.log.debug("Invalid unified diff content: %.40r... (%d " "characters)", content, len(content)) return data = {'diff': {'style': 'inline'}, 'no_id': True, 'changes': changes, 'longcol': 'File', 'shortcol': ''} add_script(req, 'common/js/diff.js') add_stylesheet(req, 'common/css/diff.css') return Chrome(self.env).render_fragment(req, 'diff_div.html', data) # 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 range(len(fromlines)): fr, to = fromlines[i], tolines[i] (start, end) = get_change_extent(fr, to) if start != 0 or end != 0: last = end+len(fr) fromlines[i] = fr[:start] + '\0' + fr[start:last] + \ '\1' + fr[last:] last = end+len(to) tolines[i] = to[:start] + '\0' + to[start:last] + \ '\1' + to[last:] space_re = re.compile(' ( +)|^ ') def htmlify(match): div, mod = divmod(len(match.group(0)), 2) return Markup(div * ' ' + mod * ' ') comments = [] changes = [] lines = iter(difflines) try: line = next(lines) while True: oldpath = oldrev = newpath = newrev = '' oldinfo = newinfo = [] binary = False # consume preamble, storing free lines in comments # (also detect the special case of git binary patches) if not line.startswith('--- '): if not line.startswith('Index: ') and line != '=' * 67: comments.append(line) if line == "GIT binary patch": binary = True diffcmd_line = comments[0] # diff --git a/... b/,,, oldpath, newpath = diffcmd_line.split()[-2:] if any(c.startswith('new file') for c in comments): oldpath = '/dev/null' if any(c.startswith('deleted file') for c in comments): newpath = '/dev/null' oldinfo = ['', oldpath] newinfo = ['', newpath] index = [c for c in comments if c.startswith('index ')] if index: # index 8f****78..1e****5c oldrev, newrev = index[0].split()[-1].split('..') oldinfo.append(oldrev) newinfo.append(newrev) line = next(lines) while line: comments.append(line) line = next(lines) else: line = next(lines) continue if not oldinfo and not newinfo: # Base filename/version from '--- <file> [rev]' oldinfo = line.split(None, 2) if len(oldinfo) > 1: oldpath = oldinfo[1] if len(oldinfo) > 2: oldrev = oldinfo[2] # Changed filename/version from '+++ <file> [rev]' line = next(lines) if not line.startswith('+++ '): self.log.debug('expected +++ after ---, got %s', line) return None newinfo = line.split(None, 2) if len(newinfo) > 1: newpath = newinfo[1] if len(newinfo) > 2: newrev = newinfo[2] shortrev = ('old', 'new') if oldpath or newpath: sep = re.compile(r'([/.~\\])') commonprefix = ''.join(os.path.commonprefix( [sep.split(newpath), sep.split(oldpath)])) commonsuffix = ''.join(os.path.commonprefix( [sep.split(newpath)[::-1], sep.split(oldpath)[::-1]])[::-1]) if len(commonprefix) > len(commonsuffix): common = commonprefix elif commonsuffix: common = commonsuffix.lstrip('/') a = oldpath[:-len(commonsuffix)] b = newpath[:-len(commonsuffix)] if len(a) < 4 and len(b) < 4: shortrev = (a, b) elif oldpath == '/dev/null': common = _("new file %(new)s", new=newpath.lstrip('b/')) shortrev = ('-', '+') elif newpath == '/dev/null': common = _("deleted file %(deleted)s", deleted=oldpath.lstrip('a/')) shortrev = ('+', '-') else: common = '(a) %s vs. (b) %s' % (oldpath, newpath) shortrev = ('a', 'b') else: common = '' groups = [] groups_title = [] changes.append({'change': 'edit', 'props': [], 'comments': '\n'.join(comments), 'binary': binary, 'diffs': groups, 'diffs_title': groups_title, 'old': {'path': common, 'rev': ' '.join(oldinfo[1:]), 'shortrev': shortrev[0]}, 'new': {'path': common, 'rev': ' '.join(newinfo[1:]), 'shortrev': shortrev[1]}}) comments = [] line = next(lines) while line: # "@@ -333,10 +329,8 @@" or "@@ -1 +1 @@ [... title ...]" r = re.match(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@' '(.*)', line) if not r: break blocks = [] groups.append(blocks) fromline, fromend, toline, toend = \ [int(x or 1) for x in r.groups()[:4]] groups_title.append(r.group(5)) last_type = extra = None fromend += fromline toend += toline line = next(lines) while fromline < fromend or toline < toend or extra: # First character is the command command = ' ' if line: command, line = line[0], line[1:] # Make a new block? if (command == ' ') != last_type: last_type = command == ' ' kind = 'unmod' if last_type else 'mod' block = {'type': kind, 'base': {'offset': fromline - 1, 'lines': []}, 'changed': {'offset': toline - 1, 'lines': []}} blocks.append(block) else: block = blocks[-1] if command == ' ': sides = ['base', 'changed'] elif command == '+': last_side = 'changed' sides = [last_side] elif command == '-': last_side = 'base' sides = [last_side] elif command == '\\' and last_side: meta = block[last_side].setdefault('meta', {}) meta[len(block[last_side]['lines'])] = True sides = [last_side] elif command == '@': # ill-formed patch groups_title[-1] = "%s (%s)" % ( groups_title[-1], _("this hunk was shorter than expected")) line = '@'+line break else: self.log.debug('expected +, - or \\, got %s', command) return None for side in sides: if side == 'base': fromline += 1 else: toline += 1 block[side]['lines'].append(line) line = next(lines) extra = line and line[0] == '\\' except StopIteration: pass # Go through all groups/blocks and mark up intraline changes, and # convert to html for o in changes: for group in o['diffs']: for b in group: base, changed = b['base'], b['changed'] f, t = base['lines'], 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 range(len(f)): line = expandtabs(f[i], tabwidth, '\0\1') line = escape(line, quotes=False) line = '<del>'.join(space_re.sub(htmlify, seg) for seg in line.split('\0')) line = line.replace('\1', '</del>') f[i] = Markup(line) if 'meta' in base and i in base['meta']: f[i] = Markup('<em>%s</em>') % f[i] for i in range(len(t)): line = expandtabs(t[i], tabwidth, '\0\1') line = escape(line, quotes=False) line = '<ins>'.join(space_re.sub(htmlify, seg) for seg in line.split('\0')) line = line.replace('\1', '</ins>') t[i] = Markup(line) if 'meta' in changed and i in changed['meta']: t[i] = Markup('<em>%s</em>') % t[i] return changes