gorillib 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/CHANGELOG.textile +6 -0
  2. data/README.textile +34 -11
  3. data/VERSION +1 -1
  4. data/gorillib.gemspec +63 -5
  5. data/lib/gorillib/enumerable/sum.rb +2 -2
  6. data/lib/gorillib/hash/compact.rb +2 -29
  7. data/lib/gorillib/hash/deep_compact.rb +2 -12
  8. data/lib/gorillib/hash/deep_dup.rb +4 -0
  9. data/lib/gorillib/hash/deep_merge.rb +2 -14
  10. data/lib/gorillib/hash/indifferent_access.rb +207 -0
  11. data/lib/gorillib/hash/keys.rb +2 -40
  12. data/lib/gorillib/hash/reverse_merge.rb +2 -24
  13. data/lib/gorillib/hash/slice.rb +2 -51
  14. data/lib/gorillib/hash/tree_merge.rb +4 -0
  15. data/lib/gorillib/hashlike.rb +824 -0
  16. data/lib/gorillib/hashlike/compact.rb +60 -0
  17. data/lib/gorillib/hashlike/deep_compact.rb +18 -0
  18. data/lib/gorillib/hashlike/deep_dup.rb +15 -0
  19. data/lib/gorillib/hashlike/deep_merge.rb +20 -0
  20. data/lib/gorillib/hashlike/hashlike_via_accessors.rb +169 -0
  21. data/lib/gorillib/hashlike/keys.rb +59 -0
  22. data/lib/gorillib/hashlike/reverse_merge.rb +31 -0
  23. data/lib/gorillib/hashlike/slice.rb +67 -0
  24. data/lib/gorillib/hashlike/tree_merge.rb +76 -0
  25. data/lib/gorillib/metaprogramming/mattr_accessor.rb +1 -1
  26. data/lib/gorillib/receiver.rb +315 -0
  27. data/lib/gorillib/receiver/active_model_shim.rb +19 -0
  28. data/lib/gorillib/receiver/acts_as_hash.rb +191 -0
  29. data/lib/gorillib/receiver/acts_as_loadable.rb +42 -0
  30. data/lib/gorillib/receiver/tree_diff.rb +74 -0
  31. data/lib/gorillib/receiver/validations.rb +30 -0
  32. data/lib/gorillib/struct/acts_as_hash.rb +108 -0
  33. data/lib/gorillib/struct/hashlike_iteration.rb +0 -0
  34. data/notes/fancy_hashes_and_receivers.textile +120 -0
  35. data/notes/hash_rdocs.textile +97 -0
  36. data/spec/hash/deep_merge_spec.rb +0 -2
  37. data/spec/hash/indifferent_access_spec.rb +391 -0
  38. data/spec/hash/slice_spec.rb +35 -12
  39. data/spec/hashlike/behave_same_as_hash_spec.rb +105 -0
  40. data/spec/hashlike/hashlike_behavior_spec.rb +824 -0
  41. data/spec/hashlike/hashlike_via_accessors_fuzzing_spec.rb +37 -0
  42. data/spec/hashlike/hashlike_via_accessors_spec.rb +262 -0
  43. data/spec/hashlike_spec.rb +302 -0
  44. data/spec/metaprogramming/aliasing_spec.rb +3 -0
  45. data/spec/metaprogramming/cattr_accessor_spec.rb +2 -0
  46. data/spec/metaprogramming/class_attribute_spec.rb +2 -0
  47. data/spec/metaprogramming/delegation_spec.rb +2 -0
  48. data/spec/metaprogramming/mattr_accessor_spec.rb +2 -0
  49. data/spec/metaprogramming/singleton_class_spec.rb +3 -0
  50. data/spec/receiver/acts_as_hash_spec.rb +286 -0
  51. data/spec/receiver_spec.rb +478 -0
  52. data/spec/spec_helper.rb +11 -6
  53. data/spec/string/truncate_spec.rb +1 -0
  54. data/spec/struct/acts_as_hash_fuzz_spec.rb +67 -0
  55. data/spec/struct/acts_as_hash_spec.rb +426 -0
  56. data/spec/support/hashlike_fuzzing_helper.rb +127 -0
  57. data/spec/support/hashlike_helper.rb +75 -0
  58. data/spec/support/hashlike_struct_helper.rb +37 -0
  59. data/spec/support/hashlike_via_delegation.rb +30 -0
  60. data/spec/support/matchers/be_array_eql.rb +12 -0
  61. data/spec/support/matchers/be_hash_eql.rb +14 -0
  62. data/spec/support/matchers/enumerate_method.rb +10 -0
  63. data/spec/support/matchers/evaluate_to_true.rb +5 -0
  64. metadata +62 -4
@@ -1,42 +1,4 @@
1
+ require 'gorillib/hashlike/keys'
1
2
  class Hash
2
- # Return a new hash with all keys converted to strings.
3
- def stringify_keys
4
- dup.stringify_keys!
5
- end unless method_defined?(:stringify_keys)
6
-
7
- # Destructively convert all keys to strings.
8
- def stringify_keys!
9
- keys.each do |key|
10
- self[key.to_s] = delete(key)
11
- end
12
- self
13
- end unless method_defined?(:stringify_keys!)
14
-
15
- # Return a new hash with all keys converted to symbols, as long as
16
- # they respond to +to_sym+.
17
- def symbolize_keys
18
- dup.symbolize_keys!
19
- end unless method_defined?(:symbolize_keys)
20
-
21
- # Destructively convert all keys to symbols, as long as they respond
22
- # to +to_sym+.
23
- def symbolize_keys!
24
- keys.each do |key|
25
- self[(key.to_sym rescue key) || key] = delete(key)
26
- end
27
- self
28
- end unless method_defined?(:symbolize_keys!)
29
-
30
- # Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
31
- # Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols
32
- # as keys, this will fail.
33
- #
34
- # ==== Examples
35
- # { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key(s): years"
36
- # { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key(s): name, age"
37
- # { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
38
- def assert_valid_keys(*valid_keys)
39
- unknown_keys = keys - [valid_keys].flatten
40
- raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
41
- end unless method_defined?(:assert_valid_keys)
3
+ include Gorillib::Hashlike::Keys
42
4
  end
@@ -1,26 +1,4 @@
1
+ require 'gorillib/hashlike/reverse_merge'
1
2
  class Hash
2
- # Allows for reverse merging two hashes where the keys in the calling hash take precedence over those
3
- # in the <tt>other_hash</tt>. This is particularly useful for initializing an option hash with default values:
4
- #
5
- # def setup(options = {})
6
- # options.reverse_merge! :size => 25, :velocity => 10
7
- # end
8
- #
9
- # Using <tt>merge</tt>, the above example would look as follows:
10
- #
11
- # def setup(options = {})
12
- # { :size => 25, :velocity => 10 }.merge(options)
13
- # end
14
- #
15
- # The default <tt>:size</tt> and <tt>:velocity</tt> are only set if the +options+ hash passed in doesn't already
16
- # have the respective key.
17
- def reverse_merge(other_hash)
18
- other_hash.merge(self)
19
- end unless method_defined?(:reverse_merge)
20
-
21
- # Performs the opposite of <tt>merge</tt>, with the keys and values from the first hash taking precedence over the second.
22
- # Modifies the receiver in place.
23
- def reverse_merge!(other_hash)
24
- merge!( other_hash ){|k,o,n| o }
25
- end unless method_defined?(:reverse_merge!)
3
+ include Gorillib::Hashlike::ReverseMerge
26
4
  end
@@ -1,53 +1,4 @@
1
+ require 'gorillib/hashlike/slice'
1
2
  class Hash
2
- # Slice a hash to include only the given keys. This is useful for
3
- # limiting an options hash to valid keys before passing to a method:
4
- #
5
- # def search(criteria = {})
6
- # assert_valid_keys(:mass, :velocity, :time)
7
- # end
8
- #
9
- # search(options.slice(:mass, :velocity, :time))
10
- #
11
- # If you have an array of keys you want to limit to, you should splat them:
12
- #
13
- # valid_keys = [:mass, :velocity, :time]
14
- # search(options.slice(*valid_keys))
15
- def slice(*keys)
16
- keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
17
- hash = self.class.new
18
- keys.each { |k| hash[k] = self[k] if has_key?(k) }
19
- hash
20
- end unless method_defined?(:slice)
21
-
22
- # Replaces the hash with only the given keys.
23
- # Returns a hash containing the removed key/value pairs
24
- # @example
25
- # hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
26
- # hsh.slice!(:a, :b)
27
- # # => {:c => 3, :d =>4}
28
- # hsh
29
- # # => {:a => 1, :b => 2}
30
- def slice!(*keys)
31
- keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
32
- omit = slice(*self.keys - keys)
33
- hash = slice(*keys)
34
- replace(hash)
35
- omit
36
- end unless method_defined?(:slice!)
37
-
38
- # Removes the given keys from the hash
39
- # Returns a hash containing the removed key/value pairs
40
- #
41
- # @example
42
- # hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
43
- # hsh.extract!(:a, :b)
44
- # # => {:a => 1, :b => 2}
45
- # hsh
46
- # # => {:c => 3, :d =>4}
47
- def extract!(*keys)
48
- result = {}
49
- keys.each {|key| result[key] = delete(key) }
50
- result
51
- end unless method_defined?(:extract!)
3
+ include Gorillib::Hashlike::Slice
52
4
  end
53
-
@@ -0,0 +1,4 @@
1
+ require 'gorillib/hashlike/tree_merge'
2
+ class Hash
3
+ include Gorillib::Hashlike::TreeMerge
4
+ end
@@ -0,0 +1,824 @@
1
+ if (RUBY_VERSION < '1.9') && (not defined?(KeyError))
2
+ class KeyError < IndexError ; end
3
+ end
4
+
5
+ module Gorillib
6
+ #
7
+ # Your class must provide #[], #[]=, #delete, and #keys --
8
+ #
9
+ # * hsh[key] Element Reference -- Retrieves the value stored for +key+.
10
+ # * hsh[key] = val Element Assignment -- Associates +val+ with +key+.
11
+ # * hsh.delete(key) Deletes & returns the value whose key is equal to +key+.
12
+ # * hsh.keys Returns a new array populated with the keys.
13
+ #
14
+ # (see Hashlike::HashlikeViaAccessors for example)
15
+ #
16
+ # Given the above, hashlike will provide the rest, defining the methods
17
+ #
18
+ # :each_pair, :each, :each_key, :each_value, :values_at, :values_of, :values,
19
+ # :size, :length, :has_key?, :include?, :key?, :member?, :has_value?, :value?,
20
+ # :fetch, :key, :assoc, :rassoc, :empty?, :merge, :update, :merge!, :reject!,
21
+ # :select!, :delete_if, :keep_if, :reject, :clear, :store, :to_hash, :invert,
22
+ # :flatten
23
+ #
24
+ # and these methods added by Enumerable:
25
+ #
26
+ # :each_cons, :each_entry, :each_slice, :each_with_index, :each_with_object,
27
+ # :entries, :to_a, :map, :collect, :collect_concat, :group_by, :flat_map,
28
+ # :inject, :reduce, :chunk, :reverse_each, :slice_before, :drop, :drop_while,
29
+ # :take, :take_while, :detect, :find, :find_all, :select, :find_index, :grep,
30
+ # :all?, :any?, :none?, :one?, :first, :count, :zip, :max, :max_by, :min,
31
+ # :min_by, :minmax, :minmax_by, :sort, :sort_by, :cycle, :partition,
32
+ #
33
+ # It does not define these methods that do exist on hash:
34
+ #
35
+ # :default, :default=, :default_proc, :default_proc=,
36
+ # :compare_by_identity, :compare_by_identity?,
37
+ # :replace, :rehash, :shift
38
+ #
39
+ # === Chinese wall
40
+ #
41
+ # With a few exceptions, all methods are defined only in terms of
42
+ #
43
+ # #[], #[]=, #delete, #keys, #each_pair and #has_key?
44
+ #
45
+ # (exceptions: merge family depend on #update; the reject/select/xx_if family
46
+ # depend on each other; #invert & #flatten call #to_hash; #rassoc calls #key)
47
+ #
48
+ # === custom iterators
49
+ #
50
+ # Hashlike typically defines the following fundamental iterators by including
51
+ # Gorillib::Hashlike::EnumerateFromKeys:
52
+ #
53
+ # :each_pair, :each, :values, :values_at, :length
54
+ #
55
+ # However, if the #each_pair method already exists on the class (as it does
56
+ # for Struct), those methods will *not* be defined. The class is held
57
+ # responsible for the implementation of all five. (Of these, #each_pair
58
+ # is the only method called from elsewhere in Hashlike, while #each is the
59
+ # only method called from Enumerable).
60
+ #
61
+ # === #convert_key (Indifferent Access)
62
+ #
63
+ # If you define #convert_key the #values_at, #has_key?, #fetch, and #assoc
64
+ # methods will use it to sanitize keys coming in from the outside. It's
65
+ # assumed that you will do the same with #[], #[]= and #delete. (see
66
+ # Gorillib::HashWithIndifferentAccess for an example).
67
+ #
68
+ module Hashlike
69
+
70
+ #
71
+ # Provides a natural default iteration behavior by iterating over #keys.
72
+ # Since most classes will want this behaviour, it is included by default
73
+ # *unless* the class has already defined an #each method.
74
+ #
75
+ # Classes that wish to define their own iteration behavior (Struct for
76
+ # example, or a database facade) must define all of the methods within this
77
+ # module.
78
+ #
79
+ module EnumerateFromKeys
80
+
81
+ #
82
+ # Calls +block+ once for each key in +hsh+, passing the key/value pair as
83
+ # parameters.
84
+ #
85
+ # If no block is given, an enumerator is returned instead.
86
+ #
87
+ # @example
88
+ # hsh = { :a => 100, :b => 200 }
89
+ # hsh.each_pair{|key, value| puts "#{key} is #{value}" }
90
+ # # produces:
91
+ # a is 100
92
+ # b is 200
93
+ #
94
+ # @example with block arity:
95
+ # hsh = {[:a,:b] => 3, [:c, :d] => 4, :e => 5}
96
+ # seen_args = []
97
+ # hsh.each_pair{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
98
+ # # => [[[:a, :b], 3, nil], [[:c, :d], 4, nil], [:e, 5, nil]]
99
+ #
100
+ # seen_args = []
101
+ # hsh.each_pair{|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
102
+ # # => [[:a, :b, 3], [:c, :d, 4], [:e, nil, 5]]
103
+ #
104
+ # @overload hsh.each_pair{|key, val| block } -> hsh
105
+ # Calls block once for each key in +hsh+
106
+ # @yield [key, val] in order, each key and its associated value
107
+ # @return [Hashlike]
108
+ #
109
+ # @overload hsh.each_pair -> an_enumerator
110
+ # with no block, returns a raw enumerator
111
+ # @return [Enumerator]
112
+ #
113
+ def each_pair
114
+ return enum_for(:each_pair) unless block_given?
115
+ keys.each do |key|
116
+ yield([key, self[key]])
117
+ end
118
+ self
119
+ end
120
+
121
+ #
122
+ # Calls +block+ once for each key in +hsh+, passing the key/value pair as
123
+ # parameters.
124
+ #
125
+ # If no block is given, an enumerator is returned instead.
126
+ #
127
+ # @example
128
+ # hsh = { :a => 100, :b => 200 }
129
+ # hsh.each{|key, value| puts "#{key} is #{value}" }
130
+ # # produces:
131
+ # a is 100
132
+ # b is 200
133
+ #
134
+ # @example with block arity:
135
+ # hsh = {[:a,:b] => 3, [:c, :d] => 4, :e => 5}
136
+ # seen_args = []
137
+ # hsh.each{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
138
+ # # => [[[:a, :b], 3, nil], [[:c, :d], 4, nil], [:e, 5, nil]]
139
+ #
140
+ # seen_args = []
141
+ # hsh.each{|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
142
+ # # => [[:a, :b, 3], [:c, :d, 4], [:e, nil, 5]]
143
+ #
144
+ # @overload hsh.each{|key, val| block } -> hsh
145
+ # Calls block once for each key in +hsh+
146
+ # @yield [key, val] in order, each key and its associated value
147
+ # @return [Hashlike]
148
+ #
149
+ # @overload hsh.each -> an_enumerator
150
+ # with no block, returns a raw enumerator
151
+ # @return [Enumerator]
152
+ #
153
+ def each(&block)
154
+ return enum_for(:each) unless block_given?
155
+ each_pair(&block)
156
+ end
157
+
158
+ #
159
+ # Returns the number of key/value pairs in the hashlike.
160
+ #
161
+ # @example
162
+ # hsh = { :d => 100, :a => 200, :v => 300, :e => 400 }
163
+ # hsh.length # => 4
164
+ # hsh.delete(:a) # => 200
165
+ # hsh.length # => 3
166
+ #
167
+ # @return [Fixnum] number of key-value pairs
168
+ #
169
+ def length
170
+ keys.length
171
+ end
172
+
173
+ #
174
+ # A new array populated with the values from +hsh+.
175
+ #
176
+ # @see Hashlike#keys.
177
+ #
178
+ # @example
179
+ # hsh = { :a => 100, :b => 200, :c => 300 }
180
+ # hsh.values # => [100, 200, 300]
181
+ #
182
+ # @return [Array] the values, in order by their key.
183
+ #
184
+ def values
185
+ [].tap{|arr| each_pair{|key, val| arr << val } }
186
+ end
187
+
188
+ #
189
+ # Array containing the values associated with the given keys.
190
+ #
191
+ # @see Hashlike#select.
192
+ #
193
+ # @example
194
+ # hsh = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
195
+ # hsh.values_at("cow", "cat") # => ["bovine", "feline"]
196
+ #
197
+ # @example
198
+ # hsh = { :a => 100, :b => 200, :c => 300 }
199
+ # hsh.values_at(:c, :a, :c, :z, :a)
200
+ # # => [300, 100, 300, nil, 100]
201
+ #
202
+ # @param *allowed_keys [Object] the keys to retrieve.
203
+ # @return [Array] the values, in order according to allowed_keys.
204
+ #
205
+ def values_at(*allowed_keys)
206
+ allowed_keys.map do |key|
207
+ key = convert_key(key) if respond_to?(:convert_key)
208
+ self[key] if has_key?(key)
209
+ end
210
+ end
211
+ end
212
+
213
+ # alias for #[]=
214
+ def store(key, val)
215
+ self[key] = val
216
+ end
217
+
218
+ # Hashlike#each_key
219
+ #
220
+ # Calls +block+ once for each key in +hsh+, passing the key as a parameter.
221
+ #
222
+ # If no block is given, an enumerator is returned instead.
223
+ #
224
+ # @example
225
+ # hsh = { :a => 100, :b => 200 }
226
+ # hsh.each_key{|key| puts key }
227
+ # # produces:
228
+ # a
229
+ # b
230
+ #
231
+ # @example with block arity:
232
+ # hsh = {[:a,:b] => 3, [:c, :d] => 4, :e => 5}
233
+ # seen_args = []
234
+ # hsh.each_key{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
235
+ # # => [[:a, :b, nil], [:c, :d, nil], [:e, nil, nil]]
236
+ #
237
+ # seen_args = []
238
+ # hsh.each_key{|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
239
+ # # => [[:a, nil, :b], [:c, nil, :d], [:e, nil, nil]]
240
+ #
241
+ # @overload hsh.each_key{|key| block } -> hsh
242
+ # Calls +block+ once for each key in +hsh+
243
+ # @yield [key] in order, each key
244
+ # @return [Hashlike]
245
+ #
246
+ # @overload hsh.each_key -> an_enumerator
247
+ # with no block, returns a raw enumerator
248
+ # @return [Enumerator]
249
+ #
250
+ def each_key
251
+ return enum_for(:each_key) unless block_given?
252
+ each_pair{|k,v| yield k }
253
+ self
254
+ end
255
+
256
+ #
257
+ # Calls +block+ once for each key in +hsh+, passing the value as a parameter.
258
+ #
259
+ # If no block is given, an enumerator is returned instead.
260
+ #
261
+ # @example
262
+ # hsh = { :a => 100, :b => 200 }
263
+ # hsh.each_value{|value| puts value }
264
+ # # produces:
265
+ # 100
266
+ # 200
267
+ #
268
+ # @example with block arity:
269
+ # hsh = {:a => [300,333], :b => [400,444], :e => 500})
270
+ # seen_args = []
271
+ # hsh.each_value{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
272
+ # # => [[300, 333, nil], [400, 444, nil], [500, nil, nil]]
273
+ #
274
+ # seen_args = []
275
+ # hsh.each_value{|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
276
+ # # => [[300, nil, 333], [400, nil, 444], [500, nil, nil]]
277
+ #
278
+ # @overload hsh.each_value{|val| block } -> hsh
279
+ # Calls +block+ once for each value in +hsh+
280
+ # @yield [val] in order by its key, each value
281
+ # @return [Hashlike]
282
+ #
283
+ # @overload hsh.each_value -> an_enumerator
284
+ # with no block, returns a raw enumerator
285
+ # @return [Enumerator]
286
+ #
287
+ def each_value
288
+ return enum_for(:each_value) unless block_given?
289
+ each_pair{|k,v| yield v }
290
+ self
291
+ end
292
+
293
+ #
294
+ # Array containing the values associated with the given keys.
295
+ #
296
+ # @see Hashlike#select.
297
+ #
298
+ # @example
299
+ # hsh = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
300
+ # hsh.values_of("cow", "cat") # => ["bovine", "feline"]
301
+ #
302
+ # @example
303
+ # hsh = { :a => 100, :b => 200, :c => 300 }
304
+ # hsh.values_of(:c, :a, :c, :z, :a)
305
+ # # => [300, 100, 300, nil, 100]
306
+ #
307
+ # @param *allowed_keys [Object] the keys to retrieve.
308
+ # @return [Array] the values, in order according to allowed_keys.
309
+ #
310
+ def values_of(*allowed_keys)
311
+ allowed_keys.map do |key|
312
+ key = convert_key(key) if respond_to?(:convert_key)
313
+ self[key] if has_key?(key)
314
+ end
315
+ end
316
+
317
+ #
318
+ # Returns true if the given key is present in +hsh+.
319
+ #
320
+ # @example
321
+ # hsh = { :a => 100, :b => 200 }
322
+ # hsh.has_key?(:a) # => true
323
+ # hsh.has_key?(:z) # => false
324
+ #
325
+ # @param key [Object] the key to check for.
326
+ # @return [true, false] true if the key is present, false otherwise
327
+ #
328
+ def has_key?(key)
329
+ key = convert_key(key) if respond_to?(:convert_key)
330
+ keys.include?(key)
331
+ end
332
+
333
+ #
334
+ # Returns true if the given value is present for some key in +hsh+.
335
+ #
336
+ # @example
337
+ # hsh = { :a => 100, :b => 200 }
338
+ # hsh.has_value?(100) # => true
339
+ # hsh.has_value?(999) # => false
340
+ #
341
+ # @param target [Object] the value to query
342
+ # @return [true, false] true if the value is present, false otherwise
343
+ #
344
+ def has_value?(target)
345
+ # don't refactor this to any? -- Struct's #any is weird
346
+ each_pair{|key, val| return true if (val == target) }
347
+ false
348
+ end
349
+
350
+ #
351
+ # Returns a value from the hashlike for the given key. If the key can't be
352
+ # found, there are several options:
353
+ # * With no other arguments, it will raise a +KeyError+ exception;
354
+ # * if default is given, then that will be returned;
355
+ # * if the optional code block is specified, then that will be run and its result returned.
356
+ #
357
+ # @example
358
+ # hsh = { :a => 100, :b => 200 }
359
+ # hsh.fetch(:a) # => 100
360
+ # hsh.fetch(:z, "go fish") # => "go fish"
361
+ # hsh.fetch(:z){|el| "go fish, #{el}"} # => "go fish, z"
362
+ #
363
+ # @example An exception is raised if the key is not found and a default value is not supplied.
364
+ # hsh = { :a => 100, :b => 200 }
365
+ # hsh.fetch(:z)
366
+ # # produces:
367
+ # prog.rb:2:in `fetch': key not found (KeyError) from prog.rb:2
368
+ #
369
+ # hsh.fetch(:z, 3)
370
+ # # => 3
371
+ #
372
+ # hsh.fetch(:z){|key| key.to_s * 5 }
373
+ # # => "zzzzz"
374
+ #
375
+ # @param key [Object] the key to query
376
+ # @param default [Object] the value to use if the key is missing
377
+ # @raise [KeyError] raised if missing, and neither +default+ nor +block+ is supplied
378
+ # @yield [key] if missing, block called with the key requested
379
+ # @return [Object] the value; if missing, the default; if missing, the
380
+ # block's return value
381
+ #
382
+ def fetch(key, default=nil, &block)
383
+ key = convert_key(key) if respond_to?(:convert_key)
384
+ warn "#{caller[0]}: warning: block supersedes default value argument" if default && block_given?
385
+ if has_key?(key) then self[key]
386
+ elsif block_given? then yield(key)
387
+ elsif default then default
388
+ else raise KeyError, "key not found: #{key.inspect}"
389
+ end
390
+ end
391
+
392
+ #
393
+ # Searches the hash for an entry whose value == +val+, returning the
394
+ # corresponding key. If not found, returns +nil+.
395
+ #
396
+ # You are guaranteed that the first matching key in #keys will be the one
397
+ # returned.
398
+ #
399
+ # @example
400
+ # hsh = { :a => 100, :b => 200 }
401
+ # hsh.key(200) # => :b
402
+ # hsh.key(999) # => nil
403
+ #
404
+ # @param val [Object] the value to look up
405
+ # @return [Object, nil] the key for the given val, or nil if missing
406
+ #
407
+ def key(val)
408
+ keys.find{|key| self[key] == val }
409
+ end
410
+
411
+ #
412
+ # Searches through the hashlike comparing obj with the key using ==.
413
+ # Returns the key-value pair (two elements array) or nil if no match is
414
+ # found.
415
+ #
416
+ # @see Array#assoc.
417
+ #
418
+ # @example
419
+ # hsh = { "colors" => ["red", "blue", "green"],
420
+ # "letters" => [:a, :b, :c ]}
421
+ # hsh.assoc("letters") # => ["letters", [:a, :b, :c]]
422
+ # hsh.assoc("foo") # => nil
423
+ #
424
+ # @return [Array, nil] the key-value pair (two elements array) or nil if no
425
+ # match is found.
426
+ #
427
+ def assoc(key)
428
+ key = convert_key(key) if respond_to?(:convert_key)
429
+ return unless has_key?(key)
430
+ [key, self[key]]
431
+ end
432
+
433
+ #
434
+ # Searches through the hashlike comparing obj with the value using ==.
435
+ # Returns the first key-value pair (two-element array) that matches, or nil
436
+ # if no match is found.
437
+ #
438
+ # @see Array#rassoc.
439
+ #
440
+ # @example
441
+ # hsh = { 1 => "one", 2 => "two", 3 => "three", "ii" => "two"}
442
+ # hsh.rassoc("two") # => [2, "two"]
443
+ # hsh.rassoc("four") # => nil
444
+ #
445
+ # @return [Array, nil] The first key-value pair (two-element array) that
446
+ # matches, or nil if no match is found
447
+ #
448
+ def rassoc(val)
449
+ key = key(val) or return
450
+ [key, self[key]]
451
+ end
452
+
453
+ #
454
+ # Returns true if the hashlike contains no key-value pairs, false otherwise.
455
+ #
456
+ # @example
457
+ # {}.empty? # => true
458
+ #
459
+ # @return [true, false] true if +hsh+ contains no key-value pairs, false otherwise
460
+ #
461
+ def empty?
462
+ keys.empty?
463
+ end
464
+
465
+ #
466
+ # Adds the contents of +other_hash+ to +hsh+. If no block is
467
+ # specified, entries with duplicate keys are overwritten with the values from
468
+ # +other_hash+, otherwise the value of each duplicate key is determined by
469
+ # calling the block with the key, its value in +hsh+ and its value in
470
+ # +other_hash+.
471
+ #
472
+ # @example
473
+ # h1 = { :a => 100, :b => 200 }
474
+ # h2 = { :b => 254, :c => 300 }
475
+ # h1.merge!(h2)
476
+ # # => { :a => 100, :b => 254, :c => 300 }
477
+ #
478
+ # h1 = { :a => 100, :b => 200 }
479
+ # h2 = { :b => 254, :c => 300 }
480
+ # h1.merge!(h2){|key, v1, v2| v1 }
481
+ # # => { :a => 100, :b => 200, :c => 300 }
482
+ #
483
+ # @overload hsh.update(other_hash) -> hsh
484
+ # Adds the contents of +other_hash+ to +hsh+. Entries with duplicate keys are
485
+ # overwritten with the values from +other_hash+
486
+ # @param other_hash [Hash, Hashlike] the hash to merge (it wins)
487
+ # @return [Hashlike] this hashlike, updated
488
+ #
489
+ # @overload hsh.update(other_hash){|key, oldval, newval| block} -> hsh
490
+ # Adds the contents of +other_hash+ to +hsh+. The value of each duplicate key
491
+ # is determined by calling the block with the key, its value in +hsh+ and its
492
+ # value in +other_hash+.
493
+ # @param other_hash [Hash, Hashlike] the hash to merge (it wins)
494
+ # @yield [Object, Object, Object] called if key exists in each +hsh+
495
+ # @return [Hashlike] this hashlike, updated
496
+ #
497
+ def update(other_hash)
498
+ raise TypeError, "can't convert #{other_hash.nil? ? 'nil' : other_hash.class} into Hash" unless other_hash.respond_to?(:each_pair)
499
+ other_hash.each_pair do |key, val|
500
+ if block_given? && has_key?(key)
501
+ val = yield(key, val, self[key])
502
+ end
503
+ self[key] = val
504
+ end
505
+ self
506
+ end
507
+
508
+ #
509
+ # Returns a new hashlike containing the contents of +other_hash+ and the
510
+ # contents of +hsh+. If no block is specified, the value for entries with
511
+ # duplicate keys will be that of +other_hash+. Otherwise the value for each
512
+ # duplicate key is determined by calling the block with the key, its value in
513
+ # +hsh+ and its value in +other_hash+.
514
+ #
515
+ # @example
516
+ # h1 = { :a => 100, :b => 200 }
517
+ # h2 = { :b => 254, :c => 300 }
518
+ # h1.merge(h2)
519
+ # # => { :a=>100, :b=>254, :c=>300 }
520
+ # h1.merge(h2){|key, oldval, newval| newval - oldval}
521
+ # # => { :a => 100, :b => 54, :c => 300 }
522
+ # h1
523
+ # # => { :a => 100, :b => 200 }
524
+ #
525
+ # @overload hsh.merge(other_hash) -> hsh
526
+ # Adds the contents of +other_hash+ to +hsh+. Entries with duplicate keys are
527
+ # overwritten with the values from +other_hash+
528
+ # @param other_hash [Hash, Hashlike] the hash to merge (it wins)
529
+ # @return [Hashlike] a new merged hashlike
530
+ #
531
+ # @overload hsh.merge(other_hash){|key, oldval, newval| block} -> hsh
532
+ # Adds the contents of +other_hash+ to +hsh+. The value of each duplicate key
533
+ # is determined by calling the block with the key, its value in +hsh+ and its
534
+ # value in +other_hash+.
535
+ # @param other_hash [Hash, Hashlike] the hash to merge (it wins)
536
+ # @yield [Object, Object, Object] called if key exists in each +hsh+
537
+ # @return [Hashlike] a new merged hashlike
538
+ #
539
+ def merge(*args, &block)
540
+ self.dup.update(*args, &block)
541
+ end
542
+
543
+ #
544
+ # Deletes every key-value pair from +hsh+ for which +block+ evaluates truthy
545
+ # (equivalent to Hashlike#delete_if), but returns nil if no changes were made.
546
+ #
547
+ # @example
548
+ # hsh = { :a => 100, :b => 200, :c => 300 }
549
+ # hsh.delete_if{|key, val| key.to_s >= "b" } # => { :a => 100 }
550
+ #
551
+ # hsh = { :a => 100, :b => 200, :c => 300 }
552
+ # hsh.delete_if{|key, val| key.to_s >= "z" } # nil
553
+ #
554
+ # @overload hsh.reject!{|key, val| block } -> hsh or nil
555
+ # Deletes every key-value pair from +hsh+ for which +block+ evaluates truthy.
556
+ # @return [Hashlike, nil]
557
+ #
558
+ # @overload hsh.reject! -> an_enumerator
559
+ # with no block, returns a raw enumerator
560
+ # @return [Enumerator]
561
+ #
562
+ def reject!(&block)
563
+ return enum_for(:reject!) unless block_given?
564
+ changed = false
565
+ each_pair do |key, val|
566
+ if yield(*[key, val].take(block.arity))
567
+ changed = true
568
+ delete(key)
569
+ end
570
+ end
571
+ changed ? self : nil
572
+ end
573
+
574
+ #
575
+ # Deletes every key-value pair from +hsh+ for which +block+ evaluates falsy
576
+ # (equivalent to Hashlike#keep_if), but returns nil if no changes were made.
577
+ #
578
+ # @example
579
+ # hsh = { :a => 100, :b => 200, :c => 300 }
580
+ # hsh.select!{|key, val| key.to_s >= "b" } # => { :b => 200, :c => 300 }
581
+ #
582
+ # hsh = { :a => 100, :b => 200, :c => 300 }
583
+ # hsh.select!{|key, val| key.to_s >= "a" } # => { :a => 100, :b => 200, :c => 300 }
584
+ #
585
+ # @overload hsh.select!{|key, val| block } -> hsh or nil
586
+ # Deletes every key-value pair from +hsh+ for which +block+ evaluates falsy.
587
+ # @return [Hashlike]
588
+ #
589
+ # @overload hsh.select! -> an_enumerator
590
+ # with no block, returns a raw enumerator
591
+ # @return [Enumerator]
592
+ #
593
+ def select!(&block)
594
+ return enum_for(:select!) unless block_given?
595
+ changed = false
596
+ each_pair do |key, val|
597
+ if not yield(*[key, val].take(block.arity))
598
+ changed = true
599
+ delete(key)
600
+ end
601
+ end
602
+ changed ? self : nil
603
+ end
604
+
605
+ #
606
+ # Deletes every key-value pair from +hsh+ for which +block+ evaluates truthy.
607
+ #
608
+ # If no block is given, an enumerator is returned instead.
609
+ #
610
+ # @example
611
+ # hsh = { :a => 100, :b => 200, :c => 300 }
612
+ # hsh.delete_if{|key, val| key.to_s >= "b" } # => { :a => 100 }
613
+ #
614
+ # hsh = { :a => 100, :b => 200, :c => 300 }
615
+ # hsh.delete_if{|key, val| key.to_s >= "z" } # => { :a => 100, :b => 200, :c => 300 }
616
+ #
617
+ # @overload hsh.delete_if{|key, val| block } -> hsh
618
+ # Deletes every key-value pair from +hsh+ for which +block+ evaluates truthy.
619
+ # @return [Hashlike]
620
+ #
621
+ # @overload hsh.delete_if -> an_enumerator
622
+ # with no block, returns a raw enumerator
623
+ # @return [Enumerator]
624
+ #
625
+ def delete_if(&block)
626
+ return enum_for(:delete_if) unless block_given?
627
+ reject!(&block)
628
+ self
629
+ end
630
+
631
+ #
632
+ # Deletes every key-value pair from +hsh+ for which +block+ evaluates falsy.
633
+ #
634
+ # If no block is given, an enumerator is returned instead.
635
+ #
636
+ # @example
637
+ # hsh = { :a => 100, :b => 200, :c => 300 }
638
+ # hsh.keep_if{|key, val| key.to_s >= "b" } # => { :b => 200, :c => 300 }
639
+ #
640
+ # hsh = { :a => 100, :b => 200, :c => 300 }
641
+ # hsh.keep_if{|key, val| key.to_s >= "a" } # => { :a => 100, :b => 200, :c => 300 }
642
+ #
643
+ # @overload hsh.keep_if{|key, val| block } -> hsh
644
+ # Deletes every key-value pair from +hsh+ for which +block+ evaluates falsy.
645
+ # @return [Hashlike]
646
+ #
647
+ # @overload hsh.keep_if -> an_enumerator
648
+ # with no block, returns a raw enumerator
649
+ # @return [Enumerator]
650
+ #
651
+ def keep_if(&block)
652
+ return enum_for(:keep_if) unless block_given?
653
+ select!(&block)
654
+ self
655
+ end
656
+
657
+ #
658
+ # Overrides the implementation in Enumerable, which iterates on keys, not
659
+ # key/value pairs
660
+ #
661
+ module OverrideEnumerable
662
+ #
663
+ # Deletes every key-value pair from +hsh+ for which +block+ evaluates to
664
+ # true (similar to Hashlike#delete_if), but works on (and returns) a copy of
665
+ # the +hsh+. Equivalent to <tt>hsh.dup.delete_if</tt>.
666
+ #
667
+ # @example
668
+ # hsh = { :a => 100, :b => 200, :c => 300 }
669
+ # hsh.reject{|key, val| key.to_s >= "b" } # => { :a => 100 }
670
+ # hsh # => { :a => 100, :b => 200, :c => 300 }
671
+ #
672
+ # hsh = { :a => 100, :b => 200, :c => 300 }
673
+ # hsh.reject{|key, val| key.to_s >= "z" } # => { :a => 100, :b => 200, :c => 300 }
674
+ # hsh # => { :a => 100, :b => 200, :c => 300 }
675
+ #
676
+ # @overload hsh.reject{|key, val| block } -> new_hashlike
677
+ # Deletes every key-value pair from +hsh+ for which +block+ evaluates truthy.
678
+ # @return [Hashlike]
679
+ #
680
+ # @overload hsh.reject -> an_enumerator
681
+ # with no block, returns a raw enumerator
682
+ # @return [Enumerator]
683
+ #
684
+ # Overrides the implementation in Enumerable, which does the keys wrong.
685
+ #
686
+ def reject(&block)
687
+ return enum_for(:reject) unless block_given?
688
+ self.dup.delete_if(&block)
689
+ end
690
+
691
+ #
692
+ # Deletes every key-value pair from +hsh+ for which +block+ evaluates to
693
+ # false (similar to Hashlike#keep_if), but works on (and returns) a copy of
694
+ # the +hsh+. Equivalent to <tt>hsh.dup.keep_if</tt>.
695
+ #
696
+ # @example
697
+ # hsh = { :a => 100, :b => 200, :c => 300 }
698
+ # hsh.select{|key, val| key.to_s >= "b" } # => { :b => 200, :c => 300 }
699
+ # hsh # => { :a => 100, :b => 200, :c => 300 }
700
+ #
701
+ # hsh = { :a => 100, :b => 200, :c => 300 }
702
+ # hsh.select{|key, val| key.to_s >= "z" } # => { }
703
+ # hsh # => { :a => 100, :b => 200, :c => 300 }
704
+ #
705
+ # @overload hsh.select{|key, val| block } -> new_hashlike
706
+ # Deletes every key-value pair from +hsh+ for which +block+ evaluates truthy.
707
+ # @return [Hashlike]
708
+ #
709
+ # @overload hsh.select -> an_enumerator
710
+ # with no block, returns a raw enumerator
711
+ # @return [Enumerator]
712
+ #
713
+ # Overrides the implementation in Enumerable, which does the keys wrong.
714
+ #
715
+ def select(&block)
716
+ return enum_for(:select) unless block_given?
717
+ self.dup.keep_if(&block)
718
+ end
719
+ end
720
+
721
+ #
722
+ # Removes all key-value pairs from +hsh+.
723
+ #
724
+ # @example
725
+ # hsh = { :a => 100, :b => 200 } # => { :a => 100, :b => 200 }
726
+ # hsh.clear # => {}
727
+ #
728
+ # @return [Hashlike] this hashlike, emptied
729
+ #
730
+ def clear
731
+ each_pair{|k,v| delete(k) }
732
+ end
733
+
734
+ #
735
+ # Returns a hash with each key set to its associated value.
736
+ #
737
+ # @example
738
+ # my_hshlike = MyHashlike.new
739
+ # my_hshlike[:a] = 100; my_hshlike[:b] = 200
740
+ # my_hshlike.to_hash # => { :a => 100, :b => 200 }
741
+ #
742
+ # @return [Hash] a new Hash instance, with each key set to its associated value.
743
+ #
744
+ def to_hash
745
+ {}.tap{|hsh| each_pair{|key, val| hsh[key] = val } }
746
+ end
747
+
748
+ #
749
+ # Returns a new hash created by using +hsh+'s values as keys, and the keys as
750
+ # values. If +hsh+ has duplicate values, the result will contain only one of
751
+ # them as a key -- which one is not predictable.
752
+ #
753
+ # @example
754
+ # hsh = { :n => 100, :m => 100, :y => 300, :d => 200, :a => 0 }
755
+ # hsh.invert # => { 0 => :a, 100 => :m, 200 => :d, 300 => :y }
756
+ #
757
+ # @return [Hash] a new hash, with values for keys and vice-versa
758
+ #
759
+ def invert
760
+ to_hash.invert
761
+ end
762
+
763
+ #
764
+ # Returns a new array that is a one-dimensional flattening of this
765
+ # hashlike. That is, for every key or value that is an array, extract its
766
+ # elements into the new array. Unlike Array#flatten, this method does not
767
+ # flatten recursively by default; pass +nil+ explicitly to flatten
768
+ # recursively. The optional level argument determines the level of
769
+ # recursion to flatten.
770
+ #
771
+ # @example
772
+ # hsh = {1=> "one", 2 => [2,"two"], 3 => "three"}
773
+ # hsh.flatten # => [1, "one", 2, [2, "two"], 3, "three"]
774
+ # hsh.flatten(2) # => [1, "one", 2, 2, "two", 3, "three"]
775
+ #
776
+ # @example with deep nesting
777
+ # hsh = { [1, 2, [3, 4]] => [1, [2, 3, [4, 5, 6]]] }
778
+ # hsh.flatten
779
+ # # => [[1, 2, [3, 4]], [1, [2, 3, [4, 5, 6]]]]
780
+ # hsh.flatten(0)
781
+ # # => [[[1, 2, [3, 4]], [1, [2, 3, [4, 5, 6]]]]]
782
+ # hsh.flatten(1)
783
+ # # => [[1, 2, [3, 4]], [1, [2, 3, [4, 5, 6]]]]
784
+ # hsh.flatten(2)
785
+ # # => [1, 2, [3, 4], 1, [2, 3, [4, 5, 6]]]
786
+ # hsh.flatten(3)
787
+ # # => [1, 2, 3, 4, 1, 2, 3, [4, 5, 6]]
788
+ # hsh.flatten(4)
789
+ # # => [1, 2, 3, 4, 1, 2, 3, 4, 5, 6]
790
+ # hsh.flatten.flatten
791
+ # # => [1, 2, 3, 4, 1, 2, 3, 4, 5, 6]
792
+ #
793
+ # @example nil level means complete flattening
794
+ # hsh.flatten(nil)
795
+ # # => [1, 2, 3, 4, 1, 2, 3, 4, 5, 6]
796
+ #
797
+ # @param level [Integer] the level of recursion to flatten, 0 by default.
798
+ # @return [Array] the flattened key-value array.
799
+ #
800
+ def flatten(*args)
801
+ to_hash.flatten(*args)
802
+ end
803
+
804
+ def self.included(base)
805
+ base.class_eval do
806
+ base.send(:include, EnumerateFromKeys) unless base.method_defined?(:each_pair)
807
+ unless base.include?(Enumerable)
808
+ base.send(:include, Enumerable)
809
+ base.send(:include, OverrideEnumerable)
810
+ end
811
+
812
+ # included here so they win out over Enumerable
813
+ alias_method :include?, :has_key?
814
+ alias_method :key?, :has_key?
815
+ alias_method :member?, :has_key?
816
+ alias_method :value?, :has_value?
817
+ alias_method :merge!, :update
818
+ alias_method :size, :length
819
+ end
820
+ end
821
+
822
+ end
823
+ end
824
+