gorillib 0.0.8 → 0.1.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.
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
+