diff-lcs 1.3 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/Contributing.md +1 -0
- data/History.md +196 -153
- data/Manifest.txt +8 -0
- data/README.rdoc +9 -10
- data/Rakefile +4 -21
- data/autotest/discover.rb +3 -1
- data/bin/htmldiff +7 -4
- data/bin/ldiff +4 -1
- data/lib/diff-lcs.rb +1 -1
- data/lib/diff/lcs.rb +177 -170
- data/lib/diff/lcs/array.rb +1 -1
- data/lib/diff/lcs/backports.rb +9 -0
- data/lib/diff/lcs/block.rb +2 -2
- data/lib/diff/lcs/callbacks.rb +15 -12
- data/lib/diff/lcs/change.rb +30 -37
- data/lib/diff/lcs/htmldiff.rb +17 -16
- data/lib/diff/lcs/hunk.rb +67 -52
- data/lib/diff/lcs/internals.rb +36 -39
- data/lib/diff/lcs/ldiff.rb +33 -25
- data/lib/diff/lcs/string.rb +1 -1
- data/spec/change_spec.rb +31 -7
- data/spec/diff_spec.rb +16 -12
- data/spec/fixtures/aX +1 -0
- data/spec/fixtures/bXaX +1 -0
- data/spec/fixtures/ldiff/output.diff +4 -0
- data/spec/fixtures/ldiff/output.diff-c +7 -0
- data/spec/fixtures/ldiff/output.diff-e +3 -0
- data/spec/fixtures/ldiff/output.diff-f +3 -0
- data/spec/fixtures/ldiff/output.diff-u +5 -0
- data/spec/hunk_spec.rb +20 -20
- data/spec/issues_spec.rb +59 -10
- data/spec/lcs_spec.rb +10 -10
- data/spec/ldiff_spec.rb +62 -29
- data/spec/patch_spec.rb +93 -99
- data/spec/sdiff_spec.rb +89 -89
- data/spec/spec_helper.rb +115 -63
- data/spec/traverse_balanced_spec.rb +173 -173
- data/spec/traverse_sequences_spec.rb +28 -28
- metadata +30 -30
data/README.rdoc
CHANGED
@@ -4,8 +4,7 @@ home :: https://github.com/halostatue/diff-lcs
|
|
4
4
|
code :: https://github.com/halostatue/diff-lcs
|
5
5
|
bugs :: https://github.com/halostatue/diff-lcs/issues
|
6
6
|
rdoc :: http://rubydoc.info/github/halostatue/diff-lcs
|
7
|
-
continuous integration :: {<img src="https://
|
8
|
-
test coverage :: {<img src="https://coveralls.io/repos/halostatue/diff-lcs/badge.svg" alt="Coverage Status" />}[https://coveralls.io/r/halostatue/diff-lcs]
|
7
|
+
continuous integration :: {<img src="https://github.com/halostatue/diff-lcs/workflows/CI/badge.svg" />}[https://github.com/halostatue/diff-lcs/actions]
|
9
8
|
|
10
9
|
== Description
|
11
10
|
|
@@ -13,10 +12,14 @@ Diff::LCS computes the difference between two Enumerable sequences using the
|
|
13
12
|
McIlroy-Hunt longest common subsequence (LCS) algorithm. It includes utilities
|
14
13
|
to create a simple HTML diff output format and a standard diff-like tool.
|
15
14
|
|
16
|
-
This is release 1.
|
17
|
-
to
|
18
|
-
|
19
|
-
|
15
|
+
This is release 1.4, providing a simple extension that allows for
|
16
|
+
Diff::LCS::Change objects to be treated implicitly as arrays. Ruby versions
|
17
|
+
below 2.5 are soft-deprecated.
|
18
|
+
|
19
|
+
This means that older versions are no longer part of the CI test suite. If any
|
20
|
+
changes have been introduced that break those versions, bug reports and patches
|
21
|
+
will be accepted, but it will be up to the reporter to verify any fixes prior
|
22
|
+
to release. A future release will completely break compatibility.
|
20
23
|
|
21
24
|
== Synopsis
|
22
25
|
|
@@ -78,7 +81,3 @@ The algorithm is described in A Fast Algorithm for Computing Longest Common
|
|
78
81
|
Subsequences</em>, CACM, vol.20, no.5, pp.350-353, May 1977, with a few minor
|
79
82
|
improvements to improve the speed. A simplified description of the algorithm,
|
80
83
|
originally written for the Perl version, was written by Mark-Jason Dominus.
|
81
|
-
|
82
|
-
:include: Contributing.rdoc
|
83
|
-
|
84
|
-
:include: License.rdoc
|
data/Rakefile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'rspec'
|
@@ -11,47 +11,30 @@ Hoe.plugin :gemspec2
|
|
11
11
|
Hoe.plugin :git
|
12
12
|
Hoe.plugin :travis
|
13
13
|
|
14
|
-
|
14
|
+
_spec = Hoe.spec 'diff-lcs' do
|
15
15
|
developer('Austin Ziegler', 'halostatue@gmail.com')
|
16
16
|
|
17
17
|
require_ruby_version '>= 1.8'
|
18
18
|
|
19
19
|
self.history_file = 'History.md'
|
20
20
|
self.readme_file = 'README.rdoc'
|
21
|
-
self.licenses = [
|
21
|
+
self.licenses = ['MIT', 'Artistic-2.0', 'GPL-2.0+']
|
22
22
|
|
23
23
|
extra_dev_deps << ['hoe-doofus', '~> 1.0']
|
24
24
|
extra_dev_deps << ['hoe-gemspec2', '~> 1.1']
|
25
25
|
extra_dev_deps << ['hoe-git', '~> 1.6']
|
26
26
|
extra_dev_deps << ['hoe-rubygems', '~> 1.0']
|
27
|
-
extra_dev_deps << ['hoe-travis', '~> 1.2']
|
28
27
|
extra_dev_deps << ['rspec', '>= 2.0', '< 4']
|
29
|
-
extra_dev_deps << ['rake', '>= 10.0', '<
|
28
|
+
extra_dev_deps << ['rake', '>= 10.0', '< 14']
|
30
29
|
extra_dev_deps << ['rdoc', '>= 0']
|
31
30
|
end
|
32
31
|
|
33
|
-
unless Rake::Task.task_defined? :test
|
34
|
-
task :test => :spec
|
35
|
-
Rake::Task['travis'].prerequisites.replace(%w(spec))
|
36
|
-
end
|
37
|
-
|
38
32
|
if RUBY_VERSION >= '2.0' && RUBY_ENGINE == 'ruby'
|
39
33
|
namespace :spec do
|
40
|
-
task :coveralls do
|
41
|
-
if ENV['CI'] or ENV['TRAVIS']
|
42
|
-
ENV['COVERALLS'] = 'yes'
|
43
|
-
Rake::Task['spec'].execute
|
44
|
-
else
|
45
|
-
Rake::Task['spec:coverage'].execute
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
34
|
desc "Runs test coverage. Only works Ruby 2.0+ and assumes 'simplecov' is installed."
|
50
35
|
task :coverage do
|
51
36
|
ENV['COVERAGE'] = 'yes'
|
52
37
|
Rake::Task['spec'].execute
|
53
38
|
end
|
54
39
|
end
|
55
|
-
|
56
|
-
# Rake::Task['travis'].prerequisites.replace(%w(spec:coveralls))
|
57
40
|
end
|
data/autotest/discover.rb
CHANGED
data/bin/htmldiff
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#!ruby -w
|
1
|
+
#! /usr/bin/env ruby -w
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'diff/lcs'
|
4
5
|
require 'diff/lcs/htmldiff'
|
@@ -10,8 +11,8 @@ rescue LoadError
|
|
10
11
|
end
|
11
12
|
|
12
13
|
if ARGV.size < 2 or ARGV.size > 3
|
13
|
-
|
14
|
-
|
14
|
+
warn "usage: #{File.basename($0)} old new [output.html]"
|
15
|
+
warn " #{File.basename($0)} old new > output.html"
|
15
16
|
exit 127
|
16
17
|
end
|
17
18
|
|
@@ -23,10 +24,12 @@ options = { :title => "diff #{ARGV[0]} #{ARGV[1]}" }
|
|
23
24
|
htmldiff = Diff::LCS::HTMLDiff.new(left, right, options)
|
24
25
|
|
25
26
|
if ARGV[2]
|
26
|
-
File.open(ARGV[2],
|
27
|
+
File.open(ARGV[2], 'w') do |f|
|
27
28
|
htmldiff.options[:output] = f
|
28
29
|
htmldiff.run
|
29
30
|
end
|
30
31
|
else
|
31
32
|
htmldiff.run
|
32
33
|
end
|
34
|
+
|
35
|
+
# vim: ft=ruby
|
data/bin/ldiff
CHANGED
data/lib/diff-lcs.rb
CHANGED
data/lib/diff/lcs.rb
CHANGED
@@ -1,24 +1,24 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Diff; end unless defined? Diff # rubocop:disable Style/Documentation
|
2
4
|
|
3
|
-
module Diff; end unless defined? Diff
|
4
5
|
# == How Diff Works (by Mark-Jason Dominus)
|
5
6
|
#
|
6
|
-
# I once read an article written by the authors of +diff+; they said that
|
7
|
-
#
|
8
|
-
# one.
|
7
|
+
# I once read an article written by the authors of +diff+; they said that they
|
8
|
+
# hard worked very hard on the algorithm until they found the right one.
|
9
9
|
#
|
10
|
-
# I think what they ended up using (and I hope someone will correct me,
|
11
|
-
#
|
12
|
-
#
|
10
|
+
# I think what they ended up using (and I hope someone will correct me, because
|
11
|
+
# I am not very confident about this) was the `longest common subsequence'
|
12
|
+
# method. In the LCS problem, you have two sequences of items:
|
13
13
|
#
|
14
14
|
# a b c d f g h j q z
|
15
15
|
# a b c d e f g i j k r x y z
|
16
16
|
#
|
17
17
|
# and you want to find the longest sequence of items that is present in both
|
18
18
|
# original sequences in the same order. That is, you want to find a new
|
19
|
-
# sequence *S* which can be obtained from the first sequence by deleting
|
20
|
-
#
|
21
|
-
#
|
19
|
+
# sequence *S* which can be obtained from the first sequence by deleting some
|
20
|
+
# items, and from the second sequence by deleting other items. You also want
|
21
|
+
# *S* to be as long as possible. In this case *S* is:
|
22
22
|
#
|
23
23
|
# a b c d f g j z
|
24
24
|
#
|
@@ -30,9 +30,9 @@ module Diff; end unless defined? Diff
|
|
30
30
|
# This module solves the LCS problem. It also includes a canned function to
|
31
31
|
# generate +diff+-like output.
|
32
32
|
#
|
33
|
-
# It might seem from the example above that the LCS of two sequences is
|
34
|
-
#
|
35
|
-
#
|
33
|
+
# It might seem from the example above that the LCS of two sequences is always
|
34
|
+
# pretty obvious, but that's not always the case, especially when the two
|
35
|
+
# sequences have many repeated elements. For example, consider
|
36
36
|
#
|
37
37
|
# a x b y c z p d q
|
38
38
|
# a b c a x b y c z
|
@@ -43,29 +43,28 @@ module Diff; end unless defined? Diff
|
|
43
43
|
# a x b y c z p d q
|
44
44
|
# a b c a b y c z
|
45
45
|
#
|
46
|
-
# This finds the common subsequence +a b c z+. But actually, the LCS is +a x
|
47
|
-
#
|
46
|
+
# This finds the common subsequence +a b c z+. But actually, the LCS is +a x b
|
47
|
+
# y c z+:
|
48
48
|
#
|
49
49
|
# a x b y c z p d q
|
50
50
|
# a b c a x b y c z
|
51
51
|
module Diff::LCS
|
52
|
-
VERSION = '1.
|
52
|
+
VERSION = '1.4.2'
|
53
53
|
end
|
54
54
|
|
55
55
|
require 'diff/lcs/callbacks'
|
56
56
|
require 'diff/lcs/internals'
|
57
57
|
|
58
|
-
module Diff::LCS
|
58
|
+
module Diff::LCS # rubocop:disable Style/Documentation
|
59
59
|
# Returns an Array containing the longest common subsequence(s) between
|
60
|
-
# +self+ and +other+. See Diff::LCS#
|
60
|
+
# +self+ and +other+. See Diff::LCS#lcs.
|
61
61
|
#
|
62
62
|
# lcs = seq1.lcs(seq2)
|
63
63
|
def lcs(other, &block) #:yields self[i] if there are matched subsequences:
|
64
64
|
Diff::LCS.lcs(self, other, &block)
|
65
65
|
end
|
66
66
|
|
67
|
-
# Returns the difference set between +self+ and +other+. See
|
68
|
-
# Diff::LCS#diff.
|
67
|
+
# Returns the difference set between +self+ and +other+. See Diff::LCS#diff.
|
69
68
|
def diff(other, callbacks = nil, &block)
|
70
69
|
Diff::LCS.diff(self, other, callbacks, &block)
|
71
70
|
end
|
@@ -79,29 +78,27 @@ module Diff::LCS
|
|
79
78
|
# Traverses the discovered longest common subsequences between +self+ and
|
80
79
|
# +other+. See Diff::LCS#traverse_sequences.
|
81
80
|
def traverse_sequences(other, callbacks = nil, &block)
|
82
|
-
traverse_sequences(self, other, callbacks ||
|
83
|
-
Diff::LCS.YieldingCallbacks, &block)
|
81
|
+
traverse_sequences(self, other, callbacks || Diff::LCS::SequenceCallbacks, &block)
|
84
82
|
end
|
85
83
|
|
86
84
|
# Traverses the discovered longest common subsequences between +self+ and
|
87
85
|
# +other+ using the alternate, balanced algorithm. See
|
88
86
|
# Diff::LCS#traverse_balanced.
|
89
87
|
def traverse_balanced(other, callbacks = nil, &block)
|
90
|
-
traverse_balanced(self, other, callbacks ||
|
91
|
-
Diff::LCS.YieldingCallbacks, &block)
|
88
|
+
traverse_balanced(self, other, callbacks || Diff::LCS::BalancedCallbacks, &block)
|
92
89
|
end
|
93
90
|
|
94
|
-
# Attempts to patch +self+ with the provided +patchset+. A new sequence
|
95
|
-
#
|
96
|
-
#
|
91
|
+
# Attempts to patch +self+ with the provided +patchset+. A new sequence based
|
92
|
+
# on +self+ and the +patchset+ will be created. See Diff::LCS#patch. Attempts
|
93
|
+
# to autodiscover the direction of the patch.
|
97
94
|
def patch(patchset)
|
98
95
|
Diff::LCS.patch(self, patchset)
|
99
96
|
end
|
100
|
-
|
97
|
+
alias unpatch patch
|
101
98
|
|
102
|
-
# Attempts to patch +self+ with the provided +patchset+. A new sequence
|
103
|
-
#
|
104
|
-
#
|
99
|
+
# Attempts to patch +self+ with the provided +patchset+. A new sequence based
|
100
|
+
# on +self+ and the +patchset+ will be created. See Diff::LCS#patch. Does no
|
101
|
+
# patch direction autodiscovery.
|
105
102
|
def patch!(patchset)
|
106
103
|
Diff::LCS.patch!(self, patchset)
|
107
104
|
end
|
@@ -114,8 +111,8 @@ module Diff::LCS
|
|
114
111
|
end
|
115
112
|
|
116
113
|
# Attempts to patch +self+ with the provided +patchset+, using #patch!. If
|
117
|
-
# the sequence this is used on supports #replace, the value of +self+ will
|
118
|
-
#
|
114
|
+
# the sequence this is used on supports #replace, the value of +self+ will be
|
115
|
+
# replaced. See Diff::LCS#patch. Does no patch direction autodiscovery.
|
119
116
|
def patch_me(patchset)
|
120
117
|
if respond_to? :replace
|
121
118
|
replace(patch!(patchset))
|
@@ -124,10 +121,9 @@ module Diff::LCS
|
|
124
121
|
end
|
125
122
|
end
|
126
123
|
|
127
|
-
# Attempts to unpatch +self+ with the provided +patchset+, using
|
128
|
-
#
|
129
|
-
#
|
130
|
-
# autodiscovery.
|
124
|
+
# Attempts to unpatch +self+ with the provided +patchset+, using #unpatch!.
|
125
|
+
# If the sequence this is used on supports #replace, the value of +self+ will
|
126
|
+
# be replaced. See Diff::LCS#unpatch. Does no patch direction autodiscovery.
|
131
127
|
def unpatch_me(patchset)
|
132
128
|
if respond_to? :replace
|
133
129
|
replace(unpatch!(patchset))
|
@@ -142,29 +138,28 @@ class << Diff::LCS
|
|
142
138
|
matches = Diff::LCS::Internals.lcs(seq1, seq2)
|
143
139
|
ret = []
|
144
140
|
string = seq1.kind_of? String
|
145
|
-
matches.each_with_index do |
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
141
|
+
matches.each_with_index do |_e, i|
|
142
|
+
next if matches[i].nil?
|
143
|
+
|
144
|
+
v = string ? seq1[i, 1] : seq1[i]
|
145
|
+
v = block[v] if block
|
146
|
+
ret << v
|
151
147
|
end
|
152
148
|
ret
|
153
149
|
end
|
154
|
-
|
150
|
+
alias LCS lcs
|
155
151
|
|
156
152
|
# #diff computes the smallest set of additions and deletions necessary to
|
157
|
-
# turn the first sequence into the second, and returns a description of
|
158
|
-
#
|
153
|
+
# turn the first sequence into the second, and returns a description of these
|
154
|
+
# changes.
|
159
155
|
#
|
160
156
|
# See Diff::LCS::DiffCallbacks for the default behaviour. An alternate
|
161
157
|
# behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If a
|
162
158
|
# Class argument is provided for +callbacks+, #diff will attempt to
|
163
|
-
# initialise it. If the +callbacks+ object (possibly initialised) responds
|
164
|
-
#
|
159
|
+
# initialise it. If the +callbacks+ object (possibly initialised) responds to
|
160
|
+
# #finish, it will be called.
|
165
161
|
def diff(seq1, seq2, callbacks = nil, &block) # :yields diff changes:
|
166
|
-
diff_traversal(:diff, seq1, seq2, callbacks || Diff::LCS::DiffCallbacks,
|
167
|
-
&block)
|
162
|
+
diff_traversal(:diff, seq1, seq2, callbacks || Diff::LCS::DiffCallbacks, &block)
|
168
163
|
end
|
169
164
|
|
170
165
|
# #sdiff computes all necessary components to show two sequences and their
|
@@ -179,18 +174,31 @@ class << Diff::LCS
|
|
179
174
|
# See Diff::LCS::SDiffCallbacks for the default behaviour. An alternate
|
180
175
|
# behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If a
|
181
176
|
# Class argument is provided for +callbacks+, #diff will attempt to
|
182
|
-
# initialise it. If the +callbacks+ object (possibly initialised) responds
|
183
|
-
#
|
177
|
+
# initialise it. If the +callbacks+ object (possibly initialised) responds to
|
178
|
+
# #finish, it will be called.
|
179
|
+
#
|
180
|
+
# Each element of a returned array is a Diff::LCS::ContextChange object,
|
181
|
+
# which can be implicitly converted to an array.
|
182
|
+
#
|
183
|
+
# Diff::LCS.sdiff(a, b).each do |action, (old_pos, old_element), (new_pos, new_element)|
|
184
|
+
# case action
|
185
|
+
# when '!'
|
186
|
+
# # replace
|
187
|
+
# when '-'
|
188
|
+
# # delete
|
189
|
+
# when '+'
|
190
|
+
# # insert
|
191
|
+
# end
|
192
|
+
# end
|
184
193
|
def sdiff(seq1, seq2, callbacks = nil, &block) #:yields diff changes:
|
185
|
-
diff_traversal(:sdiff, seq1, seq2, callbacks || Diff::LCS::SDiffCallbacks,
|
186
|
-
&block)
|
194
|
+
diff_traversal(:sdiff, seq1, seq2, callbacks || Diff::LCS::SDiffCallbacks, &block)
|
187
195
|
end
|
188
196
|
|
189
|
-
# #traverse_sequences is the most general facility provided by this
|
190
|
-
#
|
197
|
+
# #traverse_sequences is the most general facility provided by this module;
|
198
|
+
# #diff and #lcs are implemented as calls to it.
|
191
199
|
#
|
192
|
-
# The arguments to #traverse_sequences are the two sequences to traverse,
|
193
|
-
#
|
200
|
+
# The arguments to #traverse_sequences are the two sequences to traverse, and
|
201
|
+
# a callback object, like this:
|
194
202
|
#
|
195
203
|
# traverse_sequences(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
|
196
204
|
#
|
@@ -218,56 +226,55 @@ class << Diff::LCS
|
|
218
226
|
# ^
|
219
227
|
# b---+
|
220
228
|
#
|
221
|
-
# If there are two arrows (+a+ and +b+) pointing to elements of sequences
|
222
|
-
#
|
223
|
-
#
|
224
|
-
#
|
225
|
-
#
|
226
|
-
#
|
227
|
-
#
|
228
|
-
#
|
229
|
-
#
|
230
|
-
#
|
231
|
-
#
|
232
|
-
#
|
233
|
-
#
|
234
|
-
#
|
235
|
-
# that
|
236
|
-
#
|
237
|
-
#
|
238
|
-
#
|
239
|
-
#
|
240
|
-
#
|
241
|
-
#
|
242
|
-
#
|
243
|
-
#
|
244
|
-
#
|
245
|
-
#
|
246
|
-
# discarded by #traverse_sequences.
|
229
|
+
# If there are two arrows (+a+ and +b+) pointing to elements of sequences +A+
|
230
|
+
# and +B+, the arrows will initially point to the first elements of their
|
231
|
+
# respective sequences. #traverse_sequences will advance the arrows through
|
232
|
+
# the sequences one element at a time, calling a method on the user-specified
|
233
|
+
# callback object before each advance. It will advance the arrows in such a
|
234
|
+
# way that if there are elements <tt>A[i]</tt> and <tt>B[j]</tt> which are
|
235
|
+
# both equal and part of the longest common subsequence, there will be some
|
236
|
+
# moment during the execution of #traverse_sequences when arrow +a+ is
|
237
|
+
# pointing to <tt>A[i]</tt> and arrow +b+ is pointing to <tt>B[j]</tt>. When
|
238
|
+
# this happens, #traverse_sequences will call <tt>callbacks#match</tt> and
|
239
|
+
# then it will advance both arrows.
|
240
|
+
#
|
241
|
+
# Otherwise, one of the arrows is pointing to an element of its sequence that
|
242
|
+
# is not part of the longest common subsequence. #traverse_sequences will
|
243
|
+
# advance that arrow and will call <tt>callbacks#discard_a</tt> or
|
244
|
+
# <tt>callbacks#discard_b</tt>, depending on which arrow it advanced. If both
|
245
|
+
# arrows point to elements that are not part of the longest common
|
246
|
+
# subsequence, then #traverse_sequences will advance one of them and call the
|
247
|
+
# appropriate callback, but it is not specified which it will call.
|
248
|
+
#
|
249
|
+
# The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>, and
|
250
|
+
# <tt>callbacks#discard_b</tt> are invoked with an event comprising the
|
251
|
+
# action ("=", "+", or "-", respectively), the indicies +i+ and +j+, and the
|
252
|
+
# elements <tt>A[i]</tt> and <tt>B[j]</tt>. Return values are discarded by
|
253
|
+
# #traverse_sequences.
|
247
254
|
#
|
248
255
|
# === End of Sequences
|
249
256
|
#
|
250
257
|
# If arrow +a+ reaches the end of its sequence before arrow +b+ does,
|
251
|
-
# #traverse_sequence will try to call <tt>callbacks#finished_a</tt> with
|
252
|
-
#
|
253
|
-
#
|
254
|
-
#
|
255
|
-
#
|
256
|
-
#
|
258
|
+
# #traverse_sequence will try to call <tt>callbacks#finished_a</tt> with the
|
259
|
+
# last index and element of +A+ (<tt>A[-1]</tt>) and the current index and
|
260
|
+
# element of +B+ (<tt>B[j]</tt>). If <tt>callbacks#finished_a</tt> does not
|
261
|
+
# exist, then <tt>callbacks#discard_b</tt> will be called on each element of
|
262
|
+
# +B+ until the end of the sequence is reached (the call will be done with
|
263
|
+
# <tt>A[-1]</tt> and <tt>B[j]</tt> for each element).
|
257
264
|
#
|
258
265
|
# If +b+ reaches the end of +B+ before +a+ reaches the end of +A+,
|
259
266
|
# <tt>callbacks#finished_b</tt> will be called with the current index and
|
260
267
|
# element of +A+ (<tt>A[i]</tt>) and the last index and element of +B+
|
261
|
-
# (<tt>A[-1]</tt>). Again, if <tt>callbacks#finished_b</tt> does not exist
|
262
|
-
#
|
263
|
-
#
|
264
|
-
#
|
268
|
+
# (<tt>A[-1]</tt>). Again, if <tt>callbacks#finished_b</tt> does not exist on
|
269
|
+
# the callback object, then <tt>callbacks#discard_a</tt> will be called on
|
270
|
+
# each element of +A+ until the end of the sequence is reached (<tt>A[i]</tt>
|
271
|
+
# and <tt>B[-1]</tt>).
|
265
272
|
#
|
266
273
|
# There is a chance that one additional <tt>callbacks#discard_a</tt> or
|
267
|
-
# <tt>callbacks#discard_b</tt> will be called after the end of the
|
268
|
-
#
|
269
|
-
#
|
270
|
-
def traverse_sequences(seq1, seq2, callbacks = Diff::LCS::SequenceCallbacks
|
274
|
+
# <tt>callbacks#discard_b</tt> will be called after the end of the sequence
|
275
|
+
# is reached, if +a+ has not yet reached the end of +A+ or +b+ has not yet
|
276
|
+
# reached the end of +B+.
|
277
|
+
def traverse_sequences(seq1, seq2, callbacks = Diff::LCS::SequenceCallbacks) #:yields change events:
|
271
278
|
callbacks ||= Diff::LCS::SequenceCallbacks
|
272
279
|
matches = Diff::LCS::Internals.lcs(seq1, seq2)
|
273
280
|
|
@@ -293,6 +300,7 @@ class << Diff::LCS
|
|
293
300
|
else
|
294
301
|
loop do
|
295
302
|
break unless bj < b_line
|
303
|
+
|
296
304
|
bx = string ? seq2[bj, 1] : seq2[bj]
|
297
305
|
event = Diff::LCS::ContextChange.new('+', i, ax, bj, bx)
|
298
306
|
event = yield event if block_given?
|
@@ -309,12 +317,12 @@ class << Diff::LCS
|
|
309
317
|
end
|
310
318
|
ai += 1
|
311
319
|
|
312
|
-
# The last entry (if any) processed was a match. +ai+ and +bj+ point
|
313
|
-
#
|
320
|
+
# The last entry (if any) processed was a match. +ai+ and +bj+ point just
|
321
|
+
# past the last matching lines in their sequences.
|
314
322
|
while (ai < a_size) or (bj < b_size)
|
315
323
|
# last A?
|
316
324
|
if ai == a_size and bj < b_size
|
317
|
-
if callbacks.respond_to?(:finished_a) and
|
325
|
+
if callbacks.respond_to?(:finished_a) and !run_finished_a
|
318
326
|
ax = string ? seq1[-1, 1] : seq1[-1]
|
319
327
|
bx = string ? seq2[bj, 1] : seq2[bj]
|
320
328
|
event = Diff::LCS::ContextChange.new('>', (a_size - 1), ax, bj, bx)
|
@@ -336,7 +344,7 @@ class << Diff::LCS
|
|
336
344
|
|
337
345
|
# last B?
|
338
346
|
if bj == b_size and ai < a_size
|
339
|
-
if callbacks.respond_to?(:finished_b) and
|
347
|
+
if callbacks.respond_to?(:finished_b) and !run_finished_b
|
340
348
|
ax = string ? seq1[ai, 1] : seq1[ai]
|
341
349
|
bx = string ? seq2[-1, 1] : seq2[-1]
|
342
350
|
event = Diff::LCS::ContextChange.new('<', ai, ax, (b_size - 1), bx)
|
@@ -365,25 +373,25 @@ class << Diff::LCS
|
|
365
373
|
ai += 1
|
366
374
|
end
|
367
375
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
+
next unless bj < b_size
|
377
|
+
|
378
|
+
ax = string ? seq1[ai, 1] : seq1[ai]
|
379
|
+
bx = string ? seq2[bj, 1] : seq2[bj]
|
380
|
+
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
|
381
|
+
event = yield event if block_given?
|
382
|
+
callbacks.discard_b(event)
|
383
|
+
bj += 1
|
376
384
|
end
|
377
385
|
end
|
378
386
|
|
379
387
|
# #traverse_balanced is an alternative to #traverse_sequences. It uses a
|
380
|
-
# different algorithm to iterate through the entries in the computed
|
381
|
-
#
|
382
|
-
#
|
388
|
+
# different algorithm to iterate through the entries in the computed longest
|
389
|
+
# common subsequence. Instead of viewing the changes as insertions or
|
390
|
+
# deletions from one of the sequences, #traverse_balanced will report
|
383
391
|
# <em>changes</em> between the sequences.
|
384
392
|
#
|
385
|
-
# The arguments to #traverse_balanced are the two sequences to traverse
|
386
|
-
#
|
393
|
+
# The arguments to #traverse_balanced are the two sequences to traverse and a
|
394
|
+
# callback object, like this:
|
387
395
|
#
|
388
396
|
# traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
|
389
397
|
#
|
@@ -419,24 +427,23 @@ class << Diff::LCS
|
|
419
427
|
#
|
420
428
|
# === Matches
|
421
429
|
#
|
422
|
-
# If there are two arrows (+a+ and +b+) pointing to elements of sequences
|
423
|
-
#
|
424
|
-
#
|
425
|
-
#
|
426
|
-
#
|
427
|
-
#
|
428
|
-
#
|
429
|
-
#
|
430
|
-
#
|
431
|
-
#
|
432
|
-
#
|
433
|
-
# advance both arrows.
|
430
|
+
# If there are two arrows (+a+ and +b+) pointing to elements of sequences +A+
|
431
|
+
# and +B+, the arrows will initially point to the first elements of their
|
432
|
+
# respective sequences. #traverse_sequences will advance the arrows through
|
433
|
+
# the sequences one element at a time, calling a method on the user-specified
|
434
|
+
# callback object before each advance. It will advance the arrows in such a
|
435
|
+
# way that if there are elements <tt>A[i]</tt> and <tt>B[j]</tt> which are
|
436
|
+
# both equal and part of the longest common subsequence, there will be some
|
437
|
+
# moment during the execution of #traverse_sequences when arrow +a+ is
|
438
|
+
# pointing to <tt>A[i]</tt> and arrow +b+ is pointing to <tt>B[j]</tt>. When
|
439
|
+
# this happens, #traverse_sequences will call <tt>callbacks#match</tt> and
|
440
|
+
# then it will advance both arrows.
|
434
441
|
#
|
435
442
|
# === Discards
|
436
443
|
#
|
437
|
-
# Otherwise, one of the arrows is pointing to an element of its sequence
|
438
|
-
#
|
439
|
-
#
|
444
|
+
# Otherwise, one of the arrows is pointing to an element of its sequence that
|
445
|
+
# is not part of the longest common subsequence. #traverse_sequences will
|
446
|
+
# advance that arrow and will call <tt>callbacks#discard_a</tt> or
|
440
447
|
# <tt>callbacks#discard_b</tt>, depending on which arrow it advanced.
|
441
448
|
#
|
442
449
|
# === Changes
|
@@ -450,14 +457,14 @@ class << Diff::LCS
|
|
450
457
|
#
|
451
458
|
# The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>,
|
452
459
|
# <tt>callbacks#discard_b</tt>, and <tt>callbacks#change</tt> are invoked
|
453
|
-
# with an event comprising the action ("=", "+", "-", or "!",
|
454
|
-
#
|
455
|
-
#
|
456
|
-
# #traverse_balanced.
|
460
|
+
# with an event comprising the action ("=", "+", "-", or "!", respectively),
|
461
|
+
# the indicies +i+ and +j+, and the elements <tt>A[i]</tt> and <tt>B[j]</tt>.
|
462
|
+
# Return values are discarded by #traverse_balanced.
|
457
463
|
#
|
458
464
|
# === Context
|
459
|
-
#
|
460
|
-
# and +
|
465
|
+
#
|
466
|
+
# Note that +i+ and +j+ may not be the same index position, even if +a+ and
|
467
|
+
# +b+ are considered to be pointing to matching or changed elements.
|
461
468
|
def traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks)
|
462
469
|
matches = Diff::LCS::Internals.lcs(seq1, seq2)
|
463
470
|
a_size = seq1.size
|
@@ -475,6 +482,7 @@ class << Diff::LCS
|
|
475
482
|
end
|
476
483
|
|
477
484
|
break if ma >= matches.size # end of matches?
|
485
|
+
|
478
486
|
mb = matches[ma]
|
479
487
|
|
480
488
|
# Change(seq2)
|
@@ -489,7 +497,6 @@ class << Diff::LCS
|
|
489
497
|
event = yield event if block_given?
|
490
498
|
callbacks.change(event)
|
491
499
|
ai += 1
|
492
|
-
bj += 1
|
493
500
|
else
|
494
501
|
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
|
495
502
|
event = yield event if block_given?
|
@@ -499,8 +506,9 @@ class << Diff::LCS
|
|
499
506
|
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
|
500
507
|
event = yield event if block_given?
|
501
508
|
callbacks.discard_b(event)
|
502
|
-
bj += 1
|
503
509
|
end
|
510
|
+
|
511
|
+
bj += 1
|
504
512
|
when [true, false]
|
505
513
|
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
|
506
514
|
event = yield event if block_given?
|
@@ -535,7 +543,6 @@ class << Diff::LCS
|
|
535
543
|
event = yield event if block_given?
|
536
544
|
callbacks.change(event)
|
537
545
|
ai += 1
|
538
|
-
bj += 1
|
539
546
|
else
|
540
547
|
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
|
541
548
|
event = yield event if block_given?
|
@@ -545,8 +552,9 @@ class << Diff::LCS
|
|
545
552
|
event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
|
546
553
|
event = yield event if block_given?
|
547
554
|
callbacks.discard_b(event)
|
548
|
-
bj += 1
|
549
555
|
end
|
556
|
+
|
557
|
+
bj += 1
|
550
558
|
when [true, false]
|
551
559
|
event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
|
552
560
|
event = yield event if block_given?
|
@@ -562,9 +570,9 @@ class << Diff::LCS
|
|
562
570
|
end
|
563
571
|
|
564
572
|
PATCH_MAP = { #:nodoc:
|
565
|
-
:patch => { '+' => '+', '-' => '-', '!' => '!', '=' => '=' },
|
566
|
-
:unpatch => { '+' => '-', '-' => '+', '!' => '!', '=' => '=' }
|
567
|
-
}
|
573
|
+
:patch => { '+' => '+', '-' => '-', '!' => '!', '=' => '=' }.freeze,
|
574
|
+
:unpatch => { '+' => '-', '-' => '+', '!' => '!', '=' => '=' }.freeze
|
575
|
+
}.freeze
|
568
576
|
|
569
577
|
# Applies a +patchset+ to the sequence +src+ according to the +direction+
|
570
578
|
# (<tt>:patch</tt> or <tt>:unpatch</tt>), producing a new sequence.
|
@@ -577,23 +585,23 @@ class << Diff::LCS
|
|
577
585
|
#
|
578
586
|
# patch(s1, diff(s1, s2)) -> s2
|
579
587
|
#
|
580
|
-
# A +patchset+ can be considered to apply backward (<tt>:unpatch</tt>) if
|
581
|
-
#
|
588
|
+
# A +patchset+ can be considered to apply backward (<tt>:unpatch</tt>) if the
|
589
|
+
# following expression is true:
|
582
590
|
#
|
583
591
|
# patch(s2, diff(s1, s2)) -> s1
|
584
592
|
#
|
585
|
-
# If the +patchset+ contains no changes, the +src+ value will be returned
|
586
|
-
#
|
587
|
-
#
|
593
|
+
# If the +patchset+ contains no changes, the +src+ value will be returned as
|
594
|
+
# either <tt>src.dup</tt> or +src+. A +patchset+ can be deemed as having no
|
595
|
+
# changes if the following predicate returns true:
|
588
596
|
#
|
589
597
|
# patchset.empty? or
|
590
|
-
# patchset.flatten.all? { |change| change.unchanged? }
|
598
|
+
# patchset.flatten(1).all? { |change| change.unchanged? }
|
591
599
|
#
|
592
600
|
# === Patchsets
|
593
601
|
#
|
594
|
-
# A +patchset+ is always an enumerable sequence of changes, hunks of
|
595
|
-
#
|
596
|
-
#
|
602
|
+
# A +patchset+ is always an enumerable sequence of changes, hunks of changes,
|
603
|
+
# or a mix of the two. A hunk of changes is an enumerable sequence of
|
604
|
+
# changes:
|
597
605
|
#
|
598
606
|
# [ # patchset
|
599
607
|
# # change
|
@@ -602,18 +610,15 @@ class << Diff::LCS
|
|
602
610
|
# ]
|
603
611
|
# ]
|
604
612
|
#
|
605
|
-
# The +patch+ method accepts <tt>patchset</tt>s that are enumerable
|
606
|
-
#
|
607
|
-
#
|
613
|
+
# The +patch+ method accepts <tt>patchset</tt>s that are enumerable sequences
|
614
|
+
# containing either Diff::LCS::Change objects (or a subclass) or the array
|
615
|
+
# representations of those objects. Prior to application, array
|
608
616
|
# representations of Diff::LCS::Change objects will be reified.
|
609
617
|
def patch(src, patchset, direction = nil)
|
610
618
|
# Normalize the patchset.
|
611
619
|
has_changes, patchset = Diff::LCS::Internals.analyze_patchset(patchset)
|
612
620
|
|
613
|
-
|
614
|
-
return src.dup if src.respond_to? :dup
|
615
|
-
return src
|
616
|
-
end
|
621
|
+
return src.respond_to?(:dup) ? src.dup : src unless has_changes
|
617
622
|
|
618
623
|
string = src.kind_of?(String)
|
619
624
|
# Start with a new empty type of the source's class
|
@@ -625,7 +630,7 @@ class << Diff::LCS
|
|
625
630
|
|
626
631
|
patch_map = PATCH_MAP[direction]
|
627
632
|
|
628
|
-
patchset.
|
633
|
+
patchset.each do |change|
|
629
634
|
# Both Change and ContextChange support #action
|
630
635
|
action = patch_map[change.action]
|
631
636
|
|
@@ -657,8 +662,8 @@ class << Diff::LCS
|
|
657
662
|
bj += 1
|
658
663
|
end
|
659
664
|
|
660
|
-
|
661
|
-
|
665
|
+
res << el
|
666
|
+
bj += 1
|
662
667
|
when '='
|
663
668
|
# This only appears in sdiff output with the SDiff callback.
|
664
669
|
# Therefore, we only need to worry about dealing with a single
|
@@ -674,10 +679,10 @@ class << Diff::LCS
|
|
674
679
|
bj += 1
|
675
680
|
end
|
676
681
|
|
677
|
-
|
678
|
-
|
682
|
+
bj += 1
|
683
|
+
ai += 1
|
679
684
|
|
680
|
-
|
685
|
+
res << el
|
681
686
|
end
|
682
687
|
when Diff::LCS::Change
|
683
688
|
case action
|
@@ -711,15 +716,17 @@ class << Diff::LCS
|
|
711
716
|
res
|
712
717
|
end
|
713
718
|
|
714
|
-
# Given a set of patchset, convert the current version to the prior
|
715
|
-
#
|
719
|
+
# Given a set of patchset, convert the current version to the prior version.
|
720
|
+
# Does no auto-discovery.
|
716
721
|
def unpatch!(src, patchset)
|
717
722
|
patch(src, patchset, :unpatch)
|
718
723
|
end
|
719
724
|
|
720
|
-
# Given a set of patchset, convert the current version to the next
|
721
|
-
#
|
725
|
+
# Given a set of patchset, convert the current version to the next version.
|
726
|
+
# Does no auto-discovery.
|
722
727
|
def patch!(src, patchset)
|
723
728
|
patch(src, patchset, :patch)
|
724
729
|
end
|
725
730
|
end
|
731
|
+
|
732
|
+
require 'diff/lcs/backports'
|