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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +1 -0
  3. data/Code-of-Conduct.md +74 -0
  4. data/Contributing.md +119 -0
  5. data/History.md +400 -0
  6. data/{License.rdoc → License.md} +6 -5
  7. data/Manifest.txt +36 -4
  8. data/README.rdoc +35 -23
  9. data/Rakefile +106 -11
  10. data/bin/htmldiff +7 -4
  11. data/bin/ldiff +4 -1
  12. data/docs/COPYING.txt +21 -22
  13. data/docs/artistic.txt +127 -0
  14. data/lib/diff/lcs/array.rb +1 -15
  15. data/lib/diff/lcs/backports.rb +9 -0
  16. data/lib/diff/lcs/block.rb +4 -18
  17. data/lib/diff/lcs/callbacks.rb +233 -230
  18. data/lib/diff/lcs/change.rb +114 -109
  19. data/lib/diff/lcs/htmldiff.rb +17 -18
  20. data/lib/diff/lcs/hunk.rb +232 -116
  21. data/lib/diff/lcs/internals.rb +308 -0
  22. data/lib/diff/lcs/ldiff.rb +138 -177
  23. data/lib/diff/lcs/string.rb +1 -15
  24. data/lib/diff/lcs.rb +597 -963
  25. data/lib/diff-lcs.rb +1 -3
  26. data/spec/change_spec.rb +89 -0
  27. data/spec/diff_spec.rb +32 -16
  28. data/spec/fixtures/aX +1 -0
  29. data/spec/fixtures/bXaX +1 -0
  30. data/spec/fixtures/ds1.csv +50 -0
  31. data/spec/fixtures/ds2.csv +51 -0
  32. data/spec/fixtures/ldiff/output.diff +4 -0
  33. data/spec/fixtures/ldiff/output.diff-c +7 -0
  34. data/spec/fixtures/ldiff/output.diff-e +3 -0
  35. data/spec/fixtures/ldiff/output.diff-f +3 -0
  36. data/spec/fixtures/ldiff/output.diff-u +5 -0
  37. data/spec/fixtures/ldiff/output.diff.chef +4 -0
  38. data/spec/fixtures/ldiff/output.diff.chef-c +15 -0
  39. data/spec/fixtures/ldiff/output.diff.chef-e +3 -0
  40. data/spec/fixtures/ldiff/output.diff.chef-f +3 -0
  41. data/spec/fixtures/ldiff/output.diff.chef-u +9 -0
  42. data/spec/fixtures/ldiff/output.diff.chef2 +7 -0
  43. data/spec/fixtures/ldiff/output.diff.chef2-c +20 -0
  44. data/spec/fixtures/ldiff/output.diff.chef2-d +7 -0
  45. data/spec/fixtures/ldiff/output.diff.chef2-e +7 -0
  46. data/spec/fixtures/ldiff/output.diff.chef2-f +7 -0
  47. data/spec/fixtures/ldiff/output.diff.chef2-u +16 -0
  48. data/spec/fixtures/new-chef +4 -0
  49. data/spec/fixtures/new-chef2 +17 -0
  50. data/spec/fixtures/old-chef +4 -0
  51. data/spec/fixtures/old-chef2 +14 -0
  52. data/spec/hunk_spec.rb +83 -0
  53. data/spec/issues_spec.rb +154 -0
  54. data/spec/lcs_spec.rb +36 -16
  55. data/spec/ldiff_spec.rb +87 -0
  56. data/spec/patch_spec.rb +198 -172
  57. data/spec/sdiff_spec.rb +99 -89
  58. data/spec/spec_helper.rb +149 -59
  59. data/spec/traverse_balanced_spec.rb +191 -167
  60. data/spec/traverse_sequences_spec.rb +105 -51
  61. metadata +218 -99
  62. data/.gemtest +0 -0
  63. data/History.rdoc +0 -54
  64. data/diff-lcs.gemspec +0 -51
  65. 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
- # A Hunk is a group of Blocks which overlap because of the context
4
- # surrounding each block. (So if we're not using context, every hunk will
5
- # contain one block.) Used in the diff program (bin/diff).
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
- # Create a hunk using references to both the old and new data, as well as
8
- # the piece of data
9
- def initialize(data_old, data_new, piece, context, file_length_difference)
10
- # At first, a hunk will have just one Block in it
11
- @blocks = [ Diff::LCS::Block.new(piece) ]
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
- # Save the start & end of each array. If the array doesn't exist
20
- # (e.g., we're only adding items in this block), then figure out the
21
- # line number based on the line number of the other file and the
22
- # current difference in file lengths.
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 = 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
- # Change the "start" and "end" fields to note that context should be added
51
- # to this hunk
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 = (context > @start_old) ? @start_old : context
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
- if (@end_old + context) > @data_old.size
62
- add_end = @data_old.size - @end_old
63
- else
64
- add_end = context
65
- end
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
- def unshift(hunk)
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
- # Is there an overlap between hunk arg0 and old hunk arg1? Note: if end
77
- # of old hunk is one less than beginning of second, they overlap
78
- def overlaps?(hunk = nil)
79
- return nil if hunk.nil?
80
-
81
- a = (@start_old - hunk.end_old) <= 1
82
- b = (@start_new - hunk.end_new) <= 1
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
- def diff(format)
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
- raise "Unknown diff format #{format}."
129
+ fail "Unknown diff format #{format}."
100
130
  end
101
131
  end
102
132
 
103
- def each_old(block)
104
- @data_old[@start_old .. @end_old].each { |e| yield e }
105
- end
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
- # Calculate item number range. Old diff range is just like a context
117
- # diff range, except the ranges are on one line with the action between
118
- # them.
119
- s = "#{context_range(:old)}#{op_act[block.op]}#{context_range(:new)}\n"
120
- # If removing anything, just print out all the remove lines in the hunk
121
- # which is just all the remove lines in the block.
122
- @data_old[@start_old .. @end_old].each { |e| s << "< #{e}\n" } unless block.remove.empty?
123
- s << "---\n" if block.op == "!"
124
- @data_new[@start_new .. @end_new].each { |e| s << "> #{e}\n" } unless block.insert.empty?
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
- def unified_diff
129
- # Calculate item number range.
130
- s = "@@ -#{unified_range(:old)} +#{unified_range(:new)} @@\n"
131
-
132
- # Outlist starts containing the hunk of the old file. Removing an item
133
- # just means putting a '-' in front of it. Inserting an item requires
134
- # getting it from the new file and splicing it in. We splice in
135
- # +num_added+ items. Remove blocks use +num_added+ because splicing
136
- # changed the length of outlist.
137
- #
138
- # We remove +num_removed+ items. Insert blocks use +num_removed+
139
- # because their item numbers -- corresponding to positions in the NEW
140
- # file -- don't take removed items into account.
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 .. hi].collect { |e| e.gsub(/^/, ' ') }
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 = item.action.to_s # -
186
+ op = item.action.to_s # -
148
187
  offset = item.position - lo + num_added
149
- outlist[offset].gsub!(/^ /, op.to_s)
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 = item.action.to_s # +
198
+ op = item.action.to_s # +
154
199
  offset = item.position - @start_new + num_removed
155
- outlist[offset, 0] = "#{op}#{@data_new[item.position]}"
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
- s << outlist.join("\n")
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
- def context_diff
164
- s = "***************\n"
165
- s << "*** #{context_range(:old)} ****\n"
166
- r = context_range(:new)
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
- # Print out file 1 part for each block in context diff format if there
169
- # are any blocks that remove items
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.select { |e| not e.remove.empty? }
172
- if removes
173
- outlist = @data_old[lo .. hi].collect { |e| e.gsub(/^/, ' ') }
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].gsub!(/^ /) { block.op } # - or !
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
- s << outlist.join("\n")
242
+
243
+ s << outlist.join(encode("\n")) << encode("\n")
180
244
  end
181
245
 
182
- s << "\n--- #{r} ----\n"
246
+ s << encode("--- #{r} ----\n")
183
247
  lo, hi = @start_new, @end_new
184
- inserts = @blocks.select { |e| not e.insert.empty? }
185
- if inserts
186
- outlist = @data_new[lo .. hi].collect { |e| e.gsub(/^/, ' ') }
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].gsub!(/^ /) { block.op } # + or !
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
- op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
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
- if format == :reverse_ed
202
- s = "#{op_act[@blocks[0].op]}#{context_range(:old)}\n"
203
- else
204
- s = "#{context_range(:old).gsub(/,/, ' ')}#{op_act[@blocks[0].op]}\n"
205
- end
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 .. @end_new].each { |e| s << "#{e}\n" }
209
- s << ".\n"
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
- # Generate a range of item numbers to print. Only print 1 number if the
215
- # range has only one item in it. Otherwise, it's 'start,end'
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
- (s < e) ? "#{s},#{e}" : "#{e}"
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
- # Generate a range of item numbers to print for unified diff. Print
228
- # number where block starts, followed by number of lines in the block
229
- # (don't print number of lines if it's 1)
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
- first = (length < 2) ? e : s # "strange, but correct"
240
- (length == 1) ? "#{first}" : "#{first},#{length}"
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