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.
@@ -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: