hash_magic 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/hash_magic.rb ADDED
@@ -0,0 +1,359 @@
1
+ # The aim of this gem:
2
+ # 1. Don't extend Hash
3
+ # 2. Provide SlashedHash
4
+ # 3. Provide OrderedHash
5
+ # 4. Make a SlashedHash orderable
6
+ # 5. Make an OrderedHash convert correctly to an ordered SlashedHash
7
+
8
+ class Hash
9
+ def slashed
10
+ SlashedHash.new(self)
11
+ end
12
+ def ordered(*keys_in_order)
13
+ OrderedHash.new(*keys_in_order).update!(self)
14
+ end
15
+ end
16
+
17
+ # This provides all methods to act like a standard hash, EXCEPT for :[], :[]=, :clear, :delete, :empty?, :inspect, :keys and :rehash
18
+ module StandardHashMethodsInRuby # :nodoc:
19
+ # * * * * * * * * * * * * * * * * * * * * #
20
+ begin # STANDARD HASH METHODS - rewritten #
21
+ # * * * * * * * * * * * * * * * * * * * * #
22
+ include Enumerable
23
+
24
+ def store(key,value)
25
+ self[key] = value
26
+ end
27
+
28
+ def default(key)
29
+ return @default if !@default.nil?
30
+ @default_proc.is_a?(Proc) ? @default_proc.call(self, k) : nil
31
+ end
32
+
33
+ def delete=(value)
34
+ @value = value
35
+ end
36
+
37
+ def delete_if(&block)
38
+ each do |k,v|
39
+ delete(k) if block.call(k,v)
40
+ end
41
+ end
42
+
43
+ def each(&block)
44
+ each_key {|k| yield(k,self[k])}
45
+ end
46
+
47
+ def each_key(&block)
48
+ keys.each {|k| yield(k)}
49
+ end
50
+
51
+ def each_pair(&block)
52
+ each(&block)
53
+ end
54
+
55
+ def each_value(&block)
56
+ keys.each {|k| yield(self[k])}
57
+ end
58
+
59
+ def has_key?(key)
60
+ keys.include?(key)
61
+ end
62
+ alias :include? :has_key?
63
+ alias :key? :has_key?
64
+ alias :member? :has_key?
65
+
66
+ def has_value?(value)
67
+ any? {|k,v| v == value}
68
+ end
69
+ alias :value? :has_value?
70
+
71
+ def index(value)
72
+ keys[values.index(value)]
73
+ end
74
+
75
+ def invert
76
+ raise NotImplemented, "#{self.class.name} does not support invert"
77
+ end
78
+
79
+ def length
80
+ keys.length
81
+ end
82
+ alias :size :length
83
+
84
+ def to_a
85
+ inject([]) {|a,kv| a << kv; a}
86
+ end
87
+
88
+ def inject(obj,&block)
89
+ each {|k,v| obj = yield(obj,[k,v])}
90
+ obj
91
+ end
92
+
93
+ def merge(hsh,&block)
94
+ block_given? ? dup.merge!(hsh,&block) : dup.merge!(hsh)
95
+ end
96
+ alias :update :merge
97
+
98
+ def merge!(hsh,&block)
99
+ hsh.each do |k,v|
100
+ if block_given?
101
+ self[k] = block.call(k,self[k],v)
102
+ else
103
+ self[k] = v
104
+ end
105
+ end
106
+ self
107
+ end
108
+ alias :update! :merge!
109
+
110
+ def reject(&block)
111
+ h = dup
112
+ h.delete_if(&block)
113
+ h
114
+ end
115
+
116
+ def reject!(&block)
117
+ deld = false
118
+ each do |k,v|
119
+ if block.call(k,v)
120
+ delete(k)
121
+ deld = true
122
+ end
123
+ end
124
+ deld ? self : nil
125
+ end
126
+
127
+ def replace(hsh)
128
+ clear
129
+ update(hsh)
130
+ end
131
+
132
+ def select(&block)
133
+ inject([]) {|a,(k,v)| a << [k,v] if block.call(k,v); a}
134
+ end
135
+
136
+ def shift
137
+ length > 0 ? [keys[0], delete(keys[0])] : default
138
+ end
139
+
140
+ def sort(&block)
141
+ to_a.sort(&block)
142
+ end
143
+
144
+ def to_hash
145
+ self
146
+ end
147
+
148
+ def values
149
+ a = []
150
+ each_value {|v| a << v}
151
+ a
152
+ end
153
+
154
+ def values_at(*keys)
155
+ keys.collect {|k| self[k]}
156
+ end
157
+
158
+ end
159
+ # * * * * * * * * * * * * * * * * * * * * #
160
+ end
161
+
162
+ # A SlashedHash is a hash whose values can be accessed in the normal manner, or with keys that are slash('/')-separated strings.
163
+ # To get the whole hash as a single flattened level, call SlashedHash#flat. All keys are converted to strings.
164
+ # All end-of-the-chain values are kept in whatever value they are.
165
+ # s = {'a' => 'b', 'c' => {'d' => :e}}.slashed
166
+ # s['a'] #=> 'b'
167
+ # s['c'] #=> {slashed: 'd'=>:e}
168
+ # s['c']['d'] #=> :e
169
+ # s['c/d'] #=> :e
170
+ class SlashedHash < Hash
171
+ def initialize(hsh={})
172
+ raise ArgumentError, "must be a hash or array of slashed values" unless hsh.is_a?(Hash) || hsh.is_a?(Array)
173
+ @constructor = hsh.is_a?(Hash) ? hsh.class : Hash
174
+ @flat = flatten_to_hash(hsh)
175
+ end
176
+
177
+ # Standard Hash methods, plus the overwritten ones
178
+ include StandardHashMethodsInRuby
179
+ # Behaves like the usual Hash#[] method, but you can access nested hash values by composing a single key of the traversing keys joined by '/':
180
+ # hash['c']['d'] # is the same as:
181
+ # hash['c/d']
182
+ def [](key)
183
+ rg = Regexp.new("^#{key}/?")
184
+ start_obj = if @constructor == OrderedHash
185
+ @constructor.new((@flat.instance_variable_get(:@keys_in_order) || []).collect {|e| e.gsub(rg,'')})
186
+ else
187
+ @constructor.new
188
+ end
189
+ v = @flat.has_key?(key) ? @flat[key] : self.class.new(@flat.reject {|k,v| !(k == key || k =~ rg)}.inject(start_obj) {|h,(k,v)| h[k.gsub(rg,'')] = v; h})
190
+ v.is_a?(self.class) && v.empty? ? nil : v
191
+ end
192
+ # Same as above, except sets value rather than retrieving it.
193
+ def []=(key,value)
194
+ @flat.reject! {|k,v| k == key || k =~ Regexp.new("^#{key}/")}
195
+ if value.is_a?(Hash)
196
+ flatten_to_hash(value).each do |hk,hv|
197
+ @flat[key.to_s+'/'+hk.to_s] = hv
198
+ end
199
+ else
200
+ @flat[key.to_s] = value
201
+ end
202
+ end
203
+ def clear # :nodoc:
204
+ @flat.clear
205
+ end
206
+ def fetch(key,default=:ehisehoah0928309q98y30,&block) # :nodoc:
207
+ value = @flat.has_key?(key) ? @flat[key] : self.class.new(@flat.reject {|k,v| !(k == key || k =~ Regexp.new("^#{key}/"))}.inject({}) {|h,(k,v)| h[k.split('/',2)[1]] = v; h})
208
+ if value.is_a?(self.class) && value.empty?
209
+ if default == :ehisehoah0928309q98y30
210
+ if block_given?
211
+ block.call(key)
212
+ else
213
+ raise IndexError
214
+ end
215
+ value
216
+ else
217
+ default
218
+ end
219
+ else
220
+ value
221
+ end
222
+ end
223
+ # You can use slashed keys here, too.
224
+ def delete(key,&block)
225
+ value = @flat.has_key?(key) ? @flat[key] : self.class.new(@flat.reject {|k,v| !(k == key || k =~ Regexp.new("^#{key}/"))}.inject({}) {|h,(k,v)| h[k.split('/',2)[1]] = v; h})
226
+ return block.call(key) if value.is_a?(self.class) && value.empty? && block_given?
227
+ @flat.keys.reject {|k| !(k == key || k =~ Regexp.new("^#{key}/"))}.each {|k| @flat.delete(k)}
228
+ return value
229
+ end
230
+ def empty? # :nodoc:
231
+ @flat.empty?
232
+ end
233
+ # This gives you the slashed key of the value, no matter where the value is in the tree.
234
+ def index(value)
235
+ @flat.index(value)
236
+ end
237
+ def inspect # :nodoc:
238
+ @flat.inspect.insert(1,'slashed: ')
239
+ end
240
+ # This gives you only the top-level keys, no slashes. To get the list of slashed keys, do hash.flat.keys
241
+ def keys
242
+ @flat.inject([]) {|a,(k,v)| a << [k.split('/',2)].flatten[0]; a}.uniq
243
+ end
244
+ # This is rewritten to mean something slightly different than usual: Use this to restructure the hash, for cases when you
245
+ # end up with an array holding several hashes.
246
+ def rehash # :nodoc:
247
+ @flat.rehash
248
+ end
249
+
250
+ # Gives a list of all keys in all levels in the multi-level hash, joined by slashes.
251
+ # {'a'=>{'b'=>'c', 'c'=>'d'}, 'b'=>'c'}.slashed.flat.keys
252
+ # => ['a/b', 'a/c', 'b']
253
+ def flat
254
+ @flat
255
+ end
256
+ # Expands the whole hash to Hash objects ... not useful very often, it seems.
257
+ def expand
258
+ inject({}) {|h,(k,v)| h[k] = v.is_a?(SlashedHash) ? v.expand : v; h}
259
+ end
260
+ def to_string_array
261
+ flatten_to_array(flat,[])
262
+ end
263
+ def slashed # :nodoc:
264
+ self
265
+ end
266
+ # Same as ordered! but returns a new SlashedHash object instead of modifying the same.
267
+ def ordered(*keys_in_order)
268
+ dup.ordered!(*keys_in_order)
269
+ end
270
+ # Sets the SlashedArray as ordered. The *keys_in_order must be a flat array of slashed keys that specify the order for each level:
271
+ # s = {'a'=>{'b'=>'c', 'c'=>'d'}, 'b'=>'c'}.slashed
272
+ # s.ordered!('b', 'a/c', 'a/b')
273
+ # s.expand # => {'b'=>'c', 'a'=>{'c'=>'d', 'b'=>'c'}}
274
+ # # Note that the expanded hashes will *still* be ordered!
275
+ def ordered!(*keys_in_order)
276
+ return self if @constructor == OrderedHash
277
+ @constructor = OrderedHash
278
+ @flat = @flat.ordered(*keys_in_order)
279
+ self
280
+ end
281
+ def ==(other) # :nodoc:
282
+ if other.is_a?(SlashedHash)
283
+ @slashed == other.instance_variable_get(:@slashed)
284
+ elsif other.is_a?(Hash)
285
+ self == SlashedHash.new(other)
286
+ else
287
+ raise TypeError, "Cannot compare #{other.class.name} with SlashedHash"
288
+ end
289
+ end
290
+
291
+ private
292
+ def flatten_to_hash(hsh)
293
+ flat = @constructor.new
294
+ if hsh.is_a?(Array)
295
+ hsh.each do |e|
296
+ flat.merge!(flatten_to_hash(e))
297
+ end
298
+ elsif hsh.is_a?(Hash)
299
+ hsh.each do |k,v|
300
+ if v.is_a?(Hash)
301
+ flatten_to_hash(v).each do |hk,hv|
302
+ flat[k.to_s+'/'+hk.to_s] = hv
303
+ end
304
+ else
305
+ flat[k.to_s] = v
306
+ end
307
+ end
308
+ else
309
+ ks = hsh.split('/',-1)
310
+ v = ks.pop
311
+ ks = ks.join('/')
312
+ if !flat[ks].nil?
313
+ if flat[ks].is_a?(Array)
314
+ flat[ks] << v
315
+ else
316
+ flat[ks] = [flat[ks], v]
317
+ end
318
+ else
319
+ flat[ks] = v
320
+ end
321
+ end
322
+ flat
323
+ end
324
+ def flatten_to_array(value,a)
325
+ if value.is_a?(Array)
326
+ value.each {|e| flatten_to_array(e,a)}
327
+ elsif value.is_a?(Hash)
328
+ value.inject([]) {|aa,(k,v)| flatten_to_array(v,[]).each {|vv| aa << k+'/'+vv.to_s}; aa}.each {|e| a << e}
329
+ else
330
+ a << value.to_s
331
+ end
332
+ a
333
+ end
334
+ end
335
+
336
+ class OrderedHash < Hash
337
+ def initialize(*args)
338
+ @keys_in_order = args.flatten
339
+ end
340
+ include StandardHashMethodsInRuby
341
+
342
+ def []=(key,value)
343
+ @keys_in_order << key
344
+ super
345
+ end
346
+ def inspect
347
+ super.insert(1,'ordered: ')
348
+ end
349
+ def keys
350
+ super.sort {|a,b| (@keys_in_order.index(a) || -1) <=> (@keys_in_order.index(b) || -1)}
351
+ end
352
+
353
+ def ordered # :nodoc:
354
+ self
355
+ end
356
+ def slashed
357
+ SlashedHash.new(self).ordered!(@keys_in_order)
358
+ end
359
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hash_magic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Parker
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-03-31 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Adds Hash#ordered and Hash#slashed to Hash, which flavor a hash to behave in certain more humanly manners.
17
+ email: gems@behindlogic.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/hash_magic.rb
26
+ - Rakefile
27
+ - doc/classes
28
+ - doc/classes/Hash.html
29
+ - doc/classes/Hash.src
30
+ - doc/classes/Hash.src/M000012.html
31
+ - doc/classes/Hash.src/M000013.html
32
+ - doc/classes/OrderedHash.html
33
+ - doc/classes/OrderedHash.src
34
+ - doc/classes/OrderedHash.src/M000014.html
35
+ - doc/classes/OrderedHash.src/M000015.html
36
+ - doc/classes/OrderedHash.src/M000016.html
37
+ - doc/classes/OrderedHash.src/M000017.html
38
+ - doc/classes/OrderedHash.src/M000018.html
39
+ - doc/classes/SlashedHash.html
40
+ - doc/classes/SlashedHash.src
41
+ - doc/classes/SlashedHash.src/M000001.html
42
+ - doc/classes/SlashedHash.src/M000002.html
43
+ - doc/classes/SlashedHash.src/M000003.html
44
+ - doc/classes/SlashedHash.src/M000004.html
45
+ - doc/classes/SlashedHash.src/M000005.html
46
+ - doc/classes/SlashedHash.src/M000006.html
47
+ - doc/classes/SlashedHash.src/M000007.html
48
+ - doc/classes/SlashedHash.src/M000008.html
49
+ - doc/classes/SlashedHash.src/M000009.html
50
+ - doc/classes/SlashedHash.src/M000010.html
51
+ - doc/classes/SlashedHash.src/M000011.html
52
+ - doc/created.rid
53
+ - doc/files
54
+ - doc/files/lib
55
+ - doc/files/lib/hash_magic_rb.html
56
+ - doc/fr_class_index.html
57
+ - doc/fr_file_index.html
58
+ - doc/fr_method_index.html
59
+ - doc/index.html
60
+ - doc/rdoc-style.css
61
+ has_rdoc: true
62
+ homepage: http://hash_magic.rubyforge.org
63
+ post_install_message:
64
+ rdoc_options: []
65
+
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ version:
80
+ requirements: []
81
+
82
+ rubyforge_project: hash-magic
83
+ rubygems_version: 1.0.1
84
+ signing_key:
85
+ specification_version: 2
86
+ summary: Adds Hash#ordered and Hash#slashed to Hash, which flavor a hash to behave in certain more humanly manners.
87
+ test_files: []
88
+