hash-selector 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/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +69 -0
- data/Rakefile +9 -0
- data/hash_selector.gemspec +23 -0
- data/lib/hash-selector.rb +1 -0
- data/lib/hash_selector.rb +62 -0
- data/lib/hash_selector/version.rb +3 -0
- data/spec/hash_selector_spec.rb +74 -0
- data/spec/spec_helper.rb +26 -0
- metadata +102 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 4aba65b6d21271d3127dae88d963ac20b182cdc7
|
|
4
|
+
data.tar.gz: 958929bebd3c730c8ecc5c64bab8d2fed69462d9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3fdde540f8d87267856d59a5a47ef94efec344a3b0ec9daacda3bb0c46289f7c7ab5101af7cf707f914a7ed1f74e52ca8fd82fce3ef299c1b401a8d06e396140
|
|
7
|
+
data.tar.gz: 6ffd9d9d114d4248aa1672956a4febe4d03cca92cb1b7fe9e84d1847563624038731050c3bad836b69b0161f61f1a1b6da5dd0ba6df31d038156288aa8e2df3f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2015 Peter Williams
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[](https://travis-ci.org/pezra/hash-selector)
|
|
2
|
+
|
|
3
|
+
# HashSelector
|
|
4
|
+
|
|
5
|
+
Select values from deeply nested/complex hashes with ease.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
Consider a complex and deeply nested hash such as the following:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
config =
|
|
13
|
+
{ databases: [
|
|
14
|
+
{ name: "myapp_production",
|
|
15
|
+
user: "myapp",
|
|
16
|
+
host: "db1"
|
|
17
|
+
},
|
|
18
|
+
{ name: "legacy_db",
|
|
19
|
+
host: "db2"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"redis": { host: "redis_server" }
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Say we want to find the user name of legacy db but if we do not find it use a default value. With just the basic hash behavior this can be rather onerous. With `HashSelector` is it a breeze.
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
selector = HashSelector.new[:databases].find{|db| db[:name] == "myapp_production"}[:user]
|
|
30
|
+
selector.find_in(config) # => "myapp"
|
|
31
|
+
```
|
|
32
|
+
The selector definition line should be read as "a new selector that chooses the `:databases` item, finds an entry in it whose name is `myapp_production`, and selects its `:user`"
|
|
33
|
+
|
|
34
|
+
If the location specified by a `HashSelector` does not exist the hash it will raise a `KeyError` unless a block is provided. If a block is provided the block will be evaluated and its return value will returned from `#find_in`.
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
selector = HashSelector.new[:databases].find{|db| db[:name] == "myapp_test"}[:user]
|
|
38
|
+
selector.find_in(config) { "myapp" } # => "myapp"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
In this example, there find fails because there is not entry in `:databases` with a name of `myapp_test` so the default (`myapp`) is returned instead.
|
|
42
|
+
|
|
43
|
+
`HashSelector`s are immutable and may be reused any any number of different hashes.
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
Add this line to your application's Gemfile:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
gem 'hash-selector'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
And then execute:
|
|
54
|
+
|
|
55
|
+
$ bundle
|
|
56
|
+
|
|
57
|
+
Or install it yourself as:
|
|
58
|
+
|
|
59
|
+
$ gem install hash-selector
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## Contributing
|
|
64
|
+
|
|
65
|
+
1. Fork it ( https://github.com/[my-github-username]/hash_selector/fork )
|
|
66
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
67
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
68
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
69
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'hash_selector/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "hash-selector"
|
|
8
|
+
spec.version = HashSelector::VERSION
|
|
9
|
+
spec.authors = ["Peter Williams"]
|
|
10
|
+
spec.email = ["pezra@barelyenough.org"]
|
|
11
|
+
spec.summary = %q{Easily select values from deep inside hierarchical hashes.}
|
|
12
|
+
spec.homepage = "https://github.com/pezra/hash-selector"
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
|
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
spec.require_paths = ["lib"]
|
|
19
|
+
|
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
|
21
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
22
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
|
23
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require_relative "./hash_selector"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require "hash_selector/version"
|
|
2
|
+
|
|
3
|
+
# Selects a value from a complex or deeply nested hash structure with default
|
|
4
|
+
# values.
|
|
5
|
+
#
|
|
6
|
+
# Example
|
|
7
|
+
#
|
|
8
|
+
# HashSelector.new[:foo][:bar][:baz].find_in(foo: { bar: { baz: 42 } } )
|
|
9
|
+
# # => 42
|
|
10
|
+
#
|
|
11
|
+
# HashSelector.new[:foo][:bar][:baz].find_in(foo: nil) { 42 }
|
|
12
|
+
# # => 42
|
|
13
|
+
class HashSelector
|
|
14
|
+
|
|
15
|
+
# Returns a new selector that selects the specified key from result of the
|
|
16
|
+
# current selector.
|
|
17
|
+
def [](key)
|
|
18
|
+
HashSelector.new steps + [ ->(subject) {
|
|
19
|
+
begin
|
|
20
|
+
subject.fetch(key)
|
|
21
|
+
rescue IndexError
|
|
22
|
+
raise KeyError
|
|
23
|
+
end
|
|
24
|
+
} ]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns a new selector that selects an entry from the result of the current
|
|
28
|
+
# selector using a predicate block.
|
|
29
|
+
def find(&blk)
|
|
30
|
+
HashSelector.new steps + [ ->(subject) {
|
|
31
|
+
needle = subject.find(->(){ MISSING }, &blk)
|
|
32
|
+
if MISSING == needle
|
|
33
|
+
fail KeyError
|
|
34
|
+
else
|
|
35
|
+
needle
|
|
36
|
+
end
|
|
37
|
+
} ]
|
|
38
|
+
end
|
|
39
|
+
alias_method :detect, :find
|
|
40
|
+
|
|
41
|
+
# Returns the value from a hash at the location specified by this selector.
|
|
42
|
+
def find_in(hay_stack)
|
|
43
|
+
steps.reduce(hay_stack) { |search_area, step| step.call(search_area) }
|
|
44
|
+
|
|
45
|
+
rescue KeyError
|
|
46
|
+
if block_given?
|
|
47
|
+
yield hay_stack
|
|
48
|
+
else
|
|
49
|
+
raise
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
protected
|
|
54
|
+
|
|
55
|
+
MISSING = Object.new
|
|
56
|
+
|
|
57
|
+
def initialize(steps = [])
|
|
58
|
+
@steps = steps
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
attr_reader :steps
|
|
62
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
RSpec.describe "HashSelector" do
|
|
2
|
+
|
|
3
|
+
let(:data) { { foo: {
|
|
4
|
+
bar: [
|
|
5
|
+
{ baz: 1,
|
|
6
|
+
name: "alice"},
|
|
7
|
+
{ baz: 2,
|
|
8
|
+
name: "bob"}
|
|
9
|
+
] },
|
|
10
|
+
qux: 42 } }
|
|
11
|
+
|
|
12
|
+
it "can select values from top level" do
|
|
13
|
+
expect(
|
|
14
|
+
HashSelector.new[:qux]
|
|
15
|
+
.find_in(data)
|
|
16
|
+
).to eq 42
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "raises error on a index miss" do
|
|
20
|
+
expect {
|
|
21
|
+
HashSelector.new[:quux].find_in(data)
|
|
22
|
+
}.to raise_error KeyError
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "returns value of default value block on a index miss" do
|
|
26
|
+
expect(
|
|
27
|
+
HashSelector.new[:quux].find_in(data) { "whatever" }
|
|
28
|
+
).to eq "whatever"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "can select deeply nested values" do
|
|
32
|
+
expect( HashSelector.new[:foo][:bar][0][:baz].find_in(data)
|
|
33
|
+
).to eq 1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "raise error on a array index miss" do
|
|
37
|
+
expect { HashSelector.new[:foo][:bar][42].find_in(data)
|
|
38
|
+
}.to raise_error KeyError
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "returns value of default value block a array index miss" do
|
|
42
|
+
expect(
|
|
43
|
+
HashSelector.new[:foo][:bar][42].find_in(data) { "whatever" }
|
|
44
|
+
).to eq "whatever"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "can select path using #find" do
|
|
48
|
+
expect(
|
|
49
|
+
HashSelector.new[:foo][:bar].find{|it| it[:name] == "bob"}[:baz].find_in(data)
|
|
50
|
+
).to eq 2
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "can select path using #detect" do
|
|
54
|
+
expect(
|
|
55
|
+
HashSelector.new[:foo][:bar].detect{|it| it[:name] == "bob"}[:baz]
|
|
56
|
+
.find_in(data)
|
|
57
|
+
).to eq 2
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "raises error on a #find/#detect miss" do
|
|
61
|
+
expect {
|
|
62
|
+
HashSelector.new[:foo][:bar].detect{|it| it[:name] == "mallory"}
|
|
63
|
+
.find_in(data)
|
|
64
|
+
}.to raise_error KeyError
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "returns value of default value block on a #find/#detect miss" do
|
|
68
|
+
expect(
|
|
69
|
+
HashSelector.new[:foo][:bar].detect{|it| it[:name] == "mallory"}
|
|
70
|
+
.find_in(data) { "whatever" }
|
|
71
|
+
).to eq "whatever"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'hash-selector'
|
|
2
|
+
|
|
3
|
+
RSpec.configure do |config|
|
|
4
|
+
config.expect_with :rspec do |expectations|
|
|
5
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
config.mock_with :rspec do |mocks|
|
|
9
|
+
mocks.verify_partial_doubles = true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
config.filter_run :focus
|
|
13
|
+
config.run_all_when_everything_filtered = true
|
|
14
|
+
|
|
15
|
+
config.disable_monkey_patching!
|
|
16
|
+
|
|
17
|
+
if config.files_to_run.one?
|
|
18
|
+
config.default_formatter = 'doc'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
config.profile_examples = 10
|
|
22
|
+
|
|
23
|
+
config.order = :random
|
|
24
|
+
|
|
25
|
+
Kernel.srand config.seed
|
|
26
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hash-selector
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Peter Williams
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-05-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.7'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.7'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.2'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.2'
|
|
55
|
+
description:
|
|
56
|
+
email:
|
|
57
|
+
- pezra@barelyenough.org
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- ".gitignore"
|
|
63
|
+
- ".rspec"
|
|
64
|
+
- ".travis.yml"
|
|
65
|
+
- Gemfile
|
|
66
|
+
- LICENSE.txt
|
|
67
|
+
- README.md
|
|
68
|
+
- Rakefile
|
|
69
|
+
- hash_selector.gemspec
|
|
70
|
+
- lib/hash-selector.rb
|
|
71
|
+
- lib/hash_selector.rb
|
|
72
|
+
- lib/hash_selector/version.rb
|
|
73
|
+
- spec/hash_selector_spec.rb
|
|
74
|
+
- spec/spec_helper.rb
|
|
75
|
+
homepage: https://github.com/pezra/hash-selector
|
|
76
|
+
licenses:
|
|
77
|
+
- MIT
|
|
78
|
+
metadata: {}
|
|
79
|
+
post_install_message:
|
|
80
|
+
rdoc_options: []
|
|
81
|
+
require_paths:
|
|
82
|
+
- lib
|
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '0'
|
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - ">="
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '0'
|
|
93
|
+
requirements: []
|
|
94
|
+
rubyforge_project:
|
|
95
|
+
rubygems_version: 2.4.5
|
|
96
|
+
signing_key:
|
|
97
|
+
specification_version: 4
|
|
98
|
+
summary: Easily select values from deep inside hierarchical hashes.
|
|
99
|
+
test_files:
|
|
100
|
+
- spec/hash_selector_spec.rb
|
|
101
|
+
- spec/spec_helper.rb
|
|
102
|
+
has_rdoc:
|