Chris@0
|
1 module RedmineDiff
|
Chris@0
|
2 class Diff
|
Chris@0
|
3
|
Chris@0
|
4 VERSION = 0.3
|
Chris@0
|
5
|
Chris@0
|
6 def Diff.lcs(a, b)
|
Chris@0
|
7 astart = 0
|
Chris@0
|
8 bstart = 0
|
Chris@0
|
9 afinish = a.length-1
|
Chris@0
|
10 bfinish = b.length-1
|
Chris@0
|
11 mvector = []
|
Chris@909
|
12
|
Chris@0
|
13 # First we prune off any common elements at the beginning
|
Chris@0
|
14 while (astart <= afinish && bstart <= afinish && a[astart] == b[bstart])
|
Chris@0
|
15 mvector[astart] = bstart
|
Chris@0
|
16 astart += 1
|
Chris@0
|
17 bstart += 1
|
Chris@0
|
18 end
|
Chris@909
|
19
|
Chris@0
|
20 # now the end
|
Chris@0
|
21 while (astart <= afinish && bstart <= bfinish && a[afinish] == b[bfinish])
|
Chris@0
|
22 mvector[afinish] = bfinish
|
Chris@0
|
23 afinish -= 1
|
Chris@0
|
24 bfinish -= 1
|
Chris@0
|
25 end
|
Chris@0
|
26
|
Chris@0
|
27 bmatches = b.reverse_hash(bstart..bfinish)
|
Chris@0
|
28 thresh = []
|
Chris@0
|
29 links = []
|
Chris@909
|
30
|
Chris@0
|
31 (astart..afinish).each { |aindex|
|
Chris@0
|
32 aelem = a[aindex]
|
Chris@0
|
33 next unless bmatches.has_key? aelem
|
Chris@0
|
34 k = nil
|
Chris@0
|
35 bmatches[aelem].reverse.each { |bindex|
|
Chris@1115
|
36 if k && (thresh[k] > bindex) && (thresh[k-1] < bindex)
|
Chris@1115
|
37 thresh[k] = bindex
|
Chris@1115
|
38 else
|
Chris@1115
|
39 k = thresh.replacenextlarger(bindex, k)
|
Chris@1115
|
40 end
|
Chris@1115
|
41 links[k] = [ (k==0) ? nil : links[k-1], aindex, bindex ] if k
|
Chris@0
|
42 }
|
Chris@0
|
43 }
|
Chris@0
|
44
|
Chris@0
|
45 if !thresh.empty?
|
Chris@0
|
46 link = links[thresh.length-1]
|
Chris@0
|
47 while link
|
Chris@1115
|
48 mvector[link[1]] = link[2]
|
Chris@1115
|
49 link = link[0]
|
Chris@0
|
50 end
|
Chris@0
|
51 end
|
Chris@0
|
52
|
Chris@0
|
53 return mvector
|
Chris@0
|
54 end
|
Chris@0
|
55
|
Chris@0
|
56 def makediff(a, b)
|
Chris@0
|
57 mvector = Diff.lcs(a, b)
|
Chris@0
|
58 ai = bi = 0
|
Chris@0
|
59 while ai < mvector.length
|
Chris@0
|
60 bline = mvector[ai]
|
Chris@0
|
61 if bline
|
Chris@1115
|
62 while bi < bline
|
Chris@1115
|
63 discardb(bi, b[bi])
|
Chris@1115
|
64 bi += 1
|
Chris@1115
|
65 end
|
Chris@1115
|
66 match(ai, bi)
|
Chris@1115
|
67 bi += 1
|
Chris@0
|
68 else
|
Chris@1115
|
69 discarda(ai, a[ai])
|
Chris@0
|
70 end
|
Chris@0
|
71 ai += 1
|
Chris@0
|
72 end
|
Chris@0
|
73 while ai < a.length
|
Chris@0
|
74 discarda(ai, a[ai])
|
Chris@0
|
75 ai += 1
|
Chris@0
|
76 end
|
Chris@0
|
77 while bi < b.length
|
Chris@0
|
78 discardb(bi, b[bi])
|
Chris@0
|
79 bi += 1
|
Chris@0
|
80 end
|
Chris@0
|
81 match(ai, bi)
|
Chris@0
|
82 1
|
Chris@0
|
83 end
|
Chris@0
|
84
|
Chris@0
|
85 def compactdiffs
|
Chris@0
|
86 diffs = []
|
Chris@0
|
87 @diffs.each { |df|
|
Chris@0
|
88 i = 0
|
Chris@0
|
89 curdiff = []
|
Chris@0
|
90 while i < df.length
|
Chris@1115
|
91 whot = df[i][0]
|
Chris@1115
|
92 s = @isstring ? df[i][2].chr : [df[i][2]]
|
Chris@1115
|
93 p = df[i][1]
|
Chris@1115
|
94 last = df[i][1]
|
Chris@1115
|
95 i += 1
|
Chris@1115
|
96 while df[i] && df[i][0] == whot && df[i][1] == last+1
|
Chris@1115
|
97 s << df[i][2]
|
Chris@1115
|
98 last = df[i][1]
|
Chris@1115
|
99 i += 1
|
Chris@1115
|
100 end
|
Chris@1115
|
101 curdiff.push [whot, p, s]
|
Chris@0
|
102 end
|
Chris@0
|
103 diffs.push curdiff
|
Chris@0
|
104 }
|
Chris@0
|
105 return diffs
|
Chris@0
|
106 end
|
Chris@0
|
107
|
Chris@0
|
108 attr_reader :diffs, :difftype
|
Chris@0
|
109
|
Chris@0
|
110 def initialize(diffs_or_a, b = nil, isstring = nil)
|
Chris@0
|
111 if b.nil?
|
Chris@0
|
112 @diffs = diffs_or_a
|
Chris@0
|
113 @isstring = isstring
|
Chris@0
|
114 else
|
Chris@0
|
115 @diffs = []
|
Chris@0
|
116 @curdiffs = []
|
Chris@0
|
117 makediff(diffs_or_a, b)
|
Chris@0
|
118 @difftype = diffs_or_a.class
|
Chris@0
|
119 end
|
Chris@0
|
120 end
|
Chris@909
|
121
|
Chris@0
|
122 def match(ai, bi)
|
Chris@0
|
123 @diffs.push @curdiffs unless @curdiffs.empty?
|
Chris@0
|
124 @curdiffs = []
|
Chris@0
|
125 end
|
Chris@0
|
126
|
Chris@0
|
127 def discarda(i, elem)
|
Chris@0
|
128 @curdiffs.push ['-', i, elem]
|
Chris@0
|
129 end
|
Chris@0
|
130
|
Chris@0
|
131 def discardb(i, elem)
|
Chris@0
|
132 @curdiffs.push ['+', i, elem]
|
Chris@0
|
133 end
|
Chris@0
|
134
|
Chris@0
|
135 def compact
|
Chris@0
|
136 return Diff.new(compactdiffs)
|
Chris@0
|
137 end
|
Chris@0
|
138
|
Chris@0
|
139 def compact!
|
Chris@0
|
140 @diffs = compactdiffs
|
Chris@0
|
141 end
|
Chris@0
|
142
|
Chris@0
|
143 def inspect
|
Chris@0
|
144 @diffs.inspect
|
Chris@0
|
145 end
|
Chris@0
|
146
|
Chris@0
|
147 end
|
Chris@0
|
148 end
|
Chris@0
|
149
|
Chris@0
|
150 module Diffable
|
Chris@0
|
151 def diff(b)
|
Chris@0
|
152 RedmineDiff::Diff.new(self, b)
|
Chris@0
|
153 end
|
Chris@0
|
154
|
Chris@0
|
155 # Create a hash that maps elements of the array to arrays of indices
|
Chris@0
|
156 # where the elements are found.
|
Chris@0
|
157
|
Chris@0
|
158 def reverse_hash(range = (0...self.length))
|
Chris@0
|
159 revmap = {}
|
Chris@0
|
160 range.each { |i|
|
Chris@0
|
161 elem = self[i]
|
Chris@0
|
162 if revmap.has_key? elem
|
Chris@1115
|
163 revmap[elem].push i
|
Chris@0
|
164 else
|
Chris@1115
|
165 revmap[elem] = [i]
|
Chris@0
|
166 end
|
Chris@0
|
167 }
|
Chris@0
|
168 return revmap
|
Chris@0
|
169 end
|
Chris@0
|
170
|
Chris@0
|
171 def replacenextlarger(value, high = nil)
|
Chris@0
|
172 high ||= self.length
|
Chris@0
|
173 if self.empty? || value > self[-1]
|
Chris@0
|
174 push value
|
Chris@0
|
175 return high
|
Chris@0
|
176 end
|
Chris@0
|
177 # binary search for replacement point
|
Chris@0
|
178 low = 0
|
Chris@0
|
179 while low < high
|
Chris@0
|
180 index = (high+low)/2
|
Chris@0
|
181 found = self[index]
|
Chris@0
|
182 return nil if value == found
|
Chris@0
|
183 if value > found
|
Chris@1115
|
184 low = index + 1
|
Chris@0
|
185 else
|
Chris@1115
|
186 high = index
|
Chris@0
|
187 end
|
Chris@0
|
188 end
|
Chris@0
|
189
|
Chris@0
|
190 self[low] = value
|
Chris@0
|
191 # $stderr << "replace #{value} : 0/#{low}/#{init_high} (#{steps} steps) (#{init_high-low} off )\n"
|
Chris@0
|
192 # $stderr.puts self.inspect
|
Chris@0
|
193 #gets
|
Chris@0
|
194 #p length - low
|
Chris@0
|
195 return low
|
Chris@0
|
196 end
|
Chris@0
|
197
|
Chris@0
|
198 def patch(diff)
|
Chris@0
|
199 newary = nil
|
Chris@0
|
200 if diff.difftype == String
|
Chris@0
|
201 newary = diff.difftype.new('')
|
Chris@0
|
202 else
|
Chris@0
|
203 newary = diff.difftype.new
|
Chris@0
|
204 end
|
Chris@0
|
205 ai = 0
|
Chris@0
|
206 bi = 0
|
Chris@0
|
207 diff.diffs.each { |d|
|
Chris@0
|
208 d.each { |mod|
|
Chris@1115
|
209 case mod[0]
|
Chris@1115
|
210 when '-'
|
Chris@1115
|
211 while ai < mod[1]
|
Chris@1115
|
212 newary << self[ai]
|
Chris@1115
|
213 ai += 1
|
Chris@1115
|
214 bi += 1
|
Chris@1115
|
215 end
|
Chris@1115
|
216 ai += 1
|
Chris@1115
|
217 when '+'
|
Chris@1115
|
218 while bi < mod[1]
|
Chris@1115
|
219 newary << self[ai]
|
Chris@1115
|
220 ai += 1
|
Chris@1115
|
221 bi += 1
|
Chris@1115
|
222 end
|
Chris@1115
|
223 newary << mod[2]
|
Chris@1115
|
224 bi += 1
|
Chris@1115
|
225 else
|
Chris@1115
|
226 raise "Unknown diff action"
|
Chris@1115
|
227 end
|
Chris@0
|
228 }
|
Chris@0
|
229 }
|
Chris@0
|
230 while ai < self.length
|
Chris@0
|
231 newary << self[ai]
|
Chris@0
|
232 ai += 1
|
Chris@0
|
233 bi += 1
|
Chris@0
|
234 end
|
Chris@0
|
235 return newary
|
Chris@0
|
236 end
|
Chris@0
|
237 end
|
Chris@0
|
238
|
Chris@0
|
239 class Array
|
Chris@0
|
240 include Diffable
|
Chris@0
|
241 end
|
Chris@0
|
242
|
Chris@0
|
243 class String
|
Chris@0
|
244 include Diffable
|
Chris@0
|
245 end
|
Chris@0
|
246
|
Chris@0
|
247 =begin
|
Chris@0
|
248 = Diff
|
Chris@0
|
249 (({diff.rb})) - computes the differences between two arrays or
|
Chris@0
|
250 strings. Copyright (C) 2001 Lars Christensen
|
Chris@0
|
251
|
Chris@0
|
252 == Synopsis
|
Chris@0
|
253
|
Chris@0
|
254 diff = Diff.new(a, b)
|
Chris@0
|
255 b = a.patch(diff)
|
Chris@0
|
256
|
Chris@0
|
257 == Class Diff
|
Chris@0
|
258 === Class Methods
|
Chris@0
|
259 --- Diff.new(a, b)
|
Chris@0
|
260 --- a.diff(b)
|
Chris@0
|
261 Creates a Diff object which represent the differences between
|
Chris@0
|
262 ((|a|)) and ((|b|)). ((|a|)) and ((|b|)) can be either be arrays
|
Chris@0
|
263 of any objects, strings, or object of any class that include
|
Chris@0
|
264 module ((|Diffable|))
|
Chris@0
|
265
|
Chris@0
|
266 == Module Diffable
|
Chris@0
|
267 The module ((|Diffable|)) is intended to be included in any class for
|
Chris@0
|
268 which differences are to be computed. Diffable is included into String
|
Chris@0
|
269 and Array when (({diff.rb})) is (({require}))'d.
|
Chris@0
|
270
|
Chris@0
|
271 Classes including Diffable should implement (({[]})) to get element at
|
Chris@0
|
272 integer indices, (({<<})) to append elements to the object and
|
Chris@0
|
273 (({ClassName#new})) should accept 0 arguments to create a new empty
|
Chris@0
|
274 object.
|
Chris@0
|
275
|
Chris@0
|
276 === Instance Methods
|
Chris@0
|
277 --- Diffable#patch(diff)
|
Chris@0
|
278 Applies the differences from ((|diff|)) to the object ((|obj|))
|
Chris@0
|
279 and return the result. ((|obj|)) is not changed. ((|obj|)) and
|
Chris@0
|
280 can be either an array or a string, but must match the object
|
Chris@0
|
281 from which the ((|diff|)) was created.
|
Chris@0
|
282 =end
|