hash_deep_diff 0.3.1 → 0.4.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: 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