Edgewall Software

LudvigStrigeus: colordiff.2.py

File colordiff.2.py, 6.9 KB (added by ludde, 7 years ago)

Colordiff with encapsulated template

Line 
1
2supported_types = [
3    (8, 'text/x-diff')
4    ]
5
6import re
7from trac.util import escape
8from trac.versioncontrol.diff import _get_change_extent
9
10def _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
26def 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
115cs_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>&hellip;</th><td>&nbsp;</td>
144           <th>&hellip;</th><td>&nbsp;</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>&nbsp;</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>&hellip;</th><th>&hellip;</th><td>&nbsp;</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
175def 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))