| 1 | |
|---|
| 2 | supported_types = [ |
|---|
| 3 | (8, 'text/x-diff') |
|---|
| 4 | ] |
|---|
| 5 | |
|---|
| 6 | import re |
|---|
| 7 | from trac.util import escape |
|---|
| 8 | from trac.versioncontrol.diff import _get_change_extent |
|---|
| 9 | |
|---|
| 10 | def _markup_intraline_change(fromlines,tolines): |
|---|
| 11 | for i in xrange(len(fromlines)): |
|---|
| 12 | fr, to = fromlines[i], tolines[i] |
|---|
| 13 | (start, end) = _get_change_extent(fr, to) |
|---|
| 14 | if start != 0 and end != 0: |
|---|
| 15 | fromlines[i] = \ |
|---|
| 16 | fr[:start] + '\0' + fr[start:end+len(fr)] + '\1' + fr[end:] |
|---|
| 17 | tolines[i] = \ |
|---|
| 18 | to[:start] + '\0' + to[start:end+len(to)] + '\1' + to[end:] |
|---|
| 19 | |
|---|
| 20 | |
|---|
| 21 | # Translate a diff file into something suitable for inclusion in HDF |
|---|
| 22 | # The result is [(filename, revname_old, revname_new, changes)] |
|---|
| 23 | # Where changes has the same format as the result of hdf_diff |
|---|
| 24 | # If it's unable to parse a file, None is returned |
|---|
| 25 | |
|---|
| 26 | def diff_to_hdf(difflines, tabwidth): |
|---|
| 27 | space_re = re.compile(' ( +)|^ ') |
|---|
| 28 | def htmlify(match): |
|---|
| 29 | div, mod = divmod(len(match.group(0)), 2) |
|---|
| 30 | return div * ' ' + mod * ' ' |
|---|
| 31 | |
|---|
| 32 | try: |
|---|
| 33 | output = [] |
|---|
| 34 | filename,groups = None,None |
|---|
| 35 | for line in difflines: |
|---|
| 36 | # --- trac/Milestone.py (revision 1520) |
|---|
| 37 | if line.startswith('--- '): |
|---|
| 38 | words = line.split(None,2) |
|---|
| 39 | filename,fromrev = words[1],'old' |
|---|
| 40 | groups,blocks = None,None |
|---|
| 41 | continue |
|---|
| 42 | # +++ trac/Milestone.py (working copy) |
|---|
| 43 | if line.startswith('+++ '): |
|---|
| 44 | words = line.split(None,2) |
|---|
| 45 | if words[1] != filename: |
|---|
| 46 | return None |
|---|
| 47 | groups = [] |
|---|
| 48 | output.append({'filename' : filename, 'oldrev' : fromrev, 'newrev' : 'new', 'diff' : groups}) |
|---|
| 49 | continue |
|---|
| 50 | # Lines to ignore |
|---|
| 51 | if line.startswith('Index: ') or line.startswith('======'): |
|---|
| 52 | continue |
|---|
| 53 | if groups == None: return None |
|---|
| 54 | # @@ -333,10 +329,8 @@ |
|---|
| 55 | if line.startswith('@@ '): |
|---|
| 56 | r = re.match(r'@@ -(\d+),\d+ \+(\d+),\d+ @@', line) |
|---|
| 57 | if not r: return None |
|---|
| 58 | blocks = [] |
|---|
| 59 | groups.append(blocks) |
|---|
| 60 | fromline,toline = map(int, r.groups()) |
|---|
| 61 | last_type = None |
|---|
| 62 | continue |
|---|
| 63 | if blocks == None: return None |
|---|
| 64 | |
|---|
| 65 | # First character is the command |
|---|
| 66 | command,line = line[0],line[1:] |
|---|
| 67 | |
|---|
| 68 | # Make a new block? |
|---|
| 69 | if (command == ' ') != last_type: |
|---|
| 70 | last_type = command == ' ' |
|---|
| 71 | blocks.append({'type': last_type and 'unmod' or 'mod', |
|---|
| 72 | 'base.offset': fromline, 'base.lines': [], |
|---|
| 73 | 'changed.offset': toline,'changed.lines': []}) |
|---|
| 74 | if command == ' ': |
|---|
| 75 | blocks[-1]['changed.lines'].append(line) |
|---|
| 76 | blocks[-1]['base.lines'].append(line) |
|---|
| 77 | fromline += 1 |
|---|
| 78 | toline += 1 |
|---|
| 79 | elif command == '+': |
|---|
| 80 | blocks[-1]['changed.lines'].append(line) |
|---|
| 81 | toline += 1 |
|---|
| 82 | elif command == '-': |
|---|
| 83 | blocks[-1]['base.lines'].append(line) |
|---|
| 84 | fromline += 1 |
|---|
| 85 | else: |
|---|
| 86 | return None |
|---|
| 87 | # Go through all groups/blocks and mark up intraline changes, and convert to html |
|---|
| 88 | for o in output: |
|---|
| 89 | for group in o['diff']: |
|---|
| 90 | for b in group: |
|---|
| 91 | f, t = b['base.lines'], b['changed.lines'] |
|---|
| 92 | if b['type'] == 'mod': |
|---|
| 93 | if len(f) == 0: |
|---|
| 94 | b['type'] = 'add' |
|---|
| 95 | elif len(t) == 0: |
|---|
| 96 | b['type'] = 'rem' |
|---|
| 97 | elif len(f) == len(t): |
|---|
| 98 | _markup_intraline_change(f, t) |
|---|
| 99 | for i in xrange(len(f)): |
|---|
| 100 | line = f[i].expandtabs(tabwidth) |
|---|
| 101 | line = escape(line).replace('\0', '<del>') \ |
|---|
| 102 | .replace('\1', '</del>') |
|---|
| 103 | f[i] = space_re.sub(htmlify, line) |
|---|
| 104 | for i in xrange(len(t)): |
|---|
| 105 | line = t[i].expandtabs(tabwidth) |
|---|
| 106 | line = escape(line).replace('\0', '<ins>') \ |
|---|
| 107 | .replace('\1', '</ins>') |
|---|
| 108 | t[i] = space_re.sub(htmlify, line) |
|---|
| 109 | return output |
|---|
| 110 | |
|---|
| 111 | except IndexError: |
|---|
| 112 | return None |
|---|
| 113 | |
|---|
| 114 | |
|---|
| 115 | cs_contents = """ |
|---|
| 116 | <?cs include "macros.cs"?> |
|---|
| 117 | |
|---|
| 118 | <style type="text/css"> |
|---|
| 119 | @import url(<?cs var:htdocs_location ?>/css/diff.css); |
|---|
| 120 | </style> |
|---|
| 121 | <div class="diff"> |
|---|
| 122 | <ul class="entries"> |
|---|
| 123 | <?cs each:item = diff.changes ?> |
|---|
| 124 | <li class="entry"> |
|---|
| 125 | <h2><?cs var:item.filename ?></h2> |
|---|
| 126 | <table class="<?cs var:diff.style ?>" summary="Differences" cellspacing="0"> |
|---|
| 127 | <?cs if:diff.style == 'sidebyside' ?> |
|---|
| 128 | <colgroup class="base"> |
|---|
| 129 | <col class="lineno" /><col class="content" /> |
|---|
| 130 | <colgroup class="chg"> |
|---|
| 131 | <col class="lineno" /><col class="content" /> |
|---|
| 132 | </colgroup> |
|---|
| 133 | <thead><tr> |
|---|
| 134 | <th colspan="2"><?cs var:item.oldrev ?></th> |
|---|
| 135 | <th colspan="2"><?cs var:item.newrev ?></th> |
|---|
| 136 | </tr></thead> |
|---|
| 137 | <?cs each:change = item.diff ?> |
|---|
| 138 | <tbody> |
|---|
| 139 | <?cs call:diff_display(change, diff.style) ?> |
|---|
| 140 | </tbody> |
|---|
| 141 | <?cs if:name(change) < len(item.diff) - 1 ?> |
|---|
| 142 | <tbody class="skippedlines"> |
|---|
| 143 | <tr><th>…</th><td> </td> |
|---|
| 144 | <th>…</th><td> </td></tr> |
|---|
| 145 | </tbody> |
|---|
| 146 | <?cs /if ?> |
|---|
| 147 | <?cs /each ?> |
|---|
| 148 | <?cs else ?> |
|---|
| 149 | <colgroup> |
|---|
| 150 | <col class="lineno" /> |
|---|
| 151 | <col class="lineno" /> |
|---|
| 152 | <col class="content" /> |
|---|
| 153 | </colgroup> |
|---|
| 154 | <thead><tr> |
|---|
| 155 | <th title="<?cs var:item.oldrev ?>"><?cs var:item.oldrev ?></th> |
|---|
| 156 | <th title="<?cs var:item.newrev ?>"><?cs var:item.newrev ?></th> |
|---|
| 157 | <th> </th> |
|---|
| 158 | </tr></thead> |
|---|
| 159 | <?cs each:change = item.diff ?> |
|---|
| 160 | <?cs call:diff_display(change, diff.style) ?> |
|---|
| 161 | <?cs if:name(change) < len(item.diff) - 1 ?> |
|---|
| 162 | <tbody class="skippedlines"> |
|---|
| 163 | <tr><th>…</th><th>…</th><td> </td></tr> |
|---|
| 164 | </tbody> |
|---|
| 165 | <?cs /if ?> |
|---|
| 166 | <?cs /each ?><?cs |
|---|
| 167 | /if ?> |
|---|
| 168 | </table> |
|---|
| 169 | </li> |
|---|
| 170 | <?cs /each ?> |
|---|
| 171 | </ul> |
|---|
| 172 | </div> |
|---|
| 173 | """ |
|---|
| 174 | |
|---|
| 175 | def display(data, mimetype, filename, rev, env): |
|---|
| 176 | from trac.web.clearsilver import HDFWrapper |
|---|
| 177 | |
|---|
| 178 | res = diff_to_hdf(data.splitlines(), 8) |
|---|
| 179 | if not res: return None |
|---|
| 180 | |
|---|
| 181 | hdf = HDFWrapper(loadpaths=[env.get_templates_dir(), |
|---|
| 182 | env.config.get('trac', 'templates_dir')]) |
|---|
| 183 | hdf['diff.style'] = 'inline' |
|---|
| 184 | hdf['diff.changes'] = res |
|---|
| 185 | hdf['htdocs_location'] = env.config.get('trac', 'htdocs_location') |
|---|
| 186 | return hdf.render(hdf.parse(cs_contents)) |
|---|