diff-lcs 1.6.1 → 2.0.0.beta.1
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 +4 -4
- data/CHANGELOG.md +48 -0
- data/CONTRIBUTING.md +85 -34
- data/CONTRIBUTORS.md +1 -0
- data/LICENCE.md +39 -11
- data/Manifest.txt +3 -13
- data/README.md +1 -1
- data/Rakefile +29 -55
- data/SECURITY.md +27 -23
- data/lib/diff/lcs/block.rb +29 -24
- data/lib/diff/lcs/callbacks.rb +240 -242
- data/lib/diff/lcs/change.rb +84 -91
- data/lib/diff/lcs/hunk.rb +92 -155
- data/lib/diff/lcs/internals.rb +92 -96
- data/lib/diff/lcs/ldiff.rb +21 -34
- data/lib/diff/lcs/version.rb +1 -1
- data/lib/diff/lcs.rb +439 -466
- data/licenses/dco.txt +34 -0
- data/spec/hunk_spec.rb +0 -11
- data/spec/lcs_spec.rb +6 -6
- data/spec/ldiff_spec.rb +1 -1
- data/spec/spec_helper.rb +14 -16
- metadata +91 -36
- data/.rspec +0 -1
- data/bin/htmldiff +0 -35
- data/lib/diff/lcs/backports.rb +0 -9
- data/lib/diff/lcs/htmldiff.rb +0 -160
- data/mise.toml +0 -5
- data/spec/fixtures/ldiff/output.diff-e +0 -3
- data/spec/fixtures/ldiff/output.diff-f +0 -3
- data/spec/fixtures/ldiff/output.diff.chef-e +0 -3
- data/spec/fixtures/ldiff/output.diff.chef-f +0 -3
- data/spec/fixtures/ldiff/output.diff.chef2-e +0 -7
- data/spec/fixtures/ldiff/output.diff.chef2-f +0 -7
- /data/{docs → licenses}/COPYING.txt +0 -0
- /data/{docs → licenses}/artistic.txt +0 -0
data/lib/diff/lcs/internals.rb
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class << Diff::LCS
|
|
4
|
+
def callbacks_for(callbacks) # :nodoc:
|
|
5
|
+
callbacks.new
|
|
6
|
+
rescue
|
|
7
|
+
callbacks
|
|
8
|
+
end
|
|
9
|
+
private :callbacks_for
|
|
10
|
+
|
|
4
11
|
def diff_traversal(method, seq1, seq2, callbacks, &block)
|
|
5
12
|
callbacks = callbacks_for(callbacks)
|
|
13
|
+
|
|
6
14
|
case method
|
|
7
15
|
when :diff
|
|
8
16
|
traverse_sequences(seq1, seq2, callbacks)
|
|
9
17
|
when :sdiff
|
|
10
18
|
traverse_balanced(seq1, seq2, callbacks)
|
|
11
19
|
end
|
|
20
|
+
|
|
12
21
|
callbacks.finish if callbacks.respond_to? :finish
|
|
13
22
|
|
|
14
23
|
if block
|
|
15
24
|
callbacks.diffs.map do |hunk|
|
|
16
25
|
if hunk.is_a? Array
|
|
17
|
-
hunk.map {
|
|
26
|
+
hunk.map { block.call(_1) }
|
|
18
27
|
else
|
|
19
|
-
block
|
|
28
|
+
block.call(hunk)
|
|
20
29
|
end
|
|
21
30
|
end
|
|
22
31
|
else
|
|
@@ -27,18 +36,16 @@ class << Diff::LCS
|
|
|
27
36
|
end
|
|
28
37
|
|
|
29
38
|
module Diff::LCS::Internals # :nodoc:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class << Diff::LCS::Internals
|
|
33
|
-
# Compute the longest common subsequence between the sequenced
|
|
34
|
-
# Enumerables +a+ and +b+. The result is an array whose contents is such
|
|
35
|
-
# that
|
|
39
|
+
# Compute the longest common subsequence between the sequenced enumerable values `a` and
|
|
40
|
+
# `b`. The result is an array whose contents is such that
|
|
36
41
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
+
# ```ruby
|
|
43
|
+
# result = Diff::LCS::Internals.lcs(a, b)
|
|
44
|
+
# result.each_with_index do |e, i|
|
|
45
|
+
# assert_equal(a[i], b[e]) unless e.nil?
|
|
46
|
+
# end
|
|
47
|
+
# ```
|
|
48
|
+
def self.lcs(a, b)
|
|
42
49
|
a_start = b_start = 0
|
|
43
50
|
a_finish = a.size - 1
|
|
44
51
|
b_finish = b.size - 1
|
|
@@ -58,21 +65,20 @@ class << Diff::LCS::Internals
|
|
|
58
65
|
b_finish -= 1
|
|
59
66
|
end
|
|
60
67
|
|
|
61
|
-
# Now, compute the equivalence classes of positions of elements.
|
|
62
|
-
#
|
|
68
|
+
# Now, compute the equivalence classes of positions of elements. An explanation for
|
|
69
|
+
# how this works: https://codeforces.com/topic/92191
|
|
63
70
|
b_matches = position_hash(b, b_start..b_finish)
|
|
64
71
|
|
|
65
72
|
thresh = []
|
|
66
73
|
links = []
|
|
67
|
-
string = a.is_a?(String)
|
|
68
74
|
|
|
69
75
|
(a_start..a_finish).each do |i|
|
|
70
|
-
ai =
|
|
76
|
+
ai = a[i]
|
|
71
77
|
bm = b_matches[ai]
|
|
72
78
|
k = nil
|
|
73
79
|
bm.reverse_each do |j|
|
|
74
|
-
# Although the threshold check is not mandatory for this to work,
|
|
75
|
-
#
|
|
80
|
+
# Although the threshold check is not mandatory for this to work, it may have an
|
|
81
|
+
# optimization purpose.
|
|
76
82
|
# An attempt to remove it: https://github.com/halostatue/diff-lcs/pull/72
|
|
77
83
|
# Why it is reintroduced: https://github.com/halostatue/diff-lcs/issues/78
|
|
78
84
|
if k && (thresh[k] > j) && (thresh[k - 1] < j)
|
|
@@ -95,11 +101,10 @@ class << Diff::LCS::Internals
|
|
|
95
101
|
vector
|
|
96
102
|
end
|
|
97
103
|
|
|
98
|
-
# This method will analyze the provided patchset to provide a single-pass
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
def analyze_patchset(patchset, depth = 0)
|
|
104
|
+
# This method will analyze the provided patchset to provide a single-pass normalization
|
|
105
|
+
# (conversion of the array form of Diff::LCS::Change objects to the object form of same)
|
|
106
|
+
# and detection of whether the patchset represents changes to be made.
|
|
107
|
+
def self.analyze_patchset(patchset, depth = 0)
|
|
103
108
|
fail "Patchset too complex" if depth > 1
|
|
104
109
|
|
|
105
110
|
has_changes = false
|
|
@@ -115,7 +120,7 @@ class << Diff::LCS::Internals
|
|
|
115
120
|
|
|
116
121
|
patchset.each do |hunk|
|
|
117
122
|
case hunk
|
|
118
|
-
when Diff::LCS::Change
|
|
123
|
+
when Diff::LCS::Change, Diff::LCS::ContextChange
|
|
119
124
|
has_changes ||= !hunk.unchanged?
|
|
120
125
|
new_patchset << hunk
|
|
121
126
|
when Array
|
|
@@ -137,15 +142,13 @@ class << Diff::LCS::Internals
|
|
|
137
142
|
[has_changes, new_patchset]
|
|
138
143
|
end
|
|
139
144
|
|
|
140
|
-
# Examine the patchset and the source to see in which direction the
|
|
141
|
-
#
|
|
145
|
+
# Examine the patchset and the source to see in which direction the patch should be
|
|
146
|
+
# applied.
|
|
142
147
|
#
|
|
143
|
-
# WARNING: By default, this examines the whole patch, so this could take
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
|
|
147
|
-
def intuit_diff_direction(src, patchset, limit = nil)
|
|
148
|
-
string = src.is_a?(String)
|
|
148
|
+
# WARNING: By default, this examines the whole patch, so this could take some time. This
|
|
149
|
+
# also works better with Diff::LCS::ContextChange or Diff::LCS::Change as its source, as
|
|
150
|
+
# an array will cause the creation of one of the above.
|
|
151
|
+
def self.intuit_diff_direction(src, patchset, limit = nil)
|
|
149
152
|
count = left_match = left_miss = right_match = right_miss = 0
|
|
150
153
|
|
|
151
154
|
patchset.each do |change|
|
|
@@ -153,8 +156,8 @@ class << Diff::LCS::Internals
|
|
|
153
156
|
|
|
154
157
|
case change
|
|
155
158
|
when Diff::LCS::ContextChange
|
|
156
|
-
le =
|
|
157
|
-
re =
|
|
159
|
+
le = src[change.old_position]
|
|
160
|
+
re = src[change.new_position]
|
|
158
161
|
|
|
159
162
|
case change.action
|
|
160
163
|
when "-" # Remove details from the old string
|
|
@@ -183,10 +186,10 @@ class << Diff::LCS::Internals
|
|
|
183
186
|
end
|
|
184
187
|
end
|
|
185
188
|
when Diff::LCS::Change
|
|
186
|
-
# With a simplistic change, we can't tell the difference between
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
element =
|
|
189
|
+
# With a simplistic change, we can't tell the difference between the left and
|
|
190
|
+
# right on '!' actions, so we ignore those. On '=' actions, if there's a miss, we
|
|
191
|
+
# miss both left and right.
|
|
192
|
+
element = src[change.position]
|
|
190
193
|
|
|
191
194
|
case change.action
|
|
192
195
|
when "-"
|
|
@@ -235,74 +238,67 @@ class << Diff::LCS::Internals
|
|
|
235
238
|
:patch
|
|
236
239
|
end
|
|
237
240
|
else
|
|
238
|
-
fail "The provided patchset does not appear to apply to the provided
|
|
239
|
-
enumerable as either source or destination value."
|
|
241
|
+
fail "The provided patchset does not appear to apply to the provided enumerable as either source or destination value."
|
|
240
242
|
end
|
|
241
243
|
end
|
|
242
244
|
end
|
|
243
245
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
246
|
+
class << self
|
|
247
|
+
# Find the place at which `value` would normally be inserted into the Enumerable. If
|
|
248
|
+
# that place is already occupied by `value`, do nothing and return `nil`. If the place
|
|
249
|
+
# does not exist (i.e., it is off the end of the Enumerable), add it to the end.
|
|
250
|
+
# Otherwise, replace the element at that point with `value`. It is assumed that the
|
|
251
|
+
# Enumerable's values are numeric.
|
|
252
|
+
#
|
|
253
|
+
# This operation preserves the sort order.
|
|
254
|
+
def replace_next_larger(enum, value, last_index = nil)
|
|
255
|
+
# Off the end?
|
|
256
|
+
if enum.empty? || (value > enum[-1])
|
|
257
|
+
enum << value
|
|
258
|
+
return enum.size - 1
|
|
259
|
+
end
|
|
258
260
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
# Binary search for the insertion point
|
|
262
|
+
last_index ||= enum.size - 1
|
|
263
|
+
first_index = 0
|
|
264
|
+
while first_index <= last_index
|
|
265
|
+
i = (first_index + last_index) >> 1
|
|
264
266
|
|
|
265
|
-
|
|
267
|
+
found = enum[i]
|
|
266
268
|
|
|
267
|
-
|
|
269
|
+
return nil if value == found
|
|
268
270
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
if value > found
|
|
272
|
+
first_index = i + 1
|
|
273
|
+
else
|
|
274
|
+
last_index = i - 1
|
|
275
|
+
end
|
|
273
276
|
end
|
|
274
|
-
end
|
|
275
277
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
first_index
|
|
280
|
-
end
|
|
281
|
-
private :replace_next_larger
|
|
282
|
-
|
|
283
|
-
# If +vector+ maps the matching elements of another collection onto this
|
|
284
|
-
# Enumerable, compute the inverse of +vector+ that maps this Enumerable
|
|
285
|
-
# onto the collection. (Currently unused.)
|
|
286
|
-
def inverse_vector(a, vector)
|
|
287
|
-
inverse = a.dup
|
|
288
|
-
(0...vector.size).each do |i|
|
|
289
|
-
inverse[vector[i]] = i unless vector[i].nil?
|
|
278
|
+
# The insertion point is in first_index; overwrite the next larger value.
|
|
279
|
+
enum[first_index] = value
|
|
280
|
+
first_index
|
|
290
281
|
end
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
282
|
+
private :replace_next_larger
|
|
283
|
+
|
|
284
|
+
# If `vector` maps the matching elements of another collection onto this Enumerable,
|
|
285
|
+
# compute the inverse of `vector` that maps this Enumerable onto the collection.
|
|
286
|
+
# (Currently unused.)
|
|
287
|
+
def inverse_vector(a, vector)
|
|
288
|
+
inverse = a.dup
|
|
289
|
+
(0...vector.size).each do
|
|
290
|
+
inverse[vector[_1]] = i unless vector[_1].nil?
|
|
291
|
+
end
|
|
292
|
+
inverse
|
|
293
|
+
end
|
|
294
|
+
private :inverse_vector
|
|
295
|
+
|
|
296
|
+
# Returns a hash mapping each element of an Enumerable to the set of positions it
|
|
297
|
+
# occupies in the Enumerable, optionally restricted to the elements specified in the
|
|
298
|
+
# range of indexes specified by `interval`.
|
|
299
|
+
def position_hash(enum, interval)
|
|
300
|
+
Hash.new { |h, k| h[k] = [] }.tap { |hash| interval.each { hash[enum[_1]] << _1 } }
|
|
304
301
|
end
|
|
305
|
-
|
|
302
|
+
private :position_hash
|
|
306
303
|
end
|
|
307
|
-
private :position_hash
|
|
308
304
|
end
|
data/lib/diff/lcs/ldiff.rb
CHANGED
|
@@ -4,19 +4,17 @@ require "optparse"
|
|
|
4
4
|
require "diff/lcs/hunk"
|
|
5
5
|
|
|
6
6
|
class Diff::LCS::Ldiff # :nodoc:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Copyright 2004-2025 Austin Ziegler
|
|
7
|
+
BANNER = <<~COPYRIGHT
|
|
8
|
+
ldiff #{Diff::LCS::VERSION}
|
|
9
|
+
Copyright 2004-2025 Austin Ziegler
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
Part of Diff::LCS.
|
|
12
|
+
https://github.com/halostatue/diff-lcs
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
This program is free software. It may be redistributed and/or modified under
|
|
15
|
+
the terms of the GPL version 2 (or later), the Perl Artistic licence, or the
|
|
16
|
+
MIT licence.
|
|
18
17
|
COPYRIGHT
|
|
19
|
-
# standard:enable Layout/HeredocIndentation
|
|
20
18
|
|
|
21
19
|
InputInfo = Struct.new(:filename, :data, :stat) do
|
|
22
20
|
def initialize(filename)
|
|
@@ -56,12 +54,6 @@ ldiff #{Diff::LCS::VERSION}
|
|
|
56
54
|
@format = :unified
|
|
57
55
|
@lines = ctx || 3
|
|
58
56
|
end
|
|
59
|
-
o.on("-e", "Creates an 'ed' script to change", "oldfile to newfile.") do |_ctx|
|
|
60
|
-
@format = :ed
|
|
61
|
-
end
|
|
62
|
-
o.on("-f", "Creates an 'ed' script to change", "oldfile to newfile in reverse order.") do |_ctx|
|
|
63
|
-
@format = :reverse_ed
|
|
64
|
-
end
|
|
65
57
|
o.on(
|
|
66
58
|
"-a", "--text",
|
|
67
59
|
"Treat the files as text and compare them", "line-by-line, even if they do not seem", "to be text."
|
|
@@ -117,8 +109,8 @@ ldiff #{Diff::LCS::VERSION}
|
|
|
117
109
|
char_new = "+" * 3
|
|
118
110
|
end
|
|
119
111
|
|
|
120
|
-
# After we've read up to a certain point in each file, the number of
|
|
121
|
-
#
|
|
112
|
+
# After we've read up to a certain point in each file, the number of items we've read
|
|
113
|
+
# from each file will differ by FLD (could be 0).
|
|
122
114
|
file_length_difference = 0
|
|
123
115
|
|
|
124
116
|
# Test binary status
|
|
@@ -155,26 +147,23 @@ ldiff #{Diff::LCS::VERSION}
|
|
|
155
147
|
ft = info_new.stat.mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.000000000 %z")
|
|
156
148
|
output << "#{char_new} #{info_new.filename}\t#{ft}\n"
|
|
157
149
|
when :ed
|
|
158
|
-
real_output = output
|
|
159
150
|
output = []
|
|
160
151
|
end
|
|
161
152
|
|
|
162
|
-
# Loop over hunks. If a hunk overlaps with the last hunk, join them.
|
|
163
|
-
#
|
|
153
|
+
# Loop over hunks. If a hunk overlaps with the last hunk, join them. Otherwise, print
|
|
154
|
+
# out the old one.
|
|
164
155
|
oldhunk = hunk = nil
|
|
165
156
|
diffs.each do |piece|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
file_length_difference = hunk.file_length_difference
|
|
157
|
+
hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, lines, file_length_difference)
|
|
158
|
+
file_length_difference = hunk.file_length_difference
|
|
169
159
|
|
|
170
|
-
|
|
171
|
-
|
|
160
|
+
next unless oldhunk
|
|
161
|
+
next if lines.positive? && hunk.merge(oldhunk)
|
|
172
162
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
end
|
|
163
|
+
output << oldhunk.diff(format)
|
|
164
|
+
output << "\n" if format == :unified
|
|
165
|
+
ensure
|
|
166
|
+
oldhunk = hunk
|
|
178
167
|
end
|
|
179
168
|
|
|
180
169
|
last = oldhunk.diff(format, true)
|
|
@@ -182,8 +171,6 @@ ldiff #{Diff::LCS::VERSION}
|
|
|
182
171
|
|
|
183
172
|
output << last
|
|
184
173
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
true
|
|
174
|
+
1
|
|
188
175
|
end
|
|
189
176
|
end
|