diff-lcs 1.6.2 → 2.0.0.beta.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 (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -4
  3. data/CONTRIBUTING.md +91 -35
  4. data/CONTRIBUTORS.md +19 -9
  5. data/LICENCE.md +39 -11
  6. data/Manifest.txt +91 -83
  7. data/README.md +13 -9
  8. data/Rakefile +99 -73
  9. data/SECURITY.md +22 -27
  10. data/integration/compare/array_diff_spec.rb +10 -0
  11. data/integration/compare/hash_diff_spec.rb +25 -0
  12. data/integration/compare/string_diff_spec.rb +10 -0
  13. data/integration/rspec_differ_spec.rb +26 -0
  14. data/integration/rspec_expectations_spec.rb +32 -0
  15. data/integration/runner +20 -0
  16. data/lib/diff/lcs/block.rb +29 -24
  17. data/lib/diff/lcs/callbacks.rb +240 -242
  18. data/lib/diff/lcs/change.rb +102 -104
  19. data/lib/diff/lcs/hunk.rb +92 -155
  20. data/lib/diff/lcs/internals.rb +92 -96
  21. data/lib/diff/lcs/ldiff.rb +30 -38
  22. data/lib/diff/lcs/version.rb +1 -1
  23. data/lib/diff/lcs.rb +439 -466
  24. data/licenses/dco.txt +34 -0
  25. data/spec/hunk_spec.rb +32 -45
  26. data/spec/lcs_spec.rb +6 -6
  27. data/spec/ldiff_spec.rb +8 -8
  28. data/spec/spec_helper.rb +17 -27
  29. data/test/fixtures/ldiff/output.diff-c +7 -0
  30. data/test/fixtures/ldiff/output.diff-u +5 -0
  31. data/test/fixtures/ldiff/output.diff.bin2 +1 -0
  32. data/test/fixtures/ldiff/output.diff.bin2-c +1 -0
  33. data/test/fixtures/ldiff/output.diff.bin2-e +1 -0
  34. data/test/fixtures/ldiff/output.diff.bin2-f +1 -0
  35. data/test/fixtures/ldiff/output.diff.bin2-u +1 -0
  36. data/{spec → test}/fixtures/ldiff/output.diff.chef-c +2 -2
  37. data/test/fixtures/ldiff/output.diff.chef-u +9 -0
  38. data/{spec → test}/fixtures/ldiff/output.diff.chef2-c +2 -2
  39. data/{spec → test}/fixtures/ldiff/output.diff.chef2-u +2 -2
  40. data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-c +9 -0
  41. data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-u +7 -0
  42. data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-c +9 -0
  43. data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-u +7 -0
  44. data/test/fixtures/ldiff/output.diff.issue95_trailing_context-c +9 -0
  45. data/test/fixtures/ldiff/output.diff.issue95_trailing_context-u +6 -0
  46. data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1-c +2 -2
  47. data/test/fixtures/ldiff/output.diff.missing_new_line1-u +9 -0
  48. data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2-c +2 -2
  49. data/test/fixtures/ldiff/output.diff.missing_new_line2-u +9 -0
  50. data/test/test_block.rb +34 -0
  51. data/test/test_change.rb +234 -0
  52. data/test/test_diff.rb +53 -0
  53. data/test/test_helper.rb +225 -0
  54. data/test/test_hunk.rb +72 -0
  55. data/test/test_issues.rb +168 -0
  56. data/test/test_lcs.rb +47 -0
  57. data/test/test_ldiff.rb +89 -0
  58. data/test/test_patch.rb +362 -0
  59. data/test/test_sdiff.rb +167 -0
  60. data/test/test_traverse_balanced.rb +322 -0
  61. data/test/test_traverse_sequences.rb +187 -0
  62. metadata +199 -110
  63. data/.rspec +0 -1
  64. data/bin/htmldiff +0 -35
  65. data/lib/diff/lcs/backports.rb +0 -13
  66. data/lib/diff/lcs/htmldiff.rb +0 -160
  67. data/mise.toml +0 -5
  68. data/spec/fixtures/ldiff/output.diff-c +0 -7
  69. data/spec/fixtures/ldiff/output.diff-e +0 -3
  70. data/spec/fixtures/ldiff/output.diff-f +0 -3
  71. data/spec/fixtures/ldiff/output.diff-u +0 -5
  72. data/spec/fixtures/ldiff/output.diff.bin2 +0 -1
  73. data/spec/fixtures/ldiff/output.diff.bin2-c +0 -1
  74. data/spec/fixtures/ldiff/output.diff.bin2-e +0 -1
  75. data/spec/fixtures/ldiff/output.diff.bin2-f +0 -1
  76. data/spec/fixtures/ldiff/output.diff.bin2-u +0 -1
  77. data/spec/fixtures/ldiff/output.diff.chef-e +0 -3
  78. data/spec/fixtures/ldiff/output.diff.chef-f +0 -3
  79. data/spec/fixtures/ldiff/output.diff.chef-u +0 -9
  80. data/spec/fixtures/ldiff/output.diff.chef2-e +0 -7
  81. data/spec/fixtures/ldiff/output.diff.chef2-f +0 -7
  82. data/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c +0 -9
  83. data/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u +0 -7
  84. data/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c +0 -9
  85. data/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u +0 -7
  86. data/spec/fixtures/ldiff/output.diff.issue95_trailing_context-c +0 -9
  87. data/spec/fixtures/ldiff/output.diff.issue95_trailing_context-u +0 -6
  88. data/spec/fixtures/ldiff/output.diff.missing_new_line1-u +0 -9
  89. data/spec/fixtures/ldiff/output.diff.missing_new_line2-u +0 -9
  90. /data/{docs → licenses}/COPYING.txt +0 -0
  91. /data/{docs → licenses}/artistic.txt +0 -0
  92. /data/{spec → test}/fixtures/123_x +0 -0
  93. /data/{spec → test}/fixtures/456_x +0 -0
  94. /data/{spec → test}/fixtures/aX +0 -0
  95. /data/{spec → test}/fixtures/bXaX +0 -0
  96. /data/{spec → test}/fixtures/ds1.csv +0 -0
  97. /data/{spec → test}/fixtures/ds2.csv +0 -0
  98. /data/{spec → test}/fixtures/empty +0 -0
  99. /data/{spec → test}/fixtures/file1.bin +0 -0
  100. /data/{spec → test}/fixtures/file2.bin +0 -0
  101. /data/{spec → test}/fixtures/four_lines +0 -0
  102. /data/{spec → test}/fixtures/four_lines_with_missing_new_line +0 -0
  103. /data/{spec → test}/fixtures/ldiff/diff.missing_new_line1-e +0 -0
  104. /data/{spec → test}/fixtures/ldiff/diff.missing_new_line1-f +0 -0
  105. /data/{spec → test}/fixtures/ldiff/diff.missing_new_line2-e +0 -0
  106. /data/{spec → test}/fixtures/ldiff/diff.missing_new_line2-f +0 -0
  107. /data/{spec → test}/fixtures/ldiff/error.diff.chef-e +0 -0
  108. /data/{spec → test}/fixtures/ldiff/error.diff.chef-f +0 -0
  109. /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line1-e +0 -0
  110. /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line1-f +0 -0
  111. /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line2-e +0 -0
  112. /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line2-f +0 -0
  113. /data/{spec → test}/fixtures/ldiff/output.diff +0 -0
  114. /data/{spec → test}/fixtures/ldiff/output.diff.bin1 +0 -0
  115. /data/{spec → test}/fixtures/ldiff/output.diff.bin1-c +0 -0
  116. /data/{spec → test}/fixtures/ldiff/output.diff.bin1-e +0 -0
  117. /data/{spec → test}/fixtures/ldiff/output.diff.bin1-f +0 -0
  118. /data/{spec → test}/fixtures/ldiff/output.diff.bin1-u +0 -0
  119. /data/{spec → test}/fixtures/ldiff/output.diff.chef +0 -0
  120. /data/{spec → test}/fixtures/ldiff/output.diff.chef2 +0 -0
  121. /data/{spec → test}/fixtures/ldiff/output.diff.chef2-d +0 -0
  122. /data/{spec → test}/fixtures/ldiff/output.diff.empty.vs.four_lines +0 -0
  123. /data/{spec → test}/fixtures/ldiff/output.diff.empty.vs.four_lines-e +0 -0
  124. /data/{spec → test}/fixtures/ldiff/output.diff.empty.vs.four_lines-f +0 -0
  125. /data/{spec → test}/fixtures/ldiff/output.diff.four_lines.vs.empty +0 -0
  126. /data/{spec → test}/fixtures/ldiff/output.diff.four_lines.vs.empty-e +0 -0
  127. /data/{spec → test}/fixtures/ldiff/output.diff.four_lines.vs.empty-f +0 -0
  128. /data/{spec → test}/fixtures/ldiff/output.diff.issue95_trailing_context +0 -0
  129. /data/{spec → test}/fixtures/ldiff/output.diff.issue95_trailing_context-e +0 -0
  130. /data/{spec → test}/fixtures/ldiff/output.diff.issue95_trailing_context-f +0 -0
  131. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1 +0 -0
  132. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1-e +0 -0
  133. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1-f +0 -0
  134. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2 +0 -0
  135. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2-e +0 -0
  136. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2-f +0 -0
  137. /data/{spec → test}/fixtures/new-chef +0 -0
  138. /data/{spec → test}/fixtures/new-chef2 +0 -0
  139. /data/{spec → test}/fixtures/old-chef +0 -0
  140. /data/{spec → test}/fixtures/old-chef2 +0 -0
data/lib/diff/lcs.rb CHANGED
@@ -2,52 +2,62 @@
2
2
 
3
3
  module Diff; end unless defined? Diff
4
4
 
5
- # == How Diff Works (by Mark-Jason Dominus)
5
+ # ## How Diff Works (by Mark-Jason Dominus)
6
6
  #
7
- # I once read an article written by the authors of +diff+; they said that they
8
- # hard worked very hard on the algorithm until they found the right one.
7
+ # I once read an article written by the authors of `diff`; they said that they hard worked
8
+ # very hard on the algorithm until they found the right one.
9
9
  #
10
- # I think what they ended up using (and I hope someone will correct me, because
11
- # I am not very confident about this) was the `longest common subsequence'
12
- # method. In the LCS problem, you have two sequences of items:
10
+ # I think what they ended up using (and I hope someone will correct me, because I am not
11
+ # very confident about this) was the `longest common subsequence' method. In the LCS
12
+ # problem, you have two sequences of items:
13
13
  #
14
- # a b c d f g h j q z
15
- # a b c d e f g i j k r x y z
14
+ # ```
15
+ # a b c d f g h j q z
16
+ # a b c d e f g i j k r x y z
17
+ # ```
16
18
  #
17
- # and you want to find the longest sequence of items that is present in both
18
- # original sequences in the same order. That is, you want to find a new
19
- # sequence *S* which can be obtained from the first sequence by deleting some
20
- # items, and from the second sequence by deleting other items. You also want
21
- # *S* to be as long as possible. In this case *S* is:
19
+ # and you want to find the longest sequence of items that is present in both original
20
+ # sequences in the same order. That is, you want to find a new sequence *S* which can be
21
+ # obtained from the first sequence by deleting some items, and from the second sequence by
22
+ # deleting other items. You also want *S* to be as long as possible. In this case *S* is:
22
23
  #
23
- # a b c d f g j z
24
+ # ```
25
+ # a b c d f g j z
26
+ # ```
24
27
  #
25
28
  # From there it's only a small step to get diff-like output:
26
29
  #
27
- # e h i k q r x y
28
- # + - + + - + + +
30
+ # ```
31
+ # e h i k q r x y
32
+ # + - + + - + + +
33
+ # ```
29
34
  #
30
- # This module solves the LCS problem. It also includes a canned function to
31
- # generate +diff+-like output.
35
+ # This module solves the LCS problem. It also includes a canned function to generate
36
+ # `diff`-like output.
32
37
  #
33
- # It might seem from the example above that the LCS of two sequences is always
34
- # pretty obvious, but that's not always the case, especially when the two
35
- # sequences have many repeated elements. For example, consider
38
+ # It might seem from the example above that the LCS of two sequences is always pretty
39
+ # obvious, but that's not always the case, especially when the two sequences have many
40
+ # repeated elements. For example, consider
36
41
  #
37
- # a x b y c z p d q
38
- # a b c a x b y c z
42
+ # ```
43
+ # a x b y c z p d q
44
+ # a b c a x b y c z
45
+ # ```
39
46
  #
40
- # A naive approach might start by matching up the +a+ and +b+ that appear at
47
+ # A naive approach might start by matching up the `a` and `b` that appear at
41
48
  # the beginning of each sequence, like this:
42
49
  #
43
- # a x b y c z p d q
44
- # a b c a b y c z
50
+ # ```
51
+ # a x b y c z p d q
52
+ # a b c a b y c z
53
+ # ```
45
54
  #
46
- # This finds the common subsequence +a b c z+. But actually, the LCS is +a x b
47
- # y c z+:
55
+ # This finds the common subsequence `a b c z`. But actually, the LCS is `a x b y c z`:
48
56
  #
49
- # a x b y c z p d q
50
- # a b c a x b y c z
57
+ # ```
58
+ # a x b y c z p d q
59
+ # a b c a x b y c z
60
+ # ```
51
61
  module Diff::LCS
52
62
  end
53
63
 
@@ -56,71 +66,47 @@ require "diff/lcs/callbacks"
56
66
  require "diff/lcs/internals"
57
67
 
58
68
  module Diff::LCS
59
- # Returns an Array containing the longest common subsequence(s) between
60
- # +self+ and +other+. See Diff::LCS#lcs.
61
- #
62
- # lcs = seq1.lcs(seq2)
63
- #
64
- # A note when using objects: Diff::LCS only works properly when each object
65
- # can be used as a key in a Hash. This means that those objects must implement
66
- # the methods +#hash+ and +#eql?+ such that two objects containing identical values
67
- # compare identically for key purposes. That is:
68
- #
69
- # O.new('a').eql?(O.new('a')) == true &&
70
- # O.new('a').hash == O.new('a').hash
71
- def lcs(other, &block) # :yields: self[i] if there are matched subsequences
69
+ # Returns an Array containing the longest common subsequence(s) between `self` and
70
+ # `other`. See Diff::LCS.lcs.
71
+ def lcs(other, &block) = # :yields: self[i] if there are matched subsequences
72
72
  Diff::LCS.lcs(self, other, &block)
73
- end
74
73
 
75
- # Returns the difference set between +self+ and +other+. See Diff::LCS#diff.
76
- def diff(other, callbacks = nil, &block)
77
- Diff::LCS.diff(self, other, callbacks, &block)
78
- end
74
+ # Returns the difference set between `self` and `other`. See Diff::LCS.diff.
75
+ def diff(other, callbacks = nil, &block) = Diff::LCS.diff(self, other, callbacks, &block)
79
76
 
80
- # Returns the balanced ("side-by-side") difference set between +self+ and
81
- # +other+. See Diff::LCS#sdiff.
82
- def sdiff(other, callbacks = nil, &block)
83
- Diff::LCS.sdiff(self, other, callbacks, &block)
84
- end
77
+ # Returns the balanced ("side-by-side") difference set between `self` and `other`. See
78
+ # Diff::LCS.sdiff.
79
+ def sdiff(other, callbacks = nil, &block) = Diff::LCS.sdiff(self, other, callbacks, &block)
85
80
 
86
- # Traverses the discovered longest common subsequences between +self+ and
87
- # +other+. See Diff::LCS#traverse_sequences.
88
- def traverse_sequences(other, callbacks = nil, &block)
81
+ # Traverses the discovered longest common subsequences between `self` and `other`. See
82
+ # Diff::LCS.traverse_sequences.
83
+ def traverse_sequences(other, callbacks = nil, &block) =
89
84
  Diff::LCS.traverse_sequences(self, other, callbacks || Diff::LCS::SequenceCallbacks, &block)
90
- end
91
85
 
92
- # Traverses the discovered longest common subsequences between +self+ and
93
- # +other+ using the alternate, balanced algorithm. See
94
- # Diff::LCS#traverse_balanced.
95
- def traverse_balanced(other, callbacks = nil, &block)
86
+ # Traverses the discovered longest common subsequences between `self` and `other` using
87
+ # the alternate, balanced algorithm. See Diff::LCS.traverse_balanced.
88
+ def traverse_balanced(other, callbacks = nil, &block) =
96
89
  Diff::LCS.traverse_balanced(self, other, callbacks || Diff::LCS::BalancedCallbacks, &block)
97
- end
98
90
 
99
- # Attempts to patch +self+ with the provided +patchset+. A new sequence based
100
- # on +self+ and the +patchset+ will be created. See Diff::LCS#patch. Attempts
101
- # to autodiscover the direction of the patch.
102
- def patch(patchset)
103
- Diff::LCS.patch(self, patchset)
104
- end
91
+ # Attempts to patch `self` with the provided `patchset`. A new sequence based on `self`
92
+ # and the `patchset` will be created. See Diff::LCS.patch. Attempts to autodiscover the
93
+ # direction of the patch.
94
+ def patch(patchset) = Diff::LCS.patch(self, patchset)
105
95
  alias_method :unpatch, :patch
106
96
 
107
- # Attempts to patch +self+ with the provided +patchset+. A new sequence based
108
- # on +self+ and the +patchset+ will be created. See Diff::LCS#patch. Does no
109
- # patch direction autodiscovery.
110
- def patch!(patchset)
111
- Diff::LCS.patch!(self, patchset)
112
- end
97
+ # Attempts to patch `self` with the provided `patchset`. A new sequence based on `self`
98
+ # and the `patchset` will be created. See Diff::LCS.patch!. Does no patch direction
99
+ # autodiscovery.
100
+ def patch!(patchset) = Diff::LCS.patch!(self, patchset)
113
101
 
114
- # Attempts to unpatch +self+ with the provided +patchset+. A new sequence
115
- # based on +self+ and the +patchset+ will be created. See Diff::LCS#unpatch.
116
- # Does no patch direction autodiscovery.
117
- def unpatch!(patchset)
118
- Diff::LCS.unpatch!(self, patchset)
119
- end
102
+ # Attempts to unpatch `self` with the provided `patchset`. A new sequence based on
103
+ # `self` and the `patchset` will be created. See Diff::LCS.unpatch!. Does no patch
104
+ # direction autodiscovery.
105
+ def unpatch!(patchset) = Diff::LCS.unpatch!(self, patchset)
120
106
 
121
- # Attempts to patch +self+ with the provided +patchset+, using #patch!. If
122
- # the sequence this is used on supports #replace, the value of +self+ will be
123
- # replaced. See Diff::LCS#patch. Does no patch direction autodiscovery.
107
+ # Attempts to patch `self` with the provided `patchset`, using #patch!. If the sequence
108
+ # this is used on supports #replace, the value of `self` will be replaced. See
109
+ # Diff::LCS.patch!. Does no patch direction autodiscovery.
124
110
  def patch_me(patchset)
125
111
  if respond_to? :replace
126
112
  replace(patch!(patchset))
@@ -129,9 +115,9 @@ module Diff::LCS
129
115
  end
130
116
  end
131
117
 
132
- # Attempts to unpatch +self+ with the provided +patchset+, using #unpatch!.
133
- # If the sequence this is used on supports #replace, the value of +self+ will
134
- # be replaced. See Diff::LCS#unpatch. Does no patch direction autodiscovery.
118
+ # Attempts to unpatch `self` with the provided `patchset`, using #unpatch!. If the
119
+ # sequence this is used on supports #replace, the value of `self` will be replaced. See
120
+ # Diff::LCS#unpatch. Does no patch direction autodiscovery.
135
121
  def unpatch_me(patchset)
136
122
  if respond_to? :replace
137
123
  replace(unpatch!(patchset))
@@ -139,440 +125,427 @@ module Diff::LCS
139
125
  unpatch!(patchset)
140
126
  end
141
127
  end
142
- end
143
128
 
144
- class << Diff::LCS
145
- def lcs(seq1, seq2, &block) # :yields: seq1[i] for each matched
129
+ # Returns an Array containing the longest common subsequence(s) between `seq` and
130
+ # `seq2`.
131
+ #
132
+ # > NOTE on comparing objects: Diff::LCS only works properly when each object can be
133
+ # > used as a key in a Hash. This means that those objects must implement the methods
134
+ # > `#hash` and `#eql?` such that two objects containing identical values compare
135
+ # > identically for key purposes. That is:
136
+ # >
137
+ # > ```
138
+ # > O.new('a').eql?(O.new('a')) == true && O.new('a').hash == O.new('a').hash
139
+ # > ```
140
+ def self.lcs(seq1, seq2, &block) # :yields: seq1[i] for each matched
146
141
  matches = Diff::LCS::Internals.lcs(seq1, seq2)
147
- ret = []
148
- string = seq1.is_a? String
149
- matches.each_index do |i|
150
- next if matches[i].nil?
151
-
152
- v = string ? seq1[i, 1] : seq1[i]
153
- v = block[v] if block
154
- ret << v
155
- end
156
- ret
142
+ [].tap { |result|
143
+ matches.each_index do
144
+ next if matches[_1].nil?
145
+
146
+ v = seq1[_1]
147
+ v = block.call(v) if block
148
+
149
+ result << v
150
+ end
151
+ }
157
152
  end
158
- alias_method :LCS, :lcs
159
-
160
- # #diff computes the smallest set of additions and deletions necessary to
161
- # turn the first sequence into the second, and returns a description of these
162
- # changes.
163
- #
164
- # See Diff::LCS::DiffCallbacks for the default behaviour. An alternate
165
- # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If a
166
- # Class argument is provided for +callbacks+, #diff will attempt to
167
- # initialise it. If the +callbacks+ object (possibly initialised) responds to
168
- # #finish, it will be called.
169
- def diff(seq1, seq2, callbacks = nil, &block) # :yields: diff changes
153
+
154
+ # `diff` computes the smallest set of additions and deletions necessary to turn the
155
+ # first sequence into the second, and returns a description of these changes.
156
+ #
157
+ # See Diff::LCS::DiffCallbacks for the default behaviour. An alternate behaviour may be
158
+ # implemented with Diff::LCS::ContextDiffCallbacks. If the `callbacks` object responds
159
+ # to #finish, it will be called.
160
+ def self.diff(seq1, seq2, callbacks = nil, &block) = # :yields: diff changes
170
161
  diff_traversal(:diff, seq1, seq2, callbacks || Diff::LCS::DiffCallbacks, &block)
171
- end
172
162
 
173
- # #sdiff computes all necessary components to show two sequences and their
174
- # minimized differences side by side, just like the Unix utility
175
- # <em>sdiff</em> does:
176
- #
177
- # old < -
178
- # same same
179
- # before | after
180
- # - > new
181
- #
182
- # See Diff::LCS::SDiffCallbacks for the default behaviour. An alternate
183
- # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If a
184
- # Class argument is provided for +callbacks+, #diff will attempt to
185
- # initialise it. If the +callbacks+ object (possibly initialised) responds to
186
- # #finish, it will be called.
187
- #
188
- # Each element of a returned array is a Diff::LCS::ContextChange object,
189
- # which can be implicitly converted to an array.
190
- #
191
- # Diff::LCS.sdiff(a, b).each do |action, (old_pos, old_element), (new_pos, new_element)|
192
- # case action
193
- # when '!'
194
- # # replace
195
- # when '-'
196
- # # delete
197
- # when '+'
198
- # # insert
199
- # end
163
+ # `sdiff` computes all necessary components to show two sequences and their minimized
164
+ # differences side by side, just like the Unix utility _sdiff_ does:
165
+ #
166
+ #
167
+ # ```
168
+ # old < -
169
+ # same same
170
+ # before | after
171
+ # - > new
172
+ # ```
173
+ #
174
+ # See Diff::LCS::SDiffCallbacks for the default behaviour. An alternate behaviour may be
175
+ # implemented with Diff::LCS::ContextDiffCallbacks. If the `callbacks` object responds
176
+ # to #finish, it will be called.
177
+ #
178
+ # Each element of a returned array is a Diff::LCS::ContextChange object, which can be
179
+ # implicitly converted to an array.
180
+ #
181
+ # ```ruby
182
+ # Diff::LCS.sdiff(a, b).each do |action, (old_pos, old_element), (new_pos, new_element)|
183
+ # case action
184
+ # when '!'
185
+ # # replace
186
+ # when '-'
187
+ # # delete
188
+ # when '+'
189
+ # # insert
200
190
  # end
201
- def sdiff(seq1, seq2, callbacks = nil, &block) # :yields: diff changes
191
+ # end
192
+ # ```
193
+ def self.sdiff(seq1, seq2, callbacks = nil, &block) = # :yields: diff changes
202
194
  diff_traversal(:sdiff, seq1, seq2, callbacks || Diff::LCS::SDiffCallbacks, &block)
203
- end
204
195
 
205
- # #traverse_sequences is the most general facility provided by this module;
206
- # #diff and #lcs are implemented as calls to it.
207
- #
208
- # The arguments to #traverse_sequences are the two sequences to traverse, and
209
- # a callback object, like this:
210
- #
211
- # traverse_sequences(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
212
- #
213
- # == Callback Methods
214
- #
215
- # Optional callback methods are <em>emphasized</em>.
216
- #
217
- # callbacks#match:: Called when +a+ and +b+ are pointing to
218
- # common elements in +A+ and +B+.
219
- # callbacks#discard_a:: Called when +a+ is pointing to an
220
- # element not in +B+.
221
- # callbacks#discard_b:: Called when +b+ is pointing to an
222
- # element not in +A+.
223
- # <em>callbacks#finished_a</em>:: Called when +a+ has reached the end of
224
- # sequence +A+.
225
- # <em>callbacks#finished_b</em>:: Called when +b+ has reached the end of
226
- # sequence +B+.
227
- #
228
- # == Algorithm
229
- #
230
- # a---+
231
- # v
232
- # A = a b c e h j l m n p
233
- # B = b c d e f j k l m r s t
234
- # ^
235
- # b---+
236
- #
237
- # If there are two arrows (+a+ and +b+) pointing to elements of sequences +A+
238
- # and +B+, the arrows will initially point to the first elements of their
239
- # respective sequences. #traverse_sequences will advance the arrows through
240
- # the sequences one element at a time, calling a method on the user-specified
241
- # callback object before each advance. It will advance the arrows in such a
242
- # way that if there are elements <tt>A[i]</tt> and <tt>B[j]</tt> which are
243
- # both equal and part of the longest common subsequence, there will be some
244
- # moment during the execution of #traverse_sequences when arrow +a+ is
245
- # pointing to <tt>A[i]</tt> and arrow +b+ is pointing to <tt>B[j]</tt>. When
246
- # this happens, #traverse_sequences will call <tt>callbacks#match</tt> and
247
- # then it will advance both arrows.
248
- #
249
- # Otherwise, one of the arrows is pointing to an element of its sequence that
250
- # is not part of the longest common subsequence. #traverse_sequences will
251
- # advance that arrow and will call <tt>callbacks#discard_a</tt> or
252
- # <tt>callbacks#discard_b</tt>, depending on which arrow it advanced. If both
253
- # arrows point to elements that are not part of the longest common
254
- # subsequence, then #traverse_sequences will advance arrow +a+ and call the
255
- # appropriate callback, then it will advance arrow +b+ and call the appropriate
256
- # callback.
257
- #
258
- # The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>, and
259
- # <tt>callbacks#discard_b</tt> are invoked with an event comprising the
260
- # action ("=", "+", or "-", respectively), the indexes +i+ and +j+, and the
261
- # elements <tt>A[i]</tt> and <tt>B[j]</tt>. Return values are discarded by
262
- # #traverse_sequences.
263
- #
264
- # === End of Sequences
265
- #
266
- # If arrow +a+ reaches the end of its sequence before arrow +b+ does,
267
- # #traverse_sequence will try to call <tt>callbacks#finished_a</tt> with the
268
- # last index and element of +A+ (<tt>A[-1]</tt>) and the current index and
269
- # element of +B+ (<tt>B[j]</tt>). If <tt>callbacks#finished_a</tt> does not
270
- # exist, then <tt>callbacks#discard_b</tt> will be called on each element of
271
- # +B+ until the end of the sequence is reached (the call will be done with
272
- # <tt>A[-1]</tt> and <tt>B[j]</tt> for each element).
273
- #
274
- # If +b+ reaches the end of +B+ before +a+ reaches the end of +A+,
275
- # <tt>callbacks#finished_b</tt> will be called with the current index and
276
- # element of +A+ (<tt>A[i]</tt>) and the last index and element of +B+
277
- # (<tt>A[-1]</tt>). Again, if <tt>callbacks#finished_b</tt> does not exist on
278
- # the callback object, then <tt>callbacks#discard_a</tt> will be called on
279
- # each element of +A+ until the end of the sequence is reached (<tt>A[i]</tt>
280
- # and <tt>B[-1]</tt>).
281
- #
282
- # There is a chance that one additional <tt>callbacks#discard_a</tt> or
283
- # <tt>callbacks#discard_b</tt> will be called after the end of the sequence
284
- # is reached, if +a+ has not yet reached the end of +A+ or +b+ has not yet
285
- # reached the end of +B+.
286
- def traverse_sequences(seq1, seq2, callbacks = Diff::LCS::SequenceCallbacks) # :yields: change events
196
+ # #traverse_sequences is the most general facility provided by this module; #diff and
197
+ # #lcs are implemented using #traverse_sequences.
198
+ #
199
+ # The arguments to #traverse_sequence are the two sequences to traverse, and a callback
200
+ # object, like this:
201
+ #
202
+ # ```ruby
203
+ # traverse_sequences(seq1, seq2, Diff::LCS::ContextDiffCallbacks)
204
+ # ```
205
+ #
206
+ # ### Callback Methods
207
+ #
208
+ # - `callbacks#match`: Called when `a` and `b` are pointing to common elements in `A`
209
+ # and `B`.
210
+ # - `callbacks#discard_a`: Called when `a` is pointing to an element not in `B`.
211
+ # - `callbacks#discard_b`: Called when `b` is pointing to an element not in `A`.
212
+ # - `callbacks#finished_a`: Called when `a` has reached the end of sequence `A`.
213
+ # Optional.
214
+ # - `callbacks#finished_b`: Called when `b` has reached the end of sequence `B`.
215
+ # Optional.
216
+ #
217
+ # ### Algorithm
218
+ #
219
+ # ```
220
+ # a---+
221
+ # v
222
+ # A = a b c e h j l m n p
223
+ # B = b c d e f j k l m r s t
224
+ # ^
225
+ # b---+
226
+ # ```
227
+ #
228
+ # If there are two arrows (`a` and `b`) pointing to elements of sequences `A` and `B`,
229
+ # the arrows will initially point to the first elements of their respective sequences.
230
+ # #traverse_sequences will advance the arrows through the sequences one element at
231
+ # a time, calling a method on the user-specified callback object before each advance. It
232
+ # will advance the arrows in such a way that if there are elements `A[i]` and `B[j]`
233
+ # which are both equal and part of the longest common subsequence, there will be some
234
+ # moment during the execution of #traverse_sequences when arrow `a` is pointing to
235
+ # `A[i]` and arrow `b` is pointing to `B[j]`. When this happens, #traverse_sequences
236
+ # will call `callbacks#match` and then it will advance both arrows.
237
+ #
238
+ # Otherwise, one of the arrows is pointing to an element of its sequence that is not
239
+ # part of the longest common subsequence. #traverse_sequences will advance that arrow
240
+ # and will call `callbacks#discard_a` or `callbacks#discard_b`, depending on which arrow
241
+ # it advanced. If both arrows point to elements that are not part of the longest common
242
+ # subsequence, then #traverse_sequences will advance arrow `a` and call the appropriate
243
+ # callback, then it will advance arrow `b` and call the appropriate callback.
244
+ #
245
+ # The methods for `callbacks#match`, `callbacks#discard_a`, and `callbacks#discard_b`
246
+ # are invoked with an event comprising the action ("=", "+", or "-", respectively), the
247
+ # indexes `i` and `j`, and the elements `A[i]` and `B[j]`. Return values are discarded
248
+ # by #traverse_sequences.
249
+ #
250
+ # #### End of Sequences
251
+ #
252
+ # If arrow `a` reaches the end of its sequence before arrow `b` does, #traverse_sequence
253
+ # will try to call `callbacks#finished_a` with the last index and element of `A`
254
+ # (`A[-1]`) and the current index and element of `B` (`B[j]`). If `callbacks#finished_a`
255
+ # does not exist, then `callbacks#discard_b` will be called on each element of `B` until
256
+ # the end of the sequence is reached (the call will be done with `A[-1]` and `B[j]` for
257
+ # each element).
258
+ #
259
+ # If `b` reaches the end of `B` before `a` reaches the end of `A`,
260
+ # `callbacks#finished_b` will be called with the current index and element of `A`
261
+ # (`A[i]`) and the last index and element of `B` (`A[-1]`). Again, if
262
+ # `callbacks#finished_b` does not exist on the callback object, then
263
+ # `callbacks#discard_a` will be called on each element of `A` until the end of the
264
+ # sequence is reached (`A[i]` and `B[-1]`).
265
+ #
266
+ # There is a chance that one additional `callbacks#discard_a` or `callbacks#discard_b`
267
+ # will be called after the end of the sequence is reached, if `a` has not yet reached
268
+ # the end of `A` or `b` has not yet reached the end of `B`.
269
+ def self.traverse_sequences(seq1, seq2, callbacks = nil) # :yields: change events
287
270
  callbacks ||= Diff::LCS::SequenceCallbacks
288
271
  matches = Diff::LCS::Internals.lcs(seq1, seq2)
289
272
 
290
273
  run_finished_a = run_finished_b = false
291
- string = seq1.is_a?(String)
292
274
 
293
275
  a_size = seq1.size
294
276
  b_size = seq2.size
295
- ai = bj = 0
277
+ a_i = b_j = 0
296
278
 
297
279
  matches.each do |b_line|
298
280
  if b_line.nil?
299
- unless seq1[ai].nil?
300
- ax = string ? seq1[ai, 1] : seq1[ai]
301
- bx = string ? seq2[bj, 1] : seq2[bj]
281
+ unless seq1[a_i].nil?
282
+ a_x = seq1[a_i]
283
+ b_x = seq2[b_j]
302
284
 
303
- event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx)
285
+ event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
304
286
  event = yield event if block_given?
305
287
  callbacks.discard_a(event)
306
288
  end
307
289
  else
308
- ax = string ? seq1[ai, 1] : seq1[ai]
290
+ a_x = seq1[a_i]
309
291
 
310
292
  loop do
311
- break unless bj < b_line
293
+ break unless b_j < b_line
312
294
 
313
- bx = string ? seq2[bj, 1] : seq2[bj]
314
- event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx)
295
+ b_x = seq2[b_j]
296
+ event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
315
297
  event = yield event if block_given?
316
298
  callbacks.discard_b(event)
317
- bj += 1
299
+ b_j += 1
318
300
  end
319
- bx = string ? seq2[bj, 1] : seq2[bj]
320
- event = Diff::LCS::ContextChange.new("=", ai, ax, bj, bx)
301
+ b_x = seq2[b_j]
302
+ event = Diff::LCS::ContextChange.new("=", a_i, a_x, b_j, b_x)
321
303
  event = yield event if block_given?
322
304
  callbacks.match(event)
323
- bj += 1
305
+ b_j += 1
324
306
  end
325
- ai += 1
307
+
308
+ a_i += 1
326
309
  end
327
310
 
328
- # The last entry (if any) processed was a match. +ai+ and +bj+ point just
329
- # past the last matching lines in their sequences.
330
- while (ai < a_size) || (bj < b_size)
311
+ # The last entry (if any) processed was a match. `a_i` and `b_j` point just past the
312
+ # last matching lines in their sequences.
313
+ while (a_i < a_size) || (b_j < b_size)
331
314
  # last A?
332
- if ai == a_size && bj < b_size
315
+ if a_i == a_size && b_j < b_size
333
316
  if callbacks.respond_to?(:finished_a) && !run_finished_a
334
- ax = string ? seq1[-1, 1] : seq1[-1]
335
- bx = string ? seq2[bj, 1] : seq2[bj]
336
- event = Diff::LCS::ContextChange.new(">", a_size - 1, ax, bj, bx)
317
+ a_x = seq1[-1]
318
+ b_x = seq2[b_j]
319
+ event = Diff::LCS::ContextChange.new(">", a_size - 1, a_x, b_j, b_x)
337
320
  event = yield event if block_given?
338
321
  callbacks.finished_a(event)
339
322
  run_finished_a = true
340
323
  else
341
- ax = string ? seq1[ai, 1] : seq1[ai]
324
+ a_x = seq1[a_i]
342
325
  loop do
343
- bx = string ? seq2[bj, 1] : seq2[bj]
344
- event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx)
326
+ b_x = seq2[b_j]
327
+ event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
345
328
  event = yield event if block_given?
346
329
  callbacks.discard_b(event)
347
- bj += 1
348
- break unless bj < b_size
330
+ b_j += 1
331
+ break unless b_j < b_size
349
332
  end
350
333
  end
351
334
  end
352
335
 
353
336
  # last B?
354
- if bj == b_size && ai < a_size
337
+ if b_j == b_size && a_i < a_size
355
338
  if callbacks.respond_to?(:finished_b) && !run_finished_b
356
- ax = string ? seq1[ai, 1] : seq1[ai]
357
- bx = string ? seq2[-1, 1] : seq2[-1]
358
- event = Diff::LCS::ContextChange.new("<", ai, ax, b_size - 1, bx)
339
+ a_x = seq1[a_i]
340
+ b_x = seq2[-1]
341
+ event = Diff::LCS::ContextChange.new("<", a_i, a_x, b_size - 1, b_x)
359
342
  event = yield event if block_given?
360
343
  callbacks.finished_b(event)
361
344
  run_finished_b = true
362
345
  else
363
- bx = string ? seq2[bj, 1] : seq2[bj]
346
+ b_x = seq2[b_j]
364
347
  loop do
365
- ax = string ? seq1[ai, 1] : seq1[ai]
366
- event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx)
348
+ a_x = seq1[a_i]
349
+ event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
367
350
  event = yield event if block_given?
368
351
  callbacks.discard_a(event)
369
- ai += 1
370
- break unless bj < b_size
352
+ a_i += 1
353
+ break unless b_j < b_size
371
354
  end
372
355
  end
373
356
  end
374
357
 
375
- if ai < a_size
376
- ax = string ? seq1[ai, 1] : seq1[ai]
377
- bx = string ? seq2[bj, 1] : seq2[bj]
378
- event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx)
358
+ if a_i < a_size
359
+ a_x = seq1[a_i]
360
+ b_x = seq2[b_j]
361
+ event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
379
362
  event = yield event if block_given?
380
363
  callbacks.discard_a(event)
381
- ai += 1
364
+ a_i += 1
382
365
  end
383
366
 
384
- if bj < b_size
385
- ax = string ? seq1[ai, 1] : seq1[ai]
386
- bx = string ? seq2[bj, 1] : seq2[bj]
387
- event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx)
367
+ if b_j < b_size
368
+ a_x = seq1[a_i]
369
+ b_x = seq2[b_j]
370
+ event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
388
371
  event = yield event if block_given?
389
372
  callbacks.discard_b(event)
390
- bj += 1
373
+ b_j += 1
391
374
  end
392
375
  end
393
376
  end
394
377
 
395
- # #traverse_balanced is an alternative to #traverse_sequences. It uses a
396
- # different algorithm to iterate through the entries in the computed longest
397
- # common subsequence. Instead of viewing the changes as insertions or
398
- # deletions from one of the sequences, #traverse_balanced will report
399
- # <em>changes</em> between the sequences.
400
- #
401
- # The arguments to #traverse_balanced are the two sequences to traverse and a
402
- # callback object, like this:
403
- #
404
- # traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
405
- #
406
- # #sdiff is implemented with #traverse_balanced.
407
- #
408
- # == Callback Methods
409
- #
410
- # Optional callback methods are <em>emphasized</em>.
411
- #
412
- # callbacks#match:: Called when +a+ and +b+ are pointing to
413
- # common elements in +A+ and +B+.
414
- # callbacks#discard_a:: Called when +a+ is pointing to an
415
- # element not in +B+.
416
- # callbacks#discard_b:: Called when +b+ is pointing to an
417
- # element not in +A+.
418
- # <em>callbacks#change</em>:: Called when +a+ and +b+ are pointing to
419
- # the same relative position, but
420
- # <tt>A[a]</tt> and <tt>B[b]</tt> are not
421
- # the same; a <em>change</em> has
422
- # occurred.
423
- #
424
- # #traverse_balanced might be a bit slower than #traverse_sequences,
425
- # noticeable only while processing huge amounts of data.
426
- #
427
- # == Algorithm
428
- #
429
- # a---+
430
- # v
431
- # A = a b c e h j l m n p
432
- # B = b c d e f j k l m r s t
433
- # ^
434
- # b---+
435
- #
436
- # === Matches
437
- #
438
- # If there are two arrows (+a+ and +b+) pointing to elements of sequences +A+
439
- # and +B+, the arrows will initially point to the first elements of their
440
- # respective sequences. #traverse_sequences will advance the arrows through
441
- # the sequences one element at a time, calling a method on the user-specified
442
- # callback object before each advance. It will advance the arrows in such a
443
- # way that if there are elements <tt>A[i]</tt> and <tt>B[j]</tt> which are
444
- # both equal and part of the longest common subsequence, there will be some
445
- # moment during the execution of #traverse_sequences when arrow +a+ is
446
- # pointing to <tt>A[i]</tt> and arrow +b+ is pointing to <tt>B[j]</tt>. When
447
- # this happens, #traverse_sequences will call <tt>callbacks#match</tt> and
448
- # then it will advance both arrows.
449
- #
450
- # === Discards
451
- #
452
- # Otherwise, one of the arrows is pointing to an element of its sequence that
453
- # is not part of the longest common subsequence. #traverse_sequences will
454
- # advance that arrow and will call <tt>callbacks#discard_a</tt> or
455
- # <tt>callbacks#discard_b</tt>, depending on which arrow it advanced.
456
- #
457
- # === Changes
458
- #
459
- # If both +a+ and +b+ point to elements that are not part of the longest
460
- # common subsequence, then #traverse_sequences will try to call
461
- # <tt>callbacks#change</tt> and advance both arrows. If
462
- # <tt>callbacks#change</tt> is not implemented, then
463
- # <tt>callbacks#discard_a</tt> and <tt>callbacks#discard_b</tt> will be
464
- # called in turn.
465
- #
466
- # The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>,
467
- # <tt>callbacks#discard_b</tt>, and <tt>callbacks#change</tt> are invoked
468
- # with an event comprising the action ("=", "+", "-", or "!", respectively),
469
- # the indexes +i+ and +j+, and the elements <tt>A[i]</tt> and <tt>B[j]</tt>.
378
+ # #traverse_balanced is an alternative to #traverse_sequences. It uses a different
379
+ # algorithm to iterate through the entries in the computed longest common subsequence.
380
+ # Instead of viewing the changes as insertions or deletions from one of the sequences,
381
+ # #traverse_balanced will report _changes_ between the sequences.
382
+ #
383
+ # The arguments to #traverse_balanced are the two sequences to traverse and a callback
384
+ # object, like this:
385
+ #
386
+ # ```ruby
387
+ # traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks)
388
+ # ```
389
+ #
390
+ # #sdiff is implemented using #traverse_balanced.
391
+ #
392
+ # ### Callback Methods
393
+ #
394
+ # - `callbacks#match`: Called when `a` and `b` are pointing to common elements in `A`
395
+ # and `B`.
396
+ # - `callbacks#discard_a`: Called when `a` is pointing to an element not in `B`.
397
+ # - `callbacks#discard_b`: Called when `b` is pointing to an element not in `A`.
398
+ # - `callbacks#change`: Called when `a` and `b` are pointing to the same relative
399
+ # position, but `A[a]` and `B[b]` are not the same; a _change_ has occurred. Optional.
400
+ #
401
+ # #traverse_balanced might be a bit slower than #traverse_sequences, noticeable only
402
+ # while processing large amounts of data.
403
+ #
404
+ # ### Algorithm
405
+ #
406
+ # ```
407
+ # a---+
408
+ # v
409
+ # A = a b c e h j l m n p
410
+ # B = b c d e f j k l m r s t
411
+ # ^
412
+ # b---+
413
+ # ```
414
+ #
415
+ # #### Matches
416
+ #
417
+ # If there are two arrows (`a` and `b`) pointing to elements of sequences `A` and `B`,
418
+ # the arrows will initially point to the first elements of their respective sequences.
419
+ # #traverse_sequences will advance the arrows through the sequences one element at
420
+ # a time, calling a method on the user-specified callback object before each advance. It
421
+ # will advance the arrows in such a way that if there are elements `A[i]` and
422
+ # `B[j]` which are both equal and part of the longest common subsequence, there will be
423
+ # some moment during the execution of #traverse_sequences when arrow `a` is pointing to
424
+ # `A[i]` and arrow `b` is pointing to `B[j]`. When this happens, #traverse_sequences
425
+ # will call `callbacks#match` and then it will advance both arrows.
426
+ #
427
+ # #### Discards
428
+ #
429
+ # Otherwise, one of the arrows is pointing to an element of its sequence that is not
430
+ # part of the longest common subsequence. #traverse_sequences will advance that arrow
431
+ # and will call `callbacks#discard_a` or `callbacks#discard_b`, depending on which arrow
432
+ # it advanced.
433
+ #
434
+ # #### Changes
435
+ #
436
+ # If both `a` and `b` point to elements that are not part of the longest common
437
+ # subsequence, then #traverse_sequences will try to call `callbacks#change` and advance
438
+ # both arrows. If `callbacks#change` is not implemented, then `callbacks#discard_a` and
439
+ # `callbacks#discard_b` will be called in turn.
440
+ #
441
+ # The methods for `callbacks#match`, `callbacks#discard_a`, `callbacks#discard_b`, and
442
+ # `callbacks#change` are invoked with an event comprising the action ("=", "+", "-", or
443
+ # "!", respectively), the indexes `i` and `j`, and the elements `A[i]` and `B[j]`.
470
444
  # Return values are discarded by #traverse_balanced.
471
445
  #
472
446
  # === Context
473
447
  #
474
- # Note that +i+ and +j+ may not be the same index position, even if +a+ and
475
- # +b+ are considered to be pointing to matching or changed elements.
476
- def traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks)
448
+ # Note that `i` and `j` may not be the same index position, even if `a` and `b` are
449
+ # considered to be pointing to matching or changed elements.
450
+ def self.traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks)
477
451
  matches = Diff::LCS::Internals.lcs(seq1, seq2)
478
452
  a_size = seq1.size
479
453
  b_size = seq2.size
480
- ai = bj = mb = 0
481
- ma = -1
482
- string = seq1.is_a?(String)
454
+ a_i = b_j = m_b = 0
455
+ m_a = -1
483
456
 
484
457
  # Process all the lines in the match vector.
485
458
  loop do
486
- # Find next match indexes +ma+ and +mb+
459
+ # Find next match indexes `m_a` and `m_b`
487
460
  loop do
488
- ma += 1
489
- break unless ma < matches.size && matches[ma].nil?
461
+ m_a += 1
462
+ break unless m_a < matches.size && matches[m_a].nil?
490
463
  end
491
464
 
492
- break if ma >= matches.size # end of matches?
465
+ break if m_a >= matches.size # end of matches?
493
466
 
494
- mb = matches[ma]
467
+ m_b = matches[m_a]
495
468
 
496
469
  # Change(seq2)
497
- while (ai < ma) || (bj < mb)
498
- ax = string ? seq1[ai, 1] : seq1[ai]
499
- bx = string ? seq2[bj, 1] : seq2[bj]
470
+ while (a_i < m_a) || (b_j < m_b)
471
+ a_x = seq1[a_i]
472
+ b_x = seq2[b_j]
500
473
 
501
- case [(ai < ma), (bj < mb)]
474
+ case [(a_i < m_a), (b_j < m_b)]
502
475
  when [true, true]
503
476
  if callbacks.respond_to?(:change)
504
- event = Diff::LCS::ContextChange.new("!", ai, ax, bj, bx)
477
+ event = Diff::LCS::ContextChange.new("!", a_i, a_x, b_j, b_x)
505
478
  event = yield event if block_given?
506
479
  callbacks.change(event)
507
- ai += 1
480
+ a_i += 1
508
481
  else
509
- event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx)
482
+ event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
510
483
  event = yield event if block_given?
511
484
  callbacks.discard_a(event)
512
- ai += 1
513
- ax = string ? seq1[ai, 1] : seq1[ai]
514
- event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx)
485
+ a_i += 1
486
+ a_x = seq1[a_i]
487
+ event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
515
488
  event = yield event if block_given?
516
489
  callbacks.discard_b(event)
517
490
  end
518
491
 
519
- bj += 1
492
+ b_j += 1
520
493
  when [true, false]
521
- event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx)
494
+ event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
522
495
  event = yield event if block_given?
523
496
  callbacks.discard_a(event)
524
- ai += 1
497
+ a_i += 1
525
498
  when [false, true]
526
- event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx)
499
+ event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
527
500
  event = yield event if block_given?
528
501
  callbacks.discard_b(event)
529
- bj += 1
502
+ b_j += 1
530
503
  end
531
504
  end
532
505
 
533
506
  # Match
534
- ax = string ? seq1[ai, 1] : seq1[ai]
535
- bx = string ? seq2[bj, 1] : seq2[bj]
536
- event = Diff::LCS::ContextChange.new("=", ai, ax, bj, bx)
507
+ a_x = seq1[a_i]
508
+ b_x = seq2[b_j]
509
+ event = Diff::LCS::ContextChange.new("=", a_i, a_x, b_j, b_x)
537
510
  event = yield event if block_given?
538
511
  callbacks.match(event)
539
- ai += 1
540
- bj += 1
512
+ a_i += 1
513
+ b_j += 1
541
514
  end
542
515
 
543
- while (ai < a_size) || (bj < b_size)
544
- ax = string ? seq1[ai, 1] : seq1[ai]
545
- bx = string ? seq2[bj, 1] : seq2[bj]
516
+ while (a_i < a_size) || (b_j < b_size)
517
+ a_x = seq1[a_i]
518
+ b_x = seq2[b_j]
546
519
 
547
- case [(ai < a_size), (bj < b_size)]
520
+ case [(a_i < a_size), (b_j < b_size)]
548
521
  when [true, true]
549
522
  if callbacks.respond_to?(:change)
550
- event = Diff::LCS::ContextChange.new("!", ai, ax, bj, bx)
523
+ event = Diff::LCS::ContextChange.new("!", a_i, a_x, b_j, b_x)
551
524
  event = yield event if block_given?
552
525
  callbacks.change(event)
553
- ai += 1
526
+ a_i += 1
554
527
  else
555
- event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx)
528
+ event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
556
529
  event = yield event if block_given?
557
530
  callbacks.discard_a(event)
558
- ai += 1
559
- ax = string ? seq1[ai, 1] : seq1[ai]
560
- event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx)
531
+ a_i += 1
532
+ a_x = seq1[a_i]
533
+ event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
561
534
  event = yield event if block_given?
562
535
  callbacks.discard_b(event)
563
536
  end
564
537
 
565
- bj += 1
538
+ b_j += 1
566
539
  when [true, false]
567
- event = Diff::LCS::ContextChange.new("-", ai, ax, bj, bx)
540
+ event = Diff::LCS::ContextChange.new("-", a_i, a_x, b_j, b_x)
568
541
  event = yield event if block_given?
569
542
  callbacks.discard_a(event)
570
- ai += 1
543
+ a_i += 1
571
544
  when [false, true]
572
- event = Diff::LCS::ContextChange.new("+", ai, ax, bj, bx)
545
+ event = Diff::LCS::ContextChange.new("+", a_i, a_x, b_j, b_x)
573
546
  event = yield event if block_given?
574
547
  callbacks.discard_b(event)
575
- bj += 1
548
+ b_j += 1
576
549
  end
577
550
  end
578
551
  end
@@ -582,61 +555,67 @@ class << Diff::LCS
582
555
  :patch => {"+" => "+", "-" => "-", "!" => "!", "=" => "="}.freeze,
583
556
  :unpatch => {"+" => "-", "-" => "+", "!" => "!", "=" => "="}.freeze
584
557
  }.freeze
558
+ private_constant :PATCH_MAP
585
559
  # standard:enable Style/HashSyntax
586
560
 
587
- # Applies a +patchset+ to the sequence +src+ according to the +direction+
588
- # (<tt>:patch</tt> or <tt>:unpatch</tt>), producing a new sequence.
561
+ # Applies a `patchset` to the sequence `src` according to the `direction` (`:patch` or
562
+ # `:unpatch`), producing a new sequence.
589
563
  #
590
- # If the +direction+ is not specified, Diff::LCS::patch will attempt to
591
- # discover the direction of the +patchset+.
564
+ # If the `direction` is not specified, Diff::LCS::patch will attempt to discover the
565
+ # direction of the `patchset`.
592
566
  #
593
- # A +patchset+ can be considered to apply forward (<tt>:patch</tt>) if the
594
- # following expression is true:
567
+ # A `patchset` can be considered to apply forward (`:patch`) if the following expression
568
+ # is true:
595
569
  #
596
- # patch(s1, diff(s1, s2)) -> s2
570
+ # ```ruby
571
+ # patch(s1, diff(s1, s2)) # => s2
572
+ # ```
597
573
  #
598
- # A +patchset+ can be considered to apply backward (<tt>:unpatch</tt>) if the
599
- # following expression is true:
574
+ # A `patchset` can be considered to apply backward (`:unpatch`) if the following
575
+ # expression is true:
600
576
  #
601
- # patch(s2, diff(s1, s2)) -> s1
577
+ # ```ruby
578
+ # patch(s2, diff(s1, s2)) # => s1
579
+ # ```
602
580
  #
603
- # If the +patchset+ contains no changes, the +src+ value will be returned as
604
- # either <tt>src.dup</tt> or +src+. A +patchset+ can be deemed as having no
605
- # changes if the following predicate returns true:
581
+ # If the `patchset` contains no changes, the `src` value will be returned as either
582
+ # `src.dup` or `src`. A `patchset` can be deemed as having no changes if the following
583
+ # predicate returns true:
606
584
  #
607
- # patchset.empty? or
608
- # patchset.flatten(1).all? { |change| change.unchanged? }
585
+ # ```ruby
586
+ # patchset.empty? or patchset.flatten(1).all? { |change| change.unchanged? }
587
+ # ```
609
588
  #
610
- # === Patchsets
589
+ # ### Patchsets
611
590
  #
612
- # A +patchset+ is always an enumerable sequence of changes, hunks of changes,
613
- # or a mix of the two. A hunk of changes is an enumerable sequence of
614
- # changes:
591
+ # A `patchset` is always an enumerable sequence of changes, hunks of changes, or a mix
592
+ # of the two. A hunk of changes is an enumerable sequence of changes:
615
593
  #
616
- # [ # patchset
617
- # # change
618
- # [ # hunk
619
- # # change
620
- # ]
621
- # ]
594
+ # ```
595
+ # [ # patchset
596
+ # # change
597
+ # [ # hunk
598
+ # # change
599
+ # ]
600
+ # ]
601
+ # ```
622
602
  #
623
- # The +patch+ method accepts <tt>patchset</tt>s that are enumerable sequences
624
- # containing either Diff::LCS::Change objects (or a subclass) or the array
625
- # representations of those objects. Prior to application, array
626
- # representations of Diff::LCS::Change objects will be reified.
627
- def patch(src, patchset, direction = nil)
603
+ # The `patch` method accepts `patchset`s that are enumerable sequences containing either
604
+ # Diff::LCS::Change objects (or a subclass) or the array representations of those
605
+ # objects. Prior to application, array representations of Diff::LCS::Change objects will
606
+ # be reified.
607
+ def self.patch(src, patchset, direction = nil)
628
608
  # Normalize the patchset.
629
609
  has_changes, patchset = Diff::LCS::Internals.analyze_patchset(patchset)
630
610
 
631
611
  return src.respond_to?(:dup) ? src.dup : src unless has_changes
632
612
 
633
- string = src.is_a?(String)
634
613
  # Start with a new empty type of the source's class
635
614
  res = src.class.new
636
615
 
637
616
  direction ||= Diff::LCS::Internals.intuit_diff_direction(src, patchset)
638
617
 
639
- ai = bj = 0
618
+ a_i = b_j = 0
640
619
 
641
620
  patch_map = PATCH_MAP[direction]
642
621
 
@@ -659,84 +638,78 @@ class << Diff::LCS
659
638
 
660
639
  case action
661
640
  when "-" # Remove details from the old string
662
- while ai < op
663
- res << (string ? src[ai, 1] : src[ai])
664
- ai += 1
665
- bj += 1
641
+ while a_i < op
642
+ res << src[a_i]
643
+ a_i += 1
644
+ b_j += 1
666
645
  end
667
- ai += 1
646
+ a_i += 1
668
647
  when "+"
669
- while bj < np
670
- res << (string ? src[ai, 1] : src[ai])
671
- ai += 1
672
- bj += 1
648
+ while b_j < np
649
+ res << src[a_i]
650
+ a_i += 1
651
+ b_j += 1
673
652
  end
674
653
 
675
654
  res << el
676
- bj += 1
655
+ b_j += 1
677
656
  when "="
678
657
  # This only appears in sdiff output with the SDiff callback.
679
658
  # Therefore, we only need to worry about dealing with a single
680
659
  # element.
681
660
  res << el
682
661
 
683
- ai += 1
684
- bj += 1
662
+ a_i += 1
663
+ b_j += 1
685
664
  when "!"
686
- while ai < op
687
- res << (string ? src[ai, 1] : src[ai])
688
- ai += 1
689
- bj += 1
665
+ while a_i < op
666
+ res << src[a_i]
667
+ a_i += 1
668
+ b_j += 1
690
669
  end
691
670
 
692
- bj += 1
693
- ai += 1
671
+ b_j += 1
672
+ a_i += 1
694
673
 
695
674
  res << el
696
675
  end
697
676
  when Diff::LCS::Change
698
677
  case action
699
678
  when "-"
700
- while ai < change.position
701
- res << (string ? src[ai, 1] : src[ai])
702
- ai += 1
703
- bj += 1
679
+ while a_i < change.position
680
+ res << src[a_i]
681
+ a_i += 1
682
+ b_j += 1
704
683
  end
705
- ai += 1
684
+ a_i += 1
706
685
  when "+"
707
- while bj < change.position
708
- res << (string ? src[ai, 1] : src[ai])
709
- ai += 1
710
- bj += 1
686
+ while b_j < change.position
687
+ res << src[a_i]
688
+ a_i += 1
689
+ b_j += 1
711
690
  end
712
691
 
713
- bj += 1
692
+ b_j += 1
714
693
 
715
694
  res << change.element
716
695
  end
717
696
  end
718
697
  end
719
698
 
720
- while ai < src.size
721
- res << (string ? src[ai, 1] : src[ai])
722
- ai += 1
723
- bj += 1
699
+ while a_i < src.size
700
+ res << src[a_i]
701
+ a_i += 1
702
+ b_j += 1
724
703
  end
725
704
 
726
705
  res
727
706
  end
728
707
 
729
- # Given a set of patchset, convert the current version to the prior version.
730
- # Does no auto-discovery.
731
- def unpatch!(src, patchset)
732
- patch(src, patchset, :unpatch)
733
- end
708
+ # Given a patchset, convert the current version to the prior version. Does no
709
+ # auto-discovery.
710
+ def self.unpatch!(src, patchset) = patch(src, patchset, :unpatch)
734
711
 
735
- # Given a set of patchset, convert the current version to the next version.
736
- # Does no auto-discovery.
737
- def patch!(src, patchset)
738
- patch(src, patchset, :patch)
739
- end
712
+ # Given a patchset, convert the current version to the next version. Does no
713
+ # auto-discovery.
714
+ def self.patch!(src, patchset) = patch(src, patchset, :patch)
740
715
  end
741
-
742
- require "diff/lcs/backports"