hash_deep_diff 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|