summaryrefslogtreecommitdiff
path: root/lib/diff/lcs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/diff/lcs')
-rw-r--r--lib/diff/lcs/array.rb1
-rw-r--r--lib/diff/lcs/backports.rb1
-rw-r--r--lib/diff/lcs/block.rb30
-rw-r--r--lib/diff/lcs/callbacks.rb1
-rw-r--r--lib/diff/lcs/change.rb155
-rw-r--r--lib/diff/lcs/htmldiff.rb5
-rw-r--r--lib/diff/lcs/hunk.rb42
-rw-r--r--lib/diff/lcs/internals.rb29
-rw-r--r--lib/diff/lcs/ldiff.rb1
-rw-r--r--lib/diff/lcs/string.rb1
10 files changed, 183 insertions, 83 deletions
diff --git a/lib/diff/lcs/array.rb b/lib/diff/lcs/array.rb
index 88035ae..80a5c6e 100644
--- a/lib/diff/lcs/array.rb
+++ b/lib/diff/lcs/array.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# typed: strict
require 'diff/lcs'
diff --git a/lib/diff/lcs/backports.rb b/lib/diff/lcs/backports.rb
index 642fc9c..7109368 100644
--- a/lib/diff/lcs/backports.rb
+++ b/lib/diff/lcs/backports.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# typed: true
unless 0.respond_to?(:positive?)
class Fixnum # rubocop:disable Lint/UnifiedInteger, Style/Documentation
diff --git a/lib/diff/lcs/block.rb b/lib/diff/lcs/block.rb
index 430702d..d31a4d3 100644
--- a/lib/diff/lcs/block.rb
+++ b/lib/diff/lcs/block.rb
@@ -1,15 +1,31 @@
# frozen_string_literal: true
+# typed: strict
# A block is an operation removing, adding, or changing a group of items.
# Basically, this is just a list of changes, where each change adds or
# deletes a single item. Used by bin/ldiff.
class Diff::LCS::Block
- attr_reader :changes, :insert, :remove
+ extend T::Sig
+ ArrayOfChange = T.type_alias { T::Array[Diff::LCS::Change] }
+
+ # The full set of changes in the block.
+ sig { returns(ArrayOfChange) }
+ attr_reader :changes
+
+ # The insertions in the block.
+ sig { returns(ArrayOfChange) }
+ attr_reader :insert
+
+ # The deletions in the block.
+ sig { returns(ArrayOfChange) }
+ attr_reader :remove
+
+ sig { params(chunk: ArrayOfChange).void }
def initialize(chunk)
- @changes = []
- @insert = []
- @remove = []
+ @changes = T.let([], ArrayOfChange)
+ @insert = T.let([], ArrayOfChange)
+ @remove = T.let([], ArrayOfChange)
chunk.each do |item|
@changes << item
@@ -18,12 +34,14 @@ class Diff::LCS::Block
end
end
+ sig { returns(Integer) }
def diff_size
- @insert.size - @remove.size
+ insert.size - remove.size
end
+ sig { returns(String) }
def op
- case [@remove.empty?, @insert.empty?]
+ case [remove.empty?, insert.empty?]
when [false, false]
'!'
when [false, true]
diff --git a/lib/diff/lcs/callbacks.rb b/lib/diff/lcs/callbacks.rb
index a86f296..f3565a8 100644
--- a/lib/diff/lcs/callbacks.rb
+++ b/lib/diff/lcs/callbacks.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# typed: true
require 'diff/lcs/change'
diff --git a/lib/diff/lcs/change.rb b/lib/diff/lcs/change.rb
index 7977be5..520aeda 100644
--- a/lib/diff/lcs/change.rb
+++ b/lib/diff/lcs/change.rb
@@ -1,107 +1,133 @@
# frozen_string_literal: true
+# typed: strict
# Represents a simplistic (non-contextual) change. Represents the removal or
# addition of an element from either the old or the new sequenced
# enumerable.
class Diff::LCS::Change
- INTEGER = 1.class # Fixnum is deprecated in Ruby 2.4
+ extend T::Sig
+
+ ArrayT = T.type_alias{
+ T.any(
+ [String, Integer, T.untyped],
+ [String, Integer, T.untyped, Integer, T.untyped],
+ [String, [Integer, T.untyped], [Integer, T.untyped]]
+ )
+ }
+
+ ChangeT = T.type_alias { T.any(Diff::LCS::Change, Diff::LCS::ContextChange) }
# The only actions valid for changes are '+' (add), '-' (delete), '='
# (no change), '!' (changed), '<' (tail changes from first sequence), or
# '>' (tail changes from second sequence). The last two ('<>') are only
# found with Diff::LCS::diff and Diff::LCS::sdiff.
- VALID_ACTIONS = %w(+ - = ! > <).freeze
+ VALID_ACTIONS = T.let(%w(+ - = ! > <).freeze, T::Array[String])
+ sig { params(action: T.any(String, Diff::LCS::Change)).returns(T::Boolean) }
def self.valid_action?(action)
- VALID_ACTIONS.include? action
+ return false unless action.kind_of?(String)
+
+ VALID_ACTIONS.include?(action)
end
# Returns the action this Change represents.
+ sig { returns(String) }
attr_reader :action
+
# Returns the position of the Change.
+ sig { returns(Integer) }
attr_reader :position
+
# Returns the sequence element of the Change.
+ sig { returns(T.untyped) }
attr_reader :element
- def initialize(*args)
- @action, @position, @element = *args
+ sig {
+ params(
+ action: String,
+ position: Integer,
+ element: T.untyped,
+ _: T.untyped
+ ).void
+ }
+ def initialize(action, position, element, *_)
+ @action = action
+ @position = position
+ @element = element
fail "Invalid Change Action '#{@action}'" unless Diff::LCS::Change.valid_action?(@action)
- fail 'Invalid Position Type' unless @position.kind_of? INTEGER
+ fail 'Invalid Position Type' unless @position.kind_of? Integer
end
+ sig { params(_args: T.untyped).returns(String) }
def inspect(*_args)
"#<#{self.class}: #{to_a.inspect}>"
end
+ sig { returns(ArrayT) }
def to_a
[@action, @position, @element]
end
alias to_ary to_a
+ sig { params(arr: ArrayT).returns(ChangeT) }
def self.from_a(arr)
arr = arr.flatten(1)
case arr.size
when 5
- Diff::LCS::ContextChange.new(*(arr[0...5]))
+ Diff::LCS::ContextChange.new(arr[0], arr[1], arr[2], arr[3], arr[4])
when 3
- Diff::LCS::Change.new(*(arr[0...3]))
+ Diff::LCS::Change.new(arr[0], arr[1], arr[2])
else
fail 'Invalid change array format provided.'
end
end
- def to_change(action)
- args =
- case action
- when '-'
- [action, old_position, old_element]
- when '+'
- [action, new_position, new_element]
- else
- fail 'Invalid action for creating a change'
- end
-
- Diff::LCS::Change.new(*args)
- end
-
include Comparable
+ sig { params(other: ChangeT).returns(T::Boolean) }
def ==(other)
- (self.class == other.class) and
- (action == other.action) and
- (position == other.position) and
- (element == other.element)
+ self.class == other.class &&
+ action == other.action &&
+ position == other.position &&
+ element == other.element
end
+ sig { params(other: Diff::LCS::Change).returns(Integer) }
def <=>(other)
r = action <=> other.action
- r = position <=> other.position if r.zero?
- r = element <=> other.element if r.zero?
+ r = position <=> other.position if T.must(r).zero?
+ r = element <=> other.element if T.must(r).zero?
r
end
+ sig { returns(T::Boolean) }
def adding?
@action == '+'
end
+ sig { returns(T::Boolean) }
def deleting?
@action == '-'
end
+ sig { returns(T::Boolean) }
def unchanged?
@action == '='
end
+ sig { returns(T::Boolean) }
def changed?
@action == '!'
end
+ sig { returns(T::Boolean) }
def finished_a?
@action == '>'
end
+ sig { returns(T::Boolean) }
def finished_b?
@action == '<'
end
@@ -111,27 +137,51 @@ end
# elements in the old and the new sequenced enumerables as well as the action
# taken.
class Diff::LCS::ContextChange < Diff::LCS::Change
+ extend T::Sig
+
# We don't need these two values.
undef :position
undef :element
# Returns the old position being changed.
+ sig { returns(Integer) }
attr_reader :old_position
+
# Returns the new position being changed.
+ sig { returns(Integer) }
attr_reader :new_position
+
# Returns the old element being changed.
+ sig { returns(T.untyped) }
attr_reader :old_element
+
# Returns the new element being changed.
+ sig { returns(T.untyped) }
attr_reader :new_element
- def initialize(*args)
- @action, @old_position, @old_element, @new_position, @new_element = *args
+ sig {
+ params(
+ action: String,
+ old_position: Integer,
+ old_element: T.untyped,
+ new_position: Integer,
+ new_element: T.untyped,
+ _: T.untyped
+ ).void
+ }
+ def initialize(action, old_position, old_element, new_position, new_element, *_)
+ @action = action
+ @old_position = old_position
+ @old_element = old_element
+ @new_position = new_position
+ @new_element = new_element
fail "Invalid Change Action '#{@action}'" unless Diff::LCS::Change.valid_action?(@action)
- fail 'Invalid (Old) Position Type' unless @old_position.nil? or @old_position.kind_of? INTEGER
- fail 'Invalid (New) Position Type' unless @new_position.nil? or @new_position.kind_of? INTEGER
+ fail 'Invalid (Old) Position Type' unless @old_position.nil? or @old_position.kind_of? Integer
+ fail 'Invalid (New) Position Type' unless @new_position.nil? or @new_position.kind_of? Integer
end
+ sig { returns(ArrayT) }
def to_a
[
@action,
@@ -142,12 +192,26 @@ class Diff::LCS::ContextChange < Diff::LCS::Change
alias to_ary to_a
+ sig { params(action: String).returns(Diff::LCS::Change) }
+ def to_change(action)
+ case action
+ when '-'
+ Diff::LCS::Change.new(action, old_position, old_element)
+ when '+'
+ Diff::LCS::Change.new(action, new_position, new_element)
+ else
+ fail 'Invalid action for creating a change'
+ end
+ end
+
+ sig { params(arr: ArrayT).returns(ChangeT) }
def self.from_a(arr)
Diff::LCS::Change.from_a(arr)
end
# Simplifies a context change for use in some diff callbacks. '<' actions
# are converted to '-' and '>' actions are converted to '+'.
+ sig { returns(T.any(Diff::LCS::Change, Diff::LCS::ContextChange)) }
def simplify
args =
case action
@@ -164,24 +228,33 @@ class Diff::LCS::ContextChange < Diff::LCS::Change
# Simplifies a context change for use in some diff callbacks. '<' actions
# are converted to '-' and '>' actions are converted to '+'.
+ sig {
+ params(event: Diff::LCS::ContextChange).returns(T.any(
+ Diff::LCS::Change,
+ Diff::LCS::ContextChange
+ ))
+ }
def self.simplify(event)
event.simplify
end
+ sig { params(other: ChangeT).returns(T::Boolean) }
def ==(other)
- (self.class == other.class) and
- (@action == other.action) and
- (@old_position == other.old_position) and
- (@new_position == other.new_position) and
- (@old_element == other.old_element) and
- (@new_element == other.new_element)
+ return false unless other.kind_of?(Diff::LCS::ContextChange)
+
+ @action == other.action &&
+ @old_position == other.old_position &&
+ @new_position == other.new_position &&
+ @old_element == other.old_element &&
+ @new_element == other.new_element
end
+ sig { params(other: Diff::LCS::ContextChange).returns(Integer) }
def <=>(other)
r = @action <=> other.action
- r = @old_position <=> other.old_position if r.zero?
- r = @new_position <=> other.new_position if r.zero?
- r = @old_element <=> other.old_element if r.zero?
+ r = @old_position <=> other.old_position if T.must(r).zero?
+ r = @new_position <=> other.new_position if T.must(r).zero?
+ r = @old_element <=> other.old_element if T.must(r).zero?
r = @new_element <=> other.new_element if r.zero?
r
end
diff --git a/lib/diff/lcs/htmldiff.rb b/lib/diff/lcs/htmldiff.rb
index f12220b..54ce9f4 100644
--- a/lib/diff/lcs/htmldiff.rb
+++ b/lib/diff/lcs/htmldiff.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# typed: false
require 'cgi'
@@ -112,8 +113,8 @@ h1 { margin-left: 2em; }
def run
verify_options
- if @options[:expand_tabs].positive? && self.class.can_expand_tabs
- formatter = Text::Format.new
+ if @options[:expand_tabs].positive? && self.class.can_expand_tabs && defined? ::Text::Format
+ formatter = ::Text::Format.new
formatter.tabstop = @options[:expand_tabs]
@left.map! do |line| formatter.expand(line.chomp) end
diff --git a/lib/diff/lcs/hunk.rb b/lib/diff/lcs/hunk.rb
index fc5a035..ba16e11 100644
--- a/lib/diff/lcs/hunk.rb
+++ b/lib/diff/lcs/hunk.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# typed: true
require 'diff/lcs/block'
@@ -15,9 +16,10 @@ class Diff::LCS::Hunk
# piece of data.
def initialize(data_old, data_new, piece, flag_context, file_length_difference)
# At first, a hunk will have just one Block in it
- @blocks = [Diff::LCS::Block.new(piece)]
+ block = Diff::LCS::Block.new(piece)
+ @blocks = T.let([block], T::Array[Diff::LCS::Block])
- if @blocks[0].remove.empty? && @blocks[0].insert.empty?
+ if block.remove.empty? && block.insert.empty?
fail "Cannot build a hunk from #{piece.inspect}; has no add or remove actions"
end
@@ -29,7 +31,7 @@ class Diff::LCS::Hunk
@data_new = data_new
before = after = file_length_difference
- after += @blocks[0].diff_size
+ after += block.diff_size
@file_length_difference = after # The caller must get this manually
@max_diff_size = @blocks.map { |e| e.diff_size.abs }.max
@@ -37,24 +39,24 @@ class Diff::LCS::Hunk
# we're only adding items in this block), then figure out the line number
# based on the line number of the other file and the current difference in
# file lengths.
- if @blocks[0].remove.empty?
+ if block.remove.empty?
a1 = a2 = nil
else
- a1 = @blocks[0].remove[0].position
- a2 = @blocks[0].remove[-1].position
+ a1 = T.must(block.remove[0]).position
+ a2 = T.must(block.remove[-1]).position
end
- if @blocks[0].insert.empty?
+ if block.insert.empty?
b1 = b2 = nil
else
- b1 = @blocks[0].insert[0].position
- b2 = @blocks[0].insert[-1].position
+ b1 = T.must(block.insert[0]).position
+ b2 = T.must(block.insert[-1]).position
end
- @start_old = a1 || (b1 - before)
- @start_new = b1 || (a1 + before)
- @end_old = a2 || (b2 - after)
- @end_new = b2 || (a2 + after)
+ @start_old = a1 || (T.must(b1) - before)
+ @start_new = b1 || (T.must(a1) + before)
+ @end_old = a2 || (T.must(b2) - after)
+ @end_new = b2 || (T.must(a2) + after)
self.flag_context = flag_context
end
@@ -134,7 +136,7 @@ class Diff::LCS::Hunk
def old_diff(_last = false)
warn 'Expecting only one block in an old diff hunk!' if @blocks.size > 1
- block = @blocks[0]
+ block = T.must(@blocks[0])
# Calculate item number range. Old diff range is just like a context
# diff range, except the ranges are on one line with the action between
@@ -142,13 +144,13 @@ class Diff::LCS::Hunk
s = encode("#{context_range(:old, ',')}#{OLD_DIFF_OP_ACTION[block.op]}#{context_range(:new, ',')}\n")
# If removing anything, just print out all the remove lines in the hunk
# which is just all the remove lines in the block.
- unless block.remove.empty?
+ unless T.must(block).remove.empty?
@data_old[@start_old..@end_old].each { |e| s << encode('< ') + e.chomp + encode("\n") }
end
s << encode("---\n") if block.op == '!'
- unless block.insert.empty?
+ unless T.must(block).insert.empty?
@data_new[@start_new..@end_new].each { |e| s << encode('> ') + e.chomp + encode("\n") }
end
@@ -270,14 +272,16 @@ class Diff::LCS::Hunk
def ed_diff(format, _last = false)
warn 'Expecting only one block in an old diff hunk!' if @blocks.size > 1
+ block = T.must(@blocks[0])
+
s =
if format == :reverse_ed
- encode("#{ED_DIFF_OP_ACTION[@blocks[0].op]}#{context_range(:old, ',')}\n")
+ encode("#{ED_DIFF_OP_ACTION[block.op]}#{context_range(:old, ',')}\n")
else
- encode("#{context_range(:old, ' ')}#{ED_DIFF_OP_ACTION[@blocks[0].op]}\n")
+ encode("#{context_range(:old, ' ')}#{ED_DIFF_OP_ACTION[block.op]}\n")
end
- unless @blocks[0].insert.empty?
+ unless block.insert.empty?
@data_new[@start_new..@end_new].each do |e|
s << e.chomp + encode("\n")
end
diff --git a/lib/diff/lcs/internals.rb b/lib/diff/lcs/internals.rb
index 60027f2..f73ba52 100644
--- a/lib/diff/lcs/internals.rb
+++ b/lib/diff/lcs/internals.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
+# typed: true
-class << Diff::LCS
- def diff_traversal(method, seq1, seq2, callbacks, &block)
+module Diff::LCS # rubocop:disable Style/Documentation
+ def self.diff_traversal(method, seq1, seq2, callbacks, &block)
callbacks = callbacks_for(callbacks)
case method
when :diff
@@ -26,10 +27,7 @@ class << Diff::LCS
private :diff_traversal
end
-module Diff::LCS::Internals # :nodoc:
-end
-
-class << Diff::LCS::Internals
+module Diff::LCS::Internals # :nodoc: rubocop:disable Style/Documentation
# Compute the longest common subsequence between the sequenced
# Enumerables +a+ and +b+. The result is an array whose contents is such
# that
@@ -38,7 +36,7 @@ class << Diff::LCS::Internals
# result.each_with_index do |e, i|
# assert_equal(a[i], b[e]) unless e.nil?
# end
- def lcs(a, b)
+ def self.lcs(a, b)
a_start = b_start = 0
a_finish = a.size - 1
b_finish = b.size - 1
@@ -69,7 +67,8 @@ class << Diff::LCS::Internals
(a_start..a_finish).each do |i|
ai = string ? a[i, 1] : a[i]
bm = b_matches[ai]
- k = nil
+ k = T.let(nil, T.nilable(Integer))
+
bm.reverse_each do |j|
if k and (thresh[k] > j) and (thresh[k - 1] < j)
thresh[k] = j
@@ -95,10 +94,10 @@ class << Diff::LCS::Internals
# normalization (conversion of the array form of Diff::LCS::Change objects to
# the object form of same) and detection of whether the patchset represents
# changes to be made.
- def analyze_patchset(patchset, depth = 0)
+ def self.analyze_patchset(patchset, depth = 0)
fail 'Patchset too complex' if depth > 1
- has_changes = false
+ has_changes = T.let(false, T::Boolean)
new_patchset = []
# Format:
@@ -116,7 +115,7 @@ class << Diff::LCS::Internals
new_patchset << hunk
when Array
# Detect if the 'hunk' is actually an array-format change object.
- if Diff::LCS::Change.valid_action? hunk[0]
+ if Diff::LCS::Change.valid_action?(hunk[0])
hunk = Diff::LCS::Change.from_a(hunk)
has_changes ||= !hunk.unchanged?
new_patchset << hunk
@@ -140,7 +139,7 @@ class << Diff::LCS::Internals
# some time. This also works better with Diff::LCS::ContextChange or
# Diff::LCS::Change as its source, as an array will cause the creation
# of one of the above.
- def intuit_diff_direction(src, patchset, limit = nil)
+ def self.intuit_diff_direction(src, patchset, limit = nil)
string = src.kind_of?(String)
count = left_match = left_miss = right_match = right_miss = 0
@@ -245,7 +244,7 @@ enumerable as either source or destination value."
# are numeric.
#
# This operation preserves the sort order.
- def replace_next_larger(enum, value, last_index = nil)
+ def self.replace_next_larger(enum, value, last_index = nil)
# Off the end?
if enum.empty? or (value > enum[-1])
enum << value
@@ -279,7 +278,7 @@ enumerable as either source or destination value."
# If +vector+ maps the matching elements of another collection onto this
# Enumerable, compute the inverse of +vector+ that maps this Enumerable
# onto the collection. (Currently unused.)
- def inverse_vector(a, vector)
+ def self.inverse_vector(a, vector)
inverse = a.dup
(0...vector.size).each do |i|
inverse[vector[i]] = i unless vector[i].nil?
@@ -291,7 +290,7 @@ enumerable as either source or destination value."
# Returns a hash mapping each element of an Enumerable to the set of
# positions it occupies in the Enumerable, optionally restricted to the
# elements specified in the range of indexes specified by +interval+.
- def position_hash(enum, interval)
+ def self.position_hash(enum, interval)
string = enum.kind_of?(String)
hash = Hash.new { |h, k| h[k] = [] }
interval.each do |i|
diff --git a/lib/diff/lcs/ldiff.rb b/lib/diff/lcs/ldiff.rb
index 17b374c..d81e392 100644
--- a/lib/diff/lcs/ldiff.rb
+++ b/lib/diff/lcs/ldiff.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# typed: false
require 'optparse'
require 'ostruct'
diff --git a/lib/diff/lcs/string.rb b/lib/diff/lcs/string.rb
index 6bcdc74..1ee7a3c 100644
--- a/lib/diff/lcs/string.rb
+++ b/lib/diff/lcs/string.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# typed: strict
warn 'diff/lcs/string: Automatically extending String with Diff::LCS is deprecated'