hash_magic 0.0.1

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.
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
+