• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

halostatue / diff-lcs / 20625065018

31 Dec 2025 06:41PM UTC coverage: 91.336% (+3.0%) from 88.366%
20625065018

push

github

halostatue
chore: Prepare for release of diff-lcs 2.0 beta 1

Simplified build tooling, updated GitHub Actions workflows, prepared
changelog for dev release, and updated support for Ruby 3.2 through 4.x.

- Streamlined Rakefile and Gemfile and removed unnecessary complexity
- Added Ruby 4.0 support and dropped unsupported Ruby versions
- Simplified CI configuration
- Updated release and support documents.

Signed-off-by: Austin Ziegler <austin@zieglers.ca>

400 of 597 branches covered (67.0%)

1 of 1 new or added line in 1 file covered. (100.0%)

52 existing lines in 6 files now uncovered.

622 of 681 relevant lines covered (91.34%)

291.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.92
/lib/diff/lcs/callbacks.rb
1
# frozen_string_literal: true
2

3
require "diff/lcs/change"
2✔
4

5
# This callback object implements the default set of callback events, which only returns
6
# the event itself.
7
#
8
# ```ruby
9
# Diff::LCS.lcs(seq1, seq2, Diff::LCS::DefaultCallbacks)
10
# ```
11
class Diff::LCS::DefaultCallbacks
2✔
12
  # Called when two items match.
13
  def self.match(event) = event
2✔
14

15
  # Called when the old value is discarded in favour of the new value.
16
  def self.discard_a(event) = event
2✔
17

18
  # Called when the new value is discarded in favour of the old value.
19
  def self.discard_b(event) = event
2✔
20

21
  # Called when both the old and new values have changed.
22
  def self.change(event) = event
2✔
23

24
  def self.new = self
2✔
25

26
  class << self
2✔
27
    private :new
2✔
28
  end
29
end
30

31
# An alias for DefaultCallbacks used in Diff::LCS.traverse_sequences.
32
#
33
# ```ruby
34
# Diff::LCS.lcs(seq1, seq2, Diff::LCS::SequenceCallbacks)
35
# ```
36
Diff::LCS::SequenceCallbacks = Diff::LCS::DefaultCallbacks
2✔
37

38
# An alias for DefaultCallbacks used in Diff::LCS.traverse_balanced.
39
#
40
# ```ruby
41
# Diff::LCS.lcs(seq1, seq2, Diff::LCS::BalancedCallbacks)
42
# ```
43
Diff::LCS::BalancedCallbacks = Diff::LCS::DefaultCallbacks
2✔
44

45
# This will produce a compound array of simple diff change objects. Each element in the
46
# #diffs array is a `hunk` or `hunk` array, where each element in each `hunk` array is
47
# a single Change object representing the addition or removal of a single element from one
48
# of the two tested sequences. The `hunk` provides the full context for the changes.
49
#
50
# ```ruby
51
# diffs = Diff::LCS.diff(seq1, seq2)
52
# # This example shows a simplified array format.
53
# # [ [ [ '-',  0, 'a' ] ],   # 1
54
# #   [ [ '+',  2, 'd' ] ],   # 2
55
# #   [ [ '-',  4, 'h' ],     # 3
56
# #     [ '+',  4, 'f' ] ],
57
# #   [ [ '+',  6, 'k' ] ],   # 4
58
# #   [ [ '-',  8, 'n' ],     # 5
59
# #     [ '-',  9, 'p' ],
60
# #     [ '+',  9, 'r' ],
61
# #     [ '+', 10, 's' ],
62
# #     [ '+', 11, 't' ] ] ]
63
# ```
64
#
65
# There are five hunks here. The first hunk says that the `a` at position 0 of the first
66
# sequence should be deleted (`'-'`). The second hunk says that the `d` at position 2 of
67
# the second sequence should be inserted (`'+'`). The third hunk says that the `h` at
68
# position 4 of the first sequence should be removed and replaced with the `f` from
69
# position 4 of the second sequence. The other two hunks are described similarly.
70
#
71
# ### Use
72
#
73
# This callback object must be initialised and is used by the Diff::LCS#diff method.
74
#
75
# ```ruby
76
# cbo = Diff::LCS::DiffCallbacks.new
77
# Diff::LCS.lcs(seq1, seq2, cbo)
78
# cbo.finish
79
# ```
80
#
81
# Note that the call to #finish is absolutely necessary, or the last set of changes will
82
# not be visible. This callback also supports a block mode which automatically calls
83
# #finish.
84
#
85
# ```ruby
86
# result = Diff::LCS::DiffCallbacks.new { |cbo| Diff::LCS.lcs(seq1, seq2, cbo) }
87
# ```
88
#
89
# ### Simplified Array Format
90
#
91
# The simplified array format used in the example above can be obtained with:
92
#
93
# ```ruby
94
# require 'pp'
95
# pp diffs.map { |e| e.map { |f| f.to_a } }
96
# ```
97
class Diff::LCS::DiffCallbacks
2✔
98
  # Returns the difference set collected during the diff process.
99
  attr_reader :diffs
2✔
100

101
  def initialize # :yields: self
2✔
102
    @hunk = []
202✔
103
    @diffs = []
202✔
104

105
    return unless block_given?
202!
106

107
    begin
UNCOV
108
      yield self
×
109
    ensure
UNCOV
110
      finish
×
111
    end
112
  end
113

114
  # Finalizes the diff process. If an unprocessed hunk still exists, then it is appended
115
  # to the diff list.
116
  def finish = finish_hunk
2✔
117

118
  def match(_event) = finish_hunk
2✔
119

120
  def discard_a(event)
2✔
121
    @hunk << Diff::LCS::Change.new("-", event.old_position, event.old_element)
248✔
122
  end
123

124
  def discard_b(event)
2✔
125
    @hunk << Diff::LCS::Change.new("+", event.new_position, event.new_element)
258✔
126
  end
127

128
  def finish_hunk
2✔
129
    @diffs << @hunk unless @hunk.empty?
1,614✔
130
    @hunk = []
1,614✔
131
  end
132
  private :finish_hunk
2✔
133
end
134

135
# This will produce a compound array of contextual diff change objects. Each element in
136
# the #diffs array is a "hunk" array, where each element in each "hunk" array is a single
137
# change. Each change is a Diff::LCS::ContextChange that contains both the old index and
138
# new index values for the change. The "hunk" provides the full context for the changes.
139
# Both old and new objects will be presented for changed objects. `nil` will be
140
# substituted for a discarded object.
141
#
142
# ```ruby
143
# seq1 = %w(a b c e h j l m n p)
144
# seq2 = %w(b c d e f j k l m r s t)
145
#
146
# diffs = Diff::LCS.diff(seq1, seq2, Diff::LCS::ContextDiffCallbacks)
147
# # This example shows a simplified array format.
148
# # [ [ [ '-', [  0, 'a' ], [  0, nil ] ] ],   # 1
149
# #   [ [ '+', [  3, nil ], [  2, 'd' ] ] ],   # 2
150
# #   [ [ '-', [  4, 'h' ], [  4, nil ] ],     # 3
151
# #     [ '+', [  5, nil ], [  4, 'f' ] ] ],
152
# #   [ [ '+', [  6, nil ], [  6, 'k' ] ] ],   # 4
153
# #   [ [ '-', [  8, 'n' ], [  9, nil ] ],     # 5
154
# #     [ '+', [  9, nil ], [  9, 'r' ] ],
155
# #     [ '-', [  9, 'p' ], [ 10, nil ] ],
156
# #     [ '+', [ 10, nil ], [ 10, 's' ] ],
157
# #     [ '+', [ 10, nil ], [ 11, 't' ] ] ] ]
158
# ```
159
#
160
# The five hunks shown are comprised of individual changes; if there is a related set of
161
# changes, they are still shown individually.
162
#
163
# This callback can also be used with Diff::LCS#sdiff, which will produce results like:
164
#
165
# ```ruby
166
# diffs = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextCallbacks)
167
# # This example shows a simplified array format.
168
# # [ [ [ "-", [  0, "a" ], [  0, nil ] ] ],  # 1
169
# #   [ [ "+", [  3, nil ], [  2, "d" ] ] ],  # 2
170
# #   [ [ "!", [  4, "h" ], [  4, "f" ] ] ],  # 3
171
# #   [ [ "+", [  6, nil ], [  6, "k" ] ] ],  # 4
172
# #   [ [ "!", [  8, "n" ], [  9, "r" ] ],    # 5
173
# #     [ "!", [  9, "p" ], [ 10, "s" ] ],
174
# #     [ "+", [ 10, nil ], [ 11, "t" ] ] ] ]
175
# ```
176
#
177
# The five hunks are still present, but are significantly shorter in total presentation,
178
# because changed items are shown as changes ("!") instead of potentially "mismatched"
179
# pairs of additions and deletions.
180
#
181
# The result of this operation is similar to that of Diff::LCS::SDiffCallbacks. They may
182
# be compared as:
183
#
184
# ```ruby
185
# s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" }
186
# c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten(1)
187
#
188
# s == c # => true
189
# ```
190
#
191
# ### Use
192
#
193
# This callback object must be initialised and can be used by the Diff::LCS#diff or
194
# Diff::LCS#sdiff methods.
195
#
196
# ```ruby
197
# cbo = Diff::LCS::ContextDiffCallbacks.new
198
# Diff::LCS.lcs(seq1, seq2, cbo)
199
# cbo.finish
200
# ```
201
#
202
# Note that the call to #finish is absolutely necessary, or the last set of changes will
203
# not be visible. This callback also supports a block mode which automatically calls
204
# #finish.
205
#
206
# ```ruby
207
# result = Diff::LCS::ContextDiffCallbacks.new { |cbo| Diff::LCS.lcs(seq1, seq2, cbo) }
208
# ```
209
#
210
# ### Simplified Array Format
211
#
212
# The simplified array format used in the example above can be obtained with:
213
#
214
# ```ruby
215
# require 'pp'
216
# pp diffs.map { |e| e.map { |f| f.to_a } }
217
# ```
218
class Diff::LCS::ContextDiffCallbacks < Diff::LCS::DiffCallbacks
2✔
219
  def discard_a(event)
2✔
220
    @hunk << Diff::LCS::ContextChange.simplify(event)
132✔
221
  end
222

223
  def discard_b(event)
2✔
224
    @hunk << Diff::LCS::ContextChange.simplify(event)
132✔
225
  end
226

227
  def change(event)
2✔
228
    @hunk << Diff::LCS::ContextChange.simplify(event)
48✔
229
  end
230
end
231

232
# This will produce a simple array of diff change objects. Each element in the #diffs
233
# array is a single ContextChange. In the set of #diffs provided by SDiffCallbacks, both
234
# old and new objects will be presented for both changed <strong>and unchanged</strong>
235
# objects. `nil` will be substituted for a discarded object.
236
#
237
# The diffset produced by this callback, when provided to Diff::LCS#sdiff, will compute
238
# and display the necessary components to show two sequences and their minimized
239
# differences side by side, just like the Unix utility `sdiff`.
240
#
241
# ```ruby
242
# # same             same
243
# # before     |     after
244
# # old        <     -
245
# # -          >     new
246
#
247
# seq1 = %w(a b c e h j l m n p)
248
# seq2 = %w(b c d e f j k l m r s t)
249
#
250
# diffs = Diff::LCS.sdiff(seq1, seq2)
251
# # This example shows a simplified array format.
252
# # [ [ "-", [  0, "a"], [  0, nil ] ],
253
# #   [ "=", [  1, "b"], [  0, "b" ] ],
254
# #   [ "=", [  2, "c"], [  1, "c" ] ],
255
# #   [ "+", [  3, nil], [  2, "d" ] ],
256
# #   [ "=", [  3, "e"], [  3, "e" ] ],
257
# #   [ "!", [  4, "h"], [  4, "f" ] ],
258
# #   [ "=", [  5, "j"], [  5, "j" ] ],
259
# #   [ "+", [  6, nil], [  6, "k" ] ],
260
# #   [ "=", [  6, "l"], [  7, "l" ] ],
261
# #   [ "=", [  7, "m"], [  8, "m" ] ],
262
# #   [ "!", [  8, "n"], [  9, "r" ] ],
263
# #   [ "!", [  9, "p"], [ 10, "s" ] ],
264
# #   [ "+", [ 10, nil], [ 11, "t" ] ] ]
265
# ```
266
#
267
# The result of this operation is similar to that of Diff::LCS::ContextDiffCallbacks. They
268
# may be compared as:
269
#
270
# ```ruby
271
# s = Diff::LCS.sdiff(seq1, seq2).reject { |e| e.action == "=" }
272
# c = Diff::LCS.sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks).flatten(1)
273
#
274
# s == c # => true
275
# ```
276
#
277
# ### Use
278
#
279
# This callback object must be initialised and is used by the Diff::LCS#sdiff method.
280
#
281
# ```ruby
282
# cbo = Diff::LCS::SDiffCallbacks.new
283
# Diff::LCS.lcs(seq1, seq2, cbo)
284
# ```
285
#
286
# This callback also supports initialization with a block, but as there is no "finishing"
287
# to be done, this has no effect on the state of the object.
288
#
289
# ```ruby
290
# result = Diff::LCS::SDiffCallbacks.new { |cbo| Diff::LCS.lcs(seq1, seq2, cbo) }
291
# ```
292
#
293
# ### Simplified Array Format
294
#
295
# The simplified array format used in the example above can be obtained with:
296
#
297
# ```ruby
298
# require 'pp'
299
# pp diffs.map { |e| e.to_a }
300
# ```
301
class Diff::LCS::SDiffCallbacks
2✔
302
  # Returns the difference set collected during the diff process.
303
  attr_reader :diffs
2✔
304

305
  def initialize # :yields: self
2✔
306
    @diffs = []
128✔
307
    yield self if block_given?
128!
308
  end
309

310
  def match(event)
2✔
311
    @diffs << Diff::LCS::ContextChange.simplify(event)
744✔
312
  end
313

314
  def discard_a(event)
2✔
315
    @diffs << Diff::LCS::ContextChange.simplify(event)
210✔
316
  end
317

318
  def discard_b(event)
2✔
319
    @diffs << Diff::LCS::ContextChange.simplify(event)
210✔
320
  end
321

322
  def change(event)
2✔
323
    @diffs << Diff::LCS::ContextChange.simplify(event)
80✔
324
  end
325
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc