-
Notifications
You must be signed in to change notification settings - Fork 9
/
Diff.py
173 lines (150 loc) · 5.58 KB
/
Diff.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""
A diff-browsing view and utilities for use with History.
"""
from __future__ import nested_scopes
from string import join, split, atoi
import re
from struct import pack, unpack
import difflib
from DocumentTemplate.DT_Util import html_quote
from OFS.History import historicalRevision
from AccessControl import getSecurityManager, ClassSecurityInfo
from Globals import InitializeClass
from Defaults import MAX_OLD_LINES_DISPLAY, MAX_NEW_LINES_DISPLAY
import Permissions
from Utils import get_transaction, BLATHER, formattedTraceback
class PageDiffSupport:
"""
I provide methods for browsing a zwiki page's edit history details,
showing differences between edits.
"""
security = ClassSecurityInfo()
def diff(self, rev=None, REQUEST=None):
"""
Show what changed in the latest or specified revision of this page.
Uses the diffform template.
"""
if rev: brev = int(rev)
else: brev = self.revisionNumber()
atext, btext = '', ''
b = self.revision(brev)
if b:
btext = self.tounicode(b.text()) # nb old revisions might be utf-8
a = b.previousRevision()
if a:
atext = self.tounicode(a.text())
difftext = htmldiff(atext,btext)
# wiki links won't find their targets in the revisions folder
# (unless prerendered is still intact). Oh well.
bodytext = b(REQUEST=REQUEST,bare=1,show_subtopics=0)
return self.diffform(brev, difftext, bodytext, REQUEST=REQUEST)
def textDiff(self, a='', b='', verbose=1):
"""
Generate a readable plain text diff for this page's last edit.
Or, between two specified texts. See textdiff.
"""
if not (a or b):
b = self.text()
arev = self.revision(self.previousRevisionNumber())
a = arev and arev.text() or ''
return textdiff(a,b,verbose)
InitializeClass(PageDiffSupport)
def prefix(lines,prefix): return map(lambda x:prefix+x,lines)
def abbreviate(lines,prefix,maxlines=5):
output = []
if maxlines and len(lines) > maxlines:
extra = len(lines) - maxlines
for i in xrange(maxlines - 1):
output.append(prefix + lines[i])
output.append(prefix + "[%d more line%s...]" %
(extra, ((extra == 1) and '') or 's')) # not working
else:
for line in lines:
output.append(prefix + line)
return output
def addedtext(a,b):
"""Return any lines which are in b but not in a, according to difflib."""
a = split(a,'\n')
b = split(b,'\n')
r = []
for tag, alo, ahi, blo, bhi in diffcodes(a,b):
if tag in ('insert','replace'): r.extend((b[blo:bhi]))
else: pass
return '\n' + join(r,'\n')
def diffcodes(a,b):
"""Return a diff between two texts, as difflib opcodes."""
return difflib.SequenceMatcher(
isjunk=re.compile(r"\s*$").match,
a=a,
b=b).get_opcodes()
def textdiff(a, b, verbose=1):
"""
Generate readable a plain text diff between two texts.
This should optimize for human readability, as people may be
getting a lot of these in mail-outs.
verbose adds more decoration.
Each text segment is abbreviated according to built in constants,
to avoid eg generating monster mail-outs. This can be annoying.
"""
a = split(a,'\n')
b = split(b,'\n')
r = []
add, addm = r.append, r.extend
for tag, alo, ahi, blo, bhi in diffcodes(a,b):
if tag == 'replace':
if verbose: add('??changed:')
addm(abbreviate(a[alo:ahi],'-',MAX_OLD_LINES_DISPLAY))
addm(abbreviate(b[blo:bhi],'',MAX_NEW_LINES_DISPLAY))
add('')
elif tag == 'delete':
if verbose: add('--removed:')
addm(abbreviate(a[alo:ahi],'-',MAX_OLD_LINES_DISPLAY))
add('')
elif tag == 'insert':
if verbose: add('++added:')
addm(abbreviate(b[blo:bhi],'',MAX_NEW_LINES_DISPLAY))
add('')
else: # tag == 'equal'
pass
return '\n' + join(r,'\n')
def htmldiff(a,b):
"""
Generate a readable HTML-formatted diff between two texts.
Revisions are numbered backwards from the latest (0).
Alternately, a and/or b texts can be specified.
We don't bother abbreviating text segments like textDiff does.
Should it use a page template ?
"""
a = split(a,'\n')
b = split(b,'\n')
r = []
add, addm = r.append, r.extend
# diffform encloses all this in a pre, so need to avoid line
# breaks for now
def addnobr(s): r[-1] += s
for tag, alo, ahi, blo, bhi in diffcodes(a,b):
if tag == 'replace':
add('<b>changed:</b>')
addnobr('<span style="color:red;text-decoration:line-through">')
# remember to html-quote the diff segments
addm(prefix(map(html_quote, a[alo:ahi]),'-'))
addnobr('</span>')
addnobr('<span style="color:green">')
addm(map(html_quote, b[blo:bhi]))
addnobr('</span>')
add('')
elif tag == 'delete':
add('<b>removed:</b>')
addnobr('<span style="color:red;text-decoration:line-through">')
addm(prefix(map(html_quote, a[alo:ahi]),'-'))
addnobr('</span>')
add('')
elif tag == 'insert':
add('<b>added:</b>')
addnobr('<span style="color:green">')
addm(map(html_quote, b[blo:bhi]))
addnobr('</span>')
add('')
else: # tag == 'equal'
pass
return '\n' + join(r,'\n')