diff-lcs 1.1.3 → 1.5.0
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 +7 -0
- data/.rspec +1 -0
- data/Code-of-Conduct.md +74 -0
- data/Contributing.md +119 -0
- data/History.md +400 -0
- data/{License.rdoc → License.md} +6 -5
- data/Manifest.txt +36 -4
- data/README.rdoc +35 -23
- data/Rakefile +106 -11
- data/bin/htmldiff +7 -4
- data/bin/ldiff +4 -1
- data/docs/COPYING.txt +21 -22
- data/docs/artistic.txt +127 -0
- data/lib/diff/lcs/array.rb +1 -15
- data/lib/diff/lcs/backports.rb +9 -0
- data/lib/diff/lcs/block.rb +4 -18
- data/lib/diff/lcs/callbacks.rb +233 -230
- data/lib/diff/lcs/change.rb +114 -109
- data/lib/diff/lcs/htmldiff.rb +17 -18
- data/lib/diff/lcs/hunk.rb +232 -116
- data/lib/diff/lcs/internals.rb +308 -0
- data/lib/diff/lcs/ldiff.rb +138 -177
- data/lib/diff/lcs/string.rb +1 -15
- data/lib/diff/lcs.rb +597 -963
- data/lib/diff-lcs.rb +1 -3
- data/spec/change_spec.rb +89 -0
- data/spec/diff_spec.rb +32 -16
- data/spec/fixtures/aX +1 -0
- data/spec/fixtures/bXaX +1 -0
- data/spec/fixtures/ds1.csv +50 -0
- data/spec/fixtures/ds2.csv +51 -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/fixtures/ldiff/output.diff.chef +4 -0
- data/spec/fixtures/ldiff/output.diff.chef-c +15 -0
- data/spec/fixtures/ldiff/output.diff.chef-e +3 -0
- data/spec/fixtures/ldiff/output.diff.chef-f +3 -0
- data/spec/fixtures/ldiff/output.diff.chef-u +9 -0
- data/spec/fixtures/ldiff/output.diff.chef2 +7 -0
- data/spec/fixtures/ldiff/output.diff.chef2-c +20 -0
- data/spec/fixtures/ldiff/output.diff.chef2-d +7 -0
- data/spec/fixtures/ldiff/output.diff.chef2-e +7 -0
- data/spec/fixtures/ldiff/output.diff.chef2-f +7 -0
- data/spec/fixtures/ldiff/output.diff.chef2-u +16 -0
- data/spec/fixtures/new-chef +4 -0
- data/spec/fixtures/new-chef2 +17 -0
- data/spec/fixtures/old-chef +4 -0
- data/spec/fixtures/old-chef2 +14 -0
- data/spec/hunk_spec.rb +83 -0
- data/spec/issues_spec.rb +154 -0
- data/spec/lcs_spec.rb +36 -16
- data/spec/ldiff_spec.rb +87 -0
- data/spec/patch_spec.rb +198 -172
- data/spec/sdiff_spec.rb +99 -89
- data/spec/spec_helper.rb +149 -59
- data/spec/traverse_balanced_spec.rb +191 -167
- data/spec/traverse_sequences_spec.rb +105 -51
- metadata +218 -99
- data/.gemtest +0 -0
- data/History.rdoc +0 -54
- data/diff-lcs.gemspec +0 -51
- data/docs/artistic.html +0 -289
@@ -0,0 +1,308 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class << Diff::LCS
|
4
|
+
def diff_traversal(method, seq1, seq2, callbacks, &block)
|
5
|
+
callbacks = callbacks_for(callbacks)
|
6
|
+
case method
|
7
|
+
when :diff
|
8
|
+
traverse_sequences(seq1, seq2, callbacks)
|
9
|
+
when :sdiff
|
10
|
+
traverse_balanced(seq1, seq2, callbacks)
|
11
|
+
end
|
12
|
+
callbacks.finish if callbacks.respond_to? :finish
|
13
|
+
|
14
|
+
if block
|
15
|
+
callbacks.diffs.map do |hunk|
|
16
|
+
if hunk.kind_of? Array
|
17
|
+
hunk.map { |hunk_block| block[hunk_block] }
|
18
|
+
else
|
19
|
+
block[hunk]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
callbacks.diffs
|
24
|
+
end
|
25
|
+
end
|
26
|
+
private :diff_traversal
|
27
|
+
end
|
28
|
+
|
29
|
+
module Diff::LCS::Internals # :nodoc:
|
30
|
+
end
|
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
|
36
|
+
#
|
37
|
+
# result = Diff::LCS::Internals.lcs(a, b)
|
38
|
+
# result.each_with_index do |e, i|
|
39
|
+
# assert_equal(a[i], b[e]) unless e.nil?
|
40
|
+
# end
|
41
|
+
def lcs(a, b)
|
42
|
+
a_start = b_start = 0
|
43
|
+
a_finish = a.size - 1
|
44
|
+
b_finish = b.size - 1
|
45
|
+
vector = []
|
46
|
+
|
47
|
+
# Collect any common elements at the beginning...
|
48
|
+
while (a_start <= a_finish) and (b_start <= b_finish) and (a[a_start] == b[b_start])
|
49
|
+
vector[a_start] = b_start
|
50
|
+
a_start += 1
|
51
|
+
b_start += 1
|
52
|
+
end
|
53
|
+
|
54
|
+
# Now the end...
|
55
|
+
while (a_start <= a_finish) and (b_start <= b_finish) and (a[a_finish] == b[b_finish])
|
56
|
+
vector[a_finish] = b_finish
|
57
|
+
a_finish -= 1
|
58
|
+
b_finish -= 1
|
59
|
+
end
|
60
|
+
|
61
|
+
# Now, compute the equivalence classes of positions of elements.
|
62
|
+
# An explanation for how this works: https://codeforces.com/topic/92191
|
63
|
+
b_matches = position_hash(b, b_start..b_finish)
|
64
|
+
|
65
|
+
thresh = []
|
66
|
+
links = []
|
67
|
+
string = a.kind_of?(String)
|
68
|
+
|
69
|
+
(a_start..a_finish).each do |i|
|
70
|
+
ai = string ? a[i, 1] : a[i]
|
71
|
+
bm = b_matches[ai]
|
72
|
+
k = nil
|
73
|
+
bm.reverse_each do |j|
|
74
|
+
# Although the threshold check is not mandatory for this to work,
|
75
|
+
# it may have an optimization purpose
|
76
|
+
# An attempt to remove it: https://github.com/halostatue/diff-lcs/pull/72
|
77
|
+
# Why it is reintroduced: https://github.com/halostatue/diff-lcs/issues/78
|
78
|
+
if k and (thresh[k] > j) and (thresh[k - 1] < j)
|
79
|
+
thresh[k] = j
|
80
|
+
else
|
81
|
+
k = replace_next_larger(thresh, j, k)
|
82
|
+
end
|
83
|
+
links[k] = [k.positive? ? links[k - 1] : nil, i, j] unless k.nil?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
unless thresh.empty?
|
88
|
+
link = links[thresh.size - 1]
|
89
|
+
until link.nil?
|
90
|
+
vector[link[1]] = link[2]
|
91
|
+
link = link[0]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
vector
|
96
|
+
end
|
97
|
+
|
98
|
+
# This method will analyze the provided patchset to provide a single-pass
|
99
|
+
# normalization (conversion of the array form of Diff::LCS::Change objects to
|
100
|
+
# the object form of same) and detection of whether the patchset represents
|
101
|
+
# changes to be made.
|
102
|
+
def analyze_patchset(patchset, depth = 0)
|
103
|
+
fail 'Patchset too complex' if depth > 1
|
104
|
+
|
105
|
+
has_changes = false
|
106
|
+
new_patchset = []
|
107
|
+
|
108
|
+
# Format:
|
109
|
+
# [ # patchset
|
110
|
+
# # hunk (change)
|
111
|
+
# [ # hunk
|
112
|
+
# # change
|
113
|
+
# ]
|
114
|
+
# ]
|
115
|
+
|
116
|
+
patchset.each do |hunk|
|
117
|
+
case hunk
|
118
|
+
when Diff::LCS::Change
|
119
|
+
has_changes ||= !hunk.unchanged?
|
120
|
+
new_patchset << hunk
|
121
|
+
when Array
|
122
|
+
# Detect if the 'hunk' is actually an array-format change object.
|
123
|
+
if Diff::LCS::Change.valid_action? hunk[0]
|
124
|
+
hunk = Diff::LCS::Change.from_a(hunk)
|
125
|
+
has_changes ||= !hunk.unchanged?
|
126
|
+
new_patchset << hunk
|
127
|
+
else
|
128
|
+
with_changes, hunk = analyze_patchset(hunk, depth + 1)
|
129
|
+
has_changes ||= with_changes
|
130
|
+
new_patchset.concat(hunk)
|
131
|
+
end
|
132
|
+
else
|
133
|
+
fail ArgumentError, "Cannot normalise a hunk of class #{hunk.class}."
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
[has_changes, new_patchset]
|
138
|
+
end
|
139
|
+
|
140
|
+
# Examine the patchset and the source to see in which direction the
|
141
|
+
# patch should be applied.
|
142
|
+
#
|
143
|
+
# WARNING: By default, this examines the whole patch, so this could take
|
144
|
+
# some time. This also works better with Diff::LCS::ContextChange or
|
145
|
+
# Diff::LCS::Change as its source, as an array will cause the creation
|
146
|
+
# of one of the above.
|
147
|
+
def intuit_diff_direction(src, patchset, limit = nil)
|
148
|
+
string = src.kind_of?(String)
|
149
|
+
count = left_match = left_miss = right_match = right_miss = 0
|
150
|
+
|
151
|
+
patchset.each do |change|
|
152
|
+
count += 1
|
153
|
+
|
154
|
+
case change
|
155
|
+
when Diff::LCS::ContextChange
|
156
|
+
le = string ? src[change.old_position, 1] : src[change.old_position]
|
157
|
+
re = string ? src[change.new_position, 1] : src[change.new_position]
|
158
|
+
|
159
|
+
case change.action
|
160
|
+
when '-' # Remove details from the old string
|
161
|
+
if le == change.old_element
|
162
|
+
left_match += 1
|
163
|
+
else
|
164
|
+
left_miss += 1
|
165
|
+
end
|
166
|
+
when '+'
|
167
|
+
if re == change.new_element
|
168
|
+
right_match += 1
|
169
|
+
else
|
170
|
+
right_miss += 1
|
171
|
+
end
|
172
|
+
when '='
|
173
|
+
left_miss += 1 if le != change.old_element
|
174
|
+
right_miss += 1 if re != change.new_element
|
175
|
+
when '!'
|
176
|
+
if le == change.old_element
|
177
|
+
left_match += 1
|
178
|
+
elsif re == change.new_element
|
179
|
+
right_match += 1
|
180
|
+
else
|
181
|
+
left_miss += 1
|
182
|
+
right_miss += 1
|
183
|
+
end
|
184
|
+
end
|
185
|
+
when Diff::LCS::Change
|
186
|
+
# With a simplistic change, we can't tell the difference between
|
187
|
+
# the left and right on '!' actions, so we ignore those. On '='
|
188
|
+
# actions, if there's a miss, we miss both left and right.
|
189
|
+
element = string ? src[change.position, 1] : src[change.position]
|
190
|
+
|
191
|
+
case change.action
|
192
|
+
when '-'
|
193
|
+
if element == change.element
|
194
|
+
left_match += 1
|
195
|
+
else
|
196
|
+
left_miss += 1
|
197
|
+
end
|
198
|
+
when '+'
|
199
|
+
if element == change.element
|
200
|
+
right_match += 1
|
201
|
+
else
|
202
|
+
right_miss += 1
|
203
|
+
end
|
204
|
+
when '='
|
205
|
+
if element != change.element
|
206
|
+
left_miss += 1
|
207
|
+
right_miss += 1
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
break if !limit.nil? && (count > limit)
|
213
|
+
end
|
214
|
+
|
215
|
+
no_left = left_match.zero? && left_miss.positive?
|
216
|
+
no_right = right_match.zero? && right_miss.positive?
|
217
|
+
|
218
|
+
case [no_left, no_right]
|
219
|
+
when [false, true]
|
220
|
+
:patch
|
221
|
+
when [true, false]
|
222
|
+
:unpatch
|
223
|
+
else
|
224
|
+
case left_match <=> right_match
|
225
|
+
when 1
|
226
|
+
if left_miss.zero?
|
227
|
+
:patch
|
228
|
+
else
|
229
|
+
:unpatch
|
230
|
+
end
|
231
|
+
when -1
|
232
|
+
if right_miss.zero?
|
233
|
+
:unpatch
|
234
|
+
else
|
235
|
+
:patch
|
236
|
+
end
|
237
|
+
else
|
238
|
+
fail "The provided patchset does not appear to apply to the provided \
|
239
|
+
enumerable as either source or destination value."
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Find the place at which +value+ would normally be inserted into the
|
245
|
+
# Enumerable. If that place is already occupied by +value+, do nothing
|
246
|
+
# and return +nil+. If the place does not exist (i.e., it is off the end
|
247
|
+
# of the Enumerable), add it to the end. Otherwise, replace the element
|
248
|
+
# at that point with +value+. It is assumed that the Enumerable's values
|
249
|
+
# are numeric.
|
250
|
+
#
|
251
|
+
# This operation preserves the sort order.
|
252
|
+
def replace_next_larger(enum, value, last_index = nil)
|
253
|
+
# Off the end?
|
254
|
+
if enum.empty? or (value > enum[-1])
|
255
|
+
enum << value
|
256
|
+
return enum.size - 1
|
257
|
+
end
|
258
|
+
|
259
|
+
# Binary search for the insertion point
|
260
|
+
last_index ||= enum.size - 1
|
261
|
+
first_index = 0
|
262
|
+
while first_index <= last_index
|
263
|
+
i = (first_index + last_index) >> 1
|
264
|
+
|
265
|
+
found = enum[i]
|
266
|
+
|
267
|
+
return nil if value == found
|
268
|
+
|
269
|
+
if value > found
|
270
|
+
first_index = i + 1
|
271
|
+
else
|
272
|
+
last_index = i - 1
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# The insertion point is in first_index; overwrite the next larger
|
277
|
+
# value.
|
278
|
+
enum[first_index] = value
|
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?
|
290
|
+
end
|
291
|
+
inverse
|
292
|
+
end
|
293
|
+
private :inverse_vector
|
294
|
+
|
295
|
+
# Returns a hash mapping each element of an Enumerable to the set of
|
296
|
+
# positions it occupies in the Enumerable, optionally restricted to the
|
297
|
+
# elements specified in the range of indexes specified by +interval+.
|
298
|
+
def position_hash(enum, interval)
|
299
|
+
string = enum.kind_of?(String)
|
300
|
+
hash = Hash.new { |h, k| h[k] = [] }
|
301
|
+
interval.each do |i|
|
302
|
+
k = string ? enum[i, 1] : enum[i]
|
303
|
+
hash[k] << i
|
304
|
+
end
|
305
|
+
hash
|
306
|
+
end
|
307
|
+
private :position_hash
|
308
|
+
end
|
data/lib/diff/lcs/ldiff.rb
CHANGED
@@ -1,210 +1,171 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'optparse'
|
4
4
|
require 'ostruct'
|
5
5
|
require 'diff/lcs/hunk'
|
6
6
|
|
7
|
-
|
8
|
-
# ldiff [options] oldfile newfile
|
9
|
-
#
|
10
|
-
# -c:: Displays a context diff with 3 lines of context.
|
11
|
-
# -C [LINES], --context [LINES]:: Displays a context diff with LINES lines of context. Default 3 lines.
|
12
|
-
# -u:: Displays a unified diff with 3 lines of context.
|
13
|
-
# -U [LINES], --unified [LINES]:: Displays a unified diff with LINES lines of context. Default 3 lines.
|
14
|
-
# -e:: Creates an 'ed' script to change oldfile to newfile.
|
15
|
-
# -f:: Creates an 'ed' script to change oldfile to newfile in reverse order.
|
16
|
-
# -a, --text:: Treats the files as text and compares them line-by-line, even if they do not seem to be text.
|
17
|
-
# --binary:: Treats the files as binary.
|
18
|
-
# -q, --brief:: Reports only whether or not the files differ, not the details.
|
19
|
-
# --help:: Shows the command-line help.
|
20
|
-
# --version:: Shows the version of Diff::LCS.
|
21
|
-
#
|
22
|
-
# By default, runs produces an "old-style" diff, with output like UNIX diff.
|
23
|
-
#
|
24
|
-
# == Copyright
|
25
|
-
# Copyright © 2004 Austin Ziegler
|
26
|
-
#
|
27
|
-
# Part of Diff::LCS <http://rubyforge.org/projects/ruwiki/>
|
28
|
-
# Austin Ziegler <diff-lcs@halostatue.ca>
|
29
|
-
#
|
30
|
-
# This program is free software. It may be redistributed and/or modified under
|
31
|
-
# the terms of the GPL version 2 (or later), the Perl Artistic licence, or the
|
32
|
-
# Ruby licence.
|
33
|
-
module Diff::LCS::Ldiff
|
7
|
+
module Diff::LCS::Ldiff #:nodoc:
|
34
8
|
BANNER = <<-COPYRIGHT
|
35
9
|
ldiff #{Diff::LCS::VERSION}
|
36
|
-
Copyright 2004-
|
10
|
+
Copyright 2004-2019 Austin Ziegler
|
37
11
|
|
38
12
|
Part of Diff::LCS.
|
39
|
-
|
40
|
-
|
41
|
-
Austin Ziegler <diff-lcs@halostatue.ca>
|
13
|
+
https://github.com/halostatue/diff-lcs
|
42
14
|
|
43
15
|
This program is free software. It may be redistributed and/or modified under
|
44
16
|
the terms of the GPL version 2 (or later), the Perl Artistic licence, or the
|
45
17
|
MIT licence.
|
18
|
+
COPYRIGHT
|
19
|
+
end
|
46
20
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
o.on('-C', '--context [LINES]', Numeric, 'Displays a context diff with LINES lines', 'of context. Default 3 lines.') do |ctx|
|
66
|
-
@format = :context
|
67
|
-
@lines = ctx || 3
|
68
|
-
end
|
69
|
-
o.on('-u', 'Displays a unified diff with 3 lines of', 'context.') do |ctx|
|
70
|
-
@format = :unified
|
71
|
-
@lines = 3
|
72
|
-
end
|
73
|
-
o.on('-U', '--unified [LINES]', Numeric, 'Displays a unified diff with LINES lines', 'of context. Default 3 lines.') do |ctx|
|
74
|
-
@format = :unified
|
75
|
-
@lines = ctx || 3
|
76
|
-
end
|
77
|
-
o.on('-e', 'Creates an \'ed\' script to change', 'oldfile to newfile.') do |ctx|
|
78
|
-
@format = :ed
|
79
|
-
end
|
80
|
-
o.on('-f', 'Creates an \'ed\' script to change', 'oldfile to newfile in reverse order.') do |ctx|
|
81
|
-
@format = :reverse_ed
|
82
|
-
end
|
83
|
-
o.on('-a', '--text', 'Treat the files as text and compare them', 'line-by-line, even if they do not seem', 'to be text.') do |txt|
|
84
|
-
@binary = false
|
85
|
-
end
|
86
|
-
o.on('--binary', 'Treats the files as binary.') do |bin|
|
87
|
-
@binary = true
|
88
|
-
end
|
89
|
-
o.on('-q', '--brief', 'Report only whether or not the files', 'differ, not the details.') do |ctx|
|
90
|
-
@format = :report
|
91
|
-
end
|
92
|
-
o.on_tail('--help', 'Shows this text.') do
|
93
|
-
error << o
|
94
|
-
return 0
|
95
|
-
end
|
96
|
-
o.on_tail('--version', 'Shows the version of Diff::LCS.') do
|
97
|
-
error << BANNER
|
98
|
-
return 0
|
99
|
-
end
|
100
|
-
o.on_tail ""
|
101
|
-
o.on_tail 'By default, runs produces an "old-style" diff, with output like UNIX diff.'
|
102
|
-
o.parse!
|
21
|
+
class << Diff::LCS::Ldiff
|
22
|
+
attr_reader :format, :lines #:nodoc:
|
23
|
+
attr_reader :file_old, :file_new #:nodoc:
|
24
|
+
attr_reader :data_old, :data_new #:nodoc:
|
25
|
+
|
26
|
+
def run(args, _input = $stdin, output = $stdout, error = $stderr) #:nodoc:
|
27
|
+
@binary = nil
|
28
|
+
|
29
|
+
args.options do |o|
|
30
|
+
o.banner = "Usage: #{File.basename($0)} [options] oldfile newfile"
|
31
|
+
o.separator ''
|
32
|
+
o.on(
|
33
|
+
'-c', '-C', '--context [LINES]', Integer,
|
34
|
+
'Displays a context diff with LINES lines', 'of context. Default 3 lines.'
|
35
|
+
) do |ctx|
|
36
|
+
@format = :context
|
37
|
+
@lines = ctx || 3
|
103
38
|
end
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
39
|
+
o.on(
|
40
|
+
'-u', '-U', '--unified [LINES]', Integer,
|
41
|
+
'Displays a unified diff with LINES lines', 'of context. Default 3 lines.'
|
42
|
+
) do |ctx|
|
43
|
+
@format = :unified
|
44
|
+
@lines = ctx || 3
|
45
|
+
end
|
46
|
+
o.on('-e', 'Creates an \'ed\' script to change', 'oldfile to newfile.') do |_ctx|
|
47
|
+
@format = :ed
|
48
|
+
end
|
49
|
+
o.on('-f', 'Creates an \'ed\' script to change', 'oldfile to newfile in reverse order.') do |_ctx|
|
50
|
+
@format = :reverse_ed
|
51
|
+
end
|
52
|
+
o.on(
|
53
|
+
'-a', '--text',
|
54
|
+
'Treat the files as text and compare them', 'line-by-line, even if they do not seem', 'to be text.'
|
55
|
+
) do |_txt|
|
56
|
+
@binary = false
|
57
|
+
end
|
58
|
+
o.on('--binary', 'Treats the files as binary.') do |_bin|
|
59
|
+
@binary = true
|
60
|
+
end
|
61
|
+
o.on('-q', '--brief', 'Report only whether or not the files', 'differ, not the details.') do |_ctx|
|
62
|
+
@format = :report
|
63
|
+
end
|
64
|
+
o.on_tail('--help', 'Shows this text.') do
|
65
|
+
error << o
|
66
|
+
return 0
|
67
|
+
end
|
68
|
+
o.on_tail('--version', 'Shows the version of Diff::LCS.') do
|
69
|
+
error << Diff::LCS::Ldiff::BANNER
|
70
|
+
return 0
|
108
71
|
end
|
72
|
+
o.on_tail ''
|
73
|
+
o.on_tail 'By default, runs produces an "old-style" diff, with output like UNIX diff.'
|
74
|
+
o.parse!
|
75
|
+
end
|
109
76
|
|
110
|
-
|
111
|
-
|
112
|
-
|
77
|
+
unless args.size == 2
|
78
|
+
error << args.options
|
79
|
+
return 127
|
80
|
+
end
|
113
81
|
|
114
|
-
|
82
|
+
# Defaults are for old-style diff
|
83
|
+
@format ||= :old
|
84
|
+
@lines ||= 0
|
115
85
|
|
116
|
-
|
117
|
-
when :context
|
118
|
-
char_old = '*' * 3
|
119
|
-
char_new = '-' * 3
|
120
|
-
when :unified
|
121
|
-
char_old = '-' * 3
|
122
|
-
char_new = '+' * 3
|
123
|
-
end
|
86
|
+
file_old, file_new = *ARGV
|
124
87
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
# Test binary status
|
134
|
-
if @binary.nil?
|
135
|
-
old_txt = data_old[0...4096].grep(/\0/).empty?
|
136
|
-
new_txt = data_new[0...4096].grep(/\0/).empty?
|
137
|
-
@binary = (not old_txt) or (not new_txt)
|
138
|
-
old_txt = new_txt = nil
|
139
|
-
end
|
140
|
-
|
141
|
-
unless @binary
|
142
|
-
data_old = data_old.split(/\n/).map! { |e| e.chomp }
|
143
|
-
data_new = data_new.split(/\n/).map! { |e| e.chomp }
|
144
|
-
end
|
145
|
-
else
|
146
|
-
data_old = IO::readlines(file_old).map! { |e| e.chomp }
|
147
|
-
data_new = IO::readlines(file_new).map! { |e| e.chomp }
|
148
|
-
end
|
88
|
+
case @format
|
89
|
+
when :context
|
90
|
+
char_old = '*' * 3
|
91
|
+
char_new = '-' * 3
|
92
|
+
when :unified
|
93
|
+
char_old = '-' * 3
|
94
|
+
char_new = '+' * 3
|
95
|
+
end
|
149
96
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
else
|
154
|
-
diffs = Diff::LCS.diff(data_old, data_new)
|
155
|
-
diffs = nil if diffs.empty?
|
156
|
-
end
|
97
|
+
# After we've read up to a certain point in each file, the number of
|
98
|
+
# items we've read from each file will differ by FLD (could be 0).
|
99
|
+
file_length_difference = 0
|
157
100
|
|
158
|
-
|
101
|
+
data_old = IO.read(file_old)
|
102
|
+
data_new = IO.read(file_new)
|
159
103
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
104
|
+
# Test binary status
|
105
|
+
if @binary.nil?
|
106
|
+
old_txt = data_old[0, 4096].scan(/\0/).empty?
|
107
|
+
new_txt = data_new[0, 4096].scan(/\0/).empty?
|
108
|
+
@binary = !old_txt || !new_txt
|
109
|
+
end
|
164
110
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
puts "#{char_new} #{file_new}\t#{ft}"
|
170
|
-
end
|
111
|
+
unless @binary
|
112
|
+
data_old = data_old.lines.to_a
|
113
|
+
data_new = data_new.lines.to_a
|
114
|
+
end
|
171
115
|
|
172
|
-
|
173
|
-
|
174
|
-
|
116
|
+
# diff yields lots of pieces, each of which is basically a Block object
|
117
|
+
if @binary
|
118
|
+
diffs = (data_old == data_new)
|
119
|
+
else
|
120
|
+
diffs = Diff::LCS.diff(data_old, data_new)
|
121
|
+
diffs = nil if diffs.empty?
|
122
|
+
end
|
175
123
|
|
176
|
-
|
177
|
-
real_output = output
|
178
|
-
output = []
|
179
|
-
end
|
124
|
+
return 0 unless diffs
|
180
125
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
file_length_difference = hunk.file_length_difference
|
186
|
-
|
187
|
-
next unless oldhunk
|
188
|
-
|
189
|
-
if (@lines > 0) and hunk.overlaps?(oldhunk)
|
190
|
-
hunk.unshift(oldhunk)
|
191
|
-
else
|
192
|
-
output << oldhunk.diff(@format)
|
193
|
-
end
|
194
|
-
ensure
|
195
|
-
oldhunk = hunk
|
196
|
-
output << "\n"
|
197
|
-
end
|
198
|
-
end
|
126
|
+
if @format == :report
|
127
|
+
output << "Files #{file_old} and #{file_new} differ\n"
|
128
|
+
return 1
|
129
|
+
end
|
199
130
|
|
200
|
-
|
201
|
-
|
131
|
+
if (@format == :unified) or (@format == :context)
|
132
|
+
ft = File.stat(file_old).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.000000000 %z')
|
133
|
+
output << "#{char_old} #{file_old}\t#{ft}\n"
|
134
|
+
ft = File.stat(file_new).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.000000000 %z')
|
135
|
+
output << "#{char_new} #{file_new}\t#{ft}\n"
|
136
|
+
end
|
202
137
|
|
203
|
-
|
204
|
-
|
205
|
-
|
138
|
+
# Loop over hunks. If a hunk overlaps with the last hunk, join them.
|
139
|
+
# Otherwise, print out the old one.
|
140
|
+
oldhunk = hunk = nil
|
206
141
|
|
207
|
-
|
142
|
+
if @format == :ed
|
143
|
+
real_output = output
|
144
|
+
output = []
|
208
145
|
end
|
146
|
+
|
147
|
+
diffs.each do |piece|
|
148
|
+
begin # rubocop:disable Style/RedundantBegin
|
149
|
+
hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, @lines, file_length_difference)
|
150
|
+
file_length_difference = hunk.file_length_difference
|
151
|
+
|
152
|
+
next unless oldhunk
|
153
|
+
next if @lines.positive? and hunk.merge(oldhunk)
|
154
|
+
|
155
|
+
output << oldhunk.diff(@format)
|
156
|
+
output << "\n" if @format == :unified
|
157
|
+
ensure
|
158
|
+
oldhunk = hunk
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
last = oldhunk.diff(@format, true)
|
163
|
+
last << "\n" if last.respond_to?(:end_with?) && !last.end_with?("\n")
|
164
|
+
|
165
|
+
output << last
|
166
|
+
|
167
|
+
output.reverse_each { |e| real_output << e.diff(:ed_finish) } if @format == :ed
|
168
|
+
|
169
|
+
1
|
209
170
|
end
|
210
171
|
end
|
data/lib/diff/lcs/string.rb
CHANGED
@@ -1,18 +1,4 @@
|
|
1
|
-
|
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$
|
14
|
-
#++
|
15
|
-
# Includes Diff::LCS into String.
|
1
|
+
# frozen_string_literal: true
|
16
2
|
|
17
3
|
class String
|
18
4
|
include Diff::LCS
|