diff-lcs 1.3 → 1.4.4

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.
@@ -1,4 +1,4 @@
1
- # -*- ruby encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'diff/lcs'
4
4
 
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless 0.respond_to?(:positive?)
4
+ class Fixnum # rubocop:disable Lint/UnifiedInteger, Style/Documentation
5
+ def positive?
6
+ self > 0 # rubocop:disable Style/NumericPredicate
7
+ end
8
+ end
9
+ end
@@ -1,4 +1,4 @@
1
- # -*- ruby encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  # A block is an operation removing, adding, or changing a group of items.
4
4
  # Basically, this is just a list of changes, where each change adds or
@@ -1,8 +1,8 @@
1
- # -*- ruby encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'diff/lcs/change'
4
4
 
5
- module Diff::LCS
5
+ module Diff::LCS # rubocop:disable Style/Documentation
6
6
  # This callback object implements the default set of callback events,
7
7
  # which only returns the event itself. Note that #finished_a and
8
8
  # #finished_b are not implemented -- I haven't yet figured out where they
@@ -17,14 +17,17 @@ module Diff::LCS
17
17
  def match(event)
18
18
  event
19
19
  end
20
+
20
21
  # Called when the old value is discarded in favour of the new value.
21
22
  def discard_a(event)
22
23
  event
23
24
  end
25
+
24
26
  # Called when the new value is discarded in favour of the old value.
25
27
  def discard_b(event)
26
28
  event
27
29
  end
30
+
28
31
  # Called when both the old and new values have changed.
29
32
  def change(event)
30
33
  event
@@ -108,12 +111,12 @@ class Diff::LCS::DiffCallbacks
108
111
  @hunk = []
109
112
  @diffs = []
110
113
 
111
- if block_given?
112
- begin
113
- yield self
114
- ensure
115
- self.finish
116
- end
114
+ return unless block_given?
115
+
116
+ begin
117
+ yield self
118
+ ensure
119
+ finish
117
120
  end
118
121
  end
119
122
 
@@ -123,7 +126,7 @@ class Diff::LCS::DiffCallbacks
123
126
  finish_hunk
124
127
  end
125
128
 
126
- def match(event)
129
+ def match(_event)
127
130
  finish_hunk
128
131
  end
129
132
 
@@ -190,7 +193,7 @@ end
190
193
  # Diff::LCS::SDiffCallbacks. They may be compared as:
191
194
  #
192
195
  # s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" }
193
- # c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten
196
+ # c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten(1)
194
197
  #
195
198
  # s == c # -> true
196
199
  #
@@ -241,7 +244,7 @@ end
241
244
  # will compute and display the necessary components to show two sequences
242
245
  # and their minimized differences side by side, just like the Unix utility
243
246
  # +sdiff+.
244
- #
247
+ #
245
248
  # same same
246
249
  # before | after
247
250
  # old < -
@@ -270,7 +273,7 @@ end
270
273
  # Diff::LCS::ContextDiffCallbacks. They may be compared as:
271
274
  #
272
275
  # s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" }
273
- # c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten
276
+ # c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten(1)
274
277
  #
275
278
  # s == c # -> true
276
279
  #
@@ -1,16 +1,16 @@
1
- # -*- ruby encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  # Represents a simplistic (non-contextual) change. Represents the removal or
4
4
  # addition of an element from either the old or the new sequenced
5
5
  # enumerable.
6
6
  class Diff::LCS::Change
7
- IntClass = 1.class # Fixnum is deprecated in Ruby 2.4
7
+ IntClass = 1.class # Fixnum is deprecated in Ruby 2.4 # rubocop:disable Naming/ConstantName
8
8
 
9
9
  # The only actions valid for changes are '+' (add), '-' (delete), '='
10
10
  # (no change), '!' (changed), '<' (tail changes from first sequence), or
11
11
  # '>' (tail changes from second sequence). The last two ('<>') are only
12
12
  # found with Diff::LCS::diff and Diff::LCS::sdiff.
13
- VALID_ACTIONS = %W(+ - = ! > <)
13
+ VALID_ACTIONS = %w(+ - = ! > <).freeze
14
14
 
15
15
  def self.valid_action?(action)
16
16
  VALID_ACTIONS.include? action
@@ -27,20 +27,20 @@ class Diff::LCS::Change
27
27
  def initialize(*args)
28
28
  @action, @position, @element = *args
29
29
 
30
- unless Diff::LCS::Change.valid_action?(@action)
31
- raise "Invalid Change Action '#{@action}'"
32
- end
33
- raise "Invalid Position Type" unless @position.kind_of? IntClass
30
+ fail "Invalid Change Action '#{@action}'" unless Diff::LCS::Change.valid_action?(@action)
31
+ fail 'Invalid Position Type' unless @position.kind_of? IntClass
34
32
  end
35
33
 
36
- def inspect
37
- to_a.inspect
34
+ def inspect(*_args)
35
+ "#<#{self.class}: #{to_a.inspect}>"
38
36
  end
39
37
 
40
38
  def to_a
41
- [ @action, @position, @element ]
39
+ [@action, @position, @element]
42
40
  end
43
41
 
42
+ alias to_ary to_a
43
+
44
44
  def self.from_a(arr)
45
45
  arr = arr.flatten(1)
46
46
  case arr.size
@@ -49,7 +49,7 @@ class Diff::LCS::Change
49
49
  when 3
50
50
  Diff::LCS::Change.new(*(arr[0...3]))
51
51
  else
52
- raise "Invalid change array format provided."
52
+ fail 'Invalid change array format provided.'
53
53
  end
54
54
  end
55
55
 
@@ -57,15 +57,15 @@ class Diff::LCS::Change
57
57
 
58
58
  def ==(other)
59
59
  (self.class == other.class) and
60
- (self.action == other.action) and
61
- (self.position == other.position) and
62
- (self.element == other.element)
60
+ (action == other.action) and
61
+ (position == other.position) and
62
+ (element == other.element)
63
63
  end
64
64
 
65
65
  def <=>(other)
66
- r = self.action <=> other.action
67
- r = self.position <=> other.position if r.zero?
68
- r = self.element <=> other.element if r.zero?
66
+ r = action <=> other.action
67
+ r = position <=> other.position if r.zero?
68
+ r = element <=> other.element if r.zero?
69
69
  r
70
70
  end
71
71
 
@@ -114,27 +114,20 @@ class Diff::LCS::ContextChange < Diff::LCS::Change
114
114
  def initialize(*args)
115
115
  @action, @old_position, @old_element, @new_position, @new_element = *args
116
116
 
117
- unless Diff::LCS::Change.valid_action?(@action)
118
- raise "Invalid Change Action '#{@action}'"
119
- end
120
- unless @old_position.nil? or @old_position.kind_of? IntClass
121
- raise "Invalid (Old) Position Type"
122
- end
123
- unless @new_position.nil? or @new_position.kind_of? IntClass
124
- raise "Invalid (New) Position Type"
125
- end
117
+ fail "Invalid Change Action '#{@action}'" unless Diff::LCS::Change.valid_action?(@action)
118
+ fail 'Invalid (Old) Position Type' unless @old_position.nil? or @old_position.kind_of? IntClass
119
+ fail 'Invalid (New) Position Type' unless @new_position.nil? or @new_position.kind_of? IntClass
126
120
  end
127
121
 
128
122
  def to_a
129
- [ @action,
130
- [ @old_position, @old_element ],
131
- [ @new_position, @new_element ]
123
+ [
124
+ @action,
125
+ [@old_position, @old_element],
126
+ [@new_position, @new_element]
132
127
  ]
133
128
  end
134
129
 
135
- def inspect(*args)
136
- to_a.inspect
137
- end
130
+ alias to_ary to_a
138
131
 
139
132
  def self.from_a(arr)
140
133
  Diff::LCS::Change.from_a(arr)
@@ -163,11 +156,11 @@ class Diff::LCS::ContextChange < Diff::LCS::Change
163
156
 
164
157
  def ==(other)
165
158
  (self.class == other.class) and
166
- (@action == other.action) and
167
- (@old_position == other.old_position) and
168
- (@new_position == other.new_position) and
169
- (@old_element == other.old_element) and
170
- (@new_element == other.new_element)
159
+ (@action == other.action) and
160
+ (@old_position == other.old_position) and
161
+ (@new_position == other.new_position) and
162
+ (@old_element == other.old_element) and
163
+ (@new_element == other.new_element)
171
164
  end
172
165
 
173
166
  def <=>(other)
@@ -1,14 +1,15 @@
1
- # -*- ruby encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'cgi'
4
4
 
5
+ # Produce a simple HTML diff view.
5
6
  class Diff::LCS::HTMLDiff
6
7
  class << self
7
8
  attr_accessor :can_expand_tabs #:nodoc:
8
9
  end
9
10
  self.can_expand_tabs = true
10
11
 
11
- class Callbacks
12
+ class Callbacks #:nodoc:
12
13
  attr_accessor :output
13
14
  attr_accessor :match_class
14
15
  attr_accessor :only_a_class
@@ -18,14 +19,14 @@ class Diff::LCS::HTMLDiff
18
19
  @output = output
19
20
  options ||= {}
20
21
 
21
- @match_class = options[:match_class] || "match"
22
- @only_a_class = options[:only_a_class] || "only_a"
23
- @only_b_class = options[:only_b_class] || "only_b"
22
+ @match_class = options[:match_class] || 'match'
23
+ @only_a_class = options[:only_a_class] || 'only_a'
24
+ @only_b_class = options[:only_b_class] || 'only_b'
24
25
  end
25
26
 
26
27
  def htmlize(element, css_class)
27
- element = "&nbsp;" if element.empty?
28
- %Q|<pre class="#{__send__(css_class)}">#{element}</pre>\n|
28
+ element = '&nbsp;' if element.empty?
29
+ %Q(<pre class="#{__send__(css_class)}">#{element}</pre>\n)
29
30
  end
30
31
  private :htmlize
31
32
 
@@ -49,8 +50,8 @@ class Diff::LCS::HTMLDiff
49
50
  :expand_tabs => nil,
50
51
  :output => nil,
51
52
  :css => nil,
52
- :title => nil,
53
- }
53
+ :title => nil
54
+ }.freeze
54
55
 
55
56
  DEFAULT_CSS = <<-CSS
56
57
  body { margin: 0; }
@@ -96,13 +97,13 @@ h1 { margin-left: 2em; }
96
97
 
97
98
  def verify_options
98
99
  @options[:expand_tabs] ||= 4
99
- @options[:expand_tabs] = 4 if @options[:expand_tabs] < 0
100
+ @options[:expand_tabs] = 4 if @options[:expand_tabs].negative?
100
101
 
101
102
  @options[:output] ||= $stdout
102
103
 
103
104
  @options[:css] ||= DEFAULT_CSS.dup
104
105
 
105
- @options[:title] ||= "diff"
106
+ @options[:title] ||= 'diff'
106
107
  end
107
108
  private :verify_options
108
109
 
@@ -111,16 +112,16 @@ h1 { margin-left: 2em; }
111
112
  def run
112
113
  verify_options
113
114
 
114
- if @options[:expand_tabs] > 0 && self.class.can_expand_tabs
115
+ if @options[:expand_tabs].positive? && self.class.can_expand_tabs
115
116
  formatter = Text::Format.new
116
117
  formatter.tabstop = @options[:expand_tabs]
117
118
 
118
- @left.map! { |line| formatter.expand(line.chomp) }
119
- @right.map! { |line| formatter.expand(line.chomp) }
119
+ @left.map! do |line| formatter.expand(line.chomp) end
120
+ @right.map! do |line| formatter.expand(line.chomp) end
120
121
  end
121
122
 
122
- @left.map! { |line| CGI.escapeHTML(line.chomp) }
123
- @right.map! { |line| CGI.escapeHTML(line.chomp) }
123
+ @left.map! do |line| CGI.escapeHTML(line.chomp) end
124
+ @right.map! do |line| CGI.escapeHTML(line.chomp) end
124
125
 
125
126
  @options[:output] << <<-OUTPUT
126
127
  <html>
@@ -1,30 +1,43 @@
1
- # -*- ruby encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
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
19
30
 
20
31
  before = after = file_length_difference
21
32
  after += @blocks[0].diff_size
22
33
  @file_length_difference = after # The caller must get this manually
34
+ @max_diff_size = @blocks.map { |e| e.diff_size.abs }.max
35
+
23
36
 
24
37
  # 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.
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.
28
41
  if @blocks[0].remove.empty?
29
42
  a1 = a2 = nil
30
43
  else
@@ -54,20 +67,27 @@ class Diff::LCS::Hunk
54
67
 
55
68
  # Change the "start" and "end" fields to note that context should be added
56
69
  # to this hunk.
57
- attr_accessor :flag_context
58
- undef :flag_context=;
59
- def flag_context=(context) #:nodoc:
70
+ attr_accessor :flag_context # rubocop:disable Layout/EmptyLinesAroundAttributeAccessor
71
+ undef :flag_context=
72
+ def flag_context=(context) #:nodoc: # rubocop:disable Lint/DuplicateMethods
60
73
  return if context.nil? or context.zero?
61
74
 
62
- add_start = (context > @start_old) ? @start_old : context
75
+ add_start = context > @start_old ? @start_old : context
76
+
63
77
  @start_old -= add_start
64
78
  @start_new -= add_start
65
79
 
66
- if (@end_old + context) > @data_old.size
67
- add_end = @data_old.size - @end_old
68
- else
69
- add_end = context
70
- 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
+
71
91
  @end_old += add_end
72
92
  @end_new += add_end
73
93
  end
@@ -76,15 +96,13 @@ class Diff::LCS::Hunk
76
96
  # a truthy value so that if there is no overlap, you can know the merge
77
97
  # was skipped.
78
98
  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
99
+ return unless overlaps?(hunk)
100
+
101
+ @start_old = hunk.start_old
102
+ @start_new = hunk.start_new
103
+ blocks.unshift(*hunk.blocks)
86
104
  end
87
- alias_method :unshift, :merge
105
+ alias unshift merge
88
106
 
89
107
  # Determines whether there is an overlap between this hunk and the
90
108
  # provided hunk. This will be true if the difference between the two hunks
@@ -95,47 +113,53 @@ class Diff::LCS::Hunk
95
113
  end
96
114
 
97
115
  # Returns a diff string based on a format.
98
- def diff(format)
116
+ def diff(format, last = false)
99
117
  case format
100
118
  when :old
101
- old_diff
119
+ old_diff(last)
102
120
  when :unified
103
- unified_diff
121
+ unified_diff(last)
104
122
  when :context
105
- context_diff
123
+ context_diff(last)
106
124
  when :ed
107
125
  self
108
126
  when :reverse_ed, :ed_finish
109
- ed_diff(format)
127
+ ed_diff(format, last)
110
128
  else
111
- raise "Unknown diff format #{format}."
129
+ fail "Unknown diff format #{format}."
112
130
  end
113
131
  end
114
132
 
115
133
  # Note that an old diff can't have any context. Therefore, we know that
116
134
  # there's only one block in the hunk.
117
- def old_diff
118
- warn "Expecting only one block in an old diff hunk!" if @blocks.size > 1
119
- op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
135
+ def old_diff(_last = false)
136
+ warn 'Expecting only one block in an old diff hunk!' if @blocks.size > 1
120
137
 
121
138
  block = @blocks[0]
122
139
 
123
140
  # Calculate item number range. Old diff range is just like a context
124
141
  # diff range, except the ranges are on one line with the action between
125
142
  # them.
126
- s = encode("#{context_range(:old)}#{op_act[block.op]}#{context_range(:new)}\n")
143
+ s = encode("#{context_range(:old, ',')}#{OLD_DIFF_OP_ACTION[block.op]}#{context_range(:new, ',')}\n")
127
144
  # If removing anything, just print out all the remove lines in the hunk
128
145
  # 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?
130
- s << encode("---\n") if block.op == "!"
131
- @data_new[@start_new .. @end_new].each { |e| s << encode("> ") + e + encode("\n") } unless block.insert.empty?
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
+
132
156
  s
133
157
  end
134
158
  private :old_diff
135
159
 
136
- def unified_diff
160
+ def unified_diff(last = false)
137
161
  # Calculate item number range.
138
- s = encode("@@ -#{unified_range(:old)} +#{unified_range(:new)} @@\n")
162
+ s = encode("@@ -#{unified_range(:old, last)} +#{unified_range(:new, last)} @@\n")
139
163
 
140
164
  # Outlist starts containing the hunk of the old file. Removing an item
141
165
  # just means putting a '-' in front of it. Inserting an item requires
@@ -148,7 +172,14 @@ class Diff::LCS::Hunk
148
172
  # file -- don't take removed items into account.
149
173
  lo, hi, num_added, num_removed = @start_old, @end_old, 0, 0
150
174
 
151
- outlist = @data_old[lo .. hi].map { |e| e.insert(0, encode(' ')) }
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
152
183
 
153
184
  @blocks.each do |block|
154
185
  block.remove.each do |item|
@@ -157,66 +188,100 @@ class Diff::LCS::Hunk
157
188
  outlist[offset][0, 1] = encode(op)
158
189
  num_removed += 1
159
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
+
160
197
  block.insert.each do |item|
161
198
  op = item.action.to_s # +
162
199
  offset = item.position - @start_new + num_removed
163
- outlist[offset, 0] = encode(op) + @data_new[item.position]
200
+ outlist[offset, 0] = encode(op) + @data_new[item.position].chomp
164
201
  num_added += 1
165
202
  end
166
203
  end
167
204
 
205
+ outlist << encode('\') if last && new_missing_newline
206
+
168
207
  s << outlist.join(encode("\n"))
208
+
209
+ s
169
210
  end
170
211
  private :unified_diff
171
212
 
172
- def context_diff
213
+ def context_diff(last = false)
173
214
  s = encode("***************\n")
174
- s << encode("*** #{context_range(:old)} ****\n")
175
- r = context_range(:new)
215
+ s << encode("*** #{context_range(:old, ',', last)} ****\n")
216
+ r = context_range(:new, ',', last)
217
+
218
+ if last
219
+ old_missing_newline = missing_last_newline?(@data_old)
220
+ new_missing_newline = missing_last_newline?(@data_new)
221
+ end
176
222
 
177
223
  # Print out file 1 part for each block in context diff format if there
178
224
  # are any blocks that remove items
179
225
  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(' ')) }
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]
183
232
 
184
233
  removes.each do |block|
185
234
  block.remove.each do |item|
186
235
  outlist[item.position - lo][0, 1] = encode(block.op) # - or !
187
236
  end
237
+
238
+ if last && block == last_block && old_missing_newline
239
+ outlist << encode('\')
240
+ end
188
241
  end
189
- s << outlist.join("\n")
242
+
243
+ s << outlist.join(encode("\n")) << encode("\n")
190
244
  end
191
245
 
192
- s << encode("\n--- #{r} ----\n")
246
+ s << encode("--- #{r} ----\n")
193
247
  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(' ')) }
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
+
197
255
  inserts.each do |block|
198
256
  block.insert.each do |item|
199
257
  outlist[item.position - lo][0, 1] = encode(block.op) # + or !
200
258
  end
259
+
260
+ if last && block == last_block && new_missing_newline
261
+ outlist << encode('\')
262
+ end
201
263
  end
202
- s << outlist.join("\n")
264
+ s << outlist.join(encode("\n"))
203
265
  end
266
+
204
267
  s
205
268
  end
206
269
  private :context_diff
207
270
 
208
- def ed_diff(format)
209
- op_act = { "+" => 'a', "-" => 'd', "!" => "c" }
210
- 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
211
273
 
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")
216
- 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
217
280
 
218
281
  unless @blocks[0].insert.empty?
219
- @data_new[@start_new .. @end_new].each { |e| s << e + encode("\n") }
282
+ @data_new[@start_new..@end_new].each do |e|
283
+ s << e.chomp + encode("\n")
284
+ end
220
285
  s << encode(".\n")
221
286
  end
222
287
  s
@@ -225,7 +290,7 @@ class Diff::LCS::Hunk
225
290
 
226
291
  # Generate a range of item numbers to print. Only print 1 number if the
227
292
  # range has only one item in it. Otherwise, it's 'start,end'
228
- def context_range(mode, op = ',')
293
+ def context_range(mode, op, last = false)
229
294
  case mode
230
295
  when :old
231
296
  s, e = (@start_old + 1), (@end_old + 1)
@@ -233,14 +298,17 @@ class Diff::LCS::Hunk
233
298
  s, e = (@start_new + 1), (@end_new + 1)
234
299
  end
235
300
 
236
- (s < e) ? "#{s}#{op}#{e}" : "#{e}"
301
+ e -= 1 if last
302
+ e = 1 if e.zero?
303
+
304
+ s < e ? "#{s}#{op}#{e}" : e.to_s
237
305
  end
238
306
  private :context_range
239
307
 
240
308
  # Generate a range of item numbers to print for unified diff. Print number
241
309
  # where block starts, followed by number of lines in the block
242
310
  # (don't print number of lines if it's 1)
243
- def unified_range(mode)
311
+ def unified_range(mode, last)
244
312
  case mode
245
313
  when :old
246
314
  s, e = (@start_old + 1), (@end_old + 1)
@@ -248,12 +316,25 @@ class Diff::LCS::Hunk
248
316
  s, e = (@start_new + 1), (@end_new + 1)
249
317
  end
250
318
 
251
- length = e - s + 1
252
- first = (length < 2) ? e : s # "strange, but correct"
253
- (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}"
254
323
  end
255
324
  private :unified_range
256
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
+
257
338
  if String.method_defined?(:encoding)
258
339
  def encode(literal, target_encoding = @preferred_data_encoding)
259
340
  literal.encode target_encoding
@@ -263,10 +344,11 @@ class Diff::LCS::Hunk
263
344
  args.map { |arg| arg.encode(string.encoding) }
264
345
  end
265
346
  else
266
- def encode(literal, target_encoding = nil)
347
+ def encode(literal, _target_encoding = nil)
267
348
  literal
268
349
  end
269
- def encode_as(string, *args)
350
+
351
+ def encode_as(_string, *args)
270
352
  args
271
353
  end
272
354
  end