hashery 1.0.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.
@@ -0,0 +1,279 @@
1
+ require 'facets/basicobject'
2
+ #require 'facets/hash/to_h'
3
+ #require 'facets/kernel/object_class'
4
+ #require 'facets/kernel/object_hexid'
5
+
6
+ # = OpenObject
7
+ #
8
+ # OpenObject is very similar to Ruby's own OpenStruct, but it offers some
9
+ # advantages. With OpenStruct, slots with the same name as predefined
10
+ # Object methods can not be used. With OpenObject, almost any slot can be
11
+ # defined. OpenObject is a subclass of BasicObject to ensure all method
12
+ # slots, except those that are absolutely essential, are open for use.
13
+ #
14
+ #--
15
+ # If you wish to pass an OpenObject to a routine that normal takes a Hash,
16
+ # but are uncertain it can handle the distictions properly you can convert
17
+ # easily to a Hash using #as_hash! and the result will automatically be
18
+ # converted back to an OpenObject on return.
19
+ #
20
+ # o = OpenObject.new(:a=>1,:b=>2)
21
+ # o.as_hash!{ |h| h.update(:a=>6) }
22
+ # o #=> #<OpenObject {:a=>6,:b=>2}>
23
+ #++
24
+ #
25
+ # Unlike a Hash, all OpenObject's keys are symbols and all keys are converted
26
+ # to such using #to_sym on the fly.
27
+
28
+ class OpenObject < BasicObject
29
+
30
+ #PUBLIC_METHODS = /(^__|^instance_|^object_|^\W|^as$|^send$|^class$|\?$)/
31
+ #protected(*public_instance_methods.select{ |m| m !~ PUBLIC_METHODS })
32
+
33
+ def self.[](hash=nil)
34
+ new(hash)
35
+ end
36
+
37
+ # Inititalizer for OpenObject is slightly different than that of Hash.
38
+ # It does not take a default parameter, but an initial priming Hash,
39
+ # like OpenStruct. The initializer can still take a default block
40
+ # however. To set the default value use <code>#default!(value)</code>.
41
+ #
42
+ # OpenObject.new(:a=>1).default!(0)
43
+ #
44
+ def initialize(hash=nil, &yld)
45
+ @hash = Hash.new(&yld)
46
+ if hash
47
+ hash.each{ |k,v| store(k,v) }
48
+ end
49
+ end
50
+
51
+ #
52
+ def initialize_copy( orig )
53
+ orig.each{ |k,v| store(k,v) }
54
+ end
55
+
56
+ # Object inspection.
57
+ # TODO: Need to get __class__ and __id__ in hex form.
58
+ def inspect
59
+ #@hash.inspect
60
+ hexid = __id__
61
+ klass = "OpenObject" # __class__
62
+ "#<#{klass}:#{hexid} #{@hash.inspect}>"
63
+ end
64
+
65
+ # Convert to an associative array.
66
+ def to_a
67
+ @hash.to_a
68
+ end
69
+
70
+ #
71
+ def to_h
72
+ @hash.dup
73
+ end
74
+
75
+ #
76
+ def to_hash
77
+ @hash.dup
78
+ end
79
+
80
+ #
81
+ def to_openobject
82
+ self
83
+ end
84
+
85
+ # Convert to an assignment procedure.
86
+ def to_proc(response=false)
87
+ hash = @hash
88
+ if response
89
+ lambda do |o|
90
+ hash.each do |k,v|
91
+ o.__send__("#{k}=", v) rescue nil
92
+ end
93
+ end
94
+ else
95
+ lambda do |o|
96
+ hash.each{ |k,v| o.__send__("#{k}=", v) }
97
+ end
98
+ end
99
+ end
100
+
101
+ # NOT SURE ABOUT THIS
102
+ def as_hash
103
+ @hash
104
+ end
105
+
106
+ # Is a given +key+ defined?
107
+ def key?(key)
108
+ @hash.key?(key.to_sym)
109
+ end
110
+
111
+ #
112
+ def is_a?(klass)
113
+ return true if klass == Hash # TODO: Is this wise? How to fake a subclass?
114
+ return true if klass == OpenObject
115
+ false
116
+ end
117
+
118
+ # Iterate over each key-value pair.
119
+ def each(&yld)
120
+ @hash.each(&yld)
121
+ end
122
+
123
+ # Set the default value.
124
+ def default!(default)
125
+ @hash.default = default
126
+ end
127
+
128
+ # Check equality.
129
+ def ==( other )
130
+ case other
131
+ when OpenObject
132
+ @hash == other.as_hash
133
+ when Hash
134
+ @hash == other
135
+ else
136
+ if other.respond_to?(:to_hash)
137
+ @hash == other.to_hash
138
+ else
139
+ false
140
+ end
141
+ end
142
+ end
143
+
144
+ #
145
+ def eql?( other )
146
+ case other
147
+ when OpenObject
148
+ @hash.eql?(other.as_hash)
149
+ else
150
+ false
151
+ end
152
+ end
153
+
154
+ #
155
+ def <<(x)
156
+ case x
157
+ when Hash
158
+ @hash.update(x)
159
+ when Array
160
+ x.each_slice(2) do |(k,v)|
161
+ @hash[k] = v
162
+ end
163
+ end
164
+ end
165
+
166
+ #
167
+ def []=(k,v)
168
+ @hash[k.to_sym] = v
169
+ end
170
+
171
+ #
172
+ def [](k)
173
+ @hash[k.to_sym]
174
+ end
175
+
176
+ #
177
+ def merge!(other)
178
+ OpenObject.new(@hash.merge!(other))
179
+ end
180
+
181
+ #
182
+ def update!(other)
183
+ @hash.update(other)
184
+ self
185
+ end
186
+
187
+ protected
188
+
189
+ def store(k, v)
190
+ @hash.store(k.to_sym, v)
191
+ end
192
+
193
+ def fetch(k, *d, &b)
194
+ @hash.fetch(k.to_sym, *d, &b)
195
+ end
196
+
197
+ #def as_hash!
198
+ # Functor.new do |op,*a,&b|
199
+ # result = @hash.__send__(op,*a,&b)
200
+ # case result
201
+ # when Hash
202
+ # OpenObject.new(result)
203
+ # else
204
+ # result
205
+ # end
206
+ # end
207
+ #end
208
+
209
+ #def define_slot(key, value=nil)
210
+ # @hash[key.to_sym] = value
211
+ #end
212
+
213
+ #def protect_slot( key )
214
+ # (class << self; self; end).class_eval {
215
+ # protected key rescue nil
216
+ # }
217
+ #end
218
+
219
+ def method_missing(sym, *args, &blk)
220
+ type = sym.to_s[-1,1]
221
+ key = sym.to_s.sub(/[=?!]$/,'').to_sym
222
+ case type
223
+ when '='
224
+ store(key, args[0])
225
+ when '!'
226
+ @hash.__send__(key, *args, &blk)
227
+ # if key?(key)
228
+ # fetch(key)
229
+ # else
230
+ # store(key, OpenObject.new)
231
+ # end
232
+ when '?'
233
+ fetch(key)
234
+ else
235
+ fetch(key)
236
+ end
237
+ end
238
+
239
+ end
240
+
241
+ # Core Extensions
242
+
243
+ class Hash
244
+ # Convert a Hash into an OpenObject.
245
+ def to_openobject
246
+ OpenObject[self]
247
+ end
248
+ end
249
+
250
+ =begin
251
+ class NilClass
252
+ # Nil converts to an empty OpenObject.
253
+ def to_openobject
254
+ OpenObject.new
255
+ end
256
+ end
257
+
258
+ class Proc
259
+ # Translates a Proc into an OpenObject. By droping an OpenObject into
260
+ # the Proc, the resulting assignments incured as the procedure is
261
+ # evaluated produce the OpenObject. This technique is simlar to that
262
+ # of MethodProbe.
263
+ #
264
+ # p = lambda { |x|
265
+ # x.word = "Hello"
266
+ # }
267
+ # o = p.to_openobject
268
+ # o.word #=> "Hello"
269
+ #
270
+ # NOTE The Proc must have an arity of one --no more and no less.
271
+ def to_openobject
272
+ raise ArgumentError, 'bad arity for converting Proc to openobject' if arity != 1
273
+ o = OpenObject.new
274
+ self.call( o )
275
+ o
276
+ end
277
+ end
278
+ =end
279
+
@@ -0,0 +1,417 @@
1
+ # = OrderedHash
2
+ #
3
+ # The OrderedHash class is a Hash that preserves order.
4
+ # So it has some array-like extensions also. By defualt
5
+ # an OrderedHash object preserves insertion order, but any
6
+ # order can be specified including alphabetical key order.
7
+ #
8
+ # == Usage
9
+ #
10
+ # Just require this file and use OrderedHash instead of Hash.
11
+ #
12
+ # # You can do simply
13
+ # hsh = OrderedHash.new
14
+ # hsh['z'] = 1
15
+ # hsh['a'] = 2
16
+ # hsh['c'] = 3
17
+ # p hsh.keys #=> ['z','a','c']
18
+ #
19
+ # # or using OrderedHash[] method
20
+ # hsh = OrderedHash['z', 1, 'a', 2, 'c', 3]
21
+ # p hsh.keys #=> ['z','a','c']
22
+ #
23
+ # # but this don't preserve order
24
+ # hsh = OrderedHash['z'=>1, 'a'=>2, 'c'=>3]
25
+ # p hsh.keys #=> ['a','c','z']
26
+ #
27
+ # # OrderedHash has useful extensions: push, pop and unshift
28
+ # p hsh.push('to_end', 15) #=> true, key added
29
+ # p hsh.push('to_end', 30) #=> false, already - nothing happen
30
+ # p hsh.unshift('to_begin', 50) #=> true, key added
31
+ # p hsh.unshift('to_begin', 60) #=> false, already - nothing happen
32
+ # p hsh.keys #=> ["to_begin", "a", "c", "z", "to_end"]
33
+ # p hsh.pop #=> ["to_end", 15], if nothing remains, return nil
34
+ # p hsh.keys #=> ["to_begin", "a", "c", "z"]
35
+ # p hsh.shift #=> ["to_begin", 30], if nothing remains, return nil
36
+ #
37
+ # == Usage Notes
38
+ #
39
+ # * You can use #order_by to set internal sort order.
40
+ # * #<< takes a two element [k,v] array and inserts.
41
+ # * Use ::auto which creates Dictionay sub-entries as needed.
42
+ # * And ::alpha which creates a new OrderedHash sorted by key.
43
+ #
44
+ # == Acknowledgments
45
+ #
46
+ # * Andrew Johnson (merge, to_a, inspect, shift and Hash[])
47
+ # * Jeff Sharpe (reverse and reverse!)
48
+ # * Thomas Leitner (has_key? and key?)
49
+ #
50
+ # Ported from OrderHash 2.0, Copyright (c) 2005 Jan Molic
51
+
52
+ class OrderedHash
53
+
54
+ include Enumerable
55
+
56
+ class << self
57
+ #--
58
+ # TODO is this needed? Doesn't the super class do this?
59
+ #++
60
+
61
+ def [](*args)
62
+ hsh = new
63
+ if Hash === args[0]
64
+ hsh.replace(args[0])
65
+ elsif (args.size % 2) != 0
66
+ raise ArgumentError, "odd number of elements for Hash"
67
+ else
68
+ while !args.empty?
69
+ hsh[args.shift] = args.shift
70
+ end
71
+ end
72
+ hsh
73
+ end
74
+
75
+ # Like #new but the block sets the order.
76
+ #
77
+ def new_by(*args, &blk)
78
+ new(*args).order_by(&blk)
79
+ end
80
+
81
+ # Alternate to #new which creates a dictionary sorted by key.
82
+ #
83
+ # d = OrderedHash.alpha
84
+ # d["z"] = 1
85
+ # d["y"] = 2
86
+ # d["x"] = 3
87
+ # d #=> {"x"=>3,"y"=>2,"z"=>2}
88
+ #
89
+ # This is equivalent to:
90
+ #
91
+ # OrderedHash.new.order_by { |key,value| key }
92
+
93
+ def alpha(*args, &block)
94
+ new(*args, &block).order_by_key
95
+ end
96
+
97
+ # Alternate to #new which auto-creates sub-dictionaries as needed.
98
+ #
99
+ # d = OrderedHash.auto
100
+ # d["a"]["b"]["c"] = "abc" #=> { "a"=>{"b"=>{"c"=>"abc"}}}
101
+ #
102
+ def auto(*args)
103
+ #AutoOrderedHash.new(*args)
104
+ leet = lambda { |hsh, key| hsh[key] = new(&leet) }
105
+ new(*args, &leet)
106
+ end
107
+ end
108
+
109
+ # New Dictiionary.
110
+
111
+ def initialize(*args, &blk)
112
+ @order = []
113
+ @order_by = nil
114
+ if blk
115
+ dict = self # This ensure autmatic key entry effect the
116
+ oblk = lambda{ |hsh, key| blk[dict,key] } # dictionary rather then just the interal hash.
117
+ @hash = Hash.new(*args, &oblk)
118
+ else
119
+ @hash = Hash.new(*args)
120
+ end
121
+ end
122
+
123
+ def order
124
+ reorder if @order_by
125
+ @order
126
+ end
127
+
128
+ # Keep dictionary sorted by a specific sort order.
129
+
130
+ def order_by( &block )
131
+ @order_by = block
132
+ order
133
+ self
134
+ end
135
+
136
+ # Keep dictionary sorted by key.
137
+ #
138
+ # d = OrderedHash.new.order_by_key
139
+ # d["z"] = 1
140
+ # d["y"] = 2
141
+ # d["x"] = 3
142
+ # d #=> {"x"=>3,"y"=>2,"z"=>2}
143
+ #
144
+ # This is equivalent to:
145
+ #
146
+ # OrderedHash.new.order_by { |key,value| key }
147
+ #
148
+ # The initializer OrderedHash#alpha also provides this.
149
+
150
+ def order_by_key
151
+ @order_by = lambda { |k,v| k }
152
+ order
153
+ self
154
+ end
155
+
156
+ # Keep dictionary sorted by value.
157
+ #
158
+ # d = OrderedHash.new.order_by_value
159
+ # d["z"] = 1
160
+ # d["y"] = 2
161
+ # d["x"] = 3
162
+ # d #=> {"x"=>3,"y"=>2,"z"=>2}
163
+ #
164
+ # This is equivalent to:
165
+ #
166
+ # OrderedHash.new.order_by { |key,value| value }
167
+
168
+ def order_by_value
169
+ @order_by = lambda { |k,v| v }
170
+ order
171
+ self
172
+ end
173
+
174
+ #
175
+ def reorder
176
+ if @order_by
177
+ assoc = @order.collect{ |k| [k,@hash[k]] }.sort_by(&@order_by)
178
+ @order = assoc.collect{ |k,v| k }
179
+ end
180
+ @order
181
+ end
182
+
183
+ #def ==( hsh2 )
184
+ # return false if @order != hsh2.order
185
+ # super hsh2
186
+ #end
187
+
188
+ def ==(hsh2)
189
+ if hsh2.is_a?( OrderedHash )
190
+ @order == hsh2.order &&
191
+ @hash == hsh2.instance_variable_get("@hash")
192
+ else
193
+ false
194
+ end
195
+ end
196
+
197
+ def [] k
198
+ @hash[ k ]
199
+ end
200
+
201
+ def fetch(k, *a, &b)
202
+ @hash.fetch(k, *a, &b)
203
+ end
204
+
205
+ # Store operator.
206
+ #
207
+ # h[key] = value
208
+ #
209
+ # Or with additional index.
210
+ #
211
+ # h[key,index] = value
212
+
213
+ def []=(k, i=nil, v=nil)
214
+ if v
215
+ insert(i,k,v)
216
+ else
217
+ store(k,i)
218
+ end
219
+ end
220
+
221
+ def insert( i,k,v )
222
+ @order.insert( i,k )
223
+ @hash.store( k,v )
224
+ end
225
+
226
+ def store( a,b )
227
+ @order.push( a ) unless @hash.has_key?( a )
228
+ @hash.store( a,b )
229
+ end
230
+
231
+ def clear
232
+ @order = []
233
+ @hash.clear
234
+ end
235
+
236
+ def delete( key )
237
+ @order.delete( key )
238
+ @hash.delete( key )
239
+ end
240
+
241
+ def each_key
242
+ order.each { |k| yield( k ) }
243
+ self
244
+ end
245
+
246
+ def each_value
247
+ order.each { |k| yield( @hash[k] ) }
248
+ self
249
+ end
250
+
251
+ def each
252
+ order.each { |k| yield( k,@hash[k] ) }
253
+ self
254
+ end
255
+ alias each_pair each
256
+
257
+ def delete_if
258
+ order.clone.each { |k| delete k if yield(k,@hash[k]) }
259
+ self
260
+ end
261
+
262
+ def values
263
+ ary = []
264
+ order.each { |k| ary.push @hash[k] }
265
+ ary
266
+ end
267
+
268
+ def keys
269
+ order
270
+ end
271
+
272
+ def invert
273
+ hsh2 = self.class.new
274
+ order.each { |k| hsh2[@hash[k]] = k }
275
+ hsh2
276
+ end
277
+
278
+ def reject(&block)
279
+ self.dup.delete_if(&block)
280
+ end
281
+
282
+ def reject!( &block )
283
+ hsh2 = reject(&block)
284
+ self == hsh2 ? nil : hsh2
285
+ end
286
+
287
+ def replace(hsh2)
288
+ case hsh2
289
+ when Hash
290
+ @order = hsh2.keys
291
+ @hash = hsh2
292
+ else
293
+ @order = hsh2.order
294
+ @hash = hsh2.hash
295
+ end
296
+ reorder
297
+ end
298
+
299
+ def shift
300
+ key = order.first
301
+ key ? [key,delete(key)] : super
302
+ end
303
+
304
+ def unshift( k,v )
305
+ unless @hash.include?( k )
306
+ @order.unshift( k )
307
+ @hash.store( k,v )
308
+ true
309
+ else
310
+ false
311
+ end
312
+ end
313
+
314
+ def <<(kv)
315
+ push(*kv)
316
+ end
317
+
318
+ def push( k,v )
319
+ unless @hash.include?( k )
320
+ @order.push( k )
321
+ @hash.store( k,v )
322
+ true
323
+ else
324
+ false
325
+ end
326
+ end
327
+
328
+ def pop
329
+ key = order.last
330
+ key ? [key,delete(key)] : nil
331
+ end
332
+
333
+ def inspect
334
+ ary = []
335
+ each {|k,v| ary << k.inspect + "=>" + v.inspect}
336
+ '{' + ary.join(", ") + '}'
337
+ end
338
+
339
+ def dup
340
+ a = []
341
+ each{ |k,v| a << k; a << v }
342
+ self.class[*a]
343
+ end
344
+
345
+ def update( hsh2 )
346
+ hsh2.each { |k,v| self[k] = v }
347
+ reorder
348
+ self
349
+ end
350
+ alias :merge! update
351
+
352
+ def merge( hsh2 )
353
+ self.dup.update(hsh2)
354
+ end
355
+
356
+ def select
357
+ ary = []
358
+ each { |k,v| ary << [k,v] if yield k,v }
359
+ ary
360
+ end
361
+
362
+ def reverse!
363
+ @order.reverse!
364
+ self
365
+ end
366
+
367
+ def reverse
368
+ dup.reverse!
369
+ end
370
+
371
+ #
372
+ def first(x=nil)
373
+ return @hash[order.first] unless x
374
+ order.first(x).collect { |k| @hash[k] }
375
+ end
376
+
377
+ #
378
+ def last(x=nil)
379
+ return @hash[order.last] unless x
380
+ order.last(x).collect { |k| @hash[k] }
381
+ end
382
+
383
+ def length
384
+ @order.length
385
+ end
386
+ alias :size :length
387
+
388
+ def empty?
389
+ @hash.empty?
390
+ end
391
+
392
+ def has_key?(key)
393
+ @hash.has_key?(key)
394
+ end
395
+
396
+ def key?(key)
397
+ @hash.key?(key)
398
+ end
399
+
400
+ def to_a
401
+ ary = []
402
+ each { |k,v| ary << [k,v] }
403
+ ary
404
+ end
405
+
406
+ def to_s
407
+ self.to_a.to_s
408
+ end
409
+
410
+ def to_hash
411
+ @hash.dup
412
+ end
413
+
414
+ def to_h
415
+ @hash.dup
416
+ end
417
+ end