diff-lcs 1.1.3 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|