dive 0.0.1

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,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
@@ -0,0 +1,2 @@
1
+ ## 0.0.1 (17/1/12)
2
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,94 @@
1
+ # Dive: for deep hash access
2
+
3
+ Dive is a gem for accessing values within nested Hashes.
4
+
5
+ Why? Well, it all started when I was mapping human readable names taken from a Cucumber table to fields in a JSON response:
6
+
7
+ ```cucumber
8
+ Given I have some sausages with
9
+ | Name | Contents |
10
+ | Pork + Fennel | pork mince, fennel, intestine |
11
+ | Soylent Green | people |
12
+ ```
13
+
14
+ ```ruby
15
+ Given /^I have some sausages with$/ |table|
16
+ fields = {
17
+ 'Name' => :name,
18
+ 'Contents' => :ingredients
19
+ }
20
+ table.hashes.each do |row|
21
+ sausage = {}
22
+ row.each_pair do |key, value|
23
+ sausage[fields[key]] = value
24
+ end
25
+ @response[:sausages] << sausage
26
+ end
27
+ end
28
+ ```
29
+
30
+ ```ruby
31
+ @response
32
+ {
33
+ :sausages => [
34
+ {
35
+ :name => 'Pork + Fennel'
36
+ :ingredients => 'pork mince, fennel, intestine'
37
+ },
38
+ {
39
+ :name => 'Soylent Green',
40
+ :ingredients => 'people'
41
+ }
42
+ ]
43
+ }
44
+ ```
45
+
46
+ All fine and dandy until I had to map to values that were nested under the first level of the response:
47
+
48
+ ```ruby
49
+ @response
50
+ {
51
+ :sausages => [
52
+ {
53
+ :name => 'Pork + Fennel'
54
+ :ingredients => {
55
+ :casing => 'intestine',
56
+ :filling => {
57
+ :meat => 'pork mince',
58
+ :spices => 'fennel'
59
+ }
60
+ }
61
+ }
62
+ ]
63
+ }
64
+ ```
65
+
66
+ I wanted to be able to do something like this:
67
+
68
+ ```ruby
69
+ fields = {
70
+ 'Name' => :sausage_name,
71
+ 'Spices' => ':ingredients[:filling[:spices]]'
72
+ }
73
+ ```
74
+
75
+ So I did.
76
+
77
+ Check out the specs for how it behaves.
78
+
79
+ ### Installation and usage
80
+
81
+ ```ruby
82
+ gem install dive
83
+
84
+ require 'dive'
85
+ foods = { :sausages => {:pork_and_fennel => 'DELICIOUS'}}
86
+ foods[':sausages[:pork_and_fennel]']
87
+ ```
88
+
89
+ Or if you become squeamish at the idea of overriding Hash's [] method:
90
+
91
+ ```ruby
92
+ require 'dive/noninvasive'
93
+ foods.dive ':sausages[:pork_and_fennel]'
94
+ ```
@@ -0,0 +1,20 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ task :default => ['spec:invasive', 'spec:noninvasive']
5
+
6
+ namespace :spec do
7
+ rspec_opts = '-f d'
8
+
9
+ desc "Run all examples with invasive Hash extensions"
10
+ RSpec::Core::RakeTask.new('invasive') do |t|
11
+ t.pattern = './spec/invasive/**/*.rb'
12
+ t.rspec_opts = rspec_opts
13
+ end
14
+
15
+ desc "Run all examples with non-invasive Hash extensions"
16
+ RSpec::Core::RakeTask.new('noninvasive') do |t|
17
+ t.pattern = './spec/noninvasive/**/*.rb'
18
+ t.rspec_opts = rspec_opts
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "dive/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "dive"
7
+ s.version = Dive::VERSION
8
+ s.authors = ["Brent Snook"]
9
+ s.email = ["brent@fuglylogic.com"]
10
+ s.homepage = "http://github.com/brentsnook/dive"
11
+ s.summary = %q{A gem for accessing values within nested Hashes}
12
+ s.description = %q{For example: {:sausages => {:pork_and_fennel => 'DELICIOUS'}}[':sausages[:pork_and_fennel]']}
13
+
14
+ s.rubyforge_project = "dive"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec", "~> 2.7"
22
+ end
@@ -0,0 +1,2 @@
1
+ require 'dive/noninvasive'
2
+ Hash.send :include, Dive::Extensions
@@ -0,0 +1,73 @@
1
+ require 'dive/version'
2
+
3
+ module Dive
4
+
5
+ def self.included clazz
6
+ clazz.send :include, Read
7
+ clazz.send :include, Write
8
+ end
9
+
10
+ def symbolise key
11
+ is_symbol = key.respond_to?(:to_sym) && key[0] == ':'
12
+ is_symbol ? key[1..-1].to_sym : key
13
+ end
14
+
15
+ module Read
16
+
17
+ def self.included clazz
18
+ clazz.send :alias_method, :old_read, :[]
19
+ end
20
+
21
+ def dive location
22
+ has_key?(location) ? old_read(location) : dive_read(location)
23
+ end
24
+
25
+ private
26
+
27
+ def dive_read location
28
+ has_key?(symbolise(location)) ? old_read(symbolise(location)) : attempt_dive(location)
29
+ end
30
+
31
+ def default_value key
32
+ default_proc ? default_proc.call(self, key) : default
33
+ end
34
+
35
+ def attempt_dive location
36
+ matches = location.to_s.match /([^\[\]]*)\[(.*)\]/ #(key)[(remainder)]
37
+ matches ? dive_to_next_level(matches[1].strip, matches[2].strip) : default_value(location)
38
+ end
39
+
40
+ def dive_to_next_level key, remainder
41
+ value = old_read(symbolise(key))
42
+ value.respond_to?(:dive) ? value.dive(remainder) : default_value(remainder)
43
+ end
44
+ end
45
+
46
+ module Write
47
+
48
+ def self.included clazz
49
+ clazz.send :alias_method, :old_store, :[]=
50
+ end
51
+
52
+ def dive_store location, value
53
+ matches = location.to_s.match /([^\[\]]*)\[(.*)\]/
54
+ matches ? store_at_next_level(matches[1], matches[2], value): old_store(symbolise(location), value)
55
+ end
56
+
57
+ private
58
+
59
+ def store_at_next_level(key, remainder, value)
60
+ storer = has_key?(symbolise(key)) ? old_read(symbolise(key)) : {}
61
+ old_store symbolise(key), storer
62
+ storer.dive_store remainder, value
63
+ end
64
+
65
+ end
66
+
67
+ module Extensions
68
+ def self.included clazz
69
+ clazz.send :alias_method, :[], :dive
70
+ clazz.send :alias_method, :[]=, :dive_store
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,2 @@
1
+ require 'dive/dive'
2
+ Hash.send :include, Dive
@@ -0,0 +1,3 @@
1
+ module Dive
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ require File.dirname(__FILE__) + '/../../lib/dive'
2
+
3
+ describe Hash, 'when extended' do
4
+
5
+ it 'performs a dive on []' do
6
+ hash = {'first' => {'second' => {'third' => 'deep value'}}}
7
+ hash['first[second[third]]'].should == 'deep value'
8
+ end
9
+
10
+ it 'performs a dive store on []=' do
11
+ hash = {'first' => {}}
12
+ hash['first[second]'] = 'deep value'
13
+ hash['first']['second'].should == 'deep value'
14
+ end
15
+
16
+ it "allows keys that aren't strings or symbols" do
17
+ {['array'] => 'value'}[['array']].should == 'value'
18
+ end
19
+
20
+ it 'allows a nil key' do
21
+ hash = {:x => :y}
22
+ hash[nil].should == hash.default
23
+ end
24
+ end
@@ -0,0 +1,79 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Dive, 'when reading' do
4
+
5
+ describe 'with a location that matches a normal key' do
6
+ it 'retrieves the normal value' do
7
+ hash = {'first[second]' => 'normal value', 'first' => {'second' => 'deep value'}}
8
+ hash.dive('first[second]').should == 'normal value'
9
+ end
10
+ end
11
+
12
+ describe 'with a location that matches a deep key' do
13
+ it 'retrieves the deep value' do
14
+ hash = {'first' => {'second' => {'third' => 'deep value'}}}
15
+ hash.dive('first[second[third]]').should == 'deep value'
16
+ end
17
+ end
18
+
19
+ it 'retrieves values that have a symbol as key' do
20
+ hash = {:first => {:second => 'deep value'}}
21
+ hash.dive(':first[:second]').should == 'deep value'
22
+ end
23
+
24
+ it 'ignores space around location parts' do
25
+ hash = {'first' => {'second' => {'third' => 'deep value'}}}
26
+ hash.dive('first [ second [ third ] ] ').should == 'deep value'
27
+ end
28
+
29
+ describe "when a value at any part of the location doesn't exist" do
30
+ it 'retrieves the default value of the hash where no key was found' do
31
+ hash = {'first' => {'second' => Hash.new('default')}}
32
+ hash.dive('first[second[idontexist]]').should == 'default'
33
+ end
34
+ end
35
+
36
+ describe 'when a hash at any part of the location can not dive' do
37
+
38
+ before do
39
+ @hash = {'first' => {'second' => 'cantdive'}}
40
+ end
41
+
42
+ describe 'when that hash was created with a default value' do
43
+ it 'retrieves the default value' do
44
+ @hash['first'].default = 'default'
45
+ @hash.dive('first[second[cantdive[fourth]]]').should == 'default'
46
+ end
47
+ end
48
+
49
+ describe 'when that hash was created with a default proc' do
50
+
51
+ before do
52
+ @hash['first'].default_proc = proc { |hash, key| "default proc: #{key}"}
53
+ @result = @hash.dive('first[second[cantdive[fourth]]]')
54
+ end
55
+
56
+ it 'retrieves the result of evaluating the default proc' do
57
+ @result.should include('default proc')
58
+ end
59
+
60
+ it 'uses the remainder of the location as the proc key' do
61
+ @result.should include('cantdive[fourth]')
62
+ end
63
+ end
64
+ end
65
+
66
+ describe 'when no normal key or dive location exists' do
67
+ it 'retrieves the default value' do
68
+ hash = Hash.new 'default'
69
+ hash['first'] = 'value'
70
+ hash.dive('idontexist').should == 'default'
71
+ end
72
+ end
73
+
74
+ it 'still attempts to dive when the default value is not nil' do
75
+ hash = Hash.new 'default'
76
+ hash['first'] = {'second' => 'deep value'}
77
+ hash.dive('first[second]').should == 'deep value'
78
+ end
79
+ end
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/../../lib/dive/noninvasive'
@@ -0,0 +1,44 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Dive, 'when writing' do
4
+
5
+ describe 'with a deep location' do
6
+ it 'sets a deep value' do
7
+ nested_hash = {'third' => 'deep value'}
8
+ hash = {'first' => {'second' => nested_hash}}
9
+
10
+ hash.dive_store('first[second[third]]', 'deep value')
11
+
12
+ nested_hash['third'].should == 'deep value'
13
+ end
14
+ end
15
+
16
+ describe 'with a normal location' do
17
+ it 'sets a normal value' do
18
+ hash = {}
19
+ hash.dive_store 'normal', 'normal value'
20
+ hash['normal'].should == 'normal value'
21
+ end
22
+ end
23
+
24
+ describe "when parts of the location don't exist" do
25
+ it 'creates each part as a new Hash' do
26
+ hash = {}
27
+ hash.dive_store 'first[second[third]]', 'value'
28
+ hash['first']['second']['third'].should == 'value'
29
+ end
30
+ end
31
+
32
+ describe 'when the value at the end of a location is not divable' do
33
+ it 'raises a NoMethodError' do
34
+ hash = {'first' => 'not divable'}
35
+ lambda { hash.dive_store 'first[second]', 'value' }.should raise_exception(NoMethodError)
36
+ end
37
+ end
38
+
39
+ it 'recognises symbol keys' do
40
+ hash = {:first => {}}
41
+ hash.dive_store ':first[:second]', 'value'
42
+ hash[:first][:second].should == 'value'
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+
2
+ - exploratory testing
3
+ - test with 1.8.7
4
+ - update change log
5
+ - publish to gem repo
6
+
7
+ -- LATER --
8
+
9
+ - add can_dive? method - checks if there is a key
10
+ - normal override overrides has_key? also
11
+ - add support for arrays
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dive
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brent Snook
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-17 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &2152401600 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.7'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2152401600
25
+ description: ! 'For example: {:sausages => {:pork_and_fennel => ''DELICIOUS''}}['':sausages[:pork_and_fennel]'']'
26
+ email:
27
+ - brent@fuglylogic.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - .gitignore
33
+ - CHANGELOG.md
34
+ - Gemfile
35
+ - README.md
36
+ - Rakefile
37
+ - dive.gemspec
38
+ - lib/dive.rb
39
+ - lib/dive/dive.rb
40
+ - lib/dive/noninvasive.rb
41
+ - lib/dive/version.rb
42
+ - spec/invasive/hash_spec.rb
43
+ - spec/noninvasive/read_spec.rb
44
+ - spec/noninvasive/spec_helper.rb
45
+ - spec/noninvasive/write_spec.rb
46
+ - todo.txt
47
+ homepage: http://github.com/brentsnook/dive
48
+ licenses: []
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project: dive
67
+ rubygems_version: 1.8.10
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: A gem for accessing values within nested Hashes
71
+ test_files:
72
+ - spec/invasive/hash_spec.rb
73
+ - spec/noninvasive/read_spec.rb
74
+ - spec/noninvasive/spec_helper.rb
75
+ - spec/noninvasive/write_spec.rb