hashery 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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