angry_hash 0.2.2 → 0.3.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.
- data/Changelog.md +2 -1
- data/VERSION +1 -1
- data/angry_hash.gemspec +9 -4
- data/examples/{extension_tracking.rb → extension_tracking.eg.rb} +57 -1
- data/lib/angry_hash.rb +25 -111
- data/lib/angry_hash/conversion/by_reference.rb +30 -0
- data/lib/angry_hash/conversion/duplicating.rb +56 -0
- data/lib/angry_hash/extension.rb +42 -14
- data/lib/angry_hash/extension_aware.rb +54 -0
- data/lib/angry_hash/initialiser.rb +11 -0
- data/lib/angry_hash/merging.rb +56 -0
- metadata +10 -5
data/Changelog.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
## 0.2.2
|
|
2
2
|
|
|
3
3
|
Changes:
|
|
4
|
+
|
|
4
5
|
- `#to_normal_hash` can now return a hash with symbol keys. The default is still to use string keys as is default within `AngryHash`.
|
|
5
6
|
|
|
6
7
|
## 0.2.1
|
|
7
8
|
|
|
8
9
|
Changes:
|
|
9
10
|
|
|
10
|
-
- In `#merge` & friends `other` hash is now converted to an AngryHash before
|
|
11
|
+
- In `#merge` & friends `other` hash is now converted to an AngryHash before merging.
|
|
11
12
|
This ensures subhashes in `other` but not `this` end up as AngryHashes in `this` after merging.
|
|
12
13
|
|
|
13
14
|
## 0.2.0
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.3.0
|
data/angry_hash.gemspec
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |s|
|
|
7
7
|
s.name = %q{angry_hash}
|
|
8
|
-
s.version = "0.
|
|
8
|
+
s.version = "0.3.0"
|
|
9
9
|
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
11
|
s.authors = ["Lachie Cox"]
|
|
12
|
-
s.date = %q{2010-
|
|
12
|
+
s.date = %q{2010-10-20}
|
|
13
13
|
s.description = %q{A stabler mash with different emphases. Used in plus2 projects AngryMob and Igor.}
|
|
14
14
|
s.email = %q{lachie@plus2.com.au}
|
|
15
15
|
s.extra_rdoc_files = [
|
|
@@ -25,9 +25,14 @@ Gem::Specification.new do |s|
|
|
|
25
25
|
"VERSION",
|
|
26
26
|
"angry_hash.gemspec",
|
|
27
27
|
"lib/angry_hash.rb",
|
|
28
|
+
"lib/angry_hash/conversion/by_reference.rb",
|
|
29
|
+
"lib/angry_hash/conversion/duplicating.rb",
|
|
28
30
|
"lib/angry_hash/dsl.rb",
|
|
29
31
|
"lib/angry_hash/extension.rb",
|
|
30
|
-
"lib/angry_hash/
|
|
32
|
+
"lib/angry_hash/extension_aware.rb",
|
|
33
|
+
"lib/angry_hash/initialiser.rb",
|
|
34
|
+
"lib/angry_hash/merge_string.rb",
|
|
35
|
+
"lib/angry_hash/merging.rb"
|
|
31
36
|
]
|
|
32
37
|
s.homepage = %q{http://github.com/plus2/angry_hash}
|
|
33
38
|
s.rdoc_options = ["--charset=UTF-8"]
|
|
@@ -40,7 +45,7 @@ Gem::Specification.new do |s|
|
|
|
40
45
|
"examples/dsl.eg.rb",
|
|
41
46
|
"examples/dup_eg.rb",
|
|
42
47
|
"examples/eg_helper.rb",
|
|
43
|
-
"examples/extension_tracking.rb",
|
|
48
|
+
"examples/extension_tracking.eg.rb",
|
|
44
49
|
"examples/merge_eg.rb",
|
|
45
50
|
"examples/normal_hash.eg.rb"
|
|
46
51
|
]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require 'eg_helper'
|
|
2
|
-
require 'angry_hash
|
|
2
|
+
require 'angry_hash'
|
|
3
3
|
|
|
4
4
|
module Fixtures
|
|
5
5
|
module CreditCard
|
|
@@ -104,3 +104,59 @@ eg 'extensions persist with dup_with_extension' do
|
|
|
104
104
|
Assert( new_user.credit_card.valid? )
|
|
105
105
|
Assert( defined?( new_user.credit_card.valid? ) == 'method' )
|
|
106
106
|
end
|
|
107
|
+
|
|
108
|
+
def same_obj(a,b)
|
|
109
|
+
a.__id__ == b.__id__
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
eg 'extensions persist after deep_merge' do
|
|
113
|
+
user = AngryHash[ :name => 'Bob', :credit_card => {:number => 123}, :phone_numbers => {:home => {:number => '+41 0404555111'}} ]
|
|
114
|
+
user.extend Fixtures::User
|
|
115
|
+
|
|
116
|
+
Assert( user.phone_numbers.home.country_code == 41 )
|
|
117
|
+
|
|
118
|
+
new_user = user.deep_merge(:credit_card => {:number => 456})
|
|
119
|
+
|
|
120
|
+
Assert( new_user.phone_numbers.home.country_code == 41 )
|
|
121
|
+
|
|
122
|
+
Assert( ! same_obj( user.__id__, new_user.__id__ ) )
|
|
123
|
+
Assert( ! same_obj( user.phone_numbers.home.__id__, new_user.phone_numbers.home.__id__ ) )
|
|
124
|
+
|
|
125
|
+
Assert( new_user.credit_card.number == 456 )
|
|
126
|
+
Assert( new_user.phone_numbers.home.country_code == 41 )
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
eg 'extensions persist after reverse_deep_merge' do
|
|
130
|
+
user = AngryHash[ :name => 'Bob', :credit_card => {:number => 123}, :phone_numbers => {:home => {:number => '+41 0404555111'}} ]
|
|
131
|
+
user.extend Fixtures::User
|
|
132
|
+
|
|
133
|
+
Assert( user.phone_numbers.home.country_code == 41 )
|
|
134
|
+
|
|
135
|
+
# Do the reverse_deep_merge
|
|
136
|
+
new_user = user.reverse_deep_merge(:credit_card => {:number => 456})
|
|
137
|
+
|
|
138
|
+
Assert( new_user.phone_numbers.home.country_code == 41 )
|
|
139
|
+
|
|
140
|
+
Assert( ! same_obj( user.__id__, new_user.__id__ ) )
|
|
141
|
+
Assert( ! same_obj( user.phone_numbers.home.__id__, new_user.phone_numbers.home.__id__ ) )
|
|
142
|
+
|
|
143
|
+
Assert( new_user.credit_card.number == 123 )
|
|
144
|
+
Assert( new_user.phone_numbers.home.country_code == 41 )
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
eg 'extensions persist after AngryHash[]' do
|
|
148
|
+
user = AngryHash[ :name => 'Bob', :credit_card => {:number => 123}, :phone_numbers => {:home => {:number => '+41 0404555111'}} ]
|
|
149
|
+
user.extend Fixtures::User
|
|
150
|
+
|
|
151
|
+
user.__angry_hash_extension.tapp(:ahe)
|
|
152
|
+
|
|
153
|
+
num = user.phone_numbers.home
|
|
154
|
+
num.__angry_hash_extension.tapp(:ahe)
|
|
155
|
+
|
|
156
|
+
Assert(num.country_code == 41)
|
|
157
|
+
|
|
158
|
+
args = AngryHash[ :nothing_special => {:phone => user.phone_numbers.home} ]
|
|
159
|
+
|
|
160
|
+
Show(args)
|
|
161
|
+
Assert(args.nothing_special.phone.country_code == 41)
|
|
162
|
+
end
|
data/lib/angry_hash.rb
CHANGED
|
@@ -1,80 +1,49 @@
|
|
|
1
|
-
|
|
2
1
|
class AngryHash < Hash
|
|
2
|
+
require 'angry_hash/conversion/by_reference'
|
|
3
|
+
require 'angry_hash/conversion/duplicating'
|
|
4
|
+
require 'angry_hash/extension'
|
|
5
|
+
require 'angry_hash/extension_aware'
|
|
6
|
+
require 'angry_hash/merging'
|
|
7
|
+
require 'angry_hash/initialiser'
|
|
8
|
+
|
|
3
9
|
# config
|
|
4
10
|
require 'angry_hash/dsl'
|
|
5
11
|
include AngryHash::DSL
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
super(__convert(other))
|
|
10
|
-
else
|
|
11
|
-
new
|
|
12
|
-
end
|
|
13
|
-
end
|
|
13
|
+
extend AngryHash::Initialiser
|
|
14
|
+
|
|
14
15
|
|
|
15
16
|
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
|
16
17
|
alias_method :regular_reader, :[] unless method_defined?(:regular_reader)
|
|
17
18
|
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
|
18
19
|
|
|
20
|
+
# store value under key, without dup-ing.
|
|
19
21
|
def []=(key, value)
|
|
20
|
-
regular_writer(__convert_key(key),
|
|
22
|
+
regular_writer(__convert_key(key), __convert_value_without_dup(value))
|
|
21
23
|
end
|
|
22
24
|
|
|
25
|
+
# fetch value stored under key.
|
|
23
26
|
def [](key)
|
|
24
27
|
regular_reader(__convert_key(key))
|
|
25
28
|
end
|
|
26
29
|
|
|
30
|
+
# override id to fetch value stored under 'id'.
|
|
27
31
|
def id
|
|
28
32
|
regular_reader('id')
|
|
29
33
|
end
|
|
30
34
|
|
|
35
|
+
# store value under key by duping the value.
|
|
31
36
|
def dup_and_store(key,value)
|
|
32
|
-
regular_writer(__convert_key(key),
|
|
37
|
+
regular_writer(__convert_key(key), __convert_value(value))
|
|
33
38
|
end
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
def merge(hash)
|
|
37
|
-
regular_merge(self.class.__convert_without_dup(hash))
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def merge!(other_hash)
|
|
41
|
-
other_hash.each_pair { |key, value| dup_and_store(key,value) }
|
|
42
|
-
self
|
|
43
|
-
end
|
|
44
|
-
alias_method :update, :merge!
|
|
45
|
-
|
|
40
|
+
# Duplicate the AngryHash
|
|
46
41
|
def dup
|
|
47
42
|
self.class[ self ]
|
|
48
43
|
end
|
|
49
44
|
|
|
50
|
-
def deep_merge(other_hash)
|
|
51
|
-
other_hash = AngryHash[other_hash]
|
|
52
|
-
|
|
53
|
-
self.regular_merge( other_hash ) do |key, oldval, newval|
|
|
54
|
-
oldval = AngryHash.__convert_value(oldval)
|
|
55
|
-
newval = AngryHash.__convert_value(newval)
|
|
56
|
-
|
|
57
|
-
AngryHash === oldval && AngryHash === newval ? oldval.deep_merge(newval) : newval
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def deep_merge!(other_hash)
|
|
62
|
-
replace(deep_merge(other_hash))
|
|
63
|
-
self
|
|
64
|
-
end
|
|
65
|
-
alias_method :deep_update, :deep_merge!
|
|
66
|
-
|
|
67
|
-
def reverse_deep_merge(other_hash)
|
|
68
|
-
self.class.__convert_value(other_hash).deep_merge(self)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def reverse_deep_merge!(other_hash)
|
|
72
|
-
replace(reverse_deep_merge(other_hash))
|
|
73
|
-
self
|
|
74
|
-
end
|
|
75
|
-
alias_method :reverse_deep_update, :reverse_deep_merge!
|
|
76
|
-
|
|
77
45
|
|
|
46
|
+
# override normal Hash methods
|
|
78
47
|
|
|
79
48
|
def key?(key)
|
|
80
49
|
super(__convert_key(key))
|
|
@@ -99,6 +68,8 @@ class AngryHash < Hash
|
|
|
99
68
|
self
|
|
100
69
|
end
|
|
101
70
|
|
|
71
|
+
## Convert back to a plain hash
|
|
72
|
+
|
|
102
73
|
def to_normal_hash(keys=nil)
|
|
103
74
|
__to_hash(self,keys)
|
|
104
75
|
end
|
|
@@ -132,6 +103,7 @@ class AngryHash < Hash
|
|
|
132
103
|
end
|
|
133
104
|
|
|
134
105
|
|
|
106
|
+
# Support dot notation access
|
|
135
107
|
def method_missing(method,*args,&blk)
|
|
136
108
|
method_s = method.to_s
|
|
137
109
|
|
|
@@ -157,6 +129,7 @@ class AngryHash < Hash
|
|
|
157
129
|
end
|
|
158
130
|
end
|
|
159
131
|
|
|
132
|
+
# please be Strings
|
|
160
133
|
def __convert_key(key)
|
|
161
134
|
Symbol === key ? key.to_s : key
|
|
162
135
|
end
|
|
@@ -164,67 +137,8 @@ class AngryHash < Hash
|
|
|
164
137
|
Symbol === key ? key.to_s : key
|
|
165
138
|
end
|
|
166
139
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
newhash[__convert_key(k)] = __convert_value_without_dup(v)
|
|
172
|
-
newhash
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def self.__convert_value_without_dup(v)
|
|
177
|
-
v = v.to_hash if v.respond_to?(:to_hash)
|
|
178
|
-
|
|
179
|
-
case v
|
|
180
|
-
when AngryHash
|
|
181
|
-
v
|
|
182
|
-
when Hash
|
|
183
|
-
__convert_without_dup(v)
|
|
184
|
-
when Array
|
|
185
|
-
v.map {|vv| __convert_value_without_dup(vv)}
|
|
186
|
-
else
|
|
187
|
-
v
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
# duplicating convert
|
|
193
|
-
def self.__convert(hash,cycle_watch=[])
|
|
194
|
-
new_hash = hash.inject(AngryHash.new) do |hash,(k,v)|
|
|
195
|
-
hash.regular_writer( __convert_key(k), __convert_value(v,cycle_watch) )
|
|
196
|
-
hash
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
new_hash
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def self.__convert_value(v,cycle_watch=[])
|
|
203
|
-
id = v.__id__
|
|
204
|
-
|
|
205
|
-
return if cycle_watch.include? id
|
|
206
|
-
|
|
207
|
-
begin
|
|
208
|
-
cycle_watch << id
|
|
209
|
-
|
|
210
|
-
original_v = v
|
|
211
|
-
v = v.to_hash if v.respond_to?(:to_hash)
|
|
212
|
-
|
|
213
|
-
case v
|
|
214
|
-
when Hash
|
|
215
|
-
__convert(v,cycle_watch)
|
|
216
|
-
when Array
|
|
217
|
-
v.map {|vv| __convert_value(vv,cycle_watch)}
|
|
218
|
-
when Fixnum,Symbol,NilClass,TrueClass,FalseClass,Float,Bignum
|
|
219
|
-
v
|
|
220
|
-
else
|
|
221
|
-
v.dup
|
|
222
|
-
end
|
|
223
|
-
ensure
|
|
224
|
-
cycle_watch.pop
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
|
|
140
|
+
include Merging
|
|
141
|
+
include Conversion::Duplicating
|
|
142
|
+
include Conversion::ByReference
|
|
143
|
+
include ExtensionAware
|
|
228
144
|
end
|
|
229
|
-
|
|
230
|
-
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class AngryHash
|
|
2
|
+
module Conversion
|
|
3
|
+
module ByReference
|
|
4
|
+
|
|
5
|
+
# non-duplicating convert
|
|
6
|
+
def __convert_without_dup(hash)
|
|
7
|
+
hash.inject(AngryHash.new) do |newhash,(k,v)|
|
|
8
|
+
newhash[__convert_key(k)] = __convert_value_without_dup(v)
|
|
9
|
+
newhash
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def __convert_value_without_dup(v)
|
|
14
|
+
v = v.to_hash if v.respond_to?(:to_hash)
|
|
15
|
+
|
|
16
|
+
case v
|
|
17
|
+
when AngryHash
|
|
18
|
+
v
|
|
19
|
+
when Hash
|
|
20
|
+
__convert_without_dup(v)
|
|
21
|
+
when Array
|
|
22
|
+
v.map {|vv| __convert_value_without_dup(vv)}
|
|
23
|
+
else
|
|
24
|
+
v
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
class AngryHash
|
|
2
|
+
module Conversion
|
|
3
|
+
module Duplicating
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.extend ClassMethods
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
# duplicating convert
|
|
10
|
+
def __convert(hash,cycle_watch=[])
|
|
11
|
+
new_hash = hash.inject(AngryHash.new) do |hash,(k,v)|
|
|
12
|
+
hash.regular_writer( __convert_key(k), __convert_value(v,cycle_watch) )
|
|
13
|
+
hash
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
new_hash
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def __convert_value(v,cycle_watch=[])
|
|
20
|
+
id = v.__id__
|
|
21
|
+
|
|
22
|
+
return if cycle_watch.include? id
|
|
23
|
+
|
|
24
|
+
begin
|
|
25
|
+
cycle_watch << id
|
|
26
|
+
|
|
27
|
+
original_v = v
|
|
28
|
+
v = v.to_hash if v.respond_to?(:to_hash)
|
|
29
|
+
|
|
30
|
+
case v
|
|
31
|
+
when Hash
|
|
32
|
+
__convert(v,cycle_watch)
|
|
33
|
+
when Array
|
|
34
|
+
v.map {|vv| __convert_value(vv,cycle_watch)}
|
|
35
|
+
when Fixnum,Symbol,NilClass,TrueClass,FalseClass,Float,Bignum,BigDecimal
|
|
36
|
+
v
|
|
37
|
+
else
|
|
38
|
+
v.dup
|
|
39
|
+
end
|
|
40
|
+
ensure
|
|
41
|
+
cycle_watch.pop
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def __convert(hash,cycle_watch=[])
|
|
47
|
+
self.class.__convert(hash,cycle_watch)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def __convert_value(v,cycle_watch=[])
|
|
51
|
+
self.class.__convert_value(v,cycle_watch)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/angry_hash/extension.rb
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
require 'angry_hash'
|
|
2
|
-
|
|
3
1
|
class AngryHash
|
|
4
|
-
def self.new_extended(mod,seed={})
|
|
5
|
-
self[ seed ].tap {|hash| hash.extend(mod) }
|
|
6
|
-
end
|
|
7
|
-
|
|
8
2
|
module Extension
|
|
9
3
|
class << self
|
|
10
4
|
def included(base)
|
|
@@ -49,7 +43,13 @@ class AngryHash
|
|
|
49
43
|
mixin_registry[target_class][field.to_s] = [:hash, mod, options]
|
|
50
44
|
end
|
|
51
45
|
|
|
52
|
-
|
|
46
|
+
# Register a block extension - applied to objects of the defining module.
|
|
47
|
+
# This is in contrast to the other mixin types which are applied to subordinate objects.
|
|
48
|
+
def register_mixin_block(target_class, options)
|
|
49
|
+
mixin_registry[target_class]['*'] = [:block, options]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def extend_hash(hash, mod, parent_hash, block)
|
|
53
53
|
if !parent_hash.nil? && hash.nil?
|
|
54
54
|
hash = AngryHash.new
|
|
55
55
|
end
|
|
@@ -57,6 +57,11 @@ class AngryHash
|
|
|
57
57
|
hash.extend mod
|
|
58
58
|
|
|
59
59
|
hash.__parent_hash = parent_hash if hash.respond_to?(:__parent_hash=)
|
|
60
|
+
|
|
61
|
+
if block
|
|
62
|
+
hash.instance_eval(&block)
|
|
63
|
+
end
|
|
64
|
+
|
|
60
65
|
hash
|
|
61
66
|
end
|
|
62
67
|
|
|
@@ -71,29 +76,44 @@ class AngryHash
|
|
|
71
76
|
if mixin = mixin_registry[extension][field.to_s]
|
|
72
77
|
kind,mod,options = *mixin
|
|
73
78
|
|
|
79
|
+
if options[:allow_nil] && obj.nil?
|
|
80
|
+
return nil
|
|
81
|
+
end
|
|
82
|
+
|
|
74
83
|
if options.key?(:default) && obj.nil?
|
|
75
84
|
obj = options[:default]
|
|
76
85
|
end
|
|
77
86
|
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# the result of `extend_self` block
|
|
90
|
+
extend_self = if (sub_ext = mixin_registry[mod]['*']) && sub_ext[0] == :block
|
|
91
|
+
sub_ext[1][:block]
|
|
92
|
+
end
|
|
93
|
+
|
|
78
94
|
case kind
|
|
79
95
|
when :single
|
|
80
|
-
obj = extend_hash(obj,mod,parent_obj)
|
|
96
|
+
obj = extend_hash(obj,mod,parent_obj,extend_self)
|
|
81
97
|
when :array
|
|
82
98
|
# XXX - this is ok for now... we really need to typecheck, perhaps wrap in a smart-array
|
|
83
99
|
obj ||= []
|
|
84
|
-
obj = obj.map {|elt| extend_hash(elt, mod, parent_obj)}
|
|
100
|
+
obj = obj.map! {|elt| extend_hash(elt, mod, parent_obj, extend_self) }
|
|
85
101
|
when :hash
|
|
86
102
|
obj ||= {}
|
|
87
|
-
obj
|
|
88
|
-
h[k] = extend_hash(elt,mod,parent_obj)
|
|
103
|
+
obj.replace( obj.inject(AngryHash.new) {|h,(k,elt)|
|
|
104
|
+
h[k] = extend_hash(elt,mod,parent_obj, extend_self)
|
|
89
105
|
h
|
|
90
|
-
|
|
106
|
+
})
|
|
91
107
|
end
|
|
108
|
+
|
|
109
|
+
|
|
92
110
|
end
|
|
93
111
|
|
|
94
112
|
obj
|
|
95
113
|
end
|
|
96
|
-
end
|
|
114
|
+
end # Extension module methods
|
|
115
|
+
|
|
116
|
+
## Instance methods
|
|
97
117
|
|
|
98
118
|
def [](key)
|
|
99
119
|
Extension.mixin_to(self,key,super)
|
|
@@ -105,10 +125,12 @@ class AngryHash
|
|
|
105
125
|
|
|
106
126
|
def dup_with_extension
|
|
107
127
|
dup.tap {|new_hash|
|
|
108
|
-
|
|
128
|
+
AngryHash.copy_extension(self,new_hash)
|
|
109
129
|
}
|
|
110
130
|
end
|
|
111
131
|
|
|
132
|
+
## AngryHash extension attributes
|
|
133
|
+
# These should be copied using `AngryHash.copy_extension` when duping
|
|
112
134
|
def __parent_hash=(hash)
|
|
113
135
|
@__parent_hash = hash
|
|
114
136
|
end
|
|
@@ -125,6 +147,7 @@ class AngryHash
|
|
|
125
147
|
@__angry_hash_extension
|
|
126
148
|
end
|
|
127
149
|
|
|
150
|
+
|
|
128
151
|
module ClassMethods
|
|
129
152
|
def defaults(default_form=nil)
|
|
130
153
|
if default_form
|
|
@@ -151,6 +174,11 @@ class AngryHash
|
|
|
151
174
|
Extension.register_mixin_hash(self,field,mod,options)
|
|
152
175
|
end
|
|
153
176
|
|
|
177
|
+
def extend_self(options={}, &block)
|
|
178
|
+
options[:block] = block
|
|
179
|
+
Extension.register_mixin_block(self,options)
|
|
180
|
+
end
|
|
181
|
+
|
|
154
182
|
def build(seed={})
|
|
155
183
|
AngryHash[ seed ].tap {|hash|
|
|
156
184
|
self.fill_in_defaults(hash)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
class AngryHash
|
|
2
|
+
## Adds extension awareness to core AngryHash copying methods
|
|
3
|
+
module ExtensionAware
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.extend ClassMethods
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def [](seed)
|
|
10
|
+
super.tap {|hash|
|
|
11
|
+
copy_extension(seed,hash)
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def __convert(hash,cycle_watch=[])
|
|
16
|
+
super.tap {|new_hash|
|
|
17
|
+
copy_extension(hash,new_hash)
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def new_extended(mod,seed={})
|
|
22
|
+
self[ seed ].tap {|hash| hash.extend(mod) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def dup_with_extension(other)
|
|
26
|
+
if AngryHash === other
|
|
27
|
+
other.respond_to?(:dup_with_extension) ? other.dup_with_extension : other.dup
|
|
28
|
+
elsif Hash === other
|
|
29
|
+
dup_with_extension(AngryHash[other])
|
|
30
|
+
elsif
|
|
31
|
+
other.dup
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def copy_extension(from,to_hash)
|
|
36
|
+
to_hash.tap {|to|
|
|
37
|
+
to.extend(from.__angry_hash_extension) if from.respond_to?(:__angry_hash_extension)
|
|
38
|
+
to.__parent_hash = from.__parent_hash if to.respond_to?(:__parent_hash=) && from.respond_to?(:__parent_hash)
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Given a hash, detect whether it's extended & by which module.
|
|
43
|
+
def extended_with(hash)
|
|
44
|
+
hash.__angry_hash_extension if hash.respond_to?(:__angry_hash_extension)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def reverse_deep_merge(other_hash)
|
|
49
|
+
super.tap {|merged|
|
|
50
|
+
self.class.copy_extension(self,merged)
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
class AngryHash
|
|
2
|
+
module Merging
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.class_eval do
|
|
5
|
+
alias_method :regular_merge, :merge unless method_defined?(:regular_merge)
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Merging
|
|
11
|
+
|
|
12
|
+
# Shallow merge hash.
|
|
13
|
+
def merge(hash)
|
|
14
|
+
super(__convert_without_dup(hash))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Shallow merge! hash.
|
|
18
|
+
def merge!(other_hash)
|
|
19
|
+
other_hash.each_pair { |key, value| dup_and_store(key,value) }
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
alias_method :update, :merge!
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Merge deeply. The other hash's contents are favoured.
|
|
26
|
+
def deep_merge(other_hash)
|
|
27
|
+
other_hash = AngryHash[other_hash]
|
|
28
|
+
|
|
29
|
+
self.regular_merge( other_hash ) do |key, oldval, newval|
|
|
30
|
+
oldval = __convert_value(oldval)
|
|
31
|
+
newval = __convert_value(newval)
|
|
32
|
+
|
|
33
|
+
AngryHash === oldval && AngryHash === newval ? oldval.deep_merge(newval) : newval
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Merge deeply, replacing the AngryHash with the result.
|
|
38
|
+
def deep_merge!(other_hash)
|
|
39
|
+
replace(deep_merge(other_hash))
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
alias_method :deep_update, :deep_merge!
|
|
43
|
+
|
|
44
|
+
# Merge deeply in reverse. This hash's contents are favoured.
|
|
45
|
+
def reverse_deep_merge(other_hash)
|
|
46
|
+
__convert(other_hash).deep_merge(self)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Merge deeply in reverse, replacing this AngryHash with the result. This hash's contents are favoured.
|
|
50
|
+
def reverse_deep_merge!(other_hash)
|
|
51
|
+
replace(reverse_deep_merge(other_hash))
|
|
52
|
+
self
|
|
53
|
+
end
|
|
54
|
+
alias_method :reverse_deep_update, :reverse_deep_merge!
|
|
55
|
+
end
|
|
56
|
+
end
|
metadata
CHANGED
|
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
|
4
4
|
prerelease: false
|
|
5
5
|
segments:
|
|
6
6
|
- 0
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
version: 0.
|
|
7
|
+
- 3
|
|
8
|
+
- 0
|
|
9
|
+
version: 0.3.0
|
|
10
10
|
platform: ruby
|
|
11
11
|
authors:
|
|
12
12
|
- Lachie Cox
|
|
@@ -14,7 +14,7 @@ autorequire:
|
|
|
14
14
|
bindir: bin
|
|
15
15
|
cert_chain: []
|
|
16
16
|
|
|
17
|
-
date: 2010-
|
|
17
|
+
date: 2010-10-20 00:00:00 +11:00
|
|
18
18
|
default_executable:
|
|
19
19
|
dependencies: []
|
|
20
20
|
|
|
@@ -36,9 +36,14 @@ files:
|
|
|
36
36
|
- VERSION
|
|
37
37
|
- angry_hash.gemspec
|
|
38
38
|
- lib/angry_hash.rb
|
|
39
|
+
- lib/angry_hash/conversion/by_reference.rb
|
|
40
|
+
- lib/angry_hash/conversion/duplicating.rb
|
|
39
41
|
- lib/angry_hash/dsl.rb
|
|
40
42
|
- lib/angry_hash/extension.rb
|
|
43
|
+
- lib/angry_hash/extension_aware.rb
|
|
44
|
+
- lib/angry_hash/initialiser.rb
|
|
41
45
|
- lib/angry_hash/merge_string.rb
|
|
46
|
+
- lib/angry_hash/merging.rb
|
|
42
47
|
- LICENSE
|
|
43
48
|
- README.md
|
|
44
49
|
has_rdoc: true
|
|
@@ -77,6 +82,6 @@ test_files:
|
|
|
77
82
|
- examples/dsl.eg.rb
|
|
78
83
|
- examples/dup_eg.rb
|
|
79
84
|
- examples/eg_helper.rb
|
|
80
|
-
- examples/extension_tracking.rb
|
|
85
|
+
- examples/extension_tracking.eg.rb
|
|
81
86
|
- examples/merge_eg.rb
|
|
82
87
|
- examples/normal_hash.eg.rb
|