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
data/lib/diff/lcs/hunk.rb
CHANGED
@@ -1,25 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'diff/lcs/block'
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
5
|
+
# A Hunk is a group of Blocks which overlap because of the context surrounding
|
6
|
+
# each block. (So if we're not using context, every hunk will contain one
|
7
|
+
# block.) Used in the diff program (bin/ldiff).
|
6
8
|
class Diff::LCS::Hunk
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
OLD_DIFF_OP_ACTION = { '+' => 'a', '-' => 'd', '!' => 'c' }.freeze #:nodoc:
|
10
|
+
ED_DIFF_OP_ACTION = { '+' => 'a', '-' => 'd', '!' => 'c' }.freeze #:nodoc:
|
11
|
+
|
12
|
+
private_constant :OLD_DIFF_OP_ACTION, :ED_DIFF_OP_ACTION if respond_to?(:private_constant)
|
13
|
+
|
14
|
+
# Create a hunk using references to both the old and new data, as well as the
|
15
|
+
# piece of data.
|
16
|
+
def initialize(data_old, data_new, piece, flag_context, file_length_difference)
|
17
|
+
# At first, a hunk will have just one Block in it
|
18
|
+
@blocks = [Diff::LCS::Block.new(piece)]
|
19
|
+
|
20
|
+
if @blocks[0].remove.empty? && @blocks[0].insert.empty?
|
21
|
+
fail "Cannot build a hunk from #{piece.inspect}; has no add or remove actions"
|
22
|
+
end
|
23
|
+
|
24
|
+
if String.method_defined?(:encoding)
|
25
|
+
@preferred_data_encoding = data_old.fetch(0, data_new.fetch(0, '')).encoding
|
26
|
+
end
|
27
|
+
|
12
28
|
@data_old = data_old
|
13
29
|
@data_new = data_new
|
14
30
|
|
15
31
|
before = after = file_length_difference
|
16
32
|
after += @blocks[0].diff_size
|
17
33
|
@file_length_difference = after # The caller must get this manually
|
34
|
+
@max_diff_size = @blocks.map { |e| e.diff_size.abs }.max
|
35
|
+
|
18
36
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
37
|
+
# Save the start & end of each array. If the array doesn't exist (e.g.,
|
38
|
+
# we're only adding items in this block), then figure out the line number
|
39
|
+
# based on the line number of the other file and the current difference in
|
40
|
+
# file lengths.
|
23
41
|
if @blocks[0].remove.empty?
|
24
42
|
a1 = a2 = nil
|
25
43
|
else
|
@@ -39,7 +57,7 @@ class Diff::LCS::Hunk
|
|
39
57
|
@end_old = a2 || (b2 - after)
|
40
58
|
@end_new = b2 || (a2 + after)
|
41
59
|
|
42
|
-
self.flag_context =
|
60
|
+
self.flag_context = flag_context
|
43
61
|
end
|
44
62
|
|
45
63
|
attr_reader :blocks
|
@@ -47,173 +65,232 @@ class Diff::LCS::Hunk
|
|
47
65
|
attr_reader :end_old, :end_new
|
48
66
|
attr_reader :file_length_difference
|
49
67
|
|
50
|
-
|
51
|
-
|
52
|
-
attr_accessor :flag_context
|
68
|
+
# Change the "start" and "end" fields to note that context should be added
|
69
|
+
# to this hunk.
|
70
|
+
attr_accessor :flag_context # rubocop:disable Layout/EmptyLinesAroundAttributeAccessor
|
53
71
|
undef :flag_context=
|
54
|
-
def flag_context=(context) #:nodoc:
|
72
|
+
def flag_context=(context) #:nodoc: # rubocop:disable Lint/DuplicateMethods
|
55
73
|
return if context.nil? or context.zero?
|
56
74
|
|
57
|
-
add_start =
|
75
|
+
add_start = context > @start_old ? @start_old : context
|
76
|
+
|
58
77
|
@start_old -= add_start
|
59
78
|
@start_new -= add_start
|
60
79
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
80
|
+
old_size = @data_old.size
|
81
|
+
|
82
|
+
add_end =
|
83
|
+
if (@end_old + context) > old_size
|
84
|
+
old_size - @end_old
|
85
|
+
else
|
86
|
+
context
|
87
|
+
end
|
88
|
+
|
89
|
+
add_end = @max_diff_size if add_end >= old_size
|
90
|
+
|
66
91
|
@end_old += add_end
|
67
92
|
@end_new += add_end
|
68
93
|
end
|
69
94
|
|
70
|
-
|
95
|
+
# Merges this hunk and the provided hunk together if they overlap. Returns
|
96
|
+
# a truthy value so that if there is no overlap, you can know the merge
|
97
|
+
# was skipped.
|
98
|
+
def merge(hunk)
|
99
|
+
return unless overlaps?(hunk)
|
100
|
+
|
71
101
|
@start_old = hunk.start_old
|
72
102
|
@start_new = hunk.start_new
|
73
103
|
blocks.unshift(*hunk.blocks)
|
74
104
|
end
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
return (a or b)
|
105
|
+
alias unshift merge
|
106
|
+
|
107
|
+
# Determines whether there is an overlap between this hunk and the
|
108
|
+
# provided hunk. This will be true if the difference between the two hunks
|
109
|
+
# start or end positions is within one position of each other.
|
110
|
+
def overlaps?(hunk)
|
111
|
+
hunk and (((@start_old - hunk.end_old) <= 1) or
|
112
|
+
((@start_new - hunk.end_new) <= 1))
|
84
113
|
end
|
85
114
|
|
86
|
-
|
115
|
+
# Returns a diff string based on a format.
|
116
|
+
def diff(format, last = false)
|
87
117
|
case format
|
88
118
|
when :old
|
89
|
-
old_diff
|
119
|
+
old_diff(last)
|
90
120
|
when :unified
|
91
|
-
unified_diff
|
121
|
+
unified_diff(last)
|
92
122
|
when :context
|
93
|
-
context_diff
|
123
|
+
context_diff(last)
|
94
124
|
when :ed
|
95
125
|
self
|
96
126
|
when :reverse_ed, :ed_finish
|
97
|
-
ed_diff(format)
|
127
|
+
ed_diff(format, last)
|
98
128
|
else
|
99
|
-
|
129
|
+
fail "Unknown diff format #{format}."
|
100
130
|
end
|
101
131
|
end
|
102
132
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
private
|
108
|
-
# Note that an old diff can't have any context. Therefore, we know that
|
109
|
-
# there's only one block in the hunk.
|
110
|
-
def old_diff
|
111
|
-
warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1
|
112
|
-
op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
|
133
|
+
# Note that an old diff can't have any context. Therefore, we know that
|
134
|
+
# there's only one block in the hunk.
|
135
|
+
def old_diff(_last = false)
|
136
|
+
warn 'Expecting only one block in an old diff hunk!' if @blocks.size > 1
|
113
137
|
|
114
138
|
block = @blocks[0]
|
115
139
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
s = "#{context_range(:old)}#{
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
140
|
+
# Calculate item number range. Old diff range is just like a context
|
141
|
+
# diff range, except the ranges are on one line with the action between
|
142
|
+
# them.
|
143
|
+
s = encode("#{context_range(:old, ',')}#{OLD_DIFF_OP_ACTION[block.op]}#{context_range(:new, ',')}\n")
|
144
|
+
# If removing anything, just print out all the remove lines in the hunk
|
145
|
+
# which is just all the remove lines in the block.
|
146
|
+
unless block.remove.empty?
|
147
|
+
@data_old[@start_old..@end_old].each { |e| s << encode('< ') + e.chomp + encode("\n") }
|
148
|
+
end
|
149
|
+
|
150
|
+
s << encode("---\n") if block.op == '!'
|
151
|
+
|
152
|
+
unless block.insert.empty?
|
153
|
+
@data_new[@start_new..@end_new].each { |e| s << encode('> ') + e.chomp + encode("\n") }
|
154
|
+
end
|
155
|
+
|
125
156
|
s
|
126
157
|
end
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
158
|
+
private :old_diff
|
159
|
+
|
160
|
+
def unified_diff(last = false)
|
161
|
+
# Calculate item number range.
|
162
|
+
s = encode("@@ -#{unified_range(:old, last)} +#{unified_range(:new, last)} @@\n")
|
163
|
+
|
164
|
+
# Outlist starts containing the hunk of the old file. Removing an item
|
165
|
+
# just means putting a '-' in front of it. Inserting an item requires
|
166
|
+
# getting it from the new file and splicing it in. We splice in
|
167
|
+
# +num_added+ items. Remove blocks use +num_added+ because splicing
|
168
|
+
# changed the length of outlist.
|
169
|
+
#
|
170
|
+
# We remove +num_removed+ items. Insert blocks use +num_removed+
|
171
|
+
# because their item numbers -- corresponding to positions in the NEW
|
172
|
+
# file -- don't take removed items into account.
|
141
173
|
lo, hi, num_added, num_removed = @start_old, @end_old, 0, 0
|
142
174
|
|
143
|
-
outlist = @data_old[lo
|
175
|
+
outlist = @data_old[lo..hi].map { |e| String.new("#{encode(' ')}#{e.chomp}") }
|
176
|
+
|
177
|
+
last_block = blocks[-1]
|
178
|
+
|
179
|
+
if last
|
180
|
+
old_missing_newline = missing_last_newline?(@data_old)
|
181
|
+
new_missing_newline = missing_last_newline?(@data_new)
|
182
|
+
end
|
144
183
|
|
145
184
|
@blocks.each do |block|
|
146
185
|
block.remove.each do |item|
|
147
|
-
op
|
186
|
+
op = item.action.to_s # -
|
148
187
|
offset = item.position - lo + num_added
|
149
|
-
outlist[offset]
|
188
|
+
outlist[offset][0, 1] = encode(op)
|
150
189
|
num_removed += 1
|
151
190
|
end
|
191
|
+
|
192
|
+
if last && block == last_block && old_missing_newline && !new_missing_newline
|
193
|
+
outlist << encode('\')
|
194
|
+
num_removed += 1
|
195
|
+
end
|
196
|
+
|
152
197
|
block.insert.each do |item|
|
153
|
-
op
|
198
|
+
op = item.action.to_s # +
|
154
199
|
offset = item.position - @start_new + num_removed
|
155
|
-
outlist[offset, 0] =
|
200
|
+
outlist[offset, 0] = encode(op) + @data_new[item.position].chomp
|
156
201
|
num_added += 1
|
157
202
|
end
|
158
203
|
end
|
159
204
|
|
160
|
-
|
205
|
+
outlist << encode('\') if last && new_missing_newline
|
206
|
+
|
207
|
+
s << outlist.join(encode("\n"))
|
208
|
+
|
209
|
+
s
|
161
210
|
end
|
211
|
+
private :unified_diff
|
212
|
+
|
213
|
+
def context_diff(last = false)
|
214
|
+
s = encode("***************\n")
|
215
|
+
s << encode("*** #{context_range(:old, ',', last)} ****\n")
|
216
|
+
r = context_range(:new, ',', last)
|
162
217
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
218
|
+
if last
|
219
|
+
old_missing_newline = missing_last_newline?(@data_old)
|
220
|
+
new_missing_newline = missing_last_newline?(@data_new)
|
221
|
+
end
|
167
222
|
|
168
|
-
|
169
|
-
|
223
|
+
# Print out file 1 part for each block in context diff format if there
|
224
|
+
# are any blocks that remove items
|
170
225
|
lo, hi = @start_old, @end_old
|
171
|
-
removes = @blocks.
|
172
|
-
|
173
|
-
|
226
|
+
removes = @blocks.reject { |e| e.remove.empty? }
|
227
|
+
|
228
|
+
unless removes.empty?
|
229
|
+
outlist = @data_old[lo..hi].map { |e| String.new("#{encode(' ')}#{e.chomp}") }
|
230
|
+
|
231
|
+
last_block = removes[-1]
|
232
|
+
|
174
233
|
removes.each do |block|
|
175
234
|
block.remove.each do |item|
|
176
|
-
outlist[item.position - lo]
|
235
|
+
outlist[item.position - lo][0, 1] = encode(block.op) # - or !
|
236
|
+
end
|
237
|
+
|
238
|
+
if last && block == last_block && old_missing_newline
|
239
|
+
outlist << encode('\')
|
177
240
|
end
|
178
241
|
end
|
179
|
-
|
242
|
+
|
243
|
+
s << outlist.join(encode("\n")) << encode("\n")
|
180
244
|
end
|
181
245
|
|
182
|
-
s << "
|
246
|
+
s << encode("--- #{r} ----\n")
|
183
247
|
lo, hi = @start_new, @end_new
|
184
|
-
inserts = @blocks.
|
185
|
-
|
186
|
-
|
248
|
+
inserts = @blocks.reject { |e| e.insert.empty? }
|
249
|
+
|
250
|
+
unless inserts.empty?
|
251
|
+
outlist = @data_new[lo..hi].map { |e| String.new("#{encode(' ')}#{e.chomp}") }
|
252
|
+
|
253
|
+
last_block = inserts[-1]
|
254
|
+
|
187
255
|
inserts.each do |block|
|
188
256
|
block.insert.each do |item|
|
189
|
-
outlist[item.position - lo]
|
257
|
+
outlist[item.position - lo][0, 1] = encode(block.op) # + or !
|
258
|
+
end
|
259
|
+
|
260
|
+
if last && block == last_block && new_missing_newline
|
261
|
+
outlist << encode('\')
|
190
262
|
end
|
191
263
|
end
|
192
|
-
s << outlist.join("\n")
|
264
|
+
s << outlist.join(encode("\n"))
|
193
265
|
end
|
266
|
+
|
194
267
|
s
|
195
268
|
end
|
269
|
+
private :context_diff
|
196
270
|
|
197
|
-
def ed_diff(format)
|
198
|
-
|
199
|
-
warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1
|
271
|
+
def ed_diff(format, _last = false)
|
272
|
+
warn 'Expecting only one block in an old diff hunk!' if @blocks.size > 1
|
200
273
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
274
|
+
s =
|
275
|
+
if format == :reverse_ed
|
276
|
+
encode("#{ED_DIFF_OP_ACTION[@blocks[0].op]}#{context_range(:old, ',')}\n")
|
277
|
+
else
|
278
|
+
encode("#{context_range(:old, ' ')}#{ED_DIFF_OP_ACTION[@blocks[0].op]}\n")
|
279
|
+
end
|
206
280
|
|
207
281
|
unless @blocks[0].insert.empty?
|
208
|
-
@data_new[@start_new
|
209
|
-
|
282
|
+
@data_new[@start_new..@end_new].each do |e|
|
283
|
+
s << e.chomp + encode("\n")
|
284
|
+
end
|
285
|
+
s << encode(".\n")
|
210
286
|
end
|
211
287
|
s
|
212
288
|
end
|
289
|
+
private :ed_diff
|
213
290
|
|
214
|
-
|
215
|
-
|
216
|
-
def context_range(mode)
|
291
|
+
# Generate a range of item numbers to print. Only print 1 number if the
|
292
|
+
# range has only one item in it. Otherwise, it's 'start,end'
|
293
|
+
def context_range(mode, op, last = false)
|
217
294
|
case mode
|
218
295
|
when :old
|
219
296
|
s, e = (@start_old + 1), (@end_old + 1)
|
@@ -221,13 +298,17 @@ class Diff::LCS::Hunk
|
|
221
298
|
s, e = (@start_new + 1), (@end_new + 1)
|
222
299
|
end
|
223
300
|
|
224
|
-
|
301
|
+
e -= 1 if last
|
302
|
+
e = 1 if e.zero?
|
303
|
+
|
304
|
+
s < e ? "#{s}#{op}#{e}" : e.to_s
|
225
305
|
end
|
306
|
+
private :context_range
|
226
307
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
def unified_range(mode)
|
308
|
+
# Generate a range of item numbers to print for unified diff. Print number
|
309
|
+
# where block starts, followed by number of lines in the block
|
310
|
+
# (don't print number of lines if it's 1)
|
311
|
+
def unified_range(mode, last)
|
231
312
|
case mode
|
232
313
|
when :old
|
233
314
|
s, e = (@start_old + 1), (@end_old + 1)
|
@@ -235,8 +316,43 @@ class Diff::LCS::Hunk
|
|
235
316
|
s, e = (@start_new + 1), (@end_new + 1)
|
236
317
|
end
|
237
318
|
|
238
|
-
length = e - s + 1
|
239
|
-
|
240
|
-
|
319
|
+
length = e - s + (last ? 0 : 1)
|
320
|
+
|
321
|
+
first = length < 2 ? e : s # "strange, but correct"
|
322
|
+
length <= 1 ? first.to_s : "#{first},#{length}"
|
241
323
|
end
|
324
|
+
private :unified_range
|
325
|
+
|
326
|
+
def missing_last_newline?(data)
|
327
|
+
newline = encode("\n")
|
328
|
+
|
329
|
+
if data[-2]
|
330
|
+
data[-2].end_with?(newline) && !data[-1].end_with?(newline)
|
331
|
+
elsif data[-1]
|
332
|
+
!data[-1].end_with?(newline)
|
333
|
+
else
|
334
|
+
true
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
if String.method_defined?(:encoding)
|
339
|
+
def encode(literal, target_encoding = @preferred_data_encoding)
|
340
|
+
literal.encode target_encoding
|
341
|
+
end
|
342
|
+
|
343
|
+
def encode_as(string, *args)
|
344
|
+
args.map { |arg| arg.encode(string.encoding) }
|
345
|
+
end
|
346
|
+
else
|
347
|
+
def encode(literal, _target_encoding = nil)
|
348
|
+
literal
|
349
|
+
end
|
350
|
+
|
351
|
+
def encode_as(_string, *args)
|
352
|
+
args
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
private :encode
|
357
|
+
private :encode_as
|
242
358
|
end
|