hash_miner 1.0.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +36 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +146 -0
- data/Rakefile +12 -0
- data/lib/hash_miner/errors.rb +7 -0
- data/lib/hash_miner/hash.rb +170 -0
- data/lib/hash_miner/version.rb +5 -0
- data/lib/hash_miner.rb +14 -0
- data/sig/hash_miner.rbs +4 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 47cc3f1132fb53760a709194961033a327634cda3d5b5f32583cb8ab8af1cc61
|
4
|
+
data.tar.gz: 192a1594d31a7565aaa0d1bfc0835a29b2cdf57326c131bd6756aa58901fc5d8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2dde32d4292f9f66374cac7c048ec5373537bfc4b988a563202aa824a394a6b7e3b0b6af057eda8e9c481aac93910b9a188714e6e2e77760c57debdd1fd98335
|
7
|
+
data.tar.gz: 05354d5f27da7d4d0a6f0de3c58315929193596f9a08d651e83f352df6b9f3573070016bb8e84fb64eaa2ebf8395e97c4689fbf69d38476044ed991b17297233
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.7
|
3
|
+
NewCops: enable
|
4
|
+
SuggestExtensions: false
|
5
|
+
|
6
|
+
Style/StringLiterals:
|
7
|
+
Enabled: true
|
8
|
+
EnforcedStyle: single_quotes
|
9
|
+
|
10
|
+
Style/StringLiteralsInInterpolation:
|
11
|
+
Enabled: true
|
12
|
+
EnforcedStyle: single_quotes
|
13
|
+
|
14
|
+
Layout/LineLength:
|
15
|
+
Max: 130
|
16
|
+
|
17
|
+
Metrics/AbcSize:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Metrics/BlockLength:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Metrics/ClassLength:
|
24
|
+
Max: 120
|
25
|
+
|
26
|
+
Metrics/MethodLength:
|
27
|
+
Max: 40
|
28
|
+
|
29
|
+
Metrics/CyclomaticComplexity:
|
30
|
+
Max: 20
|
31
|
+
|
32
|
+
Metrics/PerceivedComplexity:
|
33
|
+
Max: 20
|
34
|
+
|
35
|
+
Style/MultilineBlockChain:
|
36
|
+
Enabled: false
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 alexo
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
# HashMiner
|
2
|
+
|
3
|
+
Ever experienced the pain of working with deeply nested hashes in Ruby?
|
4
|
+
|
5
|
+
HashMiner expands the base Hash class in Ruby to provide helpful methods to traverse and manipulate your Hash Object regardless of complexity.
|
6
|
+
|
7
|
+
The following gives a flavour of how HashMiner can help solve headaches when working with complex hashes:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'hash_miner'
|
11
|
+
|
12
|
+
hash = { my: { super: { duper: { deeply: 'nested hash' } } } }
|
13
|
+
|
14
|
+
# Deleting a key #
|
15
|
+
# Without HashMiner
|
16
|
+
hash[:my][:super].delete(:duper) # => {:deeply=>"nested hash"}
|
17
|
+
# With HashMiner
|
18
|
+
hash.deep_remove(key: :duper) # => {:my=>{:super=>{}}}
|
19
|
+
|
20
|
+
# Updating a key #
|
21
|
+
# Without HashMiner
|
22
|
+
hash[:my][:super][:duper][:deeply] = 'modified nested hash' # => "modified nested hash"
|
23
|
+
# With HashMiner
|
24
|
+
hash.deep_update(key: :deeply, value: 'modified nested hash') # => {:my=>{:super=>{:duper=>{:deeply=>"modified nested hash"}}}}
|
25
|
+
|
26
|
+
# Checking a key exists #
|
27
|
+
# Without HashMiner
|
28
|
+
hash[:my][:super][:duper][:deeply] # => "nested hash"
|
29
|
+
# With HashMiner
|
30
|
+
hash.deep_find(key: :deeply) # => ["nested hash"]
|
31
|
+
```
|
32
|
+
|
33
|
+
## Installation
|
34
|
+
|
35
|
+
Install the gem and add to the application's Gemfile by executing:
|
36
|
+
|
37
|
+
$ bundle add hash_miner
|
38
|
+
|
39
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
40
|
+
|
41
|
+
$ gem install hash_miner
|
42
|
+
|
43
|
+
## Usage
|
44
|
+
|
45
|
+
- HashMiner methods have no side-effects
|
46
|
+
|
47
|
+
### Methods available
|
48
|
+
#### deep_update
|
49
|
+
|
50
|
+
- Updates all values that match the given key
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
require 'hash_miner'
|
54
|
+
|
55
|
+
nasty_hash = { my: { pretty: [{ nasty: :hash }, nil], is: { pretty: :nasty } } }
|
56
|
+
|
57
|
+
nasty_hash.deep_update(key: :nasty, value: :complicated_hash) # => {:my=>{:pretty=>[{:nasty=>:complicated_hash}, nil], :is=>{:pretty=>:nasty}}}
|
58
|
+
|
59
|
+
# Errors on uniqueness
|
60
|
+
nasty_hash.deep_update(key: :pretty, value: :super_duper) # => throws KeyNotUniqueError
|
61
|
+
nasty_hash.deep_update(key: :pretty, value: :super_duper, error_on_uniqueness: false) # => {:my=>{:pretty=>:super_duper, :is=>{:pretty=>:super_duper}}}
|
62
|
+
|
63
|
+
# Errors on missing
|
64
|
+
nasty_hash.deep_update(key: :huh, value: :where_am_i) # => throws KeyNotFoundError
|
65
|
+
nasty_hash.deep_update(key: :pretty, value: :super_duper, error_on_missing: false) # => {:my=>{:pretty=>[{:nasty=>:hash}, nil], :is=>{:pretty=>:nasty}}, :huh=>:where_am_i}
|
66
|
+
|
67
|
+
```
|
68
|
+
---
|
69
|
+
#### deep_remove
|
70
|
+
|
71
|
+
- Removes all values that match the given key
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
require 'hash_miner'
|
75
|
+
|
76
|
+
nasty_hash = { my: { pretty: [{ nasty: :hash }, nil], is: { pretty: :nasty } } }
|
77
|
+
|
78
|
+
nasty_hash.deep_remove(key: :nasty) # => {:my=>{:pretty=>[{}, nil], :is=>{:pretty=>:nasty}}}
|
79
|
+
|
80
|
+
# Errors on uniqueness
|
81
|
+
nasty_hash.deep_remove(key: :pretty) # => throws KeyNotUniqueError
|
82
|
+
nasty_hash.deep_remove(key: :pretty, error_on_uniqueness: false) # => {:my=>{:is=>{}}}
|
83
|
+
```
|
84
|
+
---
|
85
|
+
#### deep_find
|
86
|
+
|
87
|
+
- Returns all values that match the given key
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
require 'hash_miner'
|
91
|
+
|
92
|
+
nasty_hash = { my: { pretty: [{ nasty: :hash }, nil], is: { pretty: :nasty } } }
|
93
|
+
|
94
|
+
nasty_hash.deep_find(key: :nasty) # => [:hash]
|
95
|
+
nasty_hash.deep_find(key: :pretty) # => [{:nasty=>:hash}, nil, :nasty]
|
96
|
+
```
|
97
|
+
---
|
98
|
+
#### deep_compact
|
99
|
+
|
100
|
+
- Removes nil and empty values from Hash
|
101
|
+
- Will not remove values within an Array i.e `[nil, {}]` will remain
|
102
|
+
```ruby
|
103
|
+
require 'hash_miner'
|
104
|
+
|
105
|
+
nasty_hash = { my: { pretty: [{ nasty: :hash }, nil], is: { pretty: {}, nasty: nil } } }
|
106
|
+
|
107
|
+
nasty_hash.deep_compact # => {:my=>{:pretty=>[{:nasty=>:hash}, nil]}}
|
108
|
+
```
|
109
|
+
---
|
110
|
+
#### deep_count
|
111
|
+
|
112
|
+
- Returns a count for the given key
|
113
|
+
```ruby
|
114
|
+
require 'hash_miner'
|
115
|
+
|
116
|
+
nasty_hash = { my: { pretty: [{ nasty: :hash }, nil], is: { pretty: :nasty } } }
|
117
|
+
|
118
|
+
nasty_hash.deep_count(key: :pretty) # => 2
|
119
|
+
nasty_hash.deep_count(key: :nasty) # => 1
|
120
|
+
```
|
121
|
+
---
|
122
|
+
#### deep_contains?
|
123
|
+
|
124
|
+
- Returns `true|false` depending on if given is found
|
125
|
+
```ruby
|
126
|
+
require 'hash_miner'
|
127
|
+
|
128
|
+
nasty_hash = { my: { pretty: [{ nasty: :hash }, nil], is: { pretty: :nasty } } }
|
129
|
+
|
130
|
+
nasty_hash.deep_contains?(key: :nasty) # => true
|
131
|
+
nasty_hash.deep_contains?(key: :super_nasty) # => false
|
132
|
+
```
|
133
|
+
|
134
|
+
## Development
|
135
|
+
|
136
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
137
|
+
|
138
|
+
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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
139
|
+
|
140
|
+
## Contributing
|
141
|
+
|
142
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/hash_miner.
|
143
|
+
|
144
|
+
## License
|
145
|
+
|
146
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Adds additional methods to the Hash class
|
4
|
+
class Hash
|
5
|
+
include HashMiner
|
6
|
+
|
7
|
+
# Checks if the key exists within a Hash
|
8
|
+
#
|
9
|
+
# @param key [String, Symbol] the key you want to check
|
10
|
+
# @param hash [Hash] the hash obj - used for recursive calling
|
11
|
+
#
|
12
|
+
# @return [Boolean] whether the key was found
|
13
|
+
def deep_contains?(key:, hash: self)
|
14
|
+
return nil unless hash.is_a? Hash
|
15
|
+
return true if hash.include? key
|
16
|
+
|
17
|
+
hash.filter_map do |_k, v|
|
18
|
+
case v
|
19
|
+
when Hash
|
20
|
+
[v.deep_contains?(key: key)]
|
21
|
+
when Array
|
22
|
+
[v.map { |i| deep_contains?(key: key, hash: i) }]
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end.flatten.include? true
|
27
|
+
end
|
28
|
+
|
29
|
+
# Count the number of occurrences a key has in a Hash
|
30
|
+
#
|
31
|
+
# @param key [String, Symbol]
|
32
|
+
# @param hash [Hash]
|
33
|
+
#
|
34
|
+
# @return [Integer] the number of occurrences for a given key
|
35
|
+
def deep_count(key:, hash: self)
|
36
|
+
found = 0
|
37
|
+
return found unless hash.is_a? Hash
|
38
|
+
return found unless hash.deep_contains?(key: key)
|
39
|
+
|
40
|
+
hash.each do |k, v|
|
41
|
+
found += 1 if k.eql? key
|
42
|
+
found += deep_count(hash: v, key: key) if v.is_a?(Hash)
|
43
|
+
v.each { |i| found += deep_count(hash: i, key: key) } if v.is_a?(Array)
|
44
|
+
end
|
45
|
+
|
46
|
+
found
|
47
|
+
end
|
48
|
+
|
49
|
+
# Finds the value for a given key
|
50
|
+
#
|
51
|
+
# @param key [String, Symbol]
|
52
|
+
# @param hash [Hash]
|
53
|
+
#
|
54
|
+
# @return [Array] with all values that match the key
|
55
|
+
def deep_find(key:, hash: self)
|
56
|
+
return nil unless hash.is_a? Hash
|
57
|
+
return nil unless hash.deep_contains?(key: key)
|
58
|
+
|
59
|
+
hash.filter_map do |k, v|
|
60
|
+
if k.eql? key
|
61
|
+
v
|
62
|
+
elsif v.is_a?(Hash)
|
63
|
+
deep_find(hash: v, key: key)
|
64
|
+
elsif v.is_a?(Array)
|
65
|
+
[v.filter_map { |i| deep_find(hash: i, key: key) }]
|
66
|
+
end
|
67
|
+
end.flatten
|
68
|
+
end
|
69
|
+
|
70
|
+
# Removed nil/empty from Hash
|
71
|
+
#
|
72
|
+
# @return [Hash] with no nil/empty values
|
73
|
+
def deep_compact
|
74
|
+
if is_a?(Hash)
|
75
|
+
to_h do |k, v|
|
76
|
+
case v
|
77
|
+
when Hash
|
78
|
+
[k, v.deep_compact]
|
79
|
+
when Array
|
80
|
+
[k, v.map do |i|
|
81
|
+
i.is_a?(Hash) ? i.deep_compact : i
|
82
|
+
end]
|
83
|
+
else
|
84
|
+
[k, v]
|
85
|
+
end
|
86
|
+
end.delete_if { |_, v| v.nil? || (v.respond_to?(:empty?) && v.empty?) }
|
87
|
+
else
|
88
|
+
self
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Updates specified key in nested hash.
|
93
|
+
#
|
94
|
+
# @param key [String, Symbol] the key to update
|
95
|
+
# @param value [String] the value to be set
|
96
|
+
# @param error_on_missing [Boolean] error if key missing
|
97
|
+
# @param error_on_uniqueness [Boolean] error if key not unique
|
98
|
+
#
|
99
|
+
# @return [Hash] the object with specified key updated.
|
100
|
+
def deep_update(key:, value:, error_on_missing: true, error_on_uniqueness: true)
|
101
|
+
return self unless is_a? Hash
|
102
|
+
|
103
|
+
if error_on_uniqueness && (deep_count(key: key) > 1)
|
104
|
+
raise KeyNotUniqueError, "Key: '#{key}' not unique | Pass 'error_on_uniqueness: false' if you do not care"
|
105
|
+
end
|
106
|
+
|
107
|
+
unless deep_contains?(key: key)
|
108
|
+
if error_on_missing
|
109
|
+
raise KeyNotFoundError,
|
110
|
+
"Key: '#{key}' not found in hash | Pass 'error_on_missing: false' if you do not care"
|
111
|
+
end
|
112
|
+
|
113
|
+
LOG.warn('Key not found in hash, adding to the top level')
|
114
|
+
|
115
|
+
hash = dup
|
116
|
+
hash[key] = value
|
117
|
+
return hash
|
118
|
+
end
|
119
|
+
|
120
|
+
to_h do |k, v|
|
121
|
+
if k.eql?(key)
|
122
|
+
[k, value]
|
123
|
+
elsif v.is_a?(Hash) && v.deep_contains?(key: key)
|
124
|
+
[k, v.deep_update(key: key, value: value, error_on_missing: error_on_missing, error_on_uniqueness: error_on_uniqueness)]
|
125
|
+
elsif v.is_a?(Array)
|
126
|
+
[k, v.map do |item|
|
127
|
+
if item.is_a?(Hash) && item.deep_contains?(key: key)
|
128
|
+
item.deep_update(key: key, value: value, error_on_missing: error_on_missing, error_on_uniqueness: error_on_uniqueness)
|
129
|
+
else
|
130
|
+
item
|
131
|
+
end
|
132
|
+
end]
|
133
|
+
else
|
134
|
+
[k, v]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Removes specified key from nested hash.
|
140
|
+
#
|
141
|
+
# @param key [String] the key to remove.
|
142
|
+
#
|
143
|
+
# @return [Hash] the object with specified key removed.
|
144
|
+
def deep_remove(key:, error_on_uniqueness: true)
|
145
|
+
return self unless is_a? Hash
|
146
|
+
|
147
|
+
if error_on_uniqueness && (deep_count(key: key) > 1)
|
148
|
+
raise KeyNotUniqueError, "Key: '#{key}' not unique | Pass 'error_on_uniqueness: false' if you do not care"
|
149
|
+
end
|
150
|
+
|
151
|
+
# filter_map removes nil from Array
|
152
|
+
filter_map do |k, v|
|
153
|
+
if k.eql? key
|
154
|
+
nil
|
155
|
+
elsif v.is_a?(Hash)
|
156
|
+
[k, v.deep_remove(key: key, error_on_uniqueness: error_on_uniqueness)]
|
157
|
+
elsif v.is_a?(Array)
|
158
|
+
[k, v.map do |item|
|
159
|
+
if item.is_a?(Hash) && item.deep_contains?(key: key)
|
160
|
+
item.deep_remove(key: key, error_on_uniqueness: error_on_uniqueness)
|
161
|
+
else
|
162
|
+
item
|
163
|
+
end
|
164
|
+
end]
|
165
|
+
else
|
166
|
+
[k, v]
|
167
|
+
end
|
168
|
+
end.to_h
|
169
|
+
end
|
170
|
+
end
|
data/lib/hash_miner.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'hash_miner/version'
|
4
|
+
require_relative 'hash_miner/hash'
|
5
|
+
require_relative 'hash_miner/errors'
|
6
|
+
|
7
|
+
require 'pry'
|
8
|
+
require 'logger'
|
9
|
+
|
10
|
+
module HashMiner
|
11
|
+
class Error < StandardError; end
|
12
|
+
|
13
|
+
LOG = Logger.new($stdout)
|
14
|
+
end
|
data/sig/hash_miner.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hash_miner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- alexo
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-07-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Additional Hash methods to help navigate the tricky world of nested Hashes.
|
14
|
+
email:
|
15
|
+
- alexander.omahoney@dvla.gov.uk
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".rspec"
|
21
|
+
- ".rubocop.yml"
|
22
|
+
- Gemfile
|
23
|
+
- LICENSE.txt
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- lib/hash_miner.rb
|
27
|
+
- lib/hash_miner/errors.rb
|
28
|
+
- lib/hash_miner/hash.rb
|
29
|
+
- lib/hash_miner/version.rb
|
30
|
+
- sig/hash_miner.rbs
|
31
|
+
homepage: https://github.com/dvla/hash_miner
|
32
|
+
licenses:
|
33
|
+
- MIT
|
34
|
+
metadata:
|
35
|
+
homepage_uri: https://github.com/dvla/hash_miner
|
36
|
+
source_code_uri: https://github.com/dvla/hash_miner
|
37
|
+
changelog_uri: https://github.com/dvla/hash_miner
|
38
|
+
rubygems_mfa_required: 'true'
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.7.0
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
requirements: []
|
54
|
+
rubygems_version: 3.3.7
|
55
|
+
signing_key:
|
56
|
+
specification_version: 4
|
57
|
+
summary: Additional Hash methods to help navigate the tricky world of nested Hashes.
|
58
|
+
test_files: []
|