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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43343dfc0eed6ad00b863b65b535f206b6a14544c8c23560f01954e16c7ef19b
4
- data.tar.gz: 613e764bf1a0390bfba99eceaa6e65e69355903858c229f8d150661c8a923af4
3
+ metadata.gz: 24756c15f3f48bbe108e5c2bb12c2db3e6030cef78ab91a6cf76044ca70d2dd5
4
+ data.tar.gz: a8cca8ab8a23c704f6f69c2613ded6b4d0d2c7b0e5caed34d437fe0951582a45
5
5
  SHA512:
6
- metadata.gz: 701aad97711690dbaff2719a3c0f6a4c0bbbf2a3ffdcd088b6451803c392a019dd37dba9b3cffc3a7843862b8341d6a7e17eaa2935aba8be4f069fb81160a052
7
- data.tar.gz: 920a5d22b16bb78866c752c5a60469f37af9d58f8f0f3852c1822f594223f995ed4c87f14777d3dc857a73310d1c851581edd9fdba36bc3b433d069133211d95
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
- 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.
3
+ Find the exact difference between two Hash objects and build a report to visualize it
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ [![Gem
6
+ Version](https://badge.fury.io/rb/hash_deep_diff.svg)](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
- TODO: Write usage instructions here
26
-
27
- ## Development
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
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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/[USERNAME]/hash_deep_diff.
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/left'
4
- require_relative 'delta/inner'
5
- require_relative 'delta/right'
3
+ require_relative 'delta'
6
4
 
7
5
  # :nodoc:
8
6
  module HashDeepDiff
9
- # :nodoc:
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(&block)
14
- left_delta + deep_delta(&block) + right_delta
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 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
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
- memo << diff
32
+ diff
68
33
  end
69
34
  end
70
35
  end
71
36
 
72
- def right_delta
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 = block.call(left[key])
81
- value_right = block.call(right[key])
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::Inner.new(path: path + [key], value: { left: value_left, right: value_right })
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 & right.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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HashDeepDiff
4
- VERSION = '0.3.1'
4
+ VERSION = '0.4.0'
5
5
  end
@@ -4,5 +4,6 @@ require 'hash_deep_diff/version'
4
4
  require 'hash_deep_diff/comparison'
5
5
 
6
6
  module HashDeepDiff
7
+ NO_VALUE = Class.new(NilClass)
7
8
  class Error < StandardError; end
8
9
  end
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.3.1
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 00:00:00.000000000 Z
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/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
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