diff-lcs 1.3 → 1.5.1

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.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/Contributing.md +86 -48
  3. data/History.md +370 -159
  4. data/License.md +6 -4
  5. data/Manifest.txt +23 -1
  6. data/README.rdoc +10 -10
  7. data/Rakefile +109 -36
  8. data/bin/htmldiff +9 -6
  9. data/bin/ldiff +4 -1
  10. data/lib/diff/lcs/array.rb +2 -2
  11. data/lib/diff/lcs/backports.rb +9 -0
  12. data/lib/diff/lcs/block.rb +5 -5
  13. data/lib/diff/lcs/callbacks.rb +22 -17
  14. data/lib/diff/lcs/change.rb +42 -49
  15. data/lib/diff/lcs/htmldiff.rb +21 -12
  16. data/lib/diff/lcs/hunk.rb +160 -73
  17. data/lib/diff/lcs/internals.rb +57 -56
  18. data/lib/diff/lcs/ldiff.rb +63 -57
  19. data/lib/diff/lcs/string.rb +1 -1
  20. data/lib/diff/lcs.rb +226 -210
  21. data/lib/diff-lcs.rb +2 -2
  22. data/spec/change_spec.rb +58 -34
  23. data/spec/diff_spec.rb +13 -9
  24. data/spec/fixtures/aX +1 -0
  25. data/spec/fixtures/bXaX +1 -0
  26. data/spec/fixtures/ldiff/output.diff +4 -0
  27. data/spec/fixtures/ldiff/output.diff-c +7 -0
  28. data/spec/fixtures/ldiff/output.diff-e +3 -0
  29. data/spec/fixtures/ldiff/output.diff-f +3 -0
  30. data/spec/fixtures/ldiff/output.diff-u +5 -0
  31. data/spec/fixtures/ldiff/output.diff.chef +4 -0
  32. data/spec/fixtures/ldiff/output.diff.chef-c +15 -0
  33. data/spec/fixtures/ldiff/output.diff.chef-e +3 -0
  34. data/spec/fixtures/ldiff/output.diff.chef-f +3 -0
  35. data/spec/fixtures/ldiff/output.diff.chef-u +9 -0
  36. data/spec/fixtures/ldiff/output.diff.chef2 +7 -0
  37. data/spec/fixtures/ldiff/output.diff.chef2-c +20 -0
  38. data/spec/fixtures/ldiff/output.diff.chef2-d +7 -0
  39. data/spec/fixtures/ldiff/output.diff.chef2-e +7 -0
  40. data/spec/fixtures/ldiff/output.diff.chef2-f +7 -0
  41. data/spec/fixtures/ldiff/output.diff.chef2-u +16 -0
  42. data/spec/fixtures/new-chef +4 -0
  43. data/spec/fixtures/new-chef2 +17 -0
  44. data/spec/fixtures/old-chef +4 -0
  45. data/spec/fixtures/old-chef2 +14 -0
  46. data/spec/hunk_spec.rb +48 -37
  47. data/spec/issues_spec.rb +132 -21
  48. data/spec/lcs_spec.rb +3 -3
  49. data/spec/ldiff_spec.rb +74 -32
  50. data/spec/patch_spec.rb +14 -20
  51. data/spec/sdiff_spec.rb +83 -81
  52. data/spec/spec_helper.rb +146 -91
  53. data/spec/traverse_balanced_spec.rb +138 -136
  54. data/spec/traverse_sequences_spec.rb +7 -9
  55. metadata +76 -48
  56. data/autotest/discover.rb +0 -1
@@ -1,4 +1,4 @@
1
- # -*- ruby encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  class << Diff::LCS
4
4
  def diff_traversal(method, seq1, seq2, callbacks, &block)
@@ -13,7 +13,7 @@ class << Diff::LCS
13
13
 
14
14
  if block
15
15
  callbacks.diffs.map do |hunk|
16
- if hunk.kind_of? Array
16
+ if hunk.is_a? Array
17
17
  hunk.map { |hunk_block| block[hunk_block] }
18
18
  else
19
19
  block[hunk]
@@ -44,47 +44,49 @@ class << Diff::LCS::Internals
44
44
  b_finish = b.size - 1
45
45
  vector = []
46
46
 
47
- # Prune off any common elements at the beginning...
48
- while ((a_start <= a_finish) and (b_start <= b_finish) and
49
- (a[a_start] == b[b_start]))
47
+ # Collect any common elements at the beginning...
48
+ while (a_start <= a_finish) && (b_start <= b_finish) && (a[a_start] == b[b_start])
50
49
  vector[a_start] = b_start
51
50
  a_start += 1
52
51
  b_start += 1
53
52
  end
54
- b_start = a_start
55
53
 
56
54
  # Now the end...
57
- while ((a_start <= a_finish) and (b_start <= b_finish) and
58
- (a[a_finish] == b[b_finish]))
55
+ while (a_start <= a_finish) && (b_start <= b_finish) && (a[a_finish] == b[b_finish])
59
56
  vector[a_finish] = b_finish
60
57
  a_finish -= 1
61
58
  b_finish -= 1
62
59
  end
63
60
 
64
61
  # Now, compute the equivalence classes of positions of elements.
62
+ # An explanation for how this works: https://codeforces.com/topic/92191
65
63
  b_matches = position_hash(b, b_start..b_finish)
66
64
 
67
65
  thresh = []
68
- links = []
69
- string = a.kind_of?(String)
66
+ links = []
67
+ string = a.is_a?(String)
70
68
 
71
- (a_start .. a_finish).each do |i|
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
75
73
  bm.reverse_each do |j|
76
- if k and (thresh[k] > j) and (thresh[k - 1] < 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 && (thresh[k] > j) && (thresh[k - 1] < j)
77
79
  thresh[k] = j
78
80
  else
79
81
  k = replace_next_larger(thresh, j, k)
80
82
  end
81
- links[k] = [ (k > 0) ? links[k - 1] : nil, i, j ] unless k.nil?
83
+ links[k] = [k.positive? ? links[k - 1] : nil, i, j] unless k.nil?
82
84
  end
83
85
  end
84
86
 
85
87
  unless thresh.empty?
86
88
  link = links[thresh.size - 1]
87
- while not link.nil?
89
+ until link.nil?
88
90
  vector[link[1]] = link[2]
89
91
  link = link[0]
90
92
  end
@@ -93,14 +95,15 @@ class << Diff::LCS::Internals
93
95
  vector
94
96
  end
95
97
 
96
- # This method will analyze the provided patchset to provide a
97
- # single-pass normalization (conversion of the array form of
98
- # Diff::LCS::Change objects to the object form of same) and detection of
99
- # whether the patchset represents changes to be made.
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.
100
102
  def analyze_patchset(patchset, depth = 0)
101
- raise "Patchset too complex" if depth > 1
103
+ fail "Patchset too complex" if depth > 1
102
104
 
103
105
  has_changes = false
106
+ new_patchset = []
104
107
 
105
108
  # Format:
106
109
  # [ # patchset
@@ -110,29 +113,28 @@ class << Diff::LCS::Internals
110
113
  # ]
111
114
  # ]
112
115
 
113
- patchset = patchset.map do |hunk|
116
+ patchset.each do |hunk|
114
117
  case hunk
115
118
  when Diff::LCS::Change
116
119
  has_changes ||= !hunk.unchanged?
117
- hunk
120
+ new_patchset << hunk
118
121
  when Array
119
- # Detect if the 'hunk' is actually an array-format
120
- # Change object.
122
+ # Detect if the 'hunk' is actually an array-format change object.
121
123
  if Diff::LCS::Change.valid_action? hunk[0]
122
124
  hunk = Diff::LCS::Change.from_a(hunk)
123
125
  has_changes ||= !hunk.unchanged?
124
- hunk
126
+ new_patchset << hunk
125
127
  else
126
128
  with_changes, hunk = analyze_patchset(hunk, depth + 1)
127
129
  has_changes ||= with_changes
128
- hunk.flatten
130
+ new_patchset.concat(hunk)
129
131
  end
130
132
  else
131
- raise ArgumentError, "Cannot normalise a hunk of class #{hunk.class}."
133
+ fail ArgumentError, "Cannot normalise a hunk of class #{hunk.class}."
132
134
  end
133
135
  end
134
136
 
135
- [ has_changes, patchset.flatten(1) ]
137
+ [has_changes, new_patchset]
136
138
  end
137
139
 
138
140
  # Examine the patchset and the source to see in which direction the
@@ -143,7 +145,7 @@ class << Diff::LCS::Internals
143
145
  # Diff::LCS::Change as its source, as an array will cause the creation
144
146
  # of one of the above.
145
147
  def intuit_diff_direction(src, patchset, limit = nil)
146
- string = src.kind_of?(String)
148
+ string = src.is_a?(String)
147
149
  count = left_match = left_miss = right_match = right_miss = 0
148
150
 
149
151
  patchset.each do |change|
@@ -155,31 +157,29 @@ class << Diff::LCS::Internals
155
157
  re = string ? src[change.new_position, 1] : src[change.new_position]
156
158
 
157
159
  case change.action
158
- when '-' # Remove details from the old string
160
+ when "-" # Remove details from the old string
159
161
  if le == change.old_element
160
162
  left_match += 1
161
163
  else
162
164
  left_miss += 1
163
165
  end
164
- when '+'
166
+ when "+"
165
167
  if re == change.new_element
166
168
  right_match += 1
167
169
  else
168
170
  right_miss += 1
169
171
  end
170
- when '='
172
+ when "="
171
173
  left_miss += 1 if le != change.old_element
172
174
  right_miss += 1 if re != change.new_element
173
- when '!'
175
+ when "!"
174
176
  if le == change.old_element
175
177
  left_match += 1
178
+ elsif re == change.new_element
179
+ right_match += 1
176
180
  else
177
- if re == change.new_element
178
- right_match += 1
179
- else
180
- left_miss += 1
181
- right_miss += 1
182
- end
181
+ left_miss += 1
182
+ right_miss += 1
183
183
  end
184
184
  end
185
185
  when Diff::LCS::Change
@@ -189,19 +189,19 @@ class << Diff::LCS::Internals
189
189
  element = string ? src[change.position, 1] : src[change.position]
190
190
 
191
191
  case change.action
192
- when '-'
192
+ when "-"
193
193
  if element == change.element
194
194
  left_match += 1
195
195
  else
196
196
  left_miss += 1
197
197
  end
198
- when '+'
198
+ when "+"
199
199
  if element == change.element
200
200
  right_match += 1
201
201
  else
202
202
  right_miss += 1
203
203
  end
204
- when '='
204
+ when "="
205
205
  if element != change.element
206
206
  left_miss += 1
207
207
  right_miss += 1
@@ -209,16 +209,16 @@ class << Diff::LCS::Internals
209
209
  end
210
210
  end
211
211
 
212
- break if (not limit.nil?) && (count > limit)
212
+ break if !limit.nil? && (count > limit)
213
213
  end
214
214
 
215
- no_left = (left_match == 0) && (left_miss > 0)
216
- no_right = (right_match == 0) && (right_miss > 0)
215
+ no_left = left_match.zero? && left_miss.positive?
216
+ no_right = right_match.zero? && right_miss.positive?
217
217
 
218
- case [ no_left, no_right ]
219
- when [ false, true ]
218
+ case [no_left, no_right]
219
+ when [false, true]
220
220
  :patch
221
- when [ true, false ]
221
+ when [true, false]
222
222
  :unpatch
223
223
  else
224
224
  case left_match <=> right_match
@@ -235,7 +235,8 @@ class << Diff::LCS::Internals
235
235
  :patch
236
236
  end
237
237
  else
238
- raise "The provided patchset does not appear to apply to the provided enumerable as either source or destination value."
238
+ fail "The provided patchset does not appear to apply to the provided \
239
+ enumerable as either source or destination value."
239
240
  end
240
241
  end
241
242
  end
@@ -250,22 +251,22 @@ class << Diff::LCS::Internals
250
251
  # This operation preserves the sort order.
251
252
  def replace_next_larger(enum, value, last_index = nil)
252
253
  # Off the end?
253
- if enum.empty? or (value > enum[-1])
254
+ if enum.empty? || (value > enum[-1])
254
255
  enum << value
255
256
  return enum.size - 1
256
257
  end
257
258
 
258
259
  # Binary search for the insertion point
259
- last_index ||= enum.size
260
+ last_index ||= enum.size - 1
260
261
  first_index = 0
261
- while (first_index <= last_index)
262
+ while first_index <= last_index
262
263
  i = (first_index + last_index) >> 1
263
264
 
264
265
  found = enum[i]
265
266
 
266
- if value == found
267
- return nil
268
- elsif value > found
267
+ return nil if value == found
268
+
269
+ if value > found
269
270
  first_index = i + 1
270
271
  else
271
272
  last_index = i - 1
@@ -275,7 +276,7 @@ class << Diff::LCS::Internals
275
276
  # The insertion point is in first_index; overwrite the next larger
276
277
  # value.
277
278
  enum[first_index] = value
278
- return first_index
279
+ first_index
279
280
  end
280
281
  private :replace_next_larger
281
282
 
@@ -295,7 +296,7 @@ class << Diff::LCS::Internals
295
296
  # positions it occupies in the Enumerable, optionally restricted to the
296
297
  # elements specified in the range of indexes specified by +interval+.
297
298
  def position_hash(enum, interval)
298
- string = enum.kind_of?(String)
299
+ string = enum.is_a?(String)
299
300
  hash = Hash.new { |h, k| h[k] = [] }
300
301
  interval.each do |i|
301
302
  k = string ? enum[i, 1] : enum[i]
@@ -1,13 +1,14 @@
1
- # -*- ruby encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
- require 'optparse'
4
- require 'ostruct'
5
- require 'diff/lcs/hunk'
3
+ require "optparse"
4
+ require "ostruct"
5
+ require "diff/lcs/hunk"
6
6
 
7
- module Diff::LCS::Ldiff #:nodoc:
7
+ module Diff::LCS::Ldiff # :nodoc:
8
+ # standard:disable Layout/HeredocIndentation
8
9
  BANNER = <<-COPYRIGHT
9
10
  ldiff #{Diff::LCS::VERSION}
10
- Copyright 2004-2014 Austin Ziegler
11
+ Copyright 2004-2019 Austin Ziegler
11
12
 
12
13
  Part of Diff::LCS.
13
14
  https://github.com/halostatue/diff-lcs
@@ -15,48 +16,58 @@ ldiff #{Diff::LCS::VERSION}
15
16
  This program is free software. It may be redistributed and/or modified under
16
17
  the terms of the GPL version 2 (or later), the Perl Artistic licence, or the
17
18
  MIT licence.
18
- COPYRIGHT
19
+ COPYRIGHT
20
+ # standard:enable Layout/HeredocIndentation
19
21
  end
20
22
 
21
23
  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:
24
+ attr_reader :format, :lines # :nodoc:
25
+ attr_reader :file_old, :file_new # :nodoc:
26
+ attr_reader :data_old, :data_new # :nodoc:
25
27
 
26
- def run(args, input = $stdin, output = $stdout, error = $stderr) #:nodoc:
28
+ def run(args, _input = $stdin, output = $stdout, error = $stderr) # :nodoc:
27
29
  @binary = nil
28
30
 
29
31
  args.options do |o|
30
32
  o.banner = "Usage: #{File.basename($0)} [options] oldfile newfile"
31
33
  o.separator ""
32
- o.on('-c', '-C', '--context [LINES]', Numeric, 'Displays a context diff with LINES lines', 'of context. Default 3 lines.') do |ctx|
34
+ o.on(
35
+ "-c", "-C", "--context [LINES]", Integer,
36
+ "Displays a context diff with LINES lines", "of context. Default 3 lines."
37
+ ) do |ctx|
33
38
  @format = :context
34
- @lines = ctx || 3
39
+ @lines = ctx || 3
35
40
  end
36
- o.on('-u', '-U', '--unified [LINES]', Numeric, 'Displays a unified diff with LINES lines', 'of context. Default 3 lines.') do |ctx|
41
+ o.on(
42
+ "-u", "-U", "--unified [LINES]", Integer,
43
+ "Displays a unified diff with LINES lines", "of context. Default 3 lines."
44
+ ) do |ctx|
37
45
  @format = :unified
38
- @lines = ctx || 3
46
+ @lines = ctx || 3
39
47
  end
40
- o.on('-e', 'Creates an \'ed\' script to change', 'oldfile to newfile.') do |ctx|
48
+ o.on("-e", "Creates an 'ed' script to change", "oldfile to newfile.") do |_ctx|
41
49
  @format = :ed
42
50
  end
43
- o.on('-f', 'Creates an \'ed\' script to change', 'oldfile to newfile in reverse order.') do |ctx|
51
+ o.on("-f", "Creates an 'ed' script to change", "oldfile to newfile in reverse order.") do |_ctx|
44
52
  @format = :reverse_ed
45
53
  end
46
- 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|
54
+ o.on(
55
+ "-a", "--text",
56
+ "Treat the files as text and compare them", "line-by-line, even if they do not seem", "to be text."
57
+ ) do |_txt|
47
58
  @binary = false
48
59
  end
49
- o.on('--binary', 'Treats the files as binary.') do |bin|
60
+ o.on("--binary", "Treats the files as binary.") do |_bin|
50
61
  @binary = true
51
62
  end
52
- o.on('-q', '--brief', 'Report only whether or not the files', 'differ, not the details.') do |ctx|
63
+ o.on("-q", "--brief", "Report only whether or not the files", "differ, not the details.") do |_ctx|
53
64
  @format = :report
54
65
  end
55
- o.on_tail('--help', 'Shows this text.') do
66
+ o.on_tail("--help", "Shows this text.") do
56
67
  error << o
57
68
  return 0
58
69
  end
59
- o.on_tail('--version', 'Shows the version of Diff::LCS.') do
70
+ o.on_tail("--version", "Shows the version of Diff::LCS.") do
60
71
  error << Diff::LCS::Ldiff::BANNER
61
72
  return 0
62
73
  end
@@ -72,42 +83,36 @@ class << Diff::LCS::Ldiff
72
83
 
73
84
  # Defaults are for old-style diff
74
85
  @format ||= :old
75
- @lines ||= 0
86
+ @lines ||= 0
76
87
 
77
88
  file_old, file_new = *ARGV
78
89
 
79
90
  case @format
80
91
  when :context
81
- char_old = '*' * 3
82
- char_new = '-' * 3
92
+ char_old = "*" * 3
93
+ char_new = "-" * 3
83
94
  when :unified
84
- char_old = '-' * 3
85
- char_new = '+' * 3
95
+ char_old = "-" * 3
96
+ char_new = "+" * 3
86
97
  end
87
98
 
88
99
  # After we've read up to a certain point in each file, the number of
89
100
  # items we've read from each file will differ by FLD (could be 0).
90
101
  file_length_difference = 0
91
102
 
92
- if @binary.nil? or @binary
93
- data_old = IO.read(file_old)
94
- data_new = IO.read(file_new)
103
+ data_old = File.read(file_old)
104
+ data_new = File.read(file_new)
95
105
 
96
- # Test binary status
97
- if @binary.nil?
98
- old_txt = data_old[0, 4096].scan(/\0/).empty?
99
- new_txt = data_new[0, 4096].scan(/\0/).empty?
100
- @binary = (not old_txt) or (not new_txt)
101
- old_txt = new_txt = nil
102
- end
106
+ # Test binary status
107
+ if @binary.nil?
108
+ old_txt = data_old[0, 4096].scan(/\0/).empty?
109
+ new_txt = data_new[0, 4096].scan(/\0/).empty?
110
+ @binary = !old_txt || !new_txt
111
+ end
103
112
 
104
- unless @binary
105
- data_old = data_old.split($/).map { |e| e.chomp }
106
- data_new = data_new.split($/).map { |e| e.chomp }
107
- end
108
- else
109
- data_old = IO.readlines(file_old).map { |e| e.chomp }
110
- data_new = IO.readlines(file_new).map { |e| e.chomp }
113
+ unless @binary
114
+ data_old = data_old.lines.to_a
115
+ data_new = data_new.lines.to_a
111
116
  end
112
117
 
113
118
  # diff yields lots of pieces, each of which is basically a Block object
@@ -125,10 +130,10 @@ class << Diff::LCS::Ldiff
125
130
  return 1
126
131
  end
127
132
 
128
- if (@format == :unified) or (@format == :context)
129
- ft = File.stat(file_old).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
133
+ if (@format == :unified) || (@format == :context)
134
+ ft = File.stat(file_old).mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.000000000 %z")
130
135
  output << "#{char_old} #{file_old}\t#{ft}\n"
131
- ft = File.stat(file_new).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
136
+ ft = File.stat(file_new).mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.000000000 %z")
132
137
  output << "#{char_new} #{file_new}\t#{ft}\n"
133
138
  end
134
139
 
@@ -142,26 +147,27 @@ class << Diff::LCS::Ldiff
142
147
  end
143
148
 
144
149
  diffs.each do |piece|
145
- begin
146
- hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, @lines,
147
- file_length_difference)
150
+ begin # rubocop:disable Style/RedundantBegin
151
+ hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, @lines, file_length_difference)
148
152
  file_length_difference = hunk.file_length_difference
149
153
 
150
154
  next unless oldhunk
151
- next if (@lines > 0) and hunk.merge(oldhunk)
155
+ next if @lines.positive? && hunk.merge(oldhunk)
152
156
 
153
- output << oldhunk.diff(@format) << "\n"
157
+ output << oldhunk.diff(@format)
158
+ output << "\n" if @format == :unified
154
159
  ensure
155
160
  oldhunk = hunk
156
161
  end
157
162
  end
158
163
 
159
- output << oldhunk.diff(@format) << "\n"
164
+ last = oldhunk.diff(@format, true)
165
+ last << "\n" if last.respond_to?(:end_with?) && !last.end_with?("\n")
160
166
 
161
- if @format == :ed
162
- output.reverse_each { |e| real_output << e.diff(:ed_finish) }
163
- end
167
+ output << last
168
+
169
+ output.reverse_each { |e| real_output << e.diff(:ed_finish) } if @format == :ed
164
170
 
165
- return 1
171
+ 1
166
172
  end
167
173
  end
@@ -1,4 +1,4 @@
1
- # -*- ruby encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  class String
4
4
  include Diff::LCS