super_diff 0.16.0 → 0.17.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7da584c29393f807295a740d893c8890b5d33f9507f4c0ddd914b672b5e9b71
4
- data.tar.gz: 89e8cb0b6bd46b7f109b91421636acb21356e14c1e75e0acbd6a45d22b086172
3
+ metadata.gz: f84c14c100aff4bd5ec41c27530ecc6e7a586a90d770a07617b2a591fb438658
4
+ data.tar.gz: b441eb9beafd67394df0f3b6a13d0bfafc5cd94d06593acf5780b8df7cc59df0
5
5
  SHA512:
6
- metadata.gz: 573603b3933aa21c388973e295565199e0c15660a28f7204d1a9d281f0ca320cf28e80bbaf0b9074df13c1e0ea324a5654844985a3f0db9011eb2bb5ff3b7c6c
7
- data.tar.gz: 8daa4e73a589b67ac571ea101d7080a1487daf13dc42ce0ce7ee224b9801fd8ed3399e8380c9611cec4d2e93dc5611af3c9bbd5da02c434245d9fda5f127de19
6
+ metadata.gz: 0a581414c9306226bd8a65cd5c92d8d64e35586a969beefdf2f53f08d7c97bd57d78d30dcf98fcd1f1b7f33446a2eda4f719510ac42106818e83022f3f7b0307
7
+ data.tar.gz: a5efcec3c3818c5849110701813c04dd66f8fe8edebbd3f4db031fddbb30916303331ae20489421e94dc24f0dd047af9e8e03e810d5ba5bc90a30cbe9d995524
@@ -21,195 +21,95 @@ module SuperDiff
21
21
  private
22
22
 
23
23
  def unary_operations_using_variant_of_patience_algorithm
24
- operations = []
25
24
  aks = actual.keys
26
25
  eks = expected.keys
27
- previous_ei = nil
28
- ei = 0
29
- ai = 0
30
-
31
- # When diffing a hash, we're more interested in the 'actual' version
32
- # than the 'expected' version, because that's the ultimate truth.
33
- # Therefore, the diff is presented from the perspective of the 'actual'
34
- # hash, and we start off by looping over it.
35
- while ai < aks.size
36
- ak = aks[ai]
37
- av = actual[ak]
38
- ev = expected[ak]
39
- # While we iterate over 'actual' in order, we jump all over
40
- # 'expected', trying to match up its keys with the keys in 'actual' as
41
- # much as possible.
42
- ei = eks.index(ak)
43
-
44
- if should_add_noop_operation?(ak)
45
- # (If we're here, it probably means that the key we're pointing to
46
- # in the 'actual' and 'expected' hashes have the same value.)
47
-
48
- if ei && previous_ei && (ei - previous_ei) > 1
49
- # If we've jumped from one operation in the 'expected' hash to
50
- # another operation later in 'expected' (due to the fact that the
51
- # 'expected' hash is in a different order than 'actual'), collect
52
- # any delete operations in between and add them to our operations
53
- # array as deletes before adding the noop. If we don't do this
54
- # now, then those deletes will disappear. (Again, we are mainly
55
- # iterating over 'actual', so this is the only way to catch all of
56
- # the keys in 'expected'.)
57
- (previous_ei + 1).upto(ei - 1) do |ei2|
58
- ek = eks[ei2]
59
- ev2 = expected[ek]
60
- av2 = actual[ek]
61
-
62
- next unless
63
- (!actual.include?(ek) || ev2 != av2) &&
64
- operations.none? do |operation|
65
- %i[delete noop].include?(operation.name) &&
66
- operation.key == ek
67
- end
68
-
69
- operations << Core::UnaryOperation.new(
70
- name: :delete,
71
- collection: expected,
72
- key: ek,
73
- value: ev2,
74
- index: ei2
75
- )
76
- end
77
- end
78
26
 
27
+ operations = []
28
+ lcs_callbacks = LCSCallbacks.new(
29
+ operations: operations,
30
+ expected: expected,
31
+ actual: actual,
32
+ should_add_noop_operation: method(:should_add_noop_operation?)
33
+ )
34
+ Diff::LCS.traverse_sequences(eks, aks, lcs_callbacks)
35
+
36
+ operations
37
+ end
38
+
39
+ class LCSCallbacks
40
+ extend AttrExtras.mixin
41
+
42
+ pattr_initialize %i[operations! expected! actual! should_add_noop_operation!]
43
+ public :operations
44
+
45
+ def discard_a(event)
46
+ # This key is in `expected`, but may also be in `actual`.
47
+ key = event.old_element
48
+
49
+ # We want the diff to match the key order of `actual` as much as
50
+ # possible, so if this is also a key in `actual` that's just being
51
+ # reordered, don't add any operations now. The change or noop will
52
+ # be added later.
53
+ return if actual.include?(key)
54
+
55
+ operations << Core::UnaryOperation.new(
56
+ name: :delete,
57
+ collection: expected,
58
+ key: key,
59
+ value: expected[key],
60
+ index: event.old_position
61
+ )
62
+ end
63
+
64
+ def discard_b(event)
65
+ # This key is in `actual`, but may also be in `expected`.
66
+
67
+ key = event.new_element
68
+ handle_operation_on_key_in_actual(key, event)
69
+ end
70
+
71
+ def match(event)
72
+ # This key is part of the longest common subsequence.
73
+
74
+ key = event.old_element
75
+ handle_operation_on_key_in_actual(key, event)
76
+ end
77
+
78
+ private
79
+
80
+ def handle_operation_on_key_in_actual(key, event)
81
+ if should_add_noop_operation.call(key)
79
82
  operations << Core::UnaryOperation.new(
80
83
  name: :noop,
81
84
  collection: actual,
82
- key: ak,
83
- value: av,
84
- index: ai
85
+ key: key,
86
+ value: actual[key],
87
+ index: event.new_position
85
88
  )
86
89
  else
87
- # (If we're here, it probably means that the key in 'actual' isn't
88
- # present in 'expected' or the values don't match.)
89
-
90
- if (operations.empty? || operations.last.name == :noop) &&
91
- (ai.zero? || eks.include?(aks[ai - 1]))
92
-
93
- # If we go from a match in the last iteration to a missing or
94
- # extra key in this one, or we're at the first key in 'actual' and
95
- # it's missing or extra, look for deletes in the 'expected' hash
96
- # and add them to our list of operations before we add the
97
- # inserts. In most cases we will accomplish this by backtracking a
98
- # bit to the key in 'expected' that matched the key in 'actual' we
99
- # processed in the previous iteration (or just the first key in
100
- # 'expected' if this is the first key in 'actual'), and then
101
- # iterating from there through 'expected' until we reach the end
102
- # or we hit some other condition (see below).
103
-
104
- start_index =
105
- if ai.positive?
106
- eks.index(aks[ai - 1]) + 1
107
- else
108
- 0
109
- end
110
-
111
- start_index.upto(eks.size - 1) do |ei2|
112
- ek = eks[ei2]
113
- ev = expected[ek]
114
- av2 = actual[ek]
115
-
116
- if actual.include?(ek) && ev == av2
117
- # If the key in 'expected' we've landed on happens to be a
118
- # match in 'actual', then stop, because it's going to be
119
- # handled in some future iteration of the 'actual' loop.
120
- break
121
- elsif aks[ai + 1..].any? do |k| # rubocop:disable Lint/DuplicateBranch for clarity
122
- expected.include?(k) && expected[k] != actual[k]
123
- end
124
-
125
- # While we backtracked a bit to iterate over 'expected', we
126
- # now have to look ahead. If we will end up encountering a
127
- # insert that matches this delete later, stop and go back to
128
- # iterating over 'actual'. This is because the delete we would
129
- # have added now will be added later when we encounter the
130
- # associated insert, so we don't want to add it twice.
131
- break
132
- else
133
- operations << Core::UnaryOperation.new(
134
- name: :delete,
135
- collection: expected,
136
- key: ek,
137
- value: ev,
138
- index: ei2
139
- )
140
- end
141
-
142
- next unless ek == ak && ev != av
143
-
144
- # If we're pointing to the same key in 'expected' as in
145
- # 'actual', but with different values, go ahead and add an
146
- # insert now to accompany the delete added above. That way
147
- # they appear together, which will be easier to read.
148
- operations << Core::UnaryOperation.new(
149
- name: :insert,
150
- collection: actual,
151
- key: ak,
152
- value: av,
153
- index: ai
154
- )
155
- end
156
- end
157
-
158
- if expected.include?(ak) && ev != av &&
159
- operations.none? do |op|
160
- op.name == :delete && op.key == ak
161
- end
162
-
163
- # If we're here, it means that we didn't encounter any delete
164
- # operations above for whatever reason and so we need to add a
165
- # delete to represent the fact that the value for this key has
166
- # changed.
90
+ # If the key is present in both `actual` and `expected`
91
+ # but the values don't match, create a `delete` operation
92
+ # just before the `insert`.
93
+ # (This condition will always be true for `match` events.)
94
+ if expected.include?(key)
167
95
  operations << Core::UnaryOperation.new(
168
96
  name: :delete,
169
97
  collection: expected,
170
- key: ak,
171
- value: expected[ak],
172
- index: ei
98
+ key: key,
99
+ value: expected[key],
100
+ index: event.old_position
173
101
  )
174
102
  end
175
103
 
176
- if operations.none? { |op| op.name == :insert && op.key == ak }
177
- # If we're here, it means that we didn't encounter any insert
178
- # operations above. Since we already handled delete, the only
179
- # alternative is that this key must not exist in 'expected', so
180
- # we need to add an insert.
181
- operations << Core::UnaryOperation.new(
182
- name: :insert,
183
- collection: actual,
184
- key: ak,
185
- value: av,
186
- index: ai
187
- )
188
- end
104
+ operations << Core::UnaryOperation.new(
105
+ name: :insert,
106
+ collection: actual,
107
+ key: key,
108
+ value: actual[key],
109
+ index: event.new_position
110
+ )
189
111
  end
190
-
191
- ai += 1
192
- previous_ei = ei
193
- end
194
-
195
- # The last thing to do is this: if there are keys in 'expected' that
196
- # aren't in 'actual', and they aren't associated with any inserts to
197
- # where they would have been added above, tack those deletes onto the
198
- # end of our operations array.
199
- (eks - aks - operations.map(&:key)).each do |ek|
200
- ei = eks.index(ek)
201
- ev = expected[ek]
202
-
203
- operations << Core::UnaryOperation.new(
204
- name: :delete,
205
- collection: expected,
206
- key: ek,
207
- value: ev,
208
- index: ei
209
- )
210
112
  end
211
-
212
- operations
213
113
  end
214
114
 
215
115
  def should_add_noop_operation?(key)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SuperDiff
4
- VERSION = '0.16.0'
4
+ VERSION = '0.17.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: super_diff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elliot Winkler
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-06-18 00:00:00.000000000 Z
12
+ date: 2025-10-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: attr_extras