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 +4 -4
- data/lib/super_diff/basic/operation_tree_builders/hash.rb +73 -173
- data/lib/super_diff/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f84c14c100aff4bd5ec41c27530ecc6e7a586a90d770a07617b2a591fb438658
|
|
4
|
+
data.tar.gz: b441eb9beafd67394df0f3b6a13d0bfafc5cd94d06593acf5780b8df7cc59df0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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:
|
|
83
|
-
value:
|
|
84
|
-
index:
|
|
85
|
+
key: key,
|
|
86
|
+
value: actual[key],
|
|
87
|
+
index: event.new_position
|
|
85
88
|
)
|
|
86
89
|
else
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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:
|
|
171
|
-
value: expected[
|
|
172
|
-
index:
|
|
98
|
+
key: key,
|
|
99
|
+
value: expected[key],
|
|
100
|
+
index: event.old_position
|
|
173
101
|
)
|
|
174
102
|
end
|
|
175
103
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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)
|
data/lib/super_diff/version.rb
CHANGED
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.
|
|
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-
|
|
12
|
+
date: 2025-10-24 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: attr_extras
|