angry_hash 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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 conversion.
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.2.2
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.2.2"
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-08-30}
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/merge_string.rb"
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/extension'
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
- def self.[](other=nil)
8
- if other
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), self.class.__convert_value_without_dup(value))
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), self.class.__convert_value(value))
37
+ regular_writer(__convert_key(key), __convert_value(value))
33
38
  end
34
39
 
35
- alias_method :regular_merge, :merge unless method_defined?(:regular_merge)
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
- # non-duplicating convert
169
- def self.__convert_without_dup(hash)
170
- hash.inject(AngryHash.new) do |newhash,(k,v)|
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
@@ -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
- def extend_hash(hash, mod, parent_hash)
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 = obj.inject(AngryHash.new) do |h,(k,elt)|
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
- end
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
- new_hash.extend(__angry_hash_extension) if __angry_hash_extension
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,11 @@
1
+ class AngryHash
2
+ module Initialiser
3
+ def [](other=nil)
4
+ if other
5
+ super(__convert(other))
6
+ else
7
+ new
8
+ end
9
+ end
10
+ end
11
+ 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
- - 2
8
- - 2
9
- version: 0.2.2
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-08-30 00:00:00 +10:00
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