hash_deep_diff 0.7.0 → 0.8.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/.rubocop.yml +7 -0
- data/README.md +75 -3
- data/hash_deep_diff.gemspec +2 -0
- data/lib/hash_deep_diff/change_key.rb +116 -0
- data/lib/hash_deep_diff/comparison.rb +23 -29
- data/lib/hash_deep_diff/delta.rb +24 -4
- data/lib/hash_deep_diff/factories/comparison.rb +6 -2
- data/lib/hash_deep_diff/reports/base.rb +20 -25
- data/lib/hash_deep_diff/reports/diff.rb +29 -22
- data/lib/hash_deep_diff/reports/yml.rb +53 -0
- data/lib/hash_deep_diff/version.rb +1 -1
- data/lib/hash_deep_diff.rb +2 -0
- metadata +32 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e7ceeb1c2e935cc4b6c9d27e6d718f63f627d3adeba269df367c37464f3e9c0
|
4
|
+
data.tar.gz: 00053a3c87a26ea7dff3513594c6fb85e1235be3c341bc58f30d4934faa3de57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 612c398c02060fd908f449ac44893a8a81d33f3c1f13ec86bb3042d748034f18a20fbd99559c211af58e5f70d77d0b107fda0fae6cb2be3a1f94e1e0299c0f2e
|
7
|
+
data.tar.gz: c63d6e0ddad970dce331a5460981916b821805a8a3fe8b95f5ab6ea73a029942091acdf6b9681da533630fd6da38440749419d25436979e07cd15da5a2020658
|
data/.rubocop.yml
CHANGED
@@ -10,6 +10,7 @@ AllCops:
|
|
10
10
|
- '.git/**/*'
|
11
11
|
- '.bundle/**/*'
|
12
12
|
- 'bin/*'
|
13
|
+
TargetRubyVersion: 2.6.1
|
13
14
|
|
14
15
|
Style/RedundantReturn:
|
15
16
|
Enabled: false
|
@@ -18,3 +19,9 @@ Metrics/BlockLength:
|
|
18
19
|
Exclude:
|
19
20
|
- 'test/**/test_*.rb'
|
20
21
|
- '*.gemspec'
|
22
|
+
Style/YodaCondition:
|
23
|
+
EnforcedStyle: require_for_all_comparison_operators
|
24
|
+
|
25
|
+
Minitest/AssertEmptyLiteral:
|
26
|
+
Exclude:
|
27
|
+
- 'test/unit/test_change_key.rb'
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ Status](https://img.shields.io/github/workflow/status/bpohoriletz/hash_deep_diff
|
|
7
7
|
|
8
8
|
Find the exact difference between two Hash objects and build a report to visualize it. Works for other objects too but why would you do that :/
|
9
9
|
|
10
|
-
Alternative solutions [hashdiff
|
10
|
+
Alternative solutions [hashdiff](https://github.com/liufengyun/hashdiff) by liufengyun and [hash_diff](https://github.com/CodingZeal/hash_diff) by CodingZeal
|
11
11
|
|
12
12
|
## Installation
|
13
13
|
|
@@ -26,21 +26,93 @@ Or install it yourself as:
|
|
26
26
|
$ gem install hash_deep_diff
|
27
27
|
|
28
28
|
## Usage
|
29
|
-
Basic
|
29
|
+
### Basic
|
30
30
|
|
31
31
|
```ruby
|
32
32
|
left = { a: :a }
|
33
33
|
right = { a: :b }
|
34
34
|
|
35
|
-
HashDeepDiff::Comparison.new(left, right).report
|
35
|
+
print HashDeepDiff::Comparison.new(left, right).report
|
36
36
|
```
|
37
37
|
```diff
|
38
38
|
- left[a] = a
|
39
39
|
+ left[a] = b
|
40
40
|
```
|
41
|
+
### Arrays
|
42
|
+
```ruby
|
43
|
+
left = [1, 2, { a: :a }]
|
44
|
+
right = [2, { a: :b }, 3]
|
45
|
+
|
46
|
+
print HashDeepDiff::Comparison.new(left, right).report
|
47
|
+
```
|
48
|
+
```diff
|
49
|
+
-left[...] = [1]
|
50
|
+
+left[...] = [3]
|
51
|
+
-left[{}][a] = a
|
52
|
+
+left[{}][a] = b
|
53
|
+
```
|
54
|
+
### Nesting
|
55
|
+
```ruby
|
56
|
+
left = { a: [1, 2, { a: :a } ], b: { c: [1, 2, { d: :e } ] } }
|
57
|
+
right = { a: [2, { a: :b }, 3], b: { c: { f: { g: :h } } } }
|
58
|
+
|
59
|
+
print HashDeepDiff::Comparison.new(left, right).report
|
60
|
+
```
|
61
|
+
```diff
|
62
|
+
-left[a][...] = [1]
|
63
|
+
+left[a][...] = [3]
|
64
|
+
-left[a][{}][a] = a
|
65
|
+
+left[a][{}][a] = b
|
66
|
+
+left[b][c][...][f] = {}
|
67
|
+
+left[b][c][...][f][g] = h
|
68
|
+
-left[b][c][...][f] = [1, 2]
|
69
|
+
-left[b][c][{}][d] = e
|
70
|
+
```
|
71
|
+
### Reporting Engines
|
72
|
+
You can choose from the default diff-like reporting engine (examples are above) and YML reporting engine
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
left = { a: [1, 2, { a: :a } ], b: { c: [1, 2, { d: :e } ] } }
|
76
|
+
right = { a: [2, { a: :b }, 3], b: { c: { f: { g: :h } } } }
|
77
|
+
```
|
78
|
+
#### Raw Report
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
print HashDeepDiff::Comparison.new(left, right, reporting_engine: HashDeepDiff::Reports::Yml).raw_report
|
82
|
+
=> {"additions"=>{:a=>[3, {:a=>:b}], :b=>{:c=>[{:f=>{:g=>:h}}]}},
|
83
|
+
"deletions"=>{:a=>[1, {:a=>:a}], :b=>{:c=>[1, 2, {:d=>:e}]}}}
|
84
|
+
```
|
85
|
+
|
86
|
+
#### YML Report
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
print HashDeepDiff::Comparison.new(left, right, reporting_engine: HashDeepDiff::Reports::Yml).report
|
90
|
+
|
91
|
+
---
|
92
|
+
additions:
|
93
|
+
:a:
|
94
|
+
- 3
|
95
|
+
- :a: :b
|
96
|
+
:b:
|
97
|
+
:c:
|
98
|
+
- :f:
|
99
|
+
:g: :h
|
100
|
+
deletions:
|
101
|
+
:a:
|
102
|
+
- 1
|
103
|
+
- :a: :a
|
104
|
+
:b:
|
105
|
+
:c:
|
106
|
+
- 1
|
107
|
+
- 2
|
108
|
+
- :d: :e
|
109
|
+
```
|
110
|
+
|
41
111
|
please see [Documentation](https://rdoc.info/gems/hash_deep_diff/HashDeepDiff/Comparison) for
|
42
112
|
more information or [Reporting test](https://github.com/bpohoriletz/hash_deep_diff/blob/a525d239189b0310aec3741dfc4862834805252d/test/integration/locales/test_uk_ru.rb#L59)
|
43
113
|
|
114
|
+
|
115
|
+
|
44
116
|
## Customization
|
45
117
|
|
46
118
|
You can implement and use your own reporting engines with the default `HashDeepDiff::Delta` objects as a source of the report. In order to do so implement your own version of the reporting engine (example can be found [here](https://github.com/bpohoriletz/hash_deep_diff/tree/main/lib/hash_deep_diff/reports)) and inject it into a `Comparison`
|
data/hash_deep_diff.gemspec
CHANGED
@@ -53,5 +53,7 @@ Gem::Specification.new do |spec|
|
|
53
53
|
spec.add_development_dependency 'rake', '~> 10.5.0'
|
54
54
|
spec.add_development_dependency 'rubocop', '~> 1.26.1'
|
55
55
|
spec.add_development_dependency 'rubocop-minitest', '~> 0.18.0'
|
56
|
+
spec.add_development_dependency 'rubocop-performance', '~> 1.13.3'
|
56
57
|
spec.add_development_dependency 'rubocop-rake', '~> 0.6.0'
|
58
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 2.10.0'
|
57
59
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module HashDeepDiff
|
6
|
+
# Key for a compared value inside Array or Hash
|
7
|
+
class ChangeKey
|
8
|
+
extend Forwardable
|
9
|
+
def_delegators :to_ary, :[], :+, :map, :==, :first, :shift, :empty?
|
10
|
+
# element that indicates nested Hash
|
11
|
+
NESTED_HASH = '{}'
|
12
|
+
# element that indicates Array value
|
13
|
+
ARRAY_VALUE = '...'
|
14
|
+
|
15
|
+
# based on the first element of the key returns the initial object
|
16
|
+
# to buld the change representation
|
17
|
+
# @return [Array, Hash]
|
18
|
+
def self.initial_object(values:)
|
19
|
+
return [] if values.size.positive? && [NESTED_HASH, ARRAY_VALUE].include?(values[0][0][0])
|
20
|
+
|
21
|
+
{}
|
22
|
+
end
|
23
|
+
|
24
|
+
# set the value inside Hash based on the change_key
|
25
|
+
# @return [Array, Hash]
|
26
|
+
# TOFIX; check if @path are mutated
|
27
|
+
def set(obj, value, clone_keys = path.clone)
|
28
|
+
# 1. Fetch key
|
29
|
+
current_key = clone_keys.shift
|
30
|
+
# 2. Prepare object for further processing
|
31
|
+
init_value(current_key, obj, clone_keys)
|
32
|
+
init_nesting(current_key, obj, clone_keys)
|
33
|
+
# 3. Set value - directly or recursively
|
34
|
+
set_value(current_key, obj, value, clone_keys)
|
35
|
+
recursive_set(current_key, obj, value, clone_keys)
|
36
|
+
|
37
|
+
return obj
|
38
|
+
end
|
39
|
+
|
40
|
+
# see {#to_ary}
|
41
|
+
# @return [Array]
|
42
|
+
def to_a
|
43
|
+
to_ary
|
44
|
+
end
|
45
|
+
|
46
|
+
# array with keysused to initialize the object
|
47
|
+
# @return [Array]
|
48
|
+
def to_ary
|
49
|
+
path
|
50
|
+
end
|
51
|
+
|
52
|
+
# see {#to_str}
|
53
|
+
# @return [String]
|
54
|
+
def to_s
|
55
|
+
to_str
|
56
|
+
end
|
57
|
+
|
58
|
+
# visual representation of the change key
|
59
|
+
# @return [String]
|
60
|
+
def to_str
|
61
|
+
path.map { |key| "[#{key}]" }.join
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
attr_reader :path
|
67
|
+
|
68
|
+
def initialize(path:)
|
69
|
+
@path = path.to_ary
|
70
|
+
end
|
71
|
+
|
72
|
+
# prepare an object before further processing
|
73
|
+
def init_value(current_key, obj, clone_keys)
|
74
|
+
if [ARRAY_VALUE] == clone_keys || NESTED_HASH == clone_keys.first
|
75
|
+
obj[current_key] ||= []
|
76
|
+
elsif !clone_keys.empty? && ![ARRAY_VALUE, NESTED_HASH].include?(current_key)
|
77
|
+
obj[current_key] ||= {}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# prepare nesting before further processing
|
82
|
+
def init_nesting(current_key, obj, clone_keys)
|
83
|
+
element = if NESTED_HASH == current_key
|
84
|
+
obj
|
85
|
+
elsif NESTED_HASH == clone_keys.first
|
86
|
+
obj[current_key]
|
87
|
+
end
|
88
|
+
element << {} unless element.nil? || element.last.respond_to?(:to_hash)
|
89
|
+
end
|
90
|
+
|
91
|
+
# no more nesting - set value inside object
|
92
|
+
def set_value(current_key, obj, value, clone_keys)
|
93
|
+
if ARRAY_VALUE == current_key
|
94
|
+
obj.prepend(*value)
|
95
|
+
clone_keys.pop
|
96
|
+
elsif clone_keys.empty?
|
97
|
+
obj[current_key] = value
|
98
|
+
clone_keys.pop
|
99
|
+
elsif [ARRAY_VALUE] == clone_keys
|
100
|
+
obj[current_key] = value + obj[current_key]
|
101
|
+
clone_keys.pop
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# recursion for deeply nested values
|
106
|
+
def recursive_set(current_key, obj, value, clone_keys)
|
107
|
+
if NESTED_HASH == current_key
|
108
|
+
set(obj.last, value, clone_keys)
|
109
|
+
elsif NESTED_HASH == clone_keys.first
|
110
|
+
set(obj[current_key].last, value, clone_keys[1..])
|
111
|
+
elsif !clone_keys.empty?
|
112
|
+
set(obj[current_key], value, clone_keys)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -21,36 +21,22 @@ module HashDeepDiff
|
|
21
21
|
# NO_VALUE compared to { one: { three: :c } } compared - value was added
|
22
22
|
# i.e NO_VALUE vas replaced with :c on path [:one, :three]
|
23
23
|
# [
|
24
|
-
# #<HashDeepDiff::Delta
|
25
|
-
# @
|
26
|
-
# @prefix=[:one],
|
24
|
+
# #<HashDeepDiff::Delta:0x00007fc7bc8a6e58
|
25
|
+
# @change_key=#<HashDeepDiff::ChangeKey:0x00007fc7bc8a6d40 @path=[:one, :two]>,
|
27
26
|
# @value={:left=>:a, :right=>:b}>,
|
28
|
-
# #<HashDeepDiff::Delta
|
29
|
-
# @
|
30
|
-
# @prefix=[:one],
|
27
|
+
# #<HashDeepDiff::Delta:0x00007fc7bc8a6b60
|
28
|
+
# @change_key=#<HashDeepDiff::ChangeKey:0x00007fc7bc8a6a48 @path=[:one, :zero]>,
|
31
29
|
# @value={:left=>:z, :right=>HashDeepDiff::NO_VALUE}>,
|
32
|
-
# #<HashDeepDiff::Delta
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# @value={:left=>HashDeepDiff::NO_VALUE, :right=>:c}>
|
30
|
+
# #<HashDeepDiff::Delta:0x00007fc7bc8a6930
|
31
|
+
# @change_key=#<HashDeepDiff::ChangeKey:0x00007fc7bc8a6818 @path=[:one, :three]>,
|
32
|
+
# @value={:left=>HashDeepDiff::NO_VALUE, :right=>:c}>]
|
36
33
|
# ]
|
37
34
|
class Comparison
|
38
35
|
extend Forwardable
|
39
|
-
# @!attribute [r] left
|
40
|
-
# @return [Hash] original version of the Hash
|
41
|
-
# @!attribute [r] right
|
42
|
-
# @return [Hash] Hash that the original is compared to
|
43
|
-
# @!attribute [r] path
|
44
|
-
# @return [Array<Object>] subset of keys from original Hashes to fetch compared values
|
45
|
-
# (is empty for top-level comparison)
|
46
36
|
attr_reader :reporting_engine, :delta_engine
|
47
37
|
|
48
38
|
def_delegators :comparison_factory, :comparison
|
49
|
-
|
50
|
-
# @return [String]
|
51
|
-
def report
|
52
|
-
diff.map { |simple_delta| reporting_engine.new(delta: simple_delta).to_s }.join
|
53
|
-
end
|
39
|
+
def_delegators :report_engine_factory, :report, :raw_report
|
54
40
|
|
55
41
|
# @return [Array<HashDeepDiff::Delta>]
|
56
42
|
def diff
|
@@ -79,6 +65,9 @@ module HashDeepDiff
|
|
79
65
|
|
80
66
|
private
|
81
67
|
|
68
|
+
# @!attribute [r] path
|
69
|
+
# @return [Array<Object>] subset of keys from original Hashes to fetch compared values
|
70
|
+
# (is empty for top-level comparison)
|
82
71
|
attr_reader :path
|
83
72
|
|
84
73
|
# @param [Object] original original version
|
@@ -132,18 +121,23 @@ module HashDeepDiff
|
|
132
121
|
keys.uniq
|
133
122
|
end
|
134
123
|
|
124
|
+
# factory function
|
125
|
+
# @return [HashDeepDiff::Delta]
|
126
|
+
def delta(key: NO_VALUE)
|
127
|
+
keys = path
|
128
|
+
keys += [key] unless key == NO_VALUE
|
129
|
+
|
130
|
+
HashDeepDiff::Delta.new(path: keys, value: { left: left(key), right: right(key) })
|
131
|
+
end
|
132
|
+
|
135
133
|
# @return [HashDeepDiff::Factories::Comparison]
|
136
134
|
def comparison_factory
|
137
135
|
HashDeepDiff::Factories::Comparison.new(reporting_engine: reporting_engine)
|
138
136
|
end
|
139
137
|
|
140
|
-
#
|
141
|
-
|
142
|
-
|
143
|
-
change_key = path
|
144
|
-
change_key += [key] unless key == NO_VALUE
|
145
|
-
|
146
|
-
HashDeepDiff::Delta.new(change_key: change_key, value: { left: left(key), right: right(key) })
|
138
|
+
# @return [HashDeepDiff::Reports::Base]
|
139
|
+
def report_engine_factory
|
140
|
+
reporting_engine.new(diff: diff)
|
147
141
|
end
|
148
142
|
end
|
149
143
|
end
|
data/lib/hash_deep_diff/delta.rb
CHANGED
@@ -20,7 +20,7 @@ module HashDeepDiff
|
|
20
20
|
def placebo
|
21
21
|
placebo = simple_left? ? { left: NO_VALUE, right: placebo_elment } : { left: placebo_elment, right: NO_VALUE }
|
22
22
|
|
23
|
-
[self.class.new(
|
23
|
+
[self.class.new(path: change_key, value: placebo)]
|
24
24
|
end
|
25
25
|
|
26
26
|
# true if any value is an +Array+ with hashes
|
@@ -59,11 +59,25 @@ module HashDeepDiff
|
|
59
59
|
simple_left? && simple_right?
|
60
60
|
end
|
61
61
|
|
62
|
+
# removed element(s)
|
63
|
+
def deletion
|
64
|
+
return left unless array_with_array?
|
65
|
+
|
66
|
+
return left - right
|
67
|
+
end
|
68
|
+
|
62
69
|
# Original value
|
63
70
|
def left
|
64
71
|
value[:left]
|
65
72
|
end
|
66
73
|
|
74
|
+
# added element(s)
|
75
|
+
def addition
|
76
|
+
return right unless array_with_array?
|
77
|
+
|
78
|
+
return right - left
|
79
|
+
end
|
80
|
+
|
67
81
|
# Value we compare to
|
68
82
|
def right
|
69
83
|
value[:right]
|
@@ -84,12 +98,12 @@ module HashDeepDiff
|
|
84
98
|
|
85
99
|
attr_reader :value
|
86
100
|
|
87
|
-
# @param [Array]
|
101
|
+
# @param [Array] path list of keys to fetch values we're comparing
|
88
102
|
# @param [Hash<(:left, :right), Object>] value +Hash+ object with two keys - :left and :right,
|
89
103
|
# that represents compared original value (at :left) and value we compare to (at :right)
|
90
|
-
def initialize(
|
104
|
+
def initialize(path:, value:)
|
91
105
|
@value = value
|
92
|
-
@change_key =
|
106
|
+
@change_key = HashDeepDiff::ChangeKey.new(path: path)
|
93
107
|
end
|
94
108
|
|
95
109
|
# an indication of added/removed nested Hash
|
@@ -111,5 +125,11 @@ module HashDeepDiff
|
|
111
125
|
def simple_right?
|
112
126
|
!right.respond_to?(:to_hash) && !complex_right?
|
113
127
|
end
|
128
|
+
|
129
|
+
# true if both left and right are arrays
|
130
|
+
# @return [TrueClass, FalseClass]
|
131
|
+
def array_with_array?
|
132
|
+
left.respond_to?(:to_ary) && right.respond_to?(:to_ary)
|
133
|
+
end
|
114
134
|
end
|
115
135
|
end
|
@@ -42,8 +42,8 @@ module HashDeepDiff
|
|
42
42
|
when :change
|
43
43
|
return [[value_left, value_right, change_key]] unless complex?
|
44
44
|
|
45
|
-
[[value_left, value_right, change_key + [
|
46
|
-
[nesting_left, nesting_right, change_key + [
|
45
|
+
[[value_left, value_right, change_key + [ChangeKey::ARRAY_VALUE]],
|
46
|
+
[nesting_left, nesting_right, change_key + [ChangeKey::NESTED_HASH]]]
|
47
47
|
when :deletion
|
48
48
|
[[value_left, NO_VALUE, change_key]]
|
49
49
|
when :addition
|
@@ -54,6 +54,7 @@ module HashDeepDiff
|
|
54
54
|
# original value without nested hashes
|
55
55
|
# @return [Object]
|
56
56
|
def value_left
|
57
|
+
return NO_VALUE if left.respond_to?(:to_hash) && right.respond_to?(:to_ary)
|
57
58
|
return left unless left.respond_to?(:to_ary)
|
58
59
|
|
59
60
|
left.reject { |el| el.respond_to?(:to_hash) }
|
@@ -62,6 +63,7 @@ module HashDeepDiff
|
|
62
63
|
# changed value without nested hashes
|
63
64
|
# @return [Object]
|
64
65
|
def value_right
|
66
|
+
return NO_VALUE if right.respond_to?(:to_hash) && left.respond_to?(:to_ary)
|
65
67
|
return right unless right.respond_to?(:to_ary)
|
66
68
|
|
67
69
|
right.reject { |el| el.respond_to?(:to_hash) }
|
@@ -70,6 +72,7 @@ module HashDeepDiff
|
|
70
72
|
# nested hashes from original value
|
71
73
|
# @return [Array<Hash>]
|
72
74
|
def nesting_left
|
75
|
+
return left if left.respond_to?(:to_hash)
|
73
76
|
return NO_VALUE unless complex_left?
|
74
77
|
|
75
78
|
left
|
@@ -80,6 +83,7 @@ module HashDeepDiff
|
|
80
83
|
# nested hashes from changed value
|
81
84
|
# @return [Array<Hash>]
|
82
85
|
def nesting_right
|
86
|
+
return right if right.respond_to?(:to_hash)
|
83
87
|
return NO_VALUE unless complex_right?
|
84
88
|
|
85
89
|
right
|
@@ -5,44 +5,39 @@ module HashDeepDiff
|
|
5
5
|
module Reports
|
6
6
|
# Abstract Class
|
7
7
|
class Base
|
8
|
+
# raw data for {#report}
|
9
|
+
def raw_report
|
10
|
+
raise AbstractMethodError
|
11
|
+
end
|
12
|
+
|
8
13
|
# see {#to_str}
|
9
14
|
# @return [String]
|
10
15
|
def to_s
|
11
16
|
to_str
|
12
17
|
end
|
13
18
|
|
14
|
-
#
|
19
|
+
# see {#report}
|
15
20
|
# @return [String]
|
16
21
|
def to_str
|
17
|
-
|
22
|
+
report
|
18
23
|
end
|
19
24
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
# @return [Object] original value
|
24
|
-
# @!attribute [r] new_val
|
25
|
-
# @return [Object] replacement of the original value
|
26
|
-
# @!attribute [r] change_key
|
27
|
-
# @return [Array<Object>] subset of keys from original Hashes to fetch reported values
|
28
|
-
# (is empty for top-level comparison)
|
29
|
-
attr_reader :old_val, :new_val, :change_key
|
30
|
-
|
31
|
-
# @param [Delta] delta diff to report
|
32
|
-
def initialize(delta:)
|
33
|
-
@change_key = delta.change_key.to_ary
|
34
|
-
@old_val = delta.left
|
35
|
-
@new_val = delta.right
|
36
|
-
end
|
37
|
-
|
38
|
-
# old value
|
39
|
-
def original
|
25
|
+
# A report on additions and deletions
|
26
|
+
# @return [String]
|
27
|
+
def report
|
40
28
|
raise AbstractMethodError
|
41
29
|
end
|
42
30
|
|
43
|
-
|
44
|
-
|
45
|
-
|
31
|
+
private
|
32
|
+
|
33
|
+
# @!attribute [r] diff
|
34
|
+
# @return [Array<HashDeepDiff::Delta>] set of deltas from Comparison of two objects
|
35
|
+
attr_reader :diff
|
36
|
+
|
37
|
+
# @param [Array<HashDeepDiff::Delta>] diff comparison data to report
|
38
|
+
def initialize(diff:, change_key_engine: HashDeepDiff::ChangeKey)
|
39
|
+
@diff = diff.to_ary
|
40
|
+
@change_key = change_key_engine
|
46
41
|
end
|
47
42
|
end
|
48
43
|
end
|
@@ -7,50 +7,57 @@ module HashDeepDiff
|
|
7
7
|
module Reports
|
8
8
|
# Visual representation of the {Delta} as diff
|
9
9
|
class Diff < Base
|
10
|
+
# additiond and deletions represented as diff
|
11
|
+
# @return [String]
|
12
|
+
def report
|
13
|
+
raw_report.map { |delta| original(delta) + replacement(delta) }.join
|
14
|
+
end
|
15
|
+
|
16
|
+
# additiond and deletions raw
|
17
|
+
# @return [Array<HashDeepDiff::Delta>]
|
18
|
+
def raw_report
|
19
|
+
diff
|
20
|
+
end
|
21
|
+
|
10
22
|
private
|
11
23
|
|
12
24
|
# line of the report with deleted value
|
13
25
|
# @return [String]
|
14
|
-
def original
|
15
|
-
return '' if
|
16
|
-
return "#{deletion}#{
|
17
|
-
return '' if array_deletion.empty?
|
26
|
+
def original(delta)
|
27
|
+
return '' if delta.left == NO_VALUE
|
28
|
+
return "#{deletion}#{delta.change_key} = #{delta.left}\n" unless array_to_array?(delta)
|
29
|
+
return '' if array_deletion(delta).empty?
|
18
30
|
|
19
|
-
"#{deletion}#{
|
31
|
+
"#{deletion}#{delta.change_key} = #{array_deletion(delta)}\n"
|
20
32
|
end
|
21
33
|
|
22
34
|
# line of the report with added value
|
23
35
|
# @return [String]
|
24
|
-
def replacement
|
25
|
-
return '' if
|
26
|
-
return "#{addition}#{
|
27
|
-
return '' if array_addition.empty?
|
36
|
+
def replacement(delta)
|
37
|
+
return '' if delta.right == NO_VALUE
|
38
|
+
return "#{addition}#{delta.change_key} = #{delta.right}\n" unless array_to_array?(delta)
|
39
|
+
return '' if array_addition(delta).empty?
|
28
40
|
|
29
|
-
"#{addition}#{
|
41
|
+
"#{addition}#{delta.change_key} = #{array_addition(delta)}\n"
|
30
42
|
end
|
31
43
|
|
32
44
|
# returns true if original value and replacement are instances of +Array+
|
33
45
|
# @return Bool
|
34
|
-
|
35
|
-
|
46
|
+
# TOFIX drop
|
47
|
+
def array_to_array?(delta)
|
48
|
+
delta.left.instance_of?(Array) && delta.right.instance_of?(Array)
|
36
49
|
end
|
37
50
|
|
38
51
|
# added elemnts of array
|
39
52
|
# @return [Array]
|
40
|
-
def array_addition
|
41
|
-
|
53
|
+
def array_addition(delta)
|
54
|
+
delta.right - delta.left
|
42
55
|
end
|
43
56
|
|
44
57
|
# added elemnts of array
|
45
58
|
# @return [Array]
|
46
|
-
def array_deletion
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
# Visual representation of keys from compared objects needed to fetch the compared values
|
51
|
-
# @return [String]
|
52
|
-
def path
|
53
|
-
change_key.map { |key| "[#{key}]" }.join
|
59
|
+
def array_deletion(delta)
|
60
|
+
delta.left - delta.right
|
54
61
|
end
|
55
62
|
|
56
63
|
# visual indication of addition
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require 'forwardable'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module HashDeepDiff
|
8
|
+
# Different reporting enjines for {Delta}
|
9
|
+
module Reports
|
10
|
+
# Visual representation of the {Delta} as diff
|
11
|
+
class Yml < Base
|
12
|
+
extend Forwardable
|
13
|
+
def_delegators :@change_key, :initial_object, :dig_set
|
14
|
+
|
15
|
+
# additions and deletions represented as YAML
|
16
|
+
# @return [String]
|
17
|
+
def report
|
18
|
+
YAML.dump(raw_report)
|
19
|
+
end
|
20
|
+
|
21
|
+
# additions and deletiond represented as Hash
|
22
|
+
# @return [Hash]
|
23
|
+
def raw_report
|
24
|
+
@raw = { 'additions' => initial_object(values: additions), 'deletions' => initial_object(values: deletions) }
|
25
|
+
|
26
|
+
additions.each { |(change_key, addition)| change_key.set(raw['additions'], addition) }
|
27
|
+
deletions.each { |(change_key, deletion)| change_key.set(raw['deletions'], deletion) }
|
28
|
+
|
29
|
+
return raw
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :raw
|
35
|
+
|
36
|
+
# added values
|
37
|
+
# @return [Array<HashDeepDiff::Delta>]
|
38
|
+
def additions
|
39
|
+
diff.reject { |delta| delta.right == NO_VALUE }
|
40
|
+
.map { |delta| [delta.change_key, delta.addition] }
|
41
|
+
.reject { |(_, addition)| [] == addition }
|
42
|
+
end
|
43
|
+
|
44
|
+
# deleted values
|
45
|
+
# @return [Array<HashDeepDiff::Delta>]
|
46
|
+
def deletions
|
47
|
+
diff.reject { |delta| delta.left == NO_VALUE }
|
48
|
+
.map { |delta| [delta.change_key, delta.deletion] }
|
49
|
+
.reject { |(_, deletion)| [] == deletion }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
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.8.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-05-
|
11
|
+
date: 2022-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -192,6 +192,20 @@ dependencies:
|
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: 0.18.0
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: rubocop-performance
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - "~>"
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: 1.13.3
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - "~>"
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: 1.13.3
|
195
209
|
- !ruby/object:Gem::Dependency
|
196
210
|
name: rubocop-rake
|
197
211
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,6 +220,20 @@ dependencies:
|
|
206
220
|
- - "~>"
|
207
221
|
- !ruby/object:Gem::Version
|
208
222
|
version: 0.6.0
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: rubocop-rspec
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - "~>"
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: 2.10.0
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - "~>"
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: 2.10.0
|
209
237
|
description: Find the exact difference between two Hash objects
|
210
238
|
email:
|
211
239
|
- bohdan.pohorilets@gmail.com
|
@@ -237,11 +265,13 @@ files:
|
|
237
265
|
- bin/yri
|
238
266
|
- hash_deep_diff.gemspec
|
239
267
|
- lib/hash_deep_diff.rb
|
268
|
+
- lib/hash_deep_diff/change_key.rb
|
240
269
|
- lib/hash_deep_diff/comparison.rb
|
241
270
|
- lib/hash_deep_diff/delta.rb
|
242
271
|
- lib/hash_deep_diff/factories/comparison.rb
|
243
272
|
- lib/hash_deep_diff/reports/base.rb
|
244
273
|
- lib/hash_deep_diff/reports/diff.rb
|
274
|
+
- lib/hash_deep_diff/reports/yml.rb
|
245
275
|
- lib/hash_deep_diff/version.rb
|
246
276
|
homepage: https://github.com/bpohoriletz/hash_deep_diff
|
247
277
|
licenses:
|