hash_deep_diff 0.3.1 → 0.4.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/CHANGELOG.md +0 -0
- data/README.md +15 -9
- data/lib/hash_deep_diff/acts_as_hash.rb +27 -0
- data/lib/hash_deep_diff/comparison.rb +14 -67
- data/lib/hash_deep_diff/delta.rb +80 -0
- data/lib/hash_deep_diff/version.rb +1 -1
- data/lib/hash_deep_diff.rb +1 -0
- metadata +5 -6
- data/lib/hash_deep_diff/delta/acts_as_delta.rb +0 -62
- data/lib/hash_deep_diff/delta/inner.rb +0 -44
- data/lib/hash_deep_diff/delta/left.rb +0 -24
- data/lib/hash_deep_diff/delta/right.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24756c15f3f48bbe108e5c2bb12c2db3e6030cef78ab91a6cf76044ca70d2dd5
|
4
|
+
data.tar.gz: a8cca8ab8a23c704f6f69c2613ded6b4d0d2c7b0e5caed34d437fe0951582a45
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 731ccc90f750c6796374ae83ccf9c78741e47251c7940add8f0e9c9c34417f84f8375e74c639891c4e37e5976200bb8786e4bd2dbbe9357bb9b92e8a14b46c7b
|
7
|
+
data.tar.gz: 8dfbab0f3e5fd2666e2d5eb3458d9df858e204e49cd604348676af43c511bd855dba61720702cc69c7e3ecd0751e29606eec10bef41ee1a0e8bdd4ba33db79f7
|
data/CHANGELOG.md
ADDED
File without changes
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# HashDeepDiff
|
2
2
|
|
3
|
-
|
3
|
+
Find the exact difference between two Hash objects and build a report to visualize it
|
4
4
|
|
5
|
-
|
5
|
+
[](https://badge.fury.io/rb/hash_deep_diff)
|
6
7
|
|
7
8
|
## Installation
|
8
9
|
|
@@ -21,15 +22,20 @@ Or install it yourself as:
|
|
21
22
|
$ gem install hash_deep_diff
|
22
23
|
|
23
24
|
## Usage
|
25
|
+
Basic example
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
27
|
+
```ruby
|
28
|
+
left = { a: :a }
|
29
|
+
right = { a: :b }
|
30
30
|
|
31
|
-
|
31
|
+
HashDeepDiff::Comparison.new(left, right).report
|
32
|
+
```
|
33
|
+
```diff
|
34
|
+
- left[a] = a
|
35
|
+
+ left[a] = b
|
36
|
+
```
|
32
37
|
|
33
38
|
## Contributing
|
34
39
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
40
|
+
Bug reports and pull requests are welcome on GitHub at [bpohoriletz](https://github.com/bpohoriletz/hash_deep_diff).
|
41
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module HashDeepDiff
|
6
|
+
# This module includes behavior that is needed to use deltas instead of Hash inside this gem
|
7
|
+
module ActsAsHash
|
8
|
+
def self.included(base)
|
9
|
+
base.include(InstanceMethods)
|
10
|
+
base.extend(Forwardable)
|
11
|
+
base.def_delegators :@delta, :==, :each_with_object, :each_key, :[],
|
12
|
+
:to_a, :empty?, :keys
|
13
|
+
end
|
14
|
+
|
15
|
+
# Assumes that the class will include method delta that will return a representation of an
|
16
|
+
# instance of a class as a Hash
|
17
|
+
module InstanceMethods
|
18
|
+
def to_h
|
19
|
+
@delta
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_hash
|
23
|
+
@delta
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,17 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'delta
|
4
|
-
require_relative 'delta/inner'
|
5
|
-
require_relative 'delta/right'
|
3
|
+
require_relative 'delta'
|
6
4
|
|
7
5
|
# :nodoc:
|
8
6
|
module HashDeepDiff
|
9
|
-
#
|
7
|
+
# An instrument to build and report the difference between two hash-like objects
|
10
8
|
class Comparison
|
11
9
|
attr_reader :left, :right, :path
|
12
10
|
|
13
|
-
def diff
|
14
|
-
|
11
|
+
def diff
|
12
|
+
deep_delta
|
15
13
|
end
|
16
14
|
|
17
15
|
def report
|
@@ -26,80 +24,29 @@ module HashDeepDiff
|
|
26
24
|
@path = path.to_ary
|
27
25
|
end
|
28
26
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
memo << Delta::Left.new(path: keys, value: value)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def missing_report(memo, keys, value)
|
38
|
-
if value.respond_to?(:to_hash)
|
39
|
-
value.each_key { |key| missing_report(memo, keys + [key], value[key]) }
|
40
|
-
else
|
41
|
-
memo << Delta::Right.new(path: keys, value: value)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def delta_report(memo, keys, value)
|
46
|
-
if value.respond_to?(:to_hash) && value.keys != %i[left right]
|
47
|
-
value.each_key { |key| delta_report(memo, keys + [key], value[key]) }
|
48
|
-
elsif value.instance_of?(Array) && value.size == 3 && value.all? { |el| el.respond_to?(:to_hash) }
|
49
|
-
# [{}, {}, {:i=>:i}]
|
50
|
-
extra_report(memo, keys, value[0]) unless value[0].empty?
|
51
|
-
delta_report(memo, keys, value[1]) unless value[1].empty?
|
52
|
-
missing_report(memo, keys, value[2]) unless value[2].empty?
|
53
|
-
else
|
54
|
-
memo << Delta::Inner.new(path: keys, value: value)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def deep_delta(&block)
|
59
|
-
result = delta(&block)
|
60
|
-
|
61
|
-
result.each_with_object([]) do |diff, memo|
|
62
|
-
if left.dig(*diff.path).respond_to?(:to_hash) && right.dig(*diff.path).respond_to?(:to_hash)
|
63
|
-
self.class.new(left.dig(*diff.path), right.dig(*diff.path), path + diff.path).diff.each do |diff|
|
64
|
-
memo << diff
|
65
|
-
end
|
27
|
+
def deep_delta
|
28
|
+
delta.flat_map do |diff|
|
29
|
+
if diff.complex?
|
30
|
+
self.class.new(diff.left, diff.right, diff.path).diff
|
66
31
|
else
|
67
|
-
|
32
|
+
diff
|
68
33
|
end
|
69
34
|
end
|
70
35
|
end
|
71
36
|
|
72
|
-
def
|
73
|
-
right_diff_keys.map { |key| Delta::Right.new(path: path + [key], value: right[key]) }
|
74
|
-
end
|
75
|
-
|
76
|
-
def delta(&block)
|
77
|
-
block ||= ->(val) { val }
|
78
|
-
|
37
|
+
def delta
|
79
38
|
common_keys.each_with_object([]) do |key, memo|
|
80
|
-
value_left =
|
81
|
-
value_right =
|
39
|
+
value_left = left[key] || NO_VALUE
|
40
|
+
value_right = right[key] || NO_VALUE
|
82
41
|
|
83
42
|
next if value_right.instance_of?(value_left.class) && (value_right == value_left)
|
84
43
|
|
85
|
-
memo << Delta
|
44
|
+
memo << Delta.new(path: path + [key], value: { left: value_left, right: value_right })
|
86
45
|
end
|
87
46
|
end
|
88
47
|
|
89
|
-
def left_delta
|
90
|
-
left_diff_keys.map { |key| Delta::Left.new(path: path + [key], value: left[key]) }
|
91
|
-
end
|
92
|
-
|
93
48
|
def common_keys
|
94
|
-
left.keys
|
95
|
-
end
|
96
|
-
|
97
|
-
def right_diff_keys
|
98
|
-
right.keys - left.keys
|
99
|
-
end
|
100
|
-
|
101
|
-
def left_diff_keys
|
102
|
-
left.keys - right.keys
|
49
|
+
(left.keys + right.keys).uniq
|
103
50
|
end
|
104
51
|
end
|
105
52
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'acts_as_hash'
|
4
|
+
|
5
|
+
module HashDeepDiff
|
6
|
+
# Representation of the diff of two values
|
7
|
+
# examples:
|
8
|
+
# - diff of { a: a } and {} is { a: { left: a, right: HashDeepDiff::NO_VALUE } }
|
9
|
+
# - diff of { a: a } and { a: b } is { a: { left: a, right: b } }
|
10
|
+
# - diff of {} and { a: b } is { a: { left: HashDeepDiff::NO_VALUE, right: b } }
|
11
|
+
class Delta
|
12
|
+
include ActsAsHash
|
13
|
+
|
14
|
+
def to_str
|
15
|
+
[deletion, addition].compact.join("\n")
|
16
|
+
end
|
17
|
+
|
18
|
+
def complex?
|
19
|
+
left.respond_to?(:to_hash) && right.respond_to?(:to_hash)
|
20
|
+
end
|
21
|
+
|
22
|
+
# TOFIX poor naming
|
23
|
+
# overrides parameter in initializer
|
24
|
+
def path
|
25
|
+
@prefix + [@delta.keys.first]
|
26
|
+
end
|
27
|
+
|
28
|
+
def left
|
29
|
+
@value[:left]
|
30
|
+
end
|
31
|
+
|
32
|
+
def right
|
33
|
+
@value[:right]
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
to_str
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def initialize(path:, value:)
|
43
|
+
# TOFIX this may prohibit usage of hashes with Array keys
|
44
|
+
if path.respond_to?(:to_ary)
|
45
|
+
@delta = { path[-1] => value }
|
46
|
+
@value = value
|
47
|
+
@prefix = path[0..-2]
|
48
|
+
else
|
49
|
+
@delta = { path => value }
|
50
|
+
@value = value
|
51
|
+
@prefix = []
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def deletion
|
56
|
+
return nil if left == NO_VALUE
|
57
|
+
|
58
|
+
if left.respond_to?(:to_hash)
|
59
|
+
left.keys.map { |key| "-left#{diff_prefix}[#{key}] = #{left[key]}" }.join("\n")
|
60
|
+
else
|
61
|
+
"-left#{diff_prefix} = #{left}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def addition
|
66
|
+
return nil if right == NO_VALUE
|
67
|
+
|
68
|
+
if right.respond_to?(:to_hash)
|
69
|
+
right.keys.map { |key| "+left#{diff_prefix}[#{key}] = #{right[key]}" }.join("\n")
|
70
|
+
else
|
71
|
+
"+left#{diff_prefix} = #{right}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# TOFIX poor naming
|
76
|
+
def diff_prefix
|
77
|
+
path.map { |key| "[#{key}]" }.join
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/hash_deep_diff.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hash_deep_diff
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bohdan Pohorilets
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-04-
|
11
|
+
date: 2022-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -175,6 +175,7 @@ files:
|
|
175
175
|
- ".bundle/config"
|
176
176
|
- ".gitignore"
|
177
177
|
- ".rubocop.yml"
|
178
|
+
- CHANGELOG.md
|
178
179
|
- Gemfile
|
179
180
|
- Guardfile
|
180
181
|
- README.md
|
@@ -188,11 +189,9 @@ files:
|
|
188
189
|
- bin/setup
|
189
190
|
- hash_deep_diff.gemspec
|
190
191
|
- lib/hash_deep_diff.rb
|
192
|
+
- lib/hash_deep_diff/acts_as_hash.rb
|
191
193
|
- lib/hash_deep_diff/comparison.rb
|
192
|
-
- lib/hash_deep_diff/delta
|
193
|
-
- lib/hash_deep_diff/delta/inner.rb
|
194
|
-
- lib/hash_deep_diff/delta/left.rb
|
195
|
-
- lib/hash_deep_diff/delta/right.rb
|
194
|
+
- lib/hash_deep_diff/delta.rb
|
196
195
|
- lib/hash_deep_diff/version.rb
|
197
196
|
homepage: https://github.com/bpohoriletz/hash_deep_diff
|
198
197
|
licenses:
|
@@ -1,62 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'forwardable'
|
4
|
-
|
5
|
-
module HashDeepDiff
|
6
|
-
module Delta
|
7
|
-
# This module includes behavior that is needed to use deltas instead of Hash inside this gem
|
8
|
-
module ActsAsDelta
|
9
|
-
def self.included(base)
|
10
|
-
base.prepend(Initialize)
|
11
|
-
base.include(InstanceMethods)
|
12
|
-
base.extend(Forwardable)
|
13
|
-
base.def_delegators :@delta, :==, :each_with_object, :each_key, :[],
|
14
|
-
:to_a, :empty?, :keys
|
15
|
-
end
|
16
|
-
|
17
|
-
# Assumes that the class will include method delta that will return a representation of an
|
18
|
-
# instance of a class as a Hash
|
19
|
-
module InstanceMethods
|
20
|
-
# TOFIX poor naming
|
21
|
-
def diff_prefix
|
22
|
-
path.map { |key| "[#{key}]" }.join
|
23
|
-
end
|
24
|
-
|
25
|
-
# TOFIX poor naming
|
26
|
-
def path
|
27
|
-
@prefix + [@delta.keys.first]
|
28
|
-
end
|
29
|
-
|
30
|
-
def to_h
|
31
|
-
@delta
|
32
|
-
end
|
33
|
-
|
34
|
-
def to_hash
|
35
|
-
@delta
|
36
|
-
end
|
37
|
-
|
38
|
-
def to_s
|
39
|
-
to_str
|
40
|
-
end
|
41
|
-
|
42
|
-
def to_str
|
43
|
-
raise NoMethodError, "expected #{self.class} to implement #to_str"
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# Override #initialize method
|
48
|
-
module Initialize
|
49
|
-
def initialize(path:, value:)
|
50
|
-
# TOFIX this may prohibit usage of hashes with Array keys
|
51
|
-
if path.respond_to?(:to_ary)
|
52
|
-
@delta = { path[-1] => value }
|
53
|
-
@prefix = path[0..-2]
|
54
|
-
else
|
55
|
-
@delta = { path => value }
|
56
|
-
@prefix = []
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'acts_as_delta'
|
4
|
-
|
5
|
-
module HashDeepDiff
|
6
|
-
module Delta
|
7
|
-
# Representation of the pure left diff
|
8
|
-
# i.e element that are missing in the hash on the right of the comparison
|
9
|
-
# for example left diff of { a: a } and {} is { a: a }
|
10
|
-
class Inner
|
11
|
-
include Delta::ActsAsDelta
|
12
|
-
|
13
|
-
def to_str
|
14
|
-
if @delta.values.first.respond_to?(:to_hash)
|
15
|
-
if @delta.values.first.keys == %i[left right]
|
16
|
-
if @delta.values.first[:left].respond_to?(:to_hash) && @delta.values.first[:right].respond_to?(:to_hash)
|
17
|
-
HashDeepDiff::Comparison.new(
|
18
|
-
@delta.values.first[:left],
|
19
|
-
@delta.values.first[:right],
|
20
|
-
path
|
21
|
-
).report
|
22
|
-
else
|
23
|
-
lines = <<~Q
|
24
|
-
-left#{diff_prefix} = #{@delta.values.first[:left]}
|
25
|
-
+right#{diff_prefix} = #{@delta.values.first[:right]}
|
26
|
-
Q
|
27
|
-
lines.strip
|
28
|
-
end
|
29
|
-
else
|
30
|
-
@delta.values.first.keys.map do |key|
|
31
|
-
self.class.new(path: path + [key], value: @delta.values.first[key])
|
32
|
-
end.join("\n").strip
|
33
|
-
end
|
34
|
-
else
|
35
|
-
lines = <<~Q
|
36
|
-
-left#{diff_prefix} = #{@delta.values.first[:left]}
|
37
|
-
+right#{diff_prefix} = #{@delta.values.first[:right]}
|
38
|
-
Q
|
39
|
-
lines.strip
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'acts_as_delta'
|
4
|
-
|
5
|
-
module HashDeepDiff
|
6
|
-
module Delta
|
7
|
-
# Representation of the pure left diff
|
8
|
-
# i.e element that are missing in the hash on the right of the comparison
|
9
|
-
# for example left diff of { a: a } and {} is { a: a }
|
10
|
-
class Left
|
11
|
-
include Delta::ActsAsDelta
|
12
|
-
|
13
|
-
def to_str
|
14
|
-
if @delta.values.first.respond_to?(:to_hash)
|
15
|
-
@delta.values.first.keys.map do |key|
|
16
|
-
self.class.new(path: path + [key], value: @delta.values.first[key])
|
17
|
-
end.join("\n").strip
|
18
|
-
else
|
19
|
-
"+left#{diff_prefix} = #{@delta.values.first}"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'acts_as_delta'
|
4
|
-
|
5
|
-
module HashDeepDiff
|
6
|
-
module Delta
|
7
|
-
# Representation of the pure right diff
|
8
|
-
# i.e element that are missing in the hash on the right of the comparison
|
9
|
-
# for example right diff of {} and { a: a } is { a: a }
|
10
|
-
class Right
|
11
|
-
include Delta::ActsAsDelta
|
12
|
-
|
13
|
-
def to_str
|
14
|
-
if @delta.values.first.respond_to?(:to_hash)
|
15
|
-
@delta.values.first.keys.map do |key|
|
16
|
-
self.class.new(path: path + [key], value: @delta.values.first[key])
|
17
|
-
end.join("\n").strip
|
18
|
-
else
|
19
|
-
"-left#{diff_prefix} = #{@delta.values.first}"
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|