hash_deep_diff 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ba7f377f6c2d53dd22518acfe0a285ed4d4614f9bf03c1c32f1775358a01188
4
- data.tar.gz: 827fbbd1133ff72fd11475d0c5001894c27a6e71c125ccc5da891c159538beae
3
+ metadata.gz: f27828ee0fbf8a3d07505ed475c3117d2734dd9505e57e3e12d0a360eab4df08
4
+ data.tar.gz: b23a5ef94237454e5a50cac7a59e1624a3d8e6f29d249c6d5836ddd596fe097c
5
5
  SHA512:
6
- metadata.gz: 12eb5aa92de1f7c6d43473bf6858278ffafca368537e7e9f4fd9d2dd945493c4760c9daaee03724e5699c7acd067b06e6bf3162beb38923fe9f218addbae2742
7
- data.tar.gz: f61820a61aeac40a63431a392a3352f79ae278016b2e0238e16b690f8c0a72ea26fbc0c1351face859a71683e61dab720c0cccc55953eac6759101dc4822edc3
6
+ metadata.gz: e83d2e0c8e2843f084aa82ea3e246659b6e078a7c3183f03c236bea8e4ac7848d39ed18bc2f81dfa4aecc005af4149372b5d2da28d87760ee4ff1c330f67f54a
7
+ data.tar.gz: b56714d24122c5cf9ab11616257cb75a2c2c4cb17225f2b0ad8665cbb64c1b49a6fb0d0f544a6961d5db236ef2e8896ebed8fa0d461479be24629cf6d2ecbf71
data/Guardfile CHANGED
@@ -26,6 +26,8 @@ end
26
26
  guard :minitest do
27
27
  # with Minitest::Unit
28
28
  watch(%r{^test/(.*)/?test_(.*)\.rb$})
29
- watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
29
+ # watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
30
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { 'test' }
30
31
  watch(%r{^test/test_helper\.rb$}) { 'test' }
32
+ watch(%r{^test/support/.*$}) { 'test' }
31
33
  end
data/README.md CHANGED
@@ -1,10 +1,3 @@
1
- Ideas
2
- - if not equal use sets to diff keys
3
- - use to_ary to_hash for values to find if further recursion needed
4
- - use to_a.flatten to figure out if any unacceptable classes present
5
- - do not use respond_to?(:each) in first version
6
-
7
-
8
1
  # HashDeepDiff
9
2
 
10
3
  Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/hash_deep_diff`. To experiment with that code, run `bin/console` for an interactive prompt.
@@ -39,14 +39,15 @@ Gem::Specification.new do |spec|
39
39
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
40
40
  spec.require_paths = ['lib']
41
41
 
42
- spec.add_development_dependency 'bundler'
43
- spec.add_development_dependency 'guard'
44
- spec.add_development_dependency 'guard-minitest'
45
- spec.add_development_dependency 'guard-rubocop'
46
- spec.add_development_dependency 'minitest'
47
- spec.add_development_dependency 'minitest-reporters'
48
- spec.add_development_dependency 'rake'
49
- spec.add_development_dependency 'rubocop'
50
- spec.add_development_dependency 'rubocop-minitest'
51
- spec.add_development_dependency 'rubocop-rake'
42
+ spec.add_development_dependency 'bundler', '~> 1.17.2'
43
+ spec.add_development_dependency 'guard', '~> 2.18.0'
44
+ spec.add_development_dependency 'guard-minitest', '~> 2.4.6'
45
+ spec.add_development_dependency 'guard-rubocop', '~> 1.5.0'
46
+ spec.add_development_dependency 'minitest', '~> 5.15.0'
47
+ spec.add_development_dependency 'minitest-focus', '~> 1.3.1'
48
+ spec.add_development_dependency 'minitest-reporters', '~> 1.5.0'
49
+ spec.add_development_dependency 'rake', '~> 10.5.0'
50
+ spec.add_development_dependency 'rubocop', '~> 1.26.1'
51
+ spec.add_development_dependency 'rubocop-minitest', '~> 0.18.0'
52
+ spec.add_development_dependency 'rubocop-rake', '~> 0.6.0'
52
53
  end
@@ -1,77 +1,99 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'delta/left'
4
+ require_relative 'delta/inner'
5
+ require_relative 'delta/right'
6
+
3
7
  # :nodoc:
4
8
  module HashDeepDiff
5
9
  # :nodoc:
6
10
  class Comparison
7
- attr_reader :left, :right
11
+ attr_reader :left, :right, :path
8
12
 
9
13
  def diff(&block)
10
- return [{}, {}, {}] if left == right # this is order-sensitive comparison
11
- return [left, {}, {}] if right.empty?
12
- return [{}, {}, right] if left.empty?
13
- return first_level_delta(&block) if one_level_deep?
14
+ left_delta + deep_delta(&block) + right_delta
15
+ end
14
16
 
15
- return [left_delta, deep_delta(&block), right_delta]
17
+ def report
18
+ diff.join("\n")
16
19
  end
17
20
 
18
21
  private
19
22
 
20
- def initialize(left, right)
23
+ def initialize(left, right, path = [])
21
24
  @left = left.to_hash
22
25
  @right = right.to_hash
26
+ @path = path.to_ary
23
27
  end
24
28
 
25
- def deep_delta(&block)
26
- result = delta(&block)
27
- result.keys.each_with_object({}) do |key, memo|
28
- memo[key] = result[key] && next unless left[key].instance_of?(Hash)
29
+ def extra_report(memo, keys, value)
30
+ if value.respond_to?(:to_hash)
31
+ value.each_key { |key| extra_report(memo, keys + [key], value[key]) }
32
+ else
33
+ memo << Delta::Left.new(path: keys, value: value)
34
+ end
35
+ end
29
36
 
30
- memo[key] = self.class.new(left[key], right[key]).diff
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)
31
42
  end
32
43
  end
33
44
 
34
- def first_level_delta(&block)
35
- [
36
- left_delta,
37
- delta(&block),
38
- right_delta
39
- ]
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
66
+ else
67
+ memo << diff
68
+ end
69
+ end
40
70
  end
41
71
 
42
72
  def right_delta
43
- right_diff_keys.each_with_object({}) do |key, memo|
44
- memo[key] = right[key]
73
+ right_diff_keys.each_with_object([]) do |key, memo|
74
+ memo << Delta::Right.new(path: path + [key], value: right[key])
45
75
  end
46
76
  end
47
77
 
48
78
  def delta(&block)
49
79
  block ||= ->(val) { val }
50
80
 
51
- common_keys.each_with_object({}) do |key, memo|
81
+ common_keys.each_with_object([]) do |key, memo|
52
82
  value_left = block.call(left[key])
53
83
  value_right = block.call(right[key])
54
84
 
55
85
  next if value_right.instance_of?(value_left.class) && (value_right == value_left)
56
86
 
57
- memo[key] = {
58
- left: value_left,
59
- right: value_right
60
- }
87
+ memo << Delta::Inner.new(path: path + [key], value: { left: value_left, right: value_right })
61
88
  end
62
89
  end
63
90
 
64
91
  def left_delta
65
- left_diff_keys.each_with_object({}) do |key, memo|
66
- memo[key] = left[key]
92
+ left_diff_keys.each_with_object([]) do |key, memo|
93
+ memo << Delta::Left.new(path: path + [key], value: left[key])
67
94
  end
68
95
  end
69
96
 
70
- def one_level_deep?
71
- left.values.none? { |value| value.respond_to?(:to_hash) } &&
72
- right.values.none? { |value| value.respond_to?(:to_hash) }
73
- end
74
-
75
97
  def common_keys
76
98
  left.keys & right.keys
77
99
  end
@@ -0,0 +1,62 @@
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
@@ -0,0 +1,44 @@
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
@@ -0,0 +1,24 @@
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
@@ -0,0 +1,24 @@
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HashDeepDiff
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
@@ -5,5 +5,4 @@ require 'hash_deep_diff/comparison'
5
5
 
6
6
  module HashDeepDiff
7
7
  class Error < StandardError; end
8
- # Your code goes here...
9
8
  end
metadata CHANGED
@@ -1,155 +1,169 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hash_deep_diff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.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-01 00:00:00.000000000 Z
11
+ date: 2022-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 1.17.2
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 1.17.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: guard
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 2.18.0
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 2.18.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: guard-minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 2.4.6
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: 2.4.6
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: guard-rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 1.5.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: 1.5.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: minitest
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: 5.15.0
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: 5.15.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest-focus
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.3.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.3.1
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: minitest-reporters
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
- - - ">="
101
+ - - "~>"
88
102
  - !ruby/object:Gem::Version
89
- version: '0'
103
+ version: 1.5.0
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
- - - ">="
108
+ - - "~>"
95
109
  - !ruby/object:Gem::Version
96
- version: '0'
110
+ version: 1.5.0
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: rake
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
- - - ">="
115
+ - - "~>"
102
116
  - !ruby/object:Gem::Version
103
- version: '0'
117
+ version: 10.5.0
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
- - - ">="
122
+ - - "~>"
109
123
  - !ruby/object:Gem::Version
110
- version: '0'
124
+ version: 10.5.0
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: rubocop
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
- - - ">="
129
+ - - "~>"
116
130
  - !ruby/object:Gem::Version
117
- version: '0'
131
+ version: 1.26.1
118
132
  type: :development
119
133
  prerelease: false
120
134
  version_requirements: !ruby/object:Gem::Requirement
121
135
  requirements:
122
- - - ">="
136
+ - - "~>"
123
137
  - !ruby/object:Gem::Version
124
- version: '0'
138
+ version: 1.26.1
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: rubocop-minitest
127
141
  requirement: !ruby/object:Gem::Requirement
128
142
  requirements:
129
- - - ">="
143
+ - - "~>"
130
144
  - !ruby/object:Gem::Version
131
- version: '0'
145
+ version: 0.18.0
132
146
  type: :development
133
147
  prerelease: false
134
148
  version_requirements: !ruby/object:Gem::Requirement
135
149
  requirements:
136
- - - ">="
150
+ - - "~>"
137
151
  - !ruby/object:Gem::Version
138
- version: '0'
152
+ version: 0.18.0
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: rubocop-rake
141
155
  requirement: !ruby/object:Gem::Requirement
142
156
  requirements:
143
- - - ">="
157
+ - - "~>"
144
158
  - !ruby/object:Gem::Version
145
- version: '0'
159
+ version: 0.6.0
146
160
  type: :development
147
161
  prerelease: false
148
162
  version_requirements: !ruby/object:Gem::Requirement
149
163
  requirements:
150
- - - ">="
164
+ - - "~>"
151
165
  - !ruby/object:Gem::Version
152
- version: '0'
166
+ version: 0.6.0
153
167
  description: Find the exact difference between two Hash objects
154
168
  email:
155
169
  - bohdan.pohorilets@gmail.com
@@ -175,6 +189,10 @@ files:
175
189
  - hash_deep_diff.gemspec
176
190
  - lib/hash_deep_diff.rb
177
191
  - lib/hash_deep_diff/comparison.rb
192
+ - lib/hash_deep_diff/delta/acts_as_delta.rb
193
+ - lib/hash_deep_diff/delta/inner.rb
194
+ - lib/hash_deep_diff/delta/left.rb
195
+ - lib/hash_deep_diff/delta/right.rb
178
196
  - lib/hash_deep_diff/version.rb
179
197
  homepage: https://github.com/bpohoriletz/hash_deep_diff
180
198
  licenses: