diff-lcs 1.3 → 1.6.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.
Files changed (118) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +518 -0
  3. data/CODE_OF_CONDUCT.md +128 -0
  4. data/CONTRIBUTING.md +71 -0
  5. data/CONTRIBUTORS.md +49 -0
  6. data/{License.md → LICENCE.md} +21 -20
  7. data/Manifest.txt +84 -6
  8. data/README.md +92 -0
  9. data/Rakefile +104 -46
  10. data/SECURITY.md +41 -0
  11. data/bin/htmldiff +9 -6
  12. data/bin/ldiff +4 -1
  13. data/docs/artistic.txt +1 -1
  14. data/lib/diff/lcs/array.rb +2 -2
  15. data/lib/diff/lcs/backports.rb +13 -0
  16. data/lib/diff/lcs/block.rb +5 -5
  17. data/lib/diff/lcs/callbacks.rb +22 -17
  18. data/lib/diff/lcs/change.rb +44 -51
  19. data/lib/diff/lcs/htmldiff.rb +25 -14
  20. data/lib/diff/lcs/hunk.rb +174 -71
  21. data/lib/diff/lcs/internals.rb +57 -56
  22. data/lib/diff/lcs/ldiff.rb +101 -79
  23. data/lib/diff/lcs/string.rb +1 -1
  24. data/lib/diff/lcs/version.rb +7 -0
  25. data/lib/diff/lcs.rb +229 -212
  26. data/lib/diff-lcs.rb +2 -2
  27. data/mise.toml +5 -0
  28. data/spec/change_spec.rb +58 -34
  29. data/spec/diff_spec.rb +13 -9
  30. data/spec/fixtures/123_x +2 -0
  31. data/spec/fixtures/456_x +2 -0
  32. data/spec/fixtures/aX +1 -0
  33. data/spec/fixtures/bXaX +1 -0
  34. data/spec/fixtures/empty +0 -0
  35. data/spec/fixtures/file1.bin +0 -0
  36. data/spec/fixtures/file2.bin +0 -0
  37. data/spec/fixtures/four_lines +4 -0
  38. data/spec/fixtures/four_lines_with_missing_new_line +4 -0
  39. data/spec/fixtures/ldiff/diff.missing_new_line1-e +1 -0
  40. data/spec/fixtures/ldiff/diff.missing_new_line1-f +1 -0
  41. data/spec/fixtures/ldiff/diff.missing_new_line2-e +1 -0
  42. data/spec/fixtures/ldiff/diff.missing_new_line2-f +1 -0
  43. data/spec/fixtures/ldiff/error.diff.chef-e +2 -0
  44. data/spec/fixtures/ldiff/error.diff.chef-f +2 -0
  45. data/spec/fixtures/ldiff/error.diff.missing_new_line1-e +1 -0
  46. data/spec/fixtures/ldiff/error.diff.missing_new_line1-f +1 -0
  47. data/spec/fixtures/ldiff/error.diff.missing_new_line2-e +1 -0
  48. data/spec/fixtures/ldiff/error.diff.missing_new_line2-f +1 -0
  49. data/spec/fixtures/ldiff/output.diff +4 -0
  50. data/spec/fixtures/ldiff/output.diff-c +7 -0
  51. data/spec/fixtures/ldiff/output.diff-e +3 -0
  52. data/spec/fixtures/ldiff/output.diff-f +3 -0
  53. data/spec/fixtures/ldiff/output.diff-u +5 -0
  54. data/spec/fixtures/ldiff/output.diff.bin1 +0 -0
  55. data/spec/fixtures/ldiff/output.diff.bin1-c +0 -0
  56. data/spec/fixtures/ldiff/output.diff.bin1-e +0 -0
  57. data/spec/fixtures/ldiff/output.diff.bin1-f +0 -0
  58. data/spec/fixtures/ldiff/output.diff.bin1-u +0 -0
  59. data/spec/fixtures/ldiff/output.diff.bin2 +1 -0
  60. data/spec/fixtures/ldiff/output.diff.bin2-c +1 -0
  61. data/spec/fixtures/ldiff/output.diff.bin2-e +1 -0
  62. data/spec/fixtures/ldiff/output.diff.bin2-f +1 -0
  63. data/spec/fixtures/ldiff/output.diff.bin2-u +1 -0
  64. data/spec/fixtures/ldiff/output.diff.chef +4 -0
  65. data/spec/fixtures/ldiff/output.diff.chef-c +15 -0
  66. data/spec/fixtures/ldiff/output.diff.chef-e +3 -0
  67. data/spec/fixtures/ldiff/output.diff.chef-f +3 -0
  68. data/spec/fixtures/ldiff/output.diff.chef-u +9 -0
  69. data/spec/fixtures/ldiff/output.diff.chef2 +7 -0
  70. data/spec/fixtures/ldiff/output.diff.chef2-c +20 -0
  71. data/spec/fixtures/ldiff/output.diff.chef2-d +7 -0
  72. data/spec/fixtures/ldiff/output.diff.chef2-e +7 -0
  73. data/spec/fixtures/ldiff/output.diff.chef2-f +7 -0
  74. data/spec/fixtures/ldiff/output.diff.chef2-u +16 -0
  75. data/spec/fixtures/ldiff/output.diff.empty.vs.four_lines +5 -0
  76. data/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c +9 -0
  77. data/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-e +6 -0
  78. data/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-f +6 -0
  79. data/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u +7 -0
  80. data/spec/fixtures/ldiff/output.diff.four_lines.vs.empty +5 -0
  81. data/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c +9 -0
  82. data/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-e +1 -0
  83. data/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-f +1 -0
  84. data/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u +7 -0
  85. data/spec/fixtures/ldiff/output.diff.issue95_trailing_context +4 -0
  86. data/spec/fixtures/ldiff/output.diff.issue95_trailing_context-c +9 -0
  87. data/spec/fixtures/ldiff/output.diff.issue95_trailing_context-e +3 -0
  88. data/spec/fixtures/ldiff/output.diff.issue95_trailing_context-f +3 -0
  89. data/spec/fixtures/ldiff/output.diff.issue95_trailing_context-u +6 -0
  90. data/spec/fixtures/ldiff/output.diff.missing_new_line1 +5 -0
  91. data/spec/fixtures/ldiff/output.diff.missing_new_line1-c +14 -0
  92. data/spec/fixtures/ldiff/output.diff.missing_new_line1-e +0 -0
  93. data/spec/fixtures/ldiff/output.diff.missing_new_line1-f +0 -0
  94. data/spec/fixtures/ldiff/output.diff.missing_new_line1-u +9 -0
  95. data/spec/fixtures/ldiff/output.diff.missing_new_line2 +5 -0
  96. data/spec/fixtures/ldiff/output.diff.missing_new_line2-c +14 -0
  97. data/spec/fixtures/ldiff/output.diff.missing_new_line2-e +0 -0
  98. data/spec/fixtures/ldiff/output.diff.missing_new_line2-f +0 -0
  99. data/spec/fixtures/ldiff/output.diff.missing_new_line2-u +9 -0
  100. data/spec/fixtures/new-chef +4 -0
  101. data/spec/fixtures/new-chef2 +17 -0
  102. data/spec/fixtures/old-chef +4 -0
  103. data/spec/fixtures/old-chef2 +14 -0
  104. data/spec/hunk_spec.rb +49 -38
  105. data/spec/issues_spec.rb +132 -21
  106. data/spec/lcs_spec.rb +3 -3
  107. data/spec/ldiff_spec.rb +83 -30
  108. data/spec/patch_spec.rb +14 -20
  109. data/spec/sdiff_spec.rb +83 -81
  110. data/spec/spec_helper.rb +220 -165
  111. data/spec/traverse_balanced_spec.rb +138 -136
  112. data/spec/traverse_sequences_spec.rb +7 -9
  113. metadata +127 -77
  114. data/Code-of-Conduct.md +0 -74
  115. data/Contributing.md +0 -83
  116. data/History.md +0 -220
  117. data/README.rdoc +0 -84
  118. data/autotest/discover.rb +0 -1
data/lib/diff/lcs/hunk.rb CHANGED
@@ -1,30 +1,44 @@
1
- # -*- ruby encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
- require 'diff/lcs/block'
3
+ require "diff/lcs/block"
4
4
 
5
- # A Hunk is a group of Blocks which overlap because of the context
6
- # surrounding each block. (So if we're not using context, every hunk will
7
- # 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).
8
8
  class Diff::LCS::Hunk
9
- # Create a hunk using references to both the old and new data, as well as
10
- # the piece of data.
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.
11
16
  def initialize(data_old, data_new, piece, flag_context, file_length_difference)
12
17
  # At first, a hunk will have just one Block in it
13
- @blocks = [ Diff::LCS::Block.new(piece) ]
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
+
14
24
  if String.method_defined?(:encoding)
15
- @preferred_data_encoding = data_old.fetch(0, data_new.fetch(0,'') ).encoding
25
+ @preferred_data_encoding = data_old.fetch(0) { data_new.fetch(0) { "" } }.encoding
16
26
  end
27
+
17
28
  @data_old = data_old
18
29
  @data_new = data_new
30
+ @old_empty = data_old.empty? || (data_old.size == 1 && data_old[0].empty?)
31
+ @new_empty = data_new.empty? || (data_new.size == 1 && data_new[0].empty?)
19
32
 
20
33
  before = after = file_length_difference
21
34
  after += @blocks[0].diff_size
22
35
  @file_length_difference = after # The caller must get this manually
36
+ @max_diff_size = @blocks.map { |e| e.diff_size.abs }.max
23
37
 
24
38
  # Save the start & end of each array. If the array doesn't exist (e.g.,
25
- # we're only adding items in this block), then figure out the line
26
- # number based on the line number of the other file and the current
27
- # difference in file lengths.
39
+ # we're only adding items in this block), then figure out the line number
40
+ # based on the line number of the other file and the current difference in
41
+ # file lengths.
28
42
  if @blocks[0].remove.empty?
29
43
  a1 = a2 = nil
30
44
  else
@@ -41,8 +55,8 @@ class Diff::LCS::Hunk
41
55
 
42
56
  @start_old = a1 || (b1 - before)
43
57
  @start_new = b1 || (a1 + before)
44
- @end_old = a2 || (b2 - after)
45
- @end_new = b2 || (a2 + after)
58
+ @end_old = a2 || (b2 - after)
59
+ @end_new = b2 || (a2 + after)
46
60
 
47
61
  self.flag_context = flag_context
48
62
  end
@@ -55,19 +69,24 @@ class Diff::LCS::Hunk
55
69
  # Change the "start" and "end" fields to note that context should be added
56
70
  # to this hunk.
57
71
  attr_accessor :flag_context
58
- undef :flag_context=;
59
- def flag_context=(context) #:nodoc:
60
- return if context.nil? or context.zero?
72
+ undef :flag_context=
73
+ def flag_context=(context) # :nodoc: # standard:disable Lint/DuplicateMethods
74
+ return if context.nil? || context.zero?
61
75
 
62
76
  add_start = (context > @start_old) ? @start_old : context
77
+
63
78
  @start_old -= add_start
64
79
  @start_new -= add_start
65
80
 
66
- if (@end_old + context) > @data_old.size
67
- add_end = @data_old.size - @end_old
68
- else
69
- add_end = context
70
- end
81
+ old_size = @data_old.size
82
+
83
+ add_end =
84
+ if (@end_old + context) >= old_size
85
+ old_size - @end_old - 1
86
+ else
87
+ context
88
+ end
89
+
71
90
  @end_old += add_end
72
91
  @end_new += add_end
73
92
  end
@@ -76,13 +95,11 @@ class Diff::LCS::Hunk
76
95
  # a truthy value so that if there is no overlap, you can know the merge
77
96
  # was skipped.
78
97
  def merge(hunk)
79
- if overlaps?(hunk)
80
- @start_old = hunk.start_old
81
- @start_new = hunk.start_new
82
- blocks.unshift(*hunk.blocks)
83
- else
84
- nil
85
- end
98
+ return unless overlaps?(hunk)
99
+
100
+ @start_old = hunk.start_old
101
+ @start_new = hunk.start_new
102
+ blocks.unshift(*hunk.blocks)
86
103
  end
87
104
  alias_method :unshift, :merge
88
105
 
@@ -95,45 +112,59 @@ class Diff::LCS::Hunk
95
112
  end
96
113
 
97
114
  # Returns a diff string based on a format.
98
- def diff(format)
115
+ def diff(format, last = false)
99
116
  case format
100
117
  when :old
101
- old_diff
118
+ old_diff(last)
102
119
  when :unified
103
- unified_diff
120
+ unified_diff(last)
104
121
  when :context
105
- context_diff
122
+ context_diff(last)
106
123
  when :ed
107
124
  self
108
125
  when :reverse_ed, :ed_finish
109
- ed_diff(format)
126
+ ed_diff(format, last)
110
127
  else
111
- raise "Unknown diff format #{format}."
128
+ fail "Unknown diff format #{format}."
112
129
  end
113
130
  end
114
131
 
115
132
  # Note that an old diff can't have any context. Therefore, we know that
116
133
  # there's only one block in the hunk.
117
- def old_diff
134
+ def old_diff(last = false)
118
135
  warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1
119
- op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
120
136
 
121
137
  block = @blocks[0]
122
138
 
139
+ if last
140
+ old_missing_newline = !@old_empty && missing_last_newline?(@data_old)
141
+ new_missing_newline = !@new_empty && missing_last_newline?(@data_new)
142
+ end
143
+
123
144
  # Calculate item number range. Old diff range is just like a context
124
145
  # diff range, except the ranges are on one line with the action between
125
146
  # them.
126
- s = encode("#{context_range(:old)}#{op_act[block.op]}#{context_range(:new)}\n")
147
+ s = encode("#{context_range(:old, ",")}#{OLD_DIFF_OP_ACTION[block.op]}#{context_range(:new, ",")}\n")
127
148
  # If removing anything, just print out all the remove lines in the hunk
128
149
  # which is just all the remove lines in the block.
129
- @data_old[@start_old .. @end_old].each { |e| s << encode("< ") + e + encode("\n") } unless block.remove.empty?
150
+ unless block.remove.empty?
151
+ @data_old[@start_old..@end_old].each { |e| s << encode("< ") + e.chomp + encode("\n") }
152
+ end
153
+
154
+ s << encode("\\n") if old_missing_newline && !new_missing_newline
130
155
  s << encode("---\n") if block.op == "!"
131
- @data_new[@start_new .. @end_new].each { |e| s << encode("> ") + e + encode("\n") } unless block.insert.empty?
156
+
157
+ unless block.insert.empty?
158
+ @data_new[@start_new..@end_new].each { |e| s << encode("> ") + e.chomp + encode("\n") }
159
+ end
160
+
161
+ s << encode("\\n") if new_missing_newline && !old_missing_newline
162
+
132
163
  s
133
164
  end
134
165
  private :old_diff
135
166
 
136
- def unified_diff
167
+ def unified_diff(last = false)
137
168
  # Calculate item number range.
138
169
  s = encode("@@ -#{unified_range(:old)} +#{unified_range(:new)} @@\n")
139
170
 
@@ -148,75 +179,132 @@ class Diff::LCS::Hunk
148
179
  # file -- don't take removed items into account.
149
180
  lo, hi, num_added, num_removed = @start_old, @end_old, 0, 0
150
181
 
151
- outlist = @data_old[lo .. hi].map { |e| e.insert(0, encode(' ')) }
182
+ # standard:disable Performance/UnfreezeString
183
+ outlist = @data_old[lo..hi].map { |e| String.new("#{encode(" ")}#{e.chomp}") }
184
+ # standard:enable Performance/UnfreezeString
185
+
186
+ last_block = blocks[-1]
187
+
188
+ if last
189
+ old_missing_newline = !@old_empty && missing_last_newline?(@data_old)
190
+ new_missing_newline = !@new_empty && missing_last_newline?(@data_new)
191
+ end
152
192
 
153
193
  @blocks.each do |block|
154
194
  block.remove.each do |item|
155
- op = item.action.to_s # -
195
+ op = item.action.to_s # -
156
196
  offset = item.position - lo + num_added
157
197
  outlist[offset][0, 1] = encode(op)
158
198
  num_removed += 1
159
199
  end
200
+
201
+ if last && block == last_block && old_missing_newline && !new_missing_newline
202
+ outlist << encode('\')
203
+ num_removed += 1
204
+ end
205
+
160
206
  block.insert.each do |item|
161
- op = item.action.to_s # +
207
+ op = item.action.to_s # +
162
208
  offset = item.position - @start_new + num_removed
163
- outlist[offset, 0] = encode(op) + @data_new[item.position]
209
+ outlist[offset, 0] = encode(op) + @data_new[item.position].chomp
164
210
  num_added += 1
165
211
  end
166
212
  end
167
213
 
214
+ outlist << encode('\') if last && new_missing_newline
215
+
168
216
  s << outlist.join(encode("\n"))
217
+
218
+ s
169
219
  end
170
220
  private :unified_diff
171
221
 
172
- def context_diff
222
+ def context_diff(last = false)
173
223
  s = encode("***************\n")
174
- s << encode("*** #{context_range(:old)} ****\n")
175
- r = context_range(:new)
224
+ s << encode("*** #{context_range(:old, ",")} ****\n")
225
+ r = context_range(:new, ",")
226
+
227
+ if last
228
+ old_missing_newline = missing_last_newline?(@data_old)
229
+ new_missing_newline = missing_last_newline?(@data_new)
230
+ end
176
231
 
177
232
  # Print out file 1 part for each block in context diff format if there
178
233
  # are any blocks that remove items
179
234
  lo, hi = @start_old, @end_old
180
- removes = @blocks.select { |e| not e.remove.empty? }
181
- if removes
182
- outlist = @data_old[lo .. hi].map { |e| e.insert(0, encode(' ')) }
235
+ removes = @blocks.reject { |e| e.remove.empty? }
236
+
237
+ unless removes.empty?
238
+ # standard:disable Performance/UnfreezeString
239
+ outlist = @data_old[lo..hi].map { |e| String.new("#{encode(" ")}#{e.chomp}") }
240
+ # standard:enable Performance/UnfreezeString
241
+
242
+ last_block = removes[-1]
183
243
 
184
244
  removes.each do |block|
185
245
  block.remove.each do |item|
186
246
  outlist[item.position - lo][0, 1] = encode(block.op) # - or !
187
247
  end
248
+
249
+ if last && block == last_block && old_missing_newline
250
+ outlist << encode('\')
251
+ end
188
252
  end
189
- s << outlist.join("\n")
253
+
254
+ s << outlist.join(encode("\n")) << encode("\n")
190
255
  end
191
256
 
192
- s << encode("\n--- #{r} ----\n")
257
+ s << encode("--- #{r} ----\n")
193
258
  lo, hi = @start_new, @end_new
194
- inserts = @blocks.select { |e| not e.insert.empty? }
195
- if inserts
196
- outlist = @data_new[lo .. hi].collect { |e| e.insert(0, encode(' ')) }
259
+ inserts = @blocks.reject { |e| e.insert.empty? }
260
+
261
+ unless inserts.empty?
262
+ # standard:disable Performance/UnfreezeString
263
+ outlist = @data_new[lo..hi].map { |e| String.new("#{encode(" ")}#{e.chomp}") }
264
+ # standard:enable Performance/UnfreezeString
265
+
266
+ last_block = inserts[-1]
267
+
197
268
  inserts.each do |block|
198
269
  block.insert.each do |item|
199
270
  outlist[item.position - lo][0, 1] = encode(block.op) # + or !
200
271
  end
272
+
273
+ if last && block == last_block && new_missing_newline
274
+ outlist << encode('\')
275
+ end
201
276
  end
202
- s << outlist.join("\n")
277
+ s << outlist.join(encode("\n"))
203
278
  end
279
+
204
280
  s
205
281
  end
206
282
  private :context_diff
207
283
 
208
- def ed_diff(format)
209
- op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
284
+ def ed_diff(format, last)
210
285
  warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1
211
-
212
- if format == :reverse_ed
213
- s = encode("#{op_act[@blocks[0].op]}#{context_range(:old)}\n")
214
- else
215
- s = encode("#{context_range(:old, ' ')}#{op_act[@blocks[0].op]}\n")
286
+ if last
287
+ # ed script doesn't support well incomplete lines
288
+ warn "<old_file>: No newline at end of file\n" if !@old_empty && missing_last_newline?(@data_old)
289
+ warn "<new_file>: No newline at end of file\n" if !@new_empty && missing_last_newline?(@data_new)
290
+
291
+ if @blocks[0].op == "!"
292
+ return +"" if @blocks[0].changes[0].element == @blocks[0].changes[1].element + "\n"
293
+ return +"" if @blocks[0].changes[0].element + "\n" == @blocks[0].changes[1].element
294
+ end
216
295
  end
217
296
 
297
+ s =
298
+ if format == :reverse_ed
299
+ encode("#{ED_DIFF_OP_ACTION[@blocks[0].op]}#{context_range(:old, " ")}\n")
300
+ else
301
+ encode("#{context_range(:old, ",")}#{ED_DIFF_OP_ACTION[@blocks[0].op]}\n")
302
+ end
303
+
218
304
  unless @blocks[0].insert.empty?
219
- @data_new[@start_new .. @end_new].each { |e| s << e + encode("\n") }
305
+ @data_new[@start_new..@end_new].each do |e|
306
+ s << e.chomp + encode("\n")
307
+ end
220
308
  s << encode(".\n")
221
309
  end
222
310
  s
@@ -225,7 +313,7 @@ class Diff::LCS::Hunk
225
313
 
226
314
  # Generate a range of item numbers to print. Only print 1 number if the
227
315
  # range has only one item in it. Otherwise, it's 'start,end'
228
- def context_range(mode, op = ',')
316
+ def context_range(mode, op)
229
317
  case mode
230
318
  when :old
231
319
  s, e = (@start_old + 1), (@end_old + 1)
@@ -233,7 +321,7 @@ class Diff::LCS::Hunk
233
321
  s, e = (@start_new + 1), (@end_new + 1)
234
322
  end
235
323
 
236
- (s < e) ? "#{s}#{op}#{e}" : "#{e}"
324
+ (s < e) ? "#{s}#{op}#{e}" : e.to_s
237
325
  end
238
326
  private :context_range
239
327
 
@@ -243,17 +331,31 @@ class Diff::LCS::Hunk
243
331
  def unified_range(mode)
244
332
  case mode
245
333
  when :old
334
+ return "0,0" if @old_empty
246
335
  s, e = (@start_old + 1), (@end_old + 1)
247
336
  when :new
337
+ return "0,0" if @new_empty
248
338
  s, e = (@start_new + 1), (@end_new + 1)
249
339
  end
250
340
 
251
341
  length = e - s + 1
252
- first = (length < 2) ? e : s # "strange, but correct"
253
- (length == 1) ? "#{first}" : "#{first},#{length}"
342
+
343
+ (length <= 1) ? e.to_s : "#{s},#{length}"
254
344
  end
255
345
  private :unified_range
256
346
 
347
+ def missing_last_newline?(data)
348
+ newline = encode("\n")
349
+
350
+ if data[-2]
351
+ data[-2].end_with?(newline) && !data[-1].end_with?(newline)
352
+ elsif data[-1]
353
+ !data[-1].end_with?(newline)
354
+ else
355
+ true
356
+ end
357
+ end
358
+
257
359
  if String.method_defined?(:encoding)
258
360
  def encode(literal, target_encoding = @preferred_data_encoding)
259
361
  literal.encode target_encoding
@@ -263,10 +365,11 @@ class Diff::LCS::Hunk
263
365
  args.map { |arg| arg.encode(string.encoding) }
264
366
  end
265
367
  else
266
- def encode(literal, target_encoding = nil)
368
+ def encode(literal, _target_encoding = nil)
267
369
  literal
268
370
  end
269
- def encode_as(string, *args)
371
+
372
+ def encode_as(_string, *args)
270
373
  args
271
374
  end
272
375
  end