deepstruct 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.markdown +62 -0
- data/Rakefile +1 -0
- data/deepstruct.gemspec +22 -0
- data/lib/deepstruct/version.rb +3 -0
- data/lib/deepstruct.rb +94 -0
- data/spec/deepstruct_spec.rb +119 -0
- data/spec/delegation_spec.rb +16 -0
- data/spec/spec_helper.rb +1 -0
- metadata +71 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
Deepstruct
|
2
|
+
==========
|
3
|
+
|
4
|
+
An adapter that wraps common ruby data-structures and makes them look like proper Objects.
|
5
|
+
|
6
|
+
struct = DeepStruct.wrap({"a" => { "b" => "bingo!"}})
|
7
|
+
struct.a.b
|
8
|
+
=> "bingo!"
|
9
|
+
|
10
|
+
|
11
|
+
Installation
|
12
|
+
============
|
13
|
+
|
14
|
+
Not yet published as a gem, so you'll have to
|
15
|
+
|
16
|
+
git clone git@github.com:simen/deepstruct.git
|
17
|
+
cd deepstruct
|
18
|
+
rake install
|
19
|
+
|
20
|
+
Or if you use the awesomeness that is bundler, you stick this in your Gemfile:
|
21
|
+
|
22
|
+
gem "deepstruct", :git => git@github.com:simen/deepstruct.git
|
23
|
+
|
24
|
+
Usage
|
25
|
+
=====
|
26
|
+
|
27
|
+
struct = DeepStruct.wrap({:awesome => [1,2,3, {"a" => "hello from the abyss"}]})
|
28
|
+
struct.awesome[3].a
|
29
|
+
=> "hello from the abyss"
|
30
|
+
|
31
|
+
You can also write back through the wrapper with indifferent access
|
32
|
+
|
33
|
+
struct = DeepStruct.wrap({:a => 1, "b" => 2})
|
34
|
+
struct.a = 10
|
35
|
+
struct.b = 20
|
36
|
+
struct
|
37
|
+
=> #<DeepStruct::HashWrapper {:a=>10, "b"=>20}>
|
38
|
+
|
39
|
+
When you want to you can still use hash-style syntax when accessing your DeepStructs. These accessors implement indifferent access too.
|
40
|
+
|
41
|
+
struct = DeepStruct.wrap({:a => {1 => 'One', 2 => 'Two'}})
|
42
|
+
struct.a[1]
|
43
|
+
=> "One"
|
44
|
+
struct["a"][1]
|
45
|
+
=> "One"
|
46
|
+
struct["b"] = "Hello"
|
47
|
+
struct[:b]
|
48
|
+
=> "Hello"
|
49
|
+
struct.b
|
50
|
+
=> "Hello"
|
51
|
+
|
52
|
+
If DeepStruct is getting in your way, you can always get access to the raw content by unwrapping it:
|
53
|
+
|
54
|
+
struct = Deepstruct.wrap({"hello => "world"})
|
55
|
+
struct.unwrap
|
56
|
+
=> {"hello" => "world"}
|
57
|
+
|
58
|
+
DeepStruct is a perfect companion to your json-oriented application!
|
59
|
+
|
60
|
+
struct = DeepStruct.wrap(JSON.parse('{"full_name": "Don Corleone"}'))
|
61
|
+
puts struct.to_json
|
62
|
+
{"full_name":"Don Corleone"}
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/deepstruct.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "deepstruct/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "deepstruct"
|
7
|
+
s.version = Deepstruct::VERSION
|
8
|
+
s.authors = ["Simen Svale Skogsrud"]
|
9
|
+
s.email = ["simen@bengler.no"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{A sibling of ostruct that handles deeply nested structures and basic data type detection}
|
12
|
+
s.description = %q{A sibling of ostruct that handles deeply nested structures and basic data type detection}
|
13
|
+
|
14
|
+
s.rubyforge_project = "deepstruct"
|
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"
|
22
|
+
end
|
data/lib/deepstruct.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module DeepStruct
|
2
|
+
class DeepWrapper
|
3
|
+
def initialize(value)
|
4
|
+
@value = value
|
5
|
+
end
|
6
|
+
|
7
|
+
def unwrap
|
8
|
+
@value
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](index)
|
12
|
+
return DeepStruct.wrap(@value[index])
|
13
|
+
end
|
14
|
+
|
15
|
+
def []=(index, value)
|
16
|
+
@value[index] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
"#<#{self.class} #{@value.inspect}>"
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_json
|
24
|
+
@value.to_json
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class HashWrapper < DeepWrapper
|
29
|
+
def respond_to?(method)
|
30
|
+
@value.respond_to?(method) || self.has_key?(method.to_s.chomp('='))
|
31
|
+
end
|
32
|
+
|
33
|
+
# Given a symbol or a string this yields the variant of the key that
|
34
|
+
# exists in the wrapped hash if any. If none exists (or the key is not
|
35
|
+
# a symbol or string) the input value is passed through unscathed.
|
36
|
+
def indiffrently(key, &block)
|
37
|
+
return yield(key) if @value.has_key?(key)
|
38
|
+
return yield(key.to_s) if key.is_a?(Symbol) && @value.has_key?(key.to_s)
|
39
|
+
return yield(key.to_sym) if key.is_a?(String) && @value.has_key?(key.to_sym)
|
40
|
+
return yield(key)
|
41
|
+
end
|
42
|
+
|
43
|
+
def []=(key, value)
|
44
|
+
indiffrently(key) { |key| @value[key] = value }
|
45
|
+
end
|
46
|
+
|
47
|
+
def [](key)
|
48
|
+
indiffrently(key) { |key| DeepStruct.wrap(@value[key]) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_key?(key)
|
52
|
+
indiffrently(key) { |key| @value.has_key?(key) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def method_missing(method, *args, &block)
|
56
|
+
return @value.send(method, *args, &block) if @value.respond_to?(method)
|
57
|
+
method = method.to_s
|
58
|
+
if method.chomp!('=')
|
59
|
+
raise ArgumentError, "wrong number of arguments (#{arg_count} for 1)", caller(1) if args.length != 1
|
60
|
+
self[method] = args[0]
|
61
|
+
elsif args.length == 0 && self.has_key?(method)
|
62
|
+
self[method]
|
63
|
+
else
|
64
|
+
raise NoMethodError, "undefined method `#{method}' for #{self}", caller(1)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class ArrayWrapper < DeepWrapper
|
70
|
+
include Enumerable
|
71
|
+
|
72
|
+
def each
|
73
|
+
block_given? or return enum_for(__method__)
|
74
|
+
@value.each { |o| yield(DeepStruct.wrap(o)) }
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def size
|
79
|
+
@value.size
|
80
|
+
end
|
81
|
+
alias :length :size
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.wrap(value)
|
85
|
+
case value
|
86
|
+
when Hash
|
87
|
+
return HashWrapper.new(value)
|
88
|
+
when Enumerable
|
89
|
+
return ArrayWrapper.new(value)
|
90
|
+
else
|
91
|
+
return value
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
describe DeepStruct do
|
5
|
+
it "wraps a simple hash" do
|
6
|
+
struct = DeepStruct.wrap({:a => 1, :b => 2})
|
7
|
+
struct.a.should eq(1)
|
8
|
+
struct.b.should eq(2)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "can unwrap a wrapped value" do
|
12
|
+
DeepStruct.wrap([1]).unwrap.should eq [1]
|
13
|
+
end
|
14
|
+
|
15
|
+
it "avoids wrapping common datatypes" do
|
16
|
+
DeepStruct.wrap("hello").should eq ("hello")
|
17
|
+
DeepStruct.wrap(1).should eq(1)
|
18
|
+
DeepStruct.wrap(1.0).should eq(1.0)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "implements indifferenct access" do
|
22
|
+
struct = DeepStruct.wrap({:a => 1, "b" => 2})
|
23
|
+
struct.a.should eq(1)
|
24
|
+
struct.b.should eq(2)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "writes back to the hash with the same key-type as the original hash" do
|
28
|
+
struct = DeepStruct.wrap({:a => 1, "b" => 2})
|
29
|
+
struct.a = 3
|
30
|
+
struct.b = 4
|
31
|
+
struct.a.should eq(3)
|
32
|
+
struct.b.should eq(4)
|
33
|
+
struct.keys.should_not include("a")
|
34
|
+
struct.keys.should_not include(:b)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "wraps nested hashes" do
|
38
|
+
struct = DeepStruct.wrap({:a => {:b => {:c => "hello from deep space"}}})
|
39
|
+
struct.a.b.c.should eq("hello from deep space")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "wraps arrays and support all common operations" do
|
43
|
+
struct = DeepStruct.wrap([1,2,3,4,5])
|
44
|
+
struct[3].should eq(4)
|
45
|
+
struct.sort{|a,b| b <=> a}.should eq([5,4,3,2,1])
|
46
|
+
struct.map(&:to_s).should eq(['1', '2', '3', '4', '5'])
|
47
|
+
end
|
48
|
+
|
49
|
+
it "wraps hashes nested within arrays" do
|
50
|
+
struct = DeepStruct.wrap([1, {:a => "hello"}])
|
51
|
+
struct[1].a.should eq('hello')
|
52
|
+
struct[1].class.should eq(DeepStruct::HashWrapper)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "wraps arrays nested within hashes" do
|
56
|
+
struct = DeepStruct.wrap({:a => [1,2,3]})
|
57
|
+
struct.a[1].should eq(2)
|
58
|
+
struct.a.class.should eq(DeepStruct::ArrayWrapper)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "supports being converted to json" do
|
62
|
+
struct = DeepStruct.wrap({:a => [1,2,3]})
|
63
|
+
struct.to_json.should eq('{"a":[1,2,3]}')
|
64
|
+
end
|
65
|
+
|
66
|
+
it "raises NoMethodError when reading missing keys" do
|
67
|
+
->{DeepStruct.wrap({}).not_there}.should raise_error(NoMethodError)
|
68
|
+
end
|
69
|
+
|
70
|
+
context "HashWrapper, hash-like methods" do
|
71
|
+
it "can be used as a common ruby hash with indifferent access" do
|
72
|
+
struct = DeepStruct.wrap({:a => "hello", 'b' => "world"})
|
73
|
+
struct[:a].should eq "hello"
|
74
|
+
struct['a'].should eq "hello"
|
75
|
+
struct[:b].should eq "world"
|
76
|
+
struct['b'].should eq "world"
|
77
|
+
struct['a'] = "Mmkay"
|
78
|
+
struct[:a].should eq "Mmkay"
|
79
|
+
struct.has_key?("a").should be_true
|
80
|
+
end
|
81
|
+
|
82
|
+
it "can access non string/symbol elements via hashistic syntax" do
|
83
|
+
struct = DeepStruct.wrap({1 => "One"})
|
84
|
+
struct[1].should eq 'One'
|
85
|
+
struct[2] = "Two"
|
86
|
+
struct[2].should eq 'Two'
|
87
|
+
struct['2'].should eq nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "HashWrapper, #respond_to?" do
|
92
|
+
it "responds to hash methods" do
|
93
|
+
struct = DeepStruct.wrap({:a => true})
|
94
|
+
struct.size.should eq(1)
|
95
|
+
struct.respond_to?(:size).should be_true
|
96
|
+
end
|
97
|
+
|
98
|
+
it "responds to keys that are present as symbols" do
|
99
|
+
struct = DeepStruct.wrap({:a => nil})
|
100
|
+
struct.respond_to?(:a).should be_true
|
101
|
+
end
|
102
|
+
|
103
|
+
it "responds to keys that are present as strings" do
|
104
|
+
struct = DeepStruct.wrap({'a' => nil})
|
105
|
+
struct.respond_to?(:a).should be_true
|
106
|
+
end
|
107
|
+
|
108
|
+
it "doesn't respond to missing keys" do
|
109
|
+
struct = DeepStruct.wrap({:a => true})
|
110
|
+
struct.respond_to?(:b).should be_false
|
111
|
+
end
|
112
|
+
|
113
|
+
it "responds to keys that can be assigned to" do
|
114
|
+
struct = DeepStruct.wrap({:a => true})
|
115
|
+
struct.respond_to?(:a=).should be_true
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
class PassingTheBuck < SimpleDelegator
|
5
|
+
end
|
6
|
+
|
7
|
+
describe DeepStruct do
|
8
|
+
|
9
|
+
specify "HashWrapper can be delegated to" do
|
10
|
+
buck = DeepStruct.wrap(:for_sure => 'hell, yeah!')
|
11
|
+
|
12
|
+
thing = PassingTheBuck.new(buck)
|
13
|
+
thing.for_sure.should eq('hell, yeah!')
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/../lib/deepstruct.rb'
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deepstruct
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Simen Svale Skogsrud
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-29 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70116432443300 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70116432443300
|
25
|
+
description: A sibling of ostruct that handles deeply nested structures and basic
|
26
|
+
data type detection
|
27
|
+
email:
|
28
|
+
- simen@bengler.no
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- .gitignore
|
34
|
+
- Gemfile
|
35
|
+
- README.markdown
|
36
|
+
- Rakefile
|
37
|
+
- deepstruct.gemspec
|
38
|
+
- lib/deepstruct.rb
|
39
|
+
- lib/deepstruct/version.rb
|
40
|
+
- spec/deepstruct_spec.rb
|
41
|
+
- spec/delegation_spec.rb
|
42
|
+
- spec/spec_helper.rb
|
43
|
+
homepage: ''
|
44
|
+
licenses: []
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubyforge_project: deepstruct
|
63
|
+
rubygems_version: 1.8.10
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: A sibling of ostruct that handles deeply nested structures and basic data
|
67
|
+
type detection
|
68
|
+
test_files:
|
69
|
+
- spec/deepstruct_spec.rb
|
70
|
+
- spec/delegation_spec.rb
|
71
|
+
- spec/spec_helper.rb
|