diff-lcs 1.3 → 1.4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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