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/Rakefile +78 -0
- data/doc/classes/Hash.html +177 -0
- data/doc/classes/Hash.src/M000012.html +18 -0
- data/doc/classes/Hash.src/M000013.html +18 -0
- data/doc/classes/OrderedHash.html +208 -0
- data/doc/classes/OrderedHash.src/M000014.html +18 -0
- data/doc/classes/OrderedHash.src/M000015.html +19 -0
- data/doc/classes/OrderedHash.src/M000016.html +18 -0
- data/doc/classes/OrderedHash.src/M000017.html +18 -0
- data/doc/classes/OrderedHash.src/M000018.html +18 -0
- data/doc/classes/SlashedHash.html +373 -0
- data/doc/classes/SlashedHash.src/M000001.html +20 -0
- data/doc/classes/SlashedHash.src/M000002.html +25 -0
- data/doc/classes/SlashedHash.src/M000003.html +25 -0
- data/doc/classes/SlashedHash.src/M000004.html +21 -0
- data/doc/classes/SlashedHash.src/M000005.html +18 -0
- data/doc/classes/SlashedHash.src/M000006.html +18 -0
- data/doc/classes/SlashedHash.src/M000007.html +18 -0
- data/doc/classes/SlashedHash.src/M000008.html +18 -0
- data/doc/classes/SlashedHash.src/M000009.html +18 -0
- data/doc/classes/SlashedHash.src/M000010.html +18 -0
- data/doc/classes/SlashedHash.src/M000011.html +21 -0
- data/doc/created.rid +1 -0
- data/doc/files/lib/hash_magic_rb.html +126 -0
- data/doc/fr_class_index.html +29 -0
- data/doc/fr_file_index.html +27 -0
- data/doc/fr_method_index.html +44 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/lib/hash_magic.rb +359 -0
- metadata +88 -0
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
|
+
|