diff-lcs 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ #! /usr/env/bin ruby
2
+ #--
3
+ # Copyright 2004 Austin Ziegler <diff-lcs@halostatue.ca>
4
+ # adapted from:
5
+ # Algorithm::Diff (Perl) by Ned Konz <perl@bike-nomad.com>
6
+ # Smalltalk by Mario I. Wolczko <mario@wolczko.com>
7
+ # implements McIlroy-Hunt diff algorithm
8
+ #
9
+ # This program is free software. It may be redistributed and/or modified under
10
+ # the terms of the GPL version 2 (or later), the Perl Artistic licence, or the
11
+ # Ruby licence.
12
+ #
13
+ # $Id: array.rb,v 1.3 2004/08/08 20:33:09 austin Exp $
14
+ #++
15
+ # Includes Diff::LCS into the Array built-in class.
16
+
17
+ require 'diff/lcs'
18
+
19
+ class Array
20
+ include Diff::LCS
21
+ end
@@ -0,0 +1,51 @@
1
+ #! /usr/env/bin ruby
2
+ #--
3
+ # Copyright 2004 Austin Ziegler <diff-lcs@halostatue.ca>
4
+ # adapted from:
5
+ # Algorithm::Diff (Perl) by Ned Konz <perl@bike-nomad.com>
6
+ # Smalltalk by Mario I. Wolczko <mario@wolczko.com>
7
+ # implements McIlroy-Hunt diff algorithm
8
+ #
9
+ # This program is free software. It may be redistributed and/or modified under
10
+ # the terms of the GPL version 2 (or later), the Perl Artistic licence, or the
11
+ # Ruby licence.
12
+ #
13
+ # $Id: block.rb,v 1.3 2004/08/08 20:33:09 austin Exp $
14
+ #++
15
+ # Contains Diff::LCS::Block for bin/ldiff.
16
+
17
+ # A block is an operation removing, adding, or changing a group of items.
18
+ # Basically, this is just a list of changes, where each change adds or
19
+ # deletes a single item. Used by bin/ldiff.
20
+ class Diff::LCS::Block
21
+ attr_reader :changes, :insert, :remove
22
+
23
+ def initialize(chunk)
24
+ @changes = []
25
+ @insert = []
26
+ @remove = []
27
+
28
+ chunk.each do |item|
29
+ @changes << item
30
+ @remove << item if item.deleting?
31
+ @insert << item if item.adding?
32
+ end
33
+ end
34
+
35
+ def diff_size
36
+ @insert.size - @remove.size
37
+ end
38
+
39
+ def op
40
+ case [@remove.empty?, @insert.empty?]
41
+ when [false, false]
42
+ '!'
43
+ when [false, true]
44
+ '-'
45
+ when [true, false]
46
+ '+'
47
+ else # [true, true]
48
+ '^'
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,322 @@
1
+ #! /usr/env/bin ruby
2
+ #--
3
+ # Copyright 2004 Austin Ziegler <diff-lcs@halostatue.ca>
4
+ # adapted from:
5
+ # Algorithm::Diff (Perl) by Ned Konz <perl@bike-nomad.com>
6
+ # Smalltalk by Mario I. Wolczko <mario@wolczko.com>
7
+ # implements McIlroy-Hunt diff algorithm
8
+ #
9
+ # This program is free software. It may be redistributed and/or modified under
10
+ # the terms of the GPL version 2 (or later), the Perl Artistic licence, or the
11
+ # Ruby licence.
12
+ #
13
+ # $Id: callbacks.rb,v 1.4 2004/09/14 18:51:26 austin Exp $
14
+ #++
15
+ # Contains definitions for all default callback objects.
16
+
17
+ require 'diff/lcs/change'
18
+
19
+ module Diff::LCS
20
+ # This callback object implements the default set of callback events, which
21
+ # only returns the event itself. Note that #finished_a and #finished_b are
22
+ # not implemented -- I haven't yet figured out where they would be useful.
23
+ #
24
+ # Note that this is intended to be called as is, e.g.,
25
+ #
26
+ # Diff::LCS.LCS(seq1, seq2, Diff::LCS::DefaultCallbacks)
27
+ class DefaultCallbacks
28
+ class << self
29
+ # Called when two items match.
30
+ def match(event)
31
+ event
32
+ end
33
+ # Called when the old value is discarded in favour of the new value.
34
+ def discard_a(event)
35
+ event
36
+ end
37
+ # Called when the new value is discarded in favour of the old value.
38
+ def discard_b(event)
39
+ event
40
+ end
41
+ # Called when both the old and new values have changed.
42
+ def change(event)
43
+ event
44
+ end
45
+
46
+ private :new
47
+ end
48
+ end
49
+
50
+ # An alias for DefaultCallbacks that is used in Diff::LCS#traverse_sequences.
51
+ #
52
+ # Diff::LCS.LCS(seq1, seq2, Diff::LCS::SequenceCallbacks)
53
+ SequenceCallbacks = DefaultCallbacks
54
+ # An alias for DefaultCallbacks that is used in Diff::LCS#traverse_balanced.
55
+ #
56
+ # Diff::LCS.LCS(seq1, seq2, Diff::LCS::BalancedCallbacks)
57
+ BalancedCallbacks = DefaultCallbacks
58
+ end
59
+
60
+ # This will produce a compound array of simple diff change objects. Each
61
+ # element in the #diffs array is a +hunk+ or +hunk+ array, where each
62
+ # element in each +hunk+ array is a single Change object representing the
63
+ # addition or removal of a single element from one of the two tested
64
+ # sequences. The +hunk+ provides the full context for the changes.
65
+ #
66
+ # diffs = Diff::LCS.diff(seq1, seq2)
67
+ # # This example shows a simplified array format.
68
+ # # [ [ [ '-', 0, 'a' ] ], # 1
69
+ # # [ [ '+', 2, 'd' ] ], # 2
70
+ # # [ [ '-', 4, 'h' ], # 3
71
+ # # [ '+', 4, 'f' ] ],
72
+ # # [ [ '+', 6, 'k' ] ], # 4
73
+ # # [ [ '-', 8, 'n' ], # 5
74
+ # # [ '-', 9, 'p' ],
75
+ # # [ '+', 9, 'r' ],
76
+ # # [ '+', 10, 's' ],
77
+ # # [ '+', 11, 't' ] ] ]
78
+ #
79
+ # There are five hunks here. The first hunk says that the +a+ at position 0
80
+ # of the first sequence should be deleted (<tt>'-'</tt>). The second hunk
81
+ # says that the +d+ at position 2 of the second sequence should be inserted
82
+ # (<tt>'+'</tt>). The third hunk says that the +h+ at position 4 of the
83
+ # first sequence should be removed and replaced with the +f+ from position 4
84
+ # of the second sequence. The other two hunks are described similarly.
85
+ #
86
+ # === Use
87
+ # This callback object must be initialised and is used by the Diff::LCS#diff
88
+ # method.
89
+ #
90
+ # cbo = Diff::LCS::DiffCallbacks.new
91
+ # Diff::LCS.LCS(seq1, seq2, cbo)
92
+ # cbo.finish
93
+ #
94
+ # Note that the call to #finish is absolutely necessary, or the last set of
95
+ # changes will not be visible. Alternatively, can be used as:
96
+ #
97
+ # cbo = Diff::LCS::DiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) }
98
+ #
99
+ # The necessary #finish call will be made.
100
+ #
101
+ # === Simplified Array Format
102
+ # The simplified array format used in the example above can be obtained
103
+ # with:
104
+ #
105
+ # require 'pp'
106
+ # pp diffs.map { |e| e.map { |f| f.to_a } }
107
+ class Diff::LCS::DiffCallbacks
108
+ # Returns the difference set collected during the diff process.
109
+ attr_reader :diffs
110
+
111
+ def initialize # :yields self:
112
+ @hunk = []
113
+ @diffs = []
114
+
115
+ if block_given?
116
+ begin
117
+ yield self
118
+ ensure
119
+ self.finish
120
+ end
121
+ end
122
+ end
123
+
124
+ # Finalizes the diff process. If an unprocessed hunk still exists, then it
125
+ # is appended to the diff list.
126
+ def finish
127
+ add_nonempty_hunk
128
+ end
129
+
130
+ def match(event)
131
+ add_nonempty_hunk
132
+ end
133
+
134
+ def discard_a(event)
135
+ @hunk << Diff::LCS::Change.new('-', event.old_position, event.old_element)
136
+ end
137
+
138
+ def discard_b(event)
139
+ @hunk << Diff::LCS::Change.new('+', event.new_position, event.new_element)
140
+ end
141
+
142
+ private
143
+ def add_nonempty_hunk
144
+ @diffs << @hunk unless @hunk.empty?
145
+ @hunk = []
146
+ end
147
+ end
148
+
149
+ # This will produce a compound array of contextual diff change objects. Each
150
+ # element in the #diffs array is a "hunk" array, where each element in each
151
+ # "hunk" array is a single change. Each change is a Diff::LCS::ContextChange
152
+ # that contains both the old index and new index values for the change. The
153
+ # "hunk" provides the full context for the changes. Both old and new objects
154
+ # will be presented for changed objects. +nil+ will be substituted for a
155
+ # discarded object.
156
+ #
157
+ # seq1 = %w(a b c e h j l m n p)
158
+ # seq2 = %w(b c d e f j k l m r s t)
159
+ #
160
+ # diffs = Diff::LCS.diff(seq1, seq2, Diff::LCS::ContextDiffCallbacks)
161
+ # # This example shows a simplified array format.
162
+ # # [ [ [ '-', [ 0, 'a' ], [ 0, nil ] ] ], # 1
163
+ # # [ [ '+', [ 3, nil ], [ 2, 'd' ] ] ], # 2
164
+ # # [ [ '-', [ 4, 'h' ], [ 4, nil ] ], # 3
165
+ # # [ '+', [ 5, nil ], [ 4, 'f' ] ] ],
166
+ # # [ [ '+', [ 6, nil ], [ 6, 'k' ] ] ], # 4
167
+ # # [ [ '-', [ 8, 'n' ], [ 9, nil ] ], # 5
168
+ # # [ '+', [ 9, nil ], [ 9, 'r' ] ],
169
+ # # [ '-', [ 9, 'p' ], [ 10, nil ] ],
170
+ # # [ '+', [ 10, nil ], [ 10, 's' ] ],
171
+ # # [ '+', [ 10, nil ], [ 11, 't' ] ] ] ]
172
+ #
173
+ # The five hunks shown are comprised of individual changes; if there is a
174
+ # related set of changes, they are still shown individually.
175
+ #
176
+ # This callback can also be used with Diff::LCS#sdiff, which will produce
177
+ # results like:
178
+ #
179
+ # diffs = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextCallbacks)
180
+ # # This example shows a simplified array format.
181
+ # # [ [ [ "-", [ 0, "a" ], [ 0, nil ] ] ], # 1
182
+ # # [ [ "+", [ 3, nil ], [ 2, "d" ] ] ], # 2
183
+ # # [ [ "!", [ 4, "h" ], [ 4, "f" ] ] ], # 3
184
+ # # [ [ "+", [ 6, nil ], [ 6, "k" ] ] ], # 4
185
+ # # [ [ "!", [ 8, "n" ], [ 9, "r" ] ], # 5
186
+ # # [ "!", [ 9, "p" ], [ 10, "s" ] ],
187
+ # # [ "+", [ 10, nil ], [ 11, "t" ] ] ] ]
188
+ #
189
+ # The five hunks are still present, but are significantly shorter in total
190
+ # presentation, because changed items are shown as changes ("!") instead of
191
+ # potentially "mismatched" pairs of additions and deletions.
192
+ #
193
+ # The result of this operation is similar to that of
194
+ # Diff::LCS::SDiffCallbacks. They may be compared as:
195
+ #
196
+ # s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" }
197
+ # c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten
198
+ #
199
+ # s == c # -> true
200
+ #
201
+ # === Use
202
+ # This callback object must be initialised and can be used by the
203
+ # Diff::LCS#diff or Diff::LCS#sdiff methods.
204
+ #
205
+ # cbo = Diff::LCS::ContextDiffCallbacks.new
206
+ # Diff::LCS.LCS(seq1, seq2, cbo)
207
+ # cbo.finish
208
+ #
209
+ # Note that the call to #finish is absolutely necessary, or the last set of
210
+ # changes will not be visible. Alternatively, can be used as:
211
+ #
212
+ # cbo = Diff::LCS::ContextDiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) }
213
+ #
214
+ # The necessary #finish call will be made.
215
+ #
216
+ # === Simplified Array Format
217
+ # The simplified array format used in the example above can be obtained
218
+ # with:
219
+ #
220
+ # require 'pp'
221
+ # pp diffs.map { |e| e.map { |f| f.to_a } }
222
+ class Diff::LCS::ContextDiffCallbacks < Diff::LCS::DiffCallbacks
223
+ def discard_a(event)
224
+ @hunk << Diff::LCS::ContextChange.simplify(event)
225
+ end
226
+
227
+ def discard_b(event)
228
+ @hunk << Diff::LCS::ContextChange.simplify(event)
229
+ end
230
+
231
+ def change(event)
232
+ @hunk << Diff::LCS::ContextChange.simplify(event)
233
+ end
234
+ end
235
+
236
+ # This will produce a simple array of diff change objects. Each element in
237
+ # the #diffs array is a single ContextChange. In the set of #diffs provided
238
+ # by SDiffCallbacks, both old and new objects will be presented for both
239
+ # changed <strong>and unchanged</strong> objects. +nil+ will be substituted
240
+ # for a discarded object.
241
+ #
242
+ # The diffset produced by this callback, when provided to Diff::LCS#sdiff,
243
+ # will compute and display the necessary components to show two sequences
244
+ # and their minimized differences side by side, just like the Unix utility
245
+ # +sdiff+.
246
+ #
247
+ # same same
248
+ # before | after
249
+ # old < -
250
+ # - > new
251
+ #
252
+ # seq1 = %w(a b c e h j l m n p)
253
+ # seq2 = %w(b c d e f j k l m r s t)
254
+ #
255
+ # diffs = Diff::LCS.sdiff(seq1, seq2)
256
+ # # This example shows a simplified array format.
257
+ # # [ [ "-", [ 0, "a"], [ 0, nil ] ],
258
+ # # [ "=", [ 1, "b"], [ 0, "b" ] ],
259
+ # # [ "=", [ 2, "c"], [ 1, "c" ] ],
260
+ # # [ "+", [ 3, nil], [ 2, "d" ] ],
261
+ # # [ "=", [ 3, "e"], [ 3, "e" ] ],
262
+ # # [ "!", [ 4, "h"], [ 4, "f" ] ],
263
+ # # [ "=", [ 5, "j"], [ 5, "j" ] ],
264
+ # # [ "+", [ 6, nil], [ 6, "k" ] ],
265
+ # # [ "=", [ 6, "l"], [ 7, "l" ] ],
266
+ # # [ "=", [ 7, "m"], [ 8, "m" ] ],
267
+ # # [ "!", [ 8, "n"], [ 9, "r" ] ],
268
+ # # [ "!", [ 9, "p"], [ 10, "s" ] ],
269
+ # # [ "+", [ 10, nil], [ 11, "t" ] ] ]
270
+ #
271
+ # The result of this operation is similar to that of
272
+ # Diff::LCS::ContextDiffCallbacks. They may be compared as:
273
+ #
274
+ # s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" }
275
+ # c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten
276
+ #
277
+ # s == c # -> true
278
+ #
279
+ # === Use
280
+ # This callback object must be initialised and is used by the Diff::LCS#sdiff
281
+ # method.
282
+ #
283
+ # cbo = Diff::LCS::SDiffCallbacks.new
284
+ # Diff::LCS.LCS(seq1, seq2, cbo)
285
+ #
286
+ # As with the other initialisable callback objects, Diff::LCS::SDiffCallbacks
287
+ # can be initialised with a block. As there is no "fininishing" to be done,
288
+ # this has no effect on the state of the object.
289
+ #
290
+ # cbo = Diff::LCS::SDiffCallbacks.new { |tcbo| Diff::LCS.LCS(seq1, seq2, tcbo) }
291
+ #
292
+ # === Simplified Array Format
293
+ # The simplified array format used in the example above can be obtained
294
+ # with:
295
+ #
296
+ # require 'pp'
297
+ # pp diffs.map { |e| e.to_a }
298
+ class Diff::LCS::SDiffCallbacks
299
+ # Returns the difference set collected during the diff process.
300
+ attr_reader :diffs
301
+
302
+ def initialize #:yields self:
303
+ @diffs = []
304
+ yield self if block_given?
305
+ end
306
+
307
+ def match(event)
308
+ @diffs << Diff::LCS::ContextChange.simplify(event)
309
+ end
310
+
311
+ def discard_a(event)
312
+ @diffs << Diff::LCS::ContextChange.simplify(event)
313
+ end
314
+
315
+ def discard_b(event)
316
+ @diffs << Diff::LCS::ContextChange.simplify(event)
317
+ end
318
+
319
+ def change(event)
320
+ @diffs << Diff::LCS::ContextChange.simplify(event)
321
+ end
322
+ end
@@ -0,0 +1,169 @@
1
+ #! /usr/env/bin ruby
2
+ #--
3
+ # Copyright 2004 Austin Ziegler <diff-lcs@halostatue.ca>
4
+ # adapted from:
5
+ # Algorithm::Diff (Perl) by Ned Konz <perl@bike-nomad.com>
6
+ # Smalltalk by Mario I. Wolczko <mario@wolczko.com>
7
+ # implements McIlroy-Hunt diff algorithm
8
+ #
9
+ # This program is free software. It may be redistributed and/or modified under
10
+ # the terms of the GPL version 2 (or later), the Perl Artistic licence, or the
11
+ # Ruby licence.
12
+ #
13
+ # $Id: change.rb,v 1.4 2004/08/08 20:33:09 austin Exp $
14
+ #++
15
+ # Provides Diff::LCS::Change and Diff::LCS::ContextChange.
16
+
17
+ # Centralises the change test code in Diff::LCS::Change and
18
+ # Diff::LCS::ContextChange, since it's the same for both classes.
19
+ module Diff::LCS::ChangeTypeTests
20
+ def deleting?
21
+ @action == '-'
22
+ end
23
+
24
+ def adding?
25
+ @action == '+'
26
+ end
27
+
28
+ def unchanged?
29
+ @action == '='
30
+ end
31
+
32
+ def changed?
33
+ @changed == '!'
34
+ end
35
+
36
+ def finished_a?
37
+ @changed == '>'
38
+ end
39
+
40
+ def finished_b?
41
+ @changed == '<'
42
+ end
43
+ end
44
+
45
+ # Represents a simplistic (non-contextual) change. Represents the removal or
46
+ # addition of an element from either the old or the new sequenced enumerable.
47
+ class Diff::LCS::Change
48
+ # Returns the action this Change represents. Can be '+' (#adding?), '-'
49
+ # (#deleting?), '=' (#unchanged?), # or '!' (#changed?). When created by
50
+ # Diff::LCS#diff or Diff::LCS#sdiff, it may also be '>' (#finished_a?) or
51
+ # '<' (#finished_b?).
52
+ attr_reader :action
53
+ attr_reader :position
54
+ attr_reader :element
55
+
56
+ include Comparable
57
+ def ==(other)
58
+ (self.action == other.action) and
59
+ (self.position == other.position) and
60
+ (self.element == other.element)
61
+ end
62
+
63
+ def <=>(other)
64
+ r = self.action <=> other.action
65
+ r = self.position <=> other.position if r.zero?
66
+ r = self.element <=> other.element if r.zero?
67
+ r
68
+ end
69
+
70
+ def initialize(action, position, element)
71
+ @action = action
72
+ @position = position
73
+ @element = element
74
+ end
75
+
76
+ # Creates a Change from an array produced by Change#to_a.
77
+ def to_a
78
+ [@action, @position, @element]
79
+ end
80
+
81
+ def self.from_a(arr)
82
+ Diff::LCS::Change.new(arr[0], arr[1], arr[2])
83
+ end
84
+
85
+ include Diff::LCS::ChangeTypeTests
86
+ end
87
+
88
+ # Represents a contextual change. Contains the position and values of the
89
+ # elements in the old and the new sequenced enumerables as well as the action
90
+ # taken.
91
+ class Diff::LCS::ContextChange
92
+ # Returns the action this Change represents. Can be '+' (#adding?), '-'
93
+ # (#deleting?), '=' (#unchanged?), # or '!' (#changed?). When
94
+ # created by Diff::LCS#diff or Diff::LCS#sdiff, it may also be '>'
95
+ # (#finished_a?) or '<' (#finished_b?).
96
+ attr_reader :action
97
+ attr_reader :old_position
98
+ attr_reader :old_element
99
+ attr_reader :new_position
100
+ attr_reader :new_element
101
+
102
+ include Comparable
103
+
104
+ def ==(other)
105
+ (@action == other.action) and
106
+ (@old_position == other.old_position) and
107
+ (@new_position == other.new_position) and
108
+ (@old_element == other.old_element) and
109
+ (@new_element == other.new_element)
110
+ end
111
+
112
+ def inspect(*args)
113
+ %Q(#<#{self.class.name}:#{__id__} @action=#{action} positions=#{old_position},#{new_position} elements=#{old_element.inspect},#{new_element.inspect}>)
114
+ end
115
+
116
+ def <=>(other)
117
+ r = @action <=> other.action
118
+ r = @old_position <=> other.old_position if r.zero?
119
+ r = @new_position <=> other.new_position if r.zero?
120
+ r = @old_element <=> other.old_element if r.zero?
121
+ r = @new_element <=> other.new_element if r.zero?
122
+ r
123
+ end
124
+
125
+ def initialize(action, old_position, old_element, new_position, new_element)
126
+ @action = action
127
+ @old_position = old_position
128
+ @old_element = old_element
129
+ @new_position = new_position
130
+ @new_element = new_element
131
+ end
132
+
133
+ def to_a
134
+ [@action, [@old_position, @old_element], [@new_position, @new_element]]
135
+ end
136
+
137
+ # Creates a ContextChange from an array produced by ContextChange#to_a.
138
+ def self.from_a(arr)
139
+ if arr.size == 5
140
+ Diff::LCS::ContextChange.new(arr[0], arr[1], arr[2], arr[3], arr[4])
141
+ else
142
+ Diff::LCS::ContextChange.new(arr[0], arr[1][0], arr[1][1], arr[2][0],
143
+ arr[2][1])
144
+ end
145
+ end
146
+
147
+ # Simplifies a context change for use in some diff callbacks. '<' actions
148
+ # are converted to '-' and '>' actions are converted to '+'.
149
+ def self.simplify(event)
150
+ ea = event.to_a
151
+
152
+ case ea[0]
153
+ when '-'
154
+ ea[2][1] = nil
155
+ when '<'
156
+ ea[0] = '-'
157
+ ea[2][1] = nil
158
+ when '+'
159
+ ea[1][1] = nil
160
+ when '>'
161
+ ea[0] = '+'
162
+ ea[1][1] = nil
163
+ end
164
+
165
+ Diff::LCS::ContextChange.from_a(ea)
166
+ end
167
+
168
+ include Diff::LCS::ChangeTypeTests
169
+ end