hash-selector 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ *~
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,8 @@
1
+ sudo: false
2
+ cache: bundler
3
+ language: ruby
4
+ rvm:
5
+ - "2.0.0"
6
+ - "2.1.1"
7
+ - "2.2.2"
8
+ - jruby-19mode # JRuby in 1.9 mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hash_selector.gemspec
4
+ gemspec
@@ -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.
@@ -0,0 +1,69 @@
1
+ [![Build Status](https://travis-ci.org/pezra/hash-selector.svg)](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
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ task default: :spec
7
+ rescue LoadError
8
+ end
9
+
@@ -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,3 @@
1
+ class HashSelector
2
+ VERSION = "1.0.0"
3
+ 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
@@ -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: