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/lib/diff/lcs/internals.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class << Diff::LCS
|
4
4
|
def diff_traversal(method, seq1, seq2, callbacks, &block)
|
@@ -45,8 +45,7 @@ class << Diff::LCS::Internals
|
|
45
45
|
vector = []
|
46
46
|
|
47
47
|
# Prune off any common elements at the beginning...
|
48
|
-
while (
|
49
|
-
(a[a_start] == b[b_start]))
|
48
|
+
while (a_start <= a_finish) and (b_start <= b_finish) and (a[a_start] == b[b_start])
|
50
49
|
vector[a_start] = b_start
|
51
50
|
a_start += 1
|
52
51
|
b_start += 1
|
@@ -54,8 +53,7 @@ class << Diff::LCS::Internals
|
|
54
53
|
b_start = a_start
|
55
54
|
|
56
55
|
# Now the end...
|
57
|
-
while (
|
58
|
-
(a[a_finish] == b[b_finish]))
|
56
|
+
while (a_start <= a_finish) and (b_start <= b_finish) and (a[a_finish] == b[b_finish])
|
59
57
|
vector[a_finish] = b_finish
|
60
58
|
a_finish -= 1
|
61
59
|
b_finish -= 1
|
@@ -68,7 +66,7 @@ class << Diff::LCS::Internals
|
|
68
66
|
links = []
|
69
67
|
string = a.kind_of?(String)
|
70
68
|
|
71
|
-
(a_start
|
69
|
+
(a_start..a_finish).each do |i|
|
72
70
|
ai = string ? a[i, 1] : a[i]
|
73
71
|
bm = b_matches[ai]
|
74
72
|
k = nil
|
@@ -78,13 +76,13 @@ class << Diff::LCS::Internals
|
|
78
76
|
else
|
79
77
|
k = replace_next_larger(thresh, j, k)
|
80
78
|
end
|
81
|
-
links[k] = [
|
79
|
+
links[k] = [k.positive? ? links[k - 1] : nil, i, j] unless k.nil?
|
82
80
|
end
|
83
81
|
end
|
84
82
|
|
85
83
|
unless thresh.empty?
|
86
84
|
link = links[thresh.size - 1]
|
87
|
-
|
85
|
+
until link.nil?
|
88
86
|
vector[link[1]] = link[2]
|
89
87
|
link = link[0]
|
90
88
|
end
|
@@ -93,14 +91,15 @@ class << Diff::LCS::Internals
|
|
93
91
|
vector
|
94
92
|
end
|
95
93
|
|
96
|
-
# This method will analyze the provided patchset to provide a
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
94
|
+
# This method will analyze the provided patchset to provide a single-pass
|
95
|
+
# normalization (conversion of the array form of Diff::LCS::Change objects to
|
96
|
+
# the object form of same) and detection of whether the patchset represents
|
97
|
+
# changes to be made.
|
100
98
|
def analyze_patchset(patchset, depth = 0)
|
101
|
-
|
99
|
+
fail 'Patchset too complex' if depth > 1
|
102
100
|
|
103
101
|
has_changes = false
|
102
|
+
new_patchset = []
|
104
103
|
|
105
104
|
# Format:
|
106
105
|
# [ # patchset
|
@@ -110,29 +109,28 @@ class << Diff::LCS::Internals
|
|
110
109
|
# ]
|
111
110
|
# ]
|
112
111
|
|
113
|
-
patchset
|
112
|
+
patchset.each do |hunk|
|
114
113
|
case hunk
|
115
114
|
when Diff::LCS::Change
|
116
115
|
has_changes ||= !hunk.unchanged?
|
117
|
-
hunk
|
116
|
+
new_patchset << hunk
|
118
117
|
when Array
|
119
|
-
# Detect if the 'hunk' is actually an array-format
|
120
|
-
# Change object.
|
118
|
+
# Detect if the 'hunk' is actually an array-format change object.
|
121
119
|
if Diff::LCS::Change.valid_action? hunk[0]
|
122
120
|
hunk = Diff::LCS::Change.from_a(hunk)
|
123
121
|
has_changes ||= !hunk.unchanged?
|
124
|
-
hunk
|
122
|
+
new_patchset << hunk
|
125
123
|
else
|
126
124
|
with_changes, hunk = analyze_patchset(hunk, depth + 1)
|
127
125
|
has_changes ||= with_changes
|
128
|
-
hunk
|
126
|
+
new_patchset.concat(hunk)
|
129
127
|
end
|
130
128
|
else
|
131
|
-
|
129
|
+
fail ArgumentError, "Cannot normalise a hunk of class #{hunk.class}."
|
132
130
|
end
|
133
131
|
end
|
134
132
|
|
135
|
-
[
|
133
|
+
[has_changes, new_patchset]
|
136
134
|
end
|
137
135
|
|
138
136
|
# Examine the patchset and the source to see in which direction the
|
@@ -173,13 +171,11 @@ class << Diff::LCS::Internals
|
|
173
171
|
when '!'
|
174
172
|
if le == change.old_element
|
175
173
|
left_match += 1
|
174
|
+
elsif re == change.new_element
|
175
|
+
right_match += 1
|
176
176
|
else
|
177
|
-
|
178
|
-
|
179
|
-
else
|
180
|
-
left_miss += 1
|
181
|
-
right_miss += 1
|
182
|
-
end
|
177
|
+
left_miss += 1
|
178
|
+
right_miss += 1
|
183
179
|
end
|
184
180
|
end
|
185
181
|
when Diff::LCS::Change
|
@@ -209,16 +205,16 @@ class << Diff::LCS::Internals
|
|
209
205
|
end
|
210
206
|
end
|
211
207
|
|
212
|
-
break if
|
208
|
+
break if !limit.nil? && (count > limit)
|
213
209
|
end
|
214
210
|
|
215
|
-
no_left =
|
216
|
-
no_right =
|
211
|
+
no_left = left_match.zero? && left_miss.positive?
|
212
|
+
no_right = right_match.zero? && right_miss.positive?
|
217
213
|
|
218
|
-
case [
|
219
|
-
when [
|
214
|
+
case [no_left, no_right]
|
215
|
+
when [false, true]
|
220
216
|
:patch
|
221
|
-
when [
|
217
|
+
when [true, false]
|
222
218
|
:unpatch
|
223
219
|
else
|
224
220
|
case left_match <=> right_match
|
@@ -235,7 +231,8 @@ class << Diff::LCS::Internals
|
|
235
231
|
:patch
|
236
232
|
end
|
237
233
|
else
|
238
|
-
|
234
|
+
fail "The provided patchset does not appear to apply to the provided \
|
235
|
+
enumerable as either source or destination value."
|
239
236
|
end
|
240
237
|
end
|
241
238
|
end
|
@@ -258,14 +255,14 @@ class << Diff::LCS::Internals
|
|
258
255
|
# Binary search for the insertion point
|
259
256
|
last_index ||= enum.size
|
260
257
|
first_index = 0
|
261
|
-
while
|
258
|
+
while first_index <= last_index
|
262
259
|
i = (first_index + last_index) >> 1
|
263
260
|
|
264
261
|
found = enum[i]
|
265
262
|
|
266
|
-
if value == found
|
267
|
-
|
268
|
-
|
263
|
+
return nil if value == found
|
264
|
+
|
265
|
+
if value > found
|
269
266
|
first_index = i + 1
|
270
267
|
else
|
271
268
|
last_index = i - 1
|
@@ -275,7 +272,7 @@ class << Diff::LCS::Internals
|
|
275
272
|
# The insertion point is in first_index; overwrite the next larger
|
276
273
|
# value.
|
277
274
|
enum[first_index] = value
|
278
|
-
|
275
|
+
first_index
|
279
276
|
end
|
280
277
|
private :replace_next_larger
|
281
278
|
|
data/lib/diff/lcs/ldiff.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'optparse'
|
4
4
|
require 'ostruct'
|
@@ -7,7 +7,7 @@ require 'diff/lcs/hunk'
|
|
7
7
|
module Diff::LCS::Ldiff #:nodoc:
|
8
8
|
BANNER = <<-COPYRIGHT
|
9
9
|
ldiff #{Diff::LCS::VERSION}
|
10
|
-
Copyright 2004-
|
10
|
+
Copyright 2004-2019 Austin Ziegler
|
11
11
|
|
12
12
|
Part of Diff::LCS.
|
13
13
|
https://github.com/halostatue/diff-lcs
|
@@ -15,7 +15,7 @@ ldiff #{Diff::LCS::VERSION}
|
|
15
15
|
This program is free software. It may be redistributed and/or modified under
|
16
16
|
the terms of the GPL version 2 (or later), the Perl Artistic licence, or the
|
17
17
|
MIT licence.
|
18
|
-
COPYRIGHT
|
18
|
+
COPYRIGHT
|
19
19
|
end
|
20
20
|
|
21
21
|
class << Diff::LCS::Ldiff
|
@@ -23,33 +23,42 @@ class << Diff::LCS::Ldiff
|
|
23
23
|
attr_reader :file_old, :file_new #:nodoc:
|
24
24
|
attr_reader :data_old, :data_new #:nodoc:
|
25
25
|
|
26
|
-
def run(args,
|
26
|
+
def run(args, _input = $stdin, output = $stdout, error = $stderr) #:nodoc:
|
27
27
|
@binary = nil
|
28
28
|
|
29
29
|
args.options do |o|
|
30
30
|
o.banner = "Usage: #{File.basename($0)} [options] oldfile newfile"
|
31
|
-
o.separator
|
32
|
-
o.on(
|
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|
|
33
36
|
@format = :context
|
34
37
|
@lines = ctx || 3
|
35
38
|
end
|
36
|
-
o.on(
|
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|
|
37
43
|
@format = :unified
|
38
44
|
@lines = ctx || 3
|
39
45
|
end
|
40
|
-
o.on('-e', 'Creates an \'ed\' script to change', 'oldfile to newfile.') do |
|
46
|
+
o.on('-e', 'Creates an \'ed\' script to change', 'oldfile to newfile.') do |_ctx|
|
41
47
|
@format = :ed
|
42
48
|
end
|
43
|
-
o.on('-f', 'Creates an \'ed\' script to change', 'oldfile to newfile in reverse order.') do |
|
49
|
+
o.on('-f', 'Creates an \'ed\' script to change', 'oldfile to newfile in reverse order.') do |_ctx|
|
44
50
|
@format = :reverse_ed
|
45
51
|
end
|
46
|
-
o.on(
|
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|
|
47
56
|
@binary = false
|
48
57
|
end
|
49
|
-
o.on('--binary', 'Treats the files as binary.') do |
|
58
|
+
o.on('--binary', 'Treats the files as binary.') do |_bin|
|
50
59
|
@binary = true
|
51
60
|
end
|
52
|
-
o.on('-q', '--brief', 'Report only whether or not the files', 'differ, not the details.') do |
|
61
|
+
o.on('-q', '--brief', 'Report only whether or not the files', 'differ, not the details.') do |_ctx|
|
53
62
|
@format = :report
|
54
63
|
end
|
55
64
|
o.on_tail('--help', 'Shows this text.') do
|
@@ -60,7 +69,7 @@ class << Diff::LCS::Ldiff
|
|
60
69
|
error << Diff::LCS::Ldiff::BANNER
|
61
70
|
return 0
|
62
71
|
end
|
63
|
-
o.on_tail
|
72
|
+
o.on_tail ''
|
64
73
|
o.on_tail 'By default, runs produces an "old-style" diff, with output like UNIX diff.'
|
65
74
|
o.parse!
|
66
75
|
end
|
@@ -97,8 +106,7 @@ class << Diff::LCS::Ldiff
|
|
97
106
|
if @binary.nil?
|
98
107
|
old_txt = data_old[0, 4096].scan(/\0/).empty?
|
99
108
|
new_txt = data_new[0, 4096].scan(/\0/).empty?
|
100
|
-
@binary =
|
101
|
-
old_txt = new_txt = nil
|
109
|
+
@binary = !old_txt or !new_txt
|
102
110
|
end
|
103
111
|
|
104
112
|
unless @binary
|
@@ -126,9 +134,9 @@ class << Diff::LCS::Ldiff
|
|
126
134
|
end
|
127
135
|
|
128
136
|
if (@format == :unified) or (@format == :context)
|
129
|
-
ft = File.stat(file_old).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S
|
137
|
+
ft = File.stat(file_old).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.000000000 %z')
|
130
138
|
output << "#{char_old} #{file_old}\t#{ft}\n"
|
131
|
-
ft = File.stat(file_new).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S
|
139
|
+
ft = File.stat(file_new).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.000000000 %z')
|
132
140
|
output << "#{char_new} #{file_new}\t#{ft}\n"
|
133
141
|
end
|
134
142
|
|
@@ -143,12 +151,11 @@ class << Diff::LCS::Ldiff
|
|
143
151
|
|
144
152
|
diffs.each do |piece|
|
145
153
|
begin
|
146
|
-
hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, @lines,
|
147
|
-
file_length_difference)
|
154
|
+
hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, @lines, file_length_difference)
|
148
155
|
file_length_difference = hunk.file_length_difference
|
149
156
|
|
150
157
|
next unless oldhunk
|
151
|
-
next if
|
158
|
+
next if @lines.positive? and hunk.merge(oldhunk)
|
152
159
|
|
153
160
|
output << oldhunk.diff(@format) << "\n"
|
154
161
|
ensure
|
@@ -156,12 +163,13 @@ class << Diff::LCS::Ldiff
|
|
156
163
|
end
|
157
164
|
end
|
158
165
|
|
159
|
-
|
166
|
+
last = oldhunk.diff(@format)
|
167
|
+
last << "\n" if last.respond_to?(:end_with?) && !last.end_with?("\n")
|
160
168
|
|
161
|
-
|
162
|
-
|
163
|
-
|
169
|
+
output << last
|
170
|
+
|
171
|
+
output.reverse_each { |e| real_output << e.diff(:ed_finish) } if @format == :ed
|
164
172
|
|
165
|
-
|
173
|
+
1
|
166
174
|
end
|
167
175
|
end
|
data/lib/diff/lcs/string.rb
CHANGED
data/spec/change_spec.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
describe Diff::LCS::Change do
|
6
|
-
describe
|
6
|
+
describe 'an add' do
|
7
7
|
subject { described_class.new('+', 0, 'element') }
|
8
8
|
it { should_not be_deleting }
|
9
9
|
it { should be_adding }
|
@@ -13,7 +13,7 @@ describe Diff::LCS::Change do
|
|
13
13
|
it { should_not be_finished_b }
|
14
14
|
end
|
15
15
|
|
16
|
-
describe
|
16
|
+
describe 'a delete' do
|
17
17
|
subject { described_class.new('-', 0, 'element') }
|
18
18
|
it { should be_deleting }
|
19
19
|
it { should_not be_adding }
|
@@ -23,7 +23,7 @@ describe Diff::LCS::Change do
|
|
23
23
|
it { should_not be_finished_b }
|
24
24
|
end
|
25
25
|
|
26
|
-
describe
|
26
|
+
describe 'an unchanged' do
|
27
27
|
subject { described_class.new('=', 0, 'element') }
|
28
28
|
it { should_not be_deleting }
|
29
29
|
it { should_not be_adding }
|
@@ -33,7 +33,7 @@ describe Diff::LCS::Change do
|
|
33
33
|
it { should_not be_finished_b }
|
34
34
|
end
|
35
35
|
|
36
|
-
describe
|
36
|
+
describe 'a changed' do
|
37
37
|
subject { described_class.new('!', 0, 'element') }
|
38
38
|
it { should_not be_deleting }
|
39
39
|
it { should_not be_adding }
|
@@ -43,7 +43,7 @@ describe Diff::LCS::Change do
|
|
43
43
|
it { should_not be_finished_b }
|
44
44
|
end
|
45
45
|
|
46
|
-
describe
|
46
|
+
describe 'a finished_a' do
|
47
47
|
subject { described_class.new('>', 0, 'element') }
|
48
48
|
it { should_not be_deleting }
|
49
49
|
it { should_not be_adding }
|
@@ -53,7 +53,7 @@ describe Diff::LCS::Change do
|
|
53
53
|
it { should_not be_finished_b }
|
54
54
|
end
|
55
55
|
|
56
|
-
describe
|
56
|
+
describe 'a finished_b' do
|
57
57
|
subject { described_class.new('<', 0, 'element') }
|
58
58
|
it { should_not be_deleting }
|
59
59
|
it { should_not be_adding }
|
@@ -62,4 +62,28 @@ describe Diff::LCS::Change do
|
|
62
62
|
it { should_not be_finished_a }
|
63
63
|
it { should be_finished_b }
|
64
64
|
end
|
65
|
+
|
66
|
+
describe 'as array' do
|
67
|
+
it 'should be converted' do
|
68
|
+
action, position, element = described_class.new('!', 0, 'element')
|
69
|
+
expect(action).to eq '!'
|
70
|
+
expect(position).to eq 0
|
71
|
+
expect(element).to eq 'element'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe Diff::LCS::ContextChange do
|
77
|
+
describe 'as array' do
|
78
|
+
it 'should be converted' do
|
79
|
+
action, (old_position, old_element), (new_position, new_element) =
|
80
|
+
described_class.new('!', 1, 'old_element', 2, 'new_element')
|
81
|
+
|
82
|
+
expect(action).to eq '!'
|
83
|
+
expect(old_position).to eq 1
|
84
|
+
expect(old_element).to eq 'old_element'
|
85
|
+
expect(new_position).to eq 2
|
86
|
+
expect(new_element).to eq 'new_element'
|
87
|
+
end
|
88
|
+
end
|
65
89
|
end
|
data/spec/diff_spec.rb
CHANGED
@@ -1,33 +1,37 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe Diff::LCS,
|
5
|
+
describe Diff::LCS, '.diff' do
|
6
6
|
include Diff::LCS::SpecHelper::Matchers
|
7
7
|
|
8
|
-
it
|
8
|
+
it 'correctly diffs seq1 to seq2' do
|
9
9
|
diff_s1_s2 = Diff::LCS.diff(seq1, seq2)
|
10
10
|
expect(change_diff(correct_forward_diff)).to eq(diff_s1_s2)
|
11
11
|
end
|
12
12
|
|
13
|
-
it
|
13
|
+
it 'correctly diffs seq2 to seq1' do
|
14
14
|
diff_s2_s1 = Diff::LCS.diff(seq2, seq1)
|
15
15
|
expect(change_diff(correct_backward_diff)).to eq(diff_s2_s1)
|
16
16
|
end
|
17
17
|
|
18
|
-
it
|
18
|
+
it 'correctly diffs against an empty sequence' do
|
19
19
|
diff = Diff::LCS.diff(word_sequence, [])
|
20
20
|
correct_diff = [
|
21
|
-
[
|
22
|
-
[
|
23
|
-
[
|
24
|
-
[
|
21
|
+
[
|
22
|
+
['-', 0, 'abcd'],
|
23
|
+
['-', 1, 'efgh'],
|
24
|
+
['-', 2, 'ijkl'],
|
25
|
+
['-', 3, 'mnopqrstuvwxyz']
|
26
|
+
]
|
25
27
|
]
|
26
28
|
|
27
29
|
expect(change_diff(correct_diff)).to eq(diff)
|
28
30
|
|
29
31
|
diff = Diff::LCS.diff([], word_sequence)
|
30
|
-
correct_diff.each
|
32
|
+
correct_diff.each do |hunk|
|
33
|
+
hunk.each do |change| change[0] = '+' end
|
34
|
+
end
|
31
35
|
expect(change_diff(correct_diff)).to eq(diff)
|
32
36
|
end
|
33
37
|
|
@@ -37,11 +41,11 @@ describe Diff::LCS, ".diff" do
|
|
37
41
|
expect(Diff::LCS.patch(left, Diff::LCS.diff(left, right))).to eq(right)
|
38
42
|
end
|
39
43
|
|
40
|
-
it
|
44
|
+
it 'returns an empty diff with (hello, hello)' do
|
41
45
|
expect(Diff::LCS.diff(hello, hello)).to be_empty
|
42
46
|
end
|
43
47
|
|
44
|
-
it
|
48
|
+
it 'returns an empty diff with (hello_ary, hello_ary)' do
|
45
49
|
expect(Diff::LCS.diff(hello_ary, hello_ary)).to be_empty
|
46
50
|
end
|
47
51
|
end
|