deepstruct 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.
- 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
|