motion-map 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.
- checksums.yaml +15 -0
- data/.gitignore +2 -0
- data/README.md +51 -0
- data/Rakefile +15 -0
- data/app/a.rb +16 -0
- data/app/app_delegate.rb +11 -0
- data/lib/motion-map.rb +9 -0
- data/lib/motion-map/map.rb +1028 -0
- data/lib/motion-map/version.rb +3 -0
- data/motion-map.gemspec +17 -0
- data/spec/helpers/map_helper.rb +13 -0
- data/spec/map/map_spec.rb +521 -0
- metadata +70 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YjcyN2I4NjUxNDc2NWI0Y2VlNGYxYWIwMGNkYzljY2I5MTMxMjZhNw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NDJlNTgxODg4ZDRlNzI1YzQxNjRlNmRmYzUwOGNmMDZlODJkZjUyMg==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YTEyMmM1MGU2N2NmYzUzNjUxNDJlZjE3ODEyOWMxNTc4NWIyNzMzZWFjNzJi
|
10
|
+
NzZiYWE3MzY4YTU4YzVkYjkxYWViODFlNjM0ZGI2Yjk5YjA4YTliN2EwMDFh
|
11
|
+
NjEzYjQwMjdjZTIwZmE0NDhhOTM3OWExZGJmOTIzNWY1MzhjNzE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NGYzOGZlMTQ1NDdiMmQwOTY0NzRhYTYwM2M1N2NkZTk2ZjFhNzVmZjM1ZjNk
|
14
|
+
ZTVhYjNiMDZiMDQzYjk4Y2Q5ODk5MGE0ZTRhNzQzNWU4NTVhNWIzYmZlOGIy
|
15
|
+
MjJiMmQwM2NlMDkzZDY2NGFkN2RkNDRkYTZlMWVmMDRmMzg0MmM=
|
data/.gitignore
ADDED
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# MotionMap - maps for RubyMotion!
|
2
|
+
|
3
|
+
Adding a bit more sizzles to the boring ol' hashes for RubyMotion.
|
4
|
+
|
5
|
+
## ATTA BOY!
|
6
|
+
|
7
|
+
This is most entirely lifted from the awesome Map from Ara T. Howard (https://github.com/ahoward/map).
|
8
|
+
Made some adaptations to make it work in the RubyMotion world, but all the heavy lifting is his.
|
9
|
+
|
10
|
+
Hence all credits and ATTA BOY! should go to Ara for some very nice work!
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
```
|
15
|
+
gem install motion-map
|
16
|
+
```
|
17
|
+
|
18
|
+
## From the original author
|
19
|
+
|
20
|
+
the awesome ruby container you've always wanted: a string/symbol indifferent
|
21
|
+
ordered hash that works in all rubies
|
22
|
+
|
23
|
+
maps are bitchin ordered hashes that are both ordered, string/symbol
|
24
|
+
indifferent, and have all sorts of sweetness like recursive conversion, more
|
25
|
+
robust implementation than HashWithIndifferentAccess, support for struct
|
26
|
+
like (map.foo) access, and support for option/keyword access which avoids
|
27
|
+
several nasty classes of errors in many ruby libraries
|
28
|
+
|
29
|
+
## Docs
|
30
|
+
|
31
|
+
See docs (https://github.com/ahoward/map)
|
32
|
+
|
33
|
+
NOTE: Struct and Options functionality was pulled out for this release.
|
34
|
+
|
35
|
+
## Contact
|
36
|
+
|
37
|
+
Fernand Galiana
|
38
|
+
|
39
|
+
- http://github.com/derailed
|
40
|
+
- http://twitter.com/kitesurfer
|
41
|
+
- <fernand.galiana@gmail.com>
|
42
|
+
|
43
|
+
|
44
|
+
## License
|
45
|
+
|
46
|
+
MotionMap is released under the [MIT](http://opensource.org/licenses/MIT) license.
|
47
|
+
|
48
|
+
|
49
|
+
## History
|
50
|
+
+ 0.0.1:
|
51
|
+
+ Initial drop
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$:.unshift('/Library/RubyMotion/lib')
|
2
|
+
require 'motion/project'
|
3
|
+
require './lib/motion-map'
|
4
|
+
|
5
|
+
require 'bundler'
|
6
|
+
Bundler::GemHelper.install_tasks
|
7
|
+
|
8
|
+
Motion::Project::App.setup do |app|
|
9
|
+
app.name = 'MapTest'
|
10
|
+
|
11
|
+
app.development do
|
12
|
+
app.codesign_certificate = ENV['dev_bs_certificate']
|
13
|
+
app.provisioning_profile = ENV['dev_bs_profile']
|
14
|
+
end
|
15
|
+
end
|
data/app/a.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
class A < UIViewController
|
2
|
+
def viewDidLoad
|
3
|
+
view = UIScrollView.alloc.init
|
4
|
+
view.backgroundColor = UIColor.whiteColor
|
5
|
+
m = Map.new(a:10, b:20, c:{d:30} )
|
6
|
+
view.contentSize = [320, m.count*200]
|
7
|
+
|
8
|
+
i = 0
|
9
|
+
m.each_pair do |k,v|
|
10
|
+
label = UILabel.alloc.initWithFrame( [[10, 10+i*40], [320, 40]] )
|
11
|
+
label.text = "#{k}:#{v}"
|
12
|
+
self.view.addSubview(label)
|
13
|
+
i += 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/app/app_delegate.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
class AppDelegate
|
2
|
+
def application(application, didFinishLaunchingWithOptions:launchOptions)
|
3
|
+
return true if RUBYMOTION_ENV == 'test'
|
4
|
+
|
5
|
+
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
|
6
|
+
@window.rootViewController = A.new
|
7
|
+
@window.rootViewController.wantsFullScreenLayout = true
|
8
|
+
@window.makeKeyAndVisible
|
9
|
+
true
|
10
|
+
end
|
11
|
+
end
|
data/lib/motion-map.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
unless defined?(Motion::Project::Config)
|
2
|
+
raise "motion-map must be required within a RubyMotion project Rakefile."
|
3
|
+
end
|
4
|
+
|
5
|
+
Motion::Project::App.setup do |app|
|
6
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'motion-map/*.rb')).each do |file|
|
7
|
+
app.files.unshift(file)
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,1028 @@
|
|
1
|
+
class Map < Hash
|
2
|
+
class << Map
|
3
|
+
def allocate
|
4
|
+
super.instance_eval do
|
5
|
+
@keys = []
|
6
|
+
self
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def new(*args, &block)
|
11
|
+
allocate.instance_eval do
|
12
|
+
initialize(*args, &block)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
end
|
16
|
+
alias_method '[]', 'new'
|
17
|
+
|
18
|
+
def for(*args, &block)
|
19
|
+
if(args.size == 1 and block.nil?)
|
20
|
+
return args.first if args.first.class == self
|
21
|
+
end
|
22
|
+
new(*args, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def coerce(other)
|
26
|
+
case other
|
27
|
+
when Map
|
28
|
+
other
|
29
|
+
else
|
30
|
+
allocate.update(other.to_hash)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# iterate over arguments in pairs smartly.
|
35
|
+
#
|
36
|
+
def each_pair(*args, &block)
|
37
|
+
size = args.size
|
38
|
+
parity = size % 2 == 0 ? :even : :odd
|
39
|
+
first = args.first
|
40
|
+
|
41
|
+
if block.nil?
|
42
|
+
result = []
|
43
|
+
block = lambda{|*kv| result.push(kv)}
|
44
|
+
else
|
45
|
+
result = args
|
46
|
+
end
|
47
|
+
|
48
|
+
return args if size == 0
|
49
|
+
|
50
|
+
if size == 1
|
51
|
+
if first.respond_to?(:each_pair)
|
52
|
+
first.each_pair do |key, val|
|
53
|
+
block.call(key, val)
|
54
|
+
end
|
55
|
+
return args
|
56
|
+
end
|
57
|
+
|
58
|
+
if first.respond_to?(:each_slice)
|
59
|
+
first.each_slice(2) do |key, val|
|
60
|
+
block.call(key, val)
|
61
|
+
end
|
62
|
+
return args
|
63
|
+
end
|
64
|
+
raise(ArgumentError, 'odd number of arguments for Map')
|
65
|
+
end
|
66
|
+
|
67
|
+
array_of_pairs = args.all?{|a| a.is_a?(Array) and a.size == 2}
|
68
|
+
|
69
|
+
if array_of_pairs
|
70
|
+
args.each do |pair|
|
71
|
+
key, val, *ignored = pair
|
72
|
+
block.call(key, val)
|
73
|
+
end
|
74
|
+
else
|
75
|
+
0.step(args.size - 1, 2) do |a|
|
76
|
+
key = args[a]
|
77
|
+
val = args[a + 1]
|
78
|
+
block.call(key, val)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
args
|
83
|
+
end
|
84
|
+
|
85
|
+
def intersection(a, b)
|
86
|
+
a, b, i = Map.for(a), Map.for(b), Map.new
|
87
|
+
a.depth_first_each{|key, val| i.set(key, val) if b.has?(key)}
|
88
|
+
i
|
89
|
+
end
|
90
|
+
|
91
|
+
def match(haystack, needle)
|
92
|
+
intersection(haystack, needle) == needle
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def keys
|
97
|
+
@keys ||= []
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize(*args, &block)
|
101
|
+
case args.size
|
102
|
+
when 0
|
103
|
+
super(&block)
|
104
|
+
when 1
|
105
|
+
first = args.first
|
106
|
+
case first
|
107
|
+
when nil, false
|
108
|
+
nil
|
109
|
+
when Hash
|
110
|
+
initialize_from_hash(first)
|
111
|
+
when Array
|
112
|
+
initialize_from_array(first)
|
113
|
+
else
|
114
|
+
if first.respond_to?(:to_hash)
|
115
|
+
initialize_from_hash(first.to_hash)
|
116
|
+
else
|
117
|
+
initialize_from_hash(first)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
else
|
121
|
+
initialize_from_array(args)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def initialize_from_hash(hash)
|
126
|
+
map = self
|
127
|
+
map.update(hash)
|
128
|
+
map.default = hash.default
|
129
|
+
end
|
130
|
+
|
131
|
+
def initialize_from_array(array)
|
132
|
+
map = self
|
133
|
+
Map.each_pair(array){|key, val| map[key] = val}
|
134
|
+
end
|
135
|
+
|
136
|
+
def klass
|
137
|
+
self.class
|
138
|
+
end
|
139
|
+
|
140
|
+
def Map.map_for(hash)
|
141
|
+
hash = klass.coerce(hash)
|
142
|
+
hash.default = hash.default
|
143
|
+
hash
|
144
|
+
end
|
145
|
+
def map_for(hash)
|
146
|
+
klass.map_for(hash)
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.convert_key(key)
|
150
|
+
key = key.kind_of?(Symbol) ? key.to_s : key
|
151
|
+
end
|
152
|
+
|
153
|
+
def convert_key(key)
|
154
|
+
if klass.respond_to?(:convert_key)
|
155
|
+
klass.convert_key(key)
|
156
|
+
else
|
157
|
+
Map.convert_key(key)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.convert_value(value)
|
162
|
+
case value
|
163
|
+
when Hash
|
164
|
+
coerce(value)
|
165
|
+
when Array
|
166
|
+
value.map!{|v| convert_value(v)}
|
167
|
+
else
|
168
|
+
value
|
169
|
+
end
|
170
|
+
end
|
171
|
+
def convert_value(value)
|
172
|
+
if klass.respond_to?(:convert_value)
|
173
|
+
klass.convert_value(value)
|
174
|
+
else
|
175
|
+
Map.convert_value(value)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
alias_method('convert_val', 'convert_value')
|
179
|
+
|
180
|
+
|
181
|
+
def convert(key, val)
|
182
|
+
[convert_key(key), convert_value(val)]
|
183
|
+
end
|
184
|
+
|
185
|
+
def copy
|
186
|
+
default = self.default
|
187
|
+
self.default = nil
|
188
|
+
copy = Marshal.load(Marshal.dump(self)) rescue Dup.bind(self).call()
|
189
|
+
copy.default = default
|
190
|
+
copy
|
191
|
+
ensure
|
192
|
+
self.default = default
|
193
|
+
end
|
194
|
+
|
195
|
+
Dup = instance_method(:dup) unless defined?(Dup)
|
196
|
+
|
197
|
+
def dup
|
198
|
+
copy
|
199
|
+
end
|
200
|
+
|
201
|
+
def clone
|
202
|
+
copy
|
203
|
+
end
|
204
|
+
|
205
|
+
def default(key = nil)
|
206
|
+
key.is_a?(Symbol) && include?(key = key.to_s) ? self[key] : super
|
207
|
+
end
|
208
|
+
|
209
|
+
def default=(value)
|
210
|
+
raise ArgumentError.new("Map doesn't work so well with a non-nil default value!") unless value.nil?
|
211
|
+
end
|
212
|
+
|
213
|
+
# writer/reader methods
|
214
|
+
#
|
215
|
+
alias_method '__set__', '[]=' unless method_defined?('__set__')
|
216
|
+
alias_method '__get__', '[]' unless method_defined?('__get__')
|
217
|
+
alias_method '__update__', 'update' unless method_defined?('__update__')
|
218
|
+
|
219
|
+
def []=(key, val)
|
220
|
+
key, val = convert(key, val)
|
221
|
+
keys.push(key) unless has_key?(key)
|
222
|
+
__set__(key, val)
|
223
|
+
end
|
224
|
+
alias_method 'store', '[]='
|
225
|
+
|
226
|
+
def [](key)
|
227
|
+
key = convert_key(key)
|
228
|
+
__get__(key)
|
229
|
+
end
|
230
|
+
|
231
|
+
def fetch(key, *args, &block)
|
232
|
+
key = convert_key(key)
|
233
|
+
super(key, *args, &block)
|
234
|
+
end
|
235
|
+
|
236
|
+
def key?(key)
|
237
|
+
super(convert_key(key))
|
238
|
+
end
|
239
|
+
alias_method 'include?', 'key?'
|
240
|
+
alias_method 'has_key?', 'key?'
|
241
|
+
alias_method 'member?', 'key?'
|
242
|
+
|
243
|
+
def update(*args)
|
244
|
+
Map.each_pair(*args){|key, val| store(key, val)}
|
245
|
+
self
|
246
|
+
end
|
247
|
+
alias_method 'merge!', 'update'
|
248
|
+
|
249
|
+
def merge(*args)
|
250
|
+
copy.update(*args)
|
251
|
+
end
|
252
|
+
alias_method '+', 'merge'
|
253
|
+
|
254
|
+
def reverse_merge(hash)
|
255
|
+
map = copy
|
256
|
+
map.each{|key, val| Map[key] = val unless Map.key?(key)}
|
257
|
+
map
|
258
|
+
end
|
259
|
+
|
260
|
+
def reverse_merge!(hash)
|
261
|
+
replace(reverse_merge(hash))
|
262
|
+
end
|
263
|
+
|
264
|
+
def values
|
265
|
+
array = []
|
266
|
+
keys.each{|key| array.push(self[key])}
|
267
|
+
array
|
268
|
+
end
|
269
|
+
alias_method 'vals', 'values'
|
270
|
+
|
271
|
+
def values_at(*keys)
|
272
|
+
keys.map{|key| self[key]}
|
273
|
+
end
|
274
|
+
|
275
|
+
def first
|
276
|
+
[keys.first, self[keys.first]]
|
277
|
+
end
|
278
|
+
|
279
|
+
def last
|
280
|
+
[keys.last, self[keys.last]]
|
281
|
+
end
|
282
|
+
|
283
|
+
# iterator methods
|
284
|
+
#
|
285
|
+
def each_with_index
|
286
|
+
keys.each_with_index{|key, index| yield([key, self[key]], index)}
|
287
|
+
self
|
288
|
+
end
|
289
|
+
|
290
|
+
def each_key
|
291
|
+
keys.each{|key| yield(key)}
|
292
|
+
self
|
293
|
+
end
|
294
|
+
|
295
|
+
def each_value
|
296
|
+
keys.each{|key| yield self[key]}
|
297
|
+
self
|
298
|
+
end
|
299
|
+
|
300
|
+
def each
|
301
|
+
keys.each{|key| yield(key, self[key])}
|
302
|
+
self
|
303
|
+
end
|
304
|
+
alias_method 'each_pair', 'each'
|
305
|
+
|
306
|
+
# mutators
|
307
|
+
#
|
308
|
+
def delete(key)
|
309
|
+
key = convert_key(key)
|
310
|
+
keys.delete(key)
|
311
|
+
super(key)
|
312
|
+
end
|
313
|
+
|
314
|
+
def clear
|
315
|
+
keys.clear
|
316
|
+
super
|
317
|
+
end
|
318
|
+
|
319
|
+
def delete_if
|
320
|
+
to_delete = []
|
321
|
+
keys.each{|key| to_delete.push(key) if yield(key,self[key])}
|
322
|
+
to_delete.each{|key| delete(key)}
|
323
|
+
self
|
324
|
+
end
|
325
|
+
|
326
|
+
# See: https://github.com/rubinius/rubinius/blob/98c516820d9f78bd63f29dab7d5ec9bc8692064d/kernel/common/hash19.rb#L476-L484
|
327
|
+
def keep_if( &block )
|
328
|
+
raise RuntimeError.new( "can't modify frozen #{ self.class.name }" ) if frozen?
|
329
|
+
return to_enum( :keep_if ) unless block_given?
|
330
|
+
each { | key , val | delete key unless yield( key , val ) }
|
331
|
+
self
|
332
|
+
end
|
333
|
+
|
334
|
+
def replace(*args)
|
335
|
+
clear
|
336
|
+
update(*args)
|
337
|
+
end
|
338
|
+
|
339
|
+
# ordered container specific methods
|
340
|
+
#
|
341
|
+
def shift
|
342
|
+
unless empty?
|
343
|
+
key = keys.first
|
344
|
+
val = delete(key)
|
345
|
+
[key, val]
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def unshift(*args)
|
350
|
+
Map.each_pair(*args) do |key, val|
|
351
|
+
if key?(key)
|
352
|
+
delete(key)
|
353
|
+
else
|
354
|
+
keys.unshift(key)
|
355
|
+
end
|
356
|
+
__set__(key, val)
|
357
|
+
end
|
358
|
+
self
|
359
|
+
end
|
360
|
+
|
361
|
+
def push(*args)
|
362
|
+
Map.each_pair(*args) do |key, val|
|
363
|
+
if key?(key)
|
364
|
+
delete(key)
|
365
|
+
else
|
366
|
+
keys.push(key)
|
367
|
+
end
|
368
|
+
__set__(key, val)
|
369
|
+
end
|
370
|
+
self
|
371
|
+
end
|
372
|
+
|
373
|
+
def pop
|
374
|
+
unless empty?
|
375
|
+
key = keys.last
|
376
|
+
val = delete(key)
|
377
|
+
[key, val]
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# equality / sorting / matching support
|
382
|
+
#
|
383
|
+
def ==(other)
|
384
|
+
case other
|
385
|
+
when Map
|
386
|
+
return false if keys != other.keys
|
387
|
+
super(other)
|
388
|
+
|
389
|
+
when Hash
|
390
|
+
self == Map.from_hash(other, self)
|
391
|
+
|
392
|
+
else
|
393
|
+
false
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
def <=>(other)
|
398
|
+
cmp = keys <=> klass.coerce(other).keys
|
399
|
+
return cmp unless cmp.zero?
|
400
|
+
values <=> klass.coerce(other).values
|
401
|
+
end
|
402
|
+
|
403
|
+
def =~(hash)
|
404
|
+
to_hash == klass.coerce(hash).to_hash
|
405
|
+
end
|
406
|
+
|
407
|
+
# reordering support
|
408
|
+
#
|
409
|
+
def reorder(order = {})
|
410
|
+
order = Map.for(order)
|
411
|
+
map = Map.new
|
412
|
+
keys = order.depth_first_keys | depth_first_keys
|
413
|
+
keys.each{|key| map.set(key, get(key))}
|
414
|
+
map
|
415
|
+
end
|
416
|
+
|
417
|
+
def reorder!(order = {})
|
418
|
+
replace(reorder(order))
|
419
|
+
end
|
420
|
+
|
421
|
+
# support for building ordered hasshes from a Map's own image
|
422
|
+
#
|
423
|
+
def Map.from_hash(hash, order = nil)
|
424
|
+
map = Map.for(hash)
|
425
|
+
map.reorder!(order) if order
|
426
|
+
map
|
427
|
+
end
|
428
|
+
|
429
|
+
def invert
|
430
|
+
inverted = klass.allocate
|
431
|
+
inverted.default = self.default
|
432
|
+
keys.each{|key| inverted[self[key]] = key }
|
433
|
+
inverted
|
434
|
+
end
|
435
|
+
|
436
|
+
def reject(&block)
|
437
|
+
dup.delete_if(&block)
|
438
|
+
end
|
439
|
+
|
440
|
+
def reject!(&block)
|
441
|
+
hash = reject(&block)
|
442
|
+
self == hash ? nil : hash
|
443
|
+
end
|
444
|
+
|
445
|
+
def select
|
446
|
+
array = []
|
447
|
+
each{|key, val| array << [key,val] if yield(key, val)}
|
448
|
+
array
|
449
|
+
end
|
450
|
+
|
451
|
+
def inspect(*args, &block)
|
452
|
+
super.inspect
|
453
|
+
end
|
454
|
+
|
455
|
+
def to_hash
|
456
|
+
hash = Hash.new(default)
|
457
|
+
each do |key, val|
|
458
|
+
val = val.to_hash if val.respond_to?(:to_hash)
|
459
|
+
hash[key] = val
|
460
|
+
end
|
461
|
+
hash
|
462
|
+
end
|
463
|
+
|
464
|
+
def to_array
|
465
|
+
array = []
|
466
|
+
each{|*pair| array.push(pair)}
|
467
|
+
array
|
468
|
+
end
|
469
|
+
alias_method 'to_a', 'to_array'
|
470
|
+
|
471
|
+
def to_list
|
472
|
+
list = []
|
473
|
+
each_pair do |key, val|
|
474
|
+
list[key.to_i] = val if(key.is_a?(Numeric) or key.to_s =~ %r/^\d+$/)
|
475
|
+
end
|
476
|
+
list
|
477
|
+
end
|
478
|
+
|
479
|
+
def to_s
|
480
|
+
to_array.to_s
|
481
|
+
end
|
482
|
+
|
483
|
+
# a sane method missing that only supports writing values or reading
|
484
|
+
# *previously set* values
|
485
|
+
#
|
486
|
+
def method_missing(*args, &block)
|
487
|
+
method = args.first.to_s
|
488
|
+
case method
|
489
|
+
when /=$/
|
490
|
+
key = args.shift.to_s.chomp('=')
|
491
|
+
value = args.shift
|
492
|
+
self[key] = value
|
493
|
+
when /\?$/
|
494
|
+
key = args.shift.to_s.chomp('?')
|
495
|
+
self.has?( key )
|
496
|
+
else
|
497
|
+
key = method
|
498
|
+
unless has_key?(key)
|
499
|
+
return(block ? fetch(key, &block) : super(*args))
|
500
|
+
end
|
501
|
+
self[key]
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
def respond_to?(method, *args, &block)
|
506
|
+
has_key = has_key?(method)
|
507
|
+
setter = method.to_s =~ /=\Z/o
|
508
|
+
!!((!has_key and setter) or has_key or super)
|
509
|
+
end
|
510
|
+
|
511
|
+
def id
|
512
|
+
return self[:id] if has_key?(:id)
|
513
|
+
return self[:_id] if has_key?(:_id)
|
514
|
+
raise NoMethodError
|
515
|
+
end
|
516
|
+
|
517
|
+
# support for compound key indexing and depth first iteration
|
518
|
+
#
|
519
|
+
def get(*keys)
|
520
|
+
keys = key_for(keys)
|
521
|
+
|
522
|
+
if keys.size <= 1
|
523
|
+
if !self.has_key?(keys.first) && block_given?
|
524
|
+
return yield
|
525
|
+
else
|
526
|
+
return self[keys.first]
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
keys, key = keys[0..-2], keys[-1]
|
531
|
+
collection = self
|
532
|
+
|
533
|
+
keys.each do |k|
|
534
|
+
if Map.collection_has?(collection, k)
|
535
|
+
collection = Map.collection_key(collection, k)
|
536
|
+
else
|
537
|
+
collection = nil
|
538
|
+
end
|
539
|
+
|
540
|
+
unless collection.respond_to?('[]')
|
541
|
+
leaf = collection
|
542
|
+
return leaf
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
if !Map.collection_has?(collection, key) && block_given?
|
547
|
+
default_value = yield
|
548
|
+
else
|
549
|
+
Map.collection_key(collection, key)
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
def has?(*keys)
|
554
|
+
keys = key_for(keys)
|
555
|
+
collection = self
|
556
|
+
|
557
|
+
return Map.collection_has?(collection, keys.first) if keys.size <= 1
|
558
|
+
|
559
|
+
keys, key = keys[0..-2], keys[-1]
|
560
|
+
|
561
|
+
keys.each do |k|
|
562
|
+
if Map.collection_has?(collection, k)
|
563
|
+
collection = Map.collection_key(collection, k)
|
564
|
+
else
|
565
|
+
collection = nil
|
566
|
+
end
|
567
|
+
|
568
|
+
return collection unless collection.respond_to?('[]')
|
569
|
+
end
|
570
|
+
|
571
|
+
return false unless(collection.is_a?(Hash) or collection.is_a?(Array))
|
572
|
+
|
573
|
+
Map.collection_has?(collection, key)
|
574
|
+
end
|
575
|
+
|
576
|
+
def Map.blank?(value)
|
577
|
+
return value.blank? if value.respond_to?(:blank?)
|
578
|
+
|
579
|
+
case value
|
580
|
+
when String
|
581
|
+
value.strip.empty?
|
582
|
+
when Numeric
|
583
|
+
value == 0
|
584
|
+
when false
|
585
|
+
true
|
586
|
+
else
|
587
|
+
value.respond_to?(:empty?) ? value.empty? : !value
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
def blank?(*keys)
|
592
|
+
return empty? if keys.empty?
|
593
|
+
!has?(*keys) or Map.blank?(get(*keys))
|
594
|
+
end
|
595
|
+
|
596
|
+
def Map.collection_key(collection, key, &block)
|
597
|
+
case collection
|
598
|
+
when Array
|
599
|
+
begin
|
600
|
+
key = Integer(key)
|
601
|
+
rescue
|
602
|
+
raise(IndexError, "(#{ collection.inspect })[#{ key.inspect }]")
|
603
|
+
end
|
604
|
+
collection[key]
|
605
|
+
|
606
|
+
when Hash
|
607
|
+
collection[key]
|
608
|
+
|
609
|
+
else
|
610
|
+
raise(IndexError, "(#{ collection.inspect })[#{ key.inspect }]")
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
def collection_key(*args, &block)
|
615
|
+
Map.collection_key(*args, &block)
|
616
|
+
end
|
617
|
+
|
618
|
+
def Map.collection_has?(collection, key, &block)
|
619
|
+
has_key =
|
620
|
+
case collection
|
621
|
+
when Array
|
622
|
+
key = (Integer(key) rescue -1)
|
623
|
+
(0...collection.size).include?(key)
|
624
|
+
|
625
|
+
when Hash
|
626
|
+
collection.has_key?(key)
|
627
|
+
|
628
|
+
else
|
629
|
+
raise(IndexError, "(#{ collection.inspect })[#{ key.inspect }]")
|
630
|
+
end
|
631
|
+
|
632
|
+
block.call(key) if(has_key and block)
|
633
|
+
|
634
|
+
has_key
|
635
|
+
end
|
636
|
+
|
637
|
+
def collection_has?(*args, &block)
|
638
|
+
Map.collection_has?(*args, &block)
|
639
|
+
end
|
640
|
+
|
641
|
+
def Map.collection_set(collection, key, value, &block)
|
642
|
+
set_key = false
|
643
|
+
|
644
|
+
case collection
|
645
|
+
when Array
|
646
|
+
begin
|
647
|
+
key = Integer(key)
|
648
|
+
rescue
|
649
|
+
raise(IndexError, "(#{ collection.inspect })[#{ key.inspect }]=#{ value.inspect }")
|
650
|
+
end
|
651
|
+
set_key = true
|
652
|
+
collection[key] = value
|
653
|
+
|
654
|
+
when Hash
|
655
|
+
set_key = true
|
656
|
+
collection[key] = value
|
657
|
+
|
658
|
+
else
|
659
|
+
raise(IndexError, "(#{ collection.inspect })[#{ key.inspect }]=#{ value.inspect }")
|
660
|
+
end
|
661
|
+
|
662
|
+
block.call(key) if(set_key and block)
|
663
|
+
|
664
|
+
[key, value]
|
665
|
+
end
|
666
|
+
|
667
|
+
def collection_set(*args, &block)
|
668
|
+
Map.collection_set(*args, &block)
|
669
|
+
end
|
670
|
+
|
671
|
+
def set(*args)
|
672
|
+
case
|
673
|
+
when args.empty?
|
674
|
+
return []
|
675
|
+
when args.size == 1 && args.first.is_a?(Hash)
|
676
|
+
hash = args.shift
|
677
|
+
else
|
678
|
+
hash = {}
|
679
|
+
value = args.pop
|
680
|
+
key = Array(args).flatten
|
681
|
+
hash[key] = value
|
682
|
+
end
|
683
|
+
|
684
|
+
strategy = hash.map{|key, value| [Array(key), value]}
|
685
|
+
|
686
|
+
strategy.each do |key, value|
|
687
|
+
leaf_for(key, :autovivify => true) do |leaf, k|
|
688
|
+
Map.collection_set(leaf, k, value)
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
self
|
693
|
+
end
|
694
|
+
|
695
|
+
def add(*args)
|
696
|
+
case
|
697
|
+
when args.empty?
|
698
|
+
return []
|
699
|
+
when args.size == 1 && args.first.is_a?(Hash)
|
700
|
+
hash = args.shift
|
701
|
+
else
|
702
|
+
hash = {}
|
703
|
+
value = args.pop
|
704
|
+
key = Array(args).flatten
|
705
|
+
hash[key] = value
|
706
|
+
end
|
707
|
+
|
708
|
+
exploded = Map.explode(hash)
|
709
|
+
|
710
|
+
exploded[:branches].each do |key, type|
|
711
|
+
set(key, type.new) unless get(key).is_a?(type)
|
712
|
+
end
|
713
|
+
|
714
|
+
exploded[:leaves].each do |key, value|
|
715
|
+
set(key, value)
|
716
|
+
end
|
717
|
+
|
718
|
+
self
|
719
|
+
end
|
720
|
+
|
721
|
+
def Map.explode(hash)
|
722
|
+
accum = {:branches => [], :leaves => []}
|
723
|
+
|
724
|
+
hash.each do |key, value|
|
725
|
+
Map._explode(key, value, accum)
|
726
|
+
end
|
727
|
+
|
728
|
+
branches = accum[:branches]
|
729
|
+
leaves = accum[:leaves]
|
730
|
+
|
731
|
+
sort_by_key_size = proc{|a,b| a.first.size <=> b.first.size}
|
732
|
+
|
733
|
+
branches.sort!(&sort_by_key_size)
|
734
|
+
leaves.sort!(&sort_by_key_size)
|
735
|
+
|
736
|
+
accum
|
737
|
+
end
|
738
|
+
|
739
|
+
def Map._explode(key, value, accum = {:branches => [], :leaves => []})
|
740
|
+
key = Array(key).flatten
|
741
|
+
|
742
|
+
case value
|
743
|
+
when Array
|
744
|
+
accum[:branches].push([key, Array])
|
745
|
+
|
746
|
+
value.each_with_index do |v, k|
|
747
|
+
Map._explode(key + [k], v, accum)
|
748
|
+
end
|
749
|
+
|
750
|
+
when Hash
|
751
|
+
accum[:branches].push([key, Map])
|
752
|
+
|
753
|
+
value.each do |k, v|
|
754
|
+
Map._explode(key + [k], v, accum)
|
755
|
+
end
|
756
|
+
|
757
|
+
else
|
758
|
+
accum[:leaves].push([key, value])
|
759
|
+
end
|
760
|
+
|
761
|
+
accum
|
762
|
+
end
|
763
|
+
|
764
|
+
def Map.add(*args)
|
765
|
+
args.flatten!
|
766
|
+
args.compact!
|
767
|
+
|
768
|
+
Map.for(args.shift).tap do |map|
|
769
|
+
args.each{|arg| map.add(arg)}
|
770
|
+
end
|
771
|
+
end
|
772
|
+
|
773
|
+
def Map.combine(*args)
|
774
|
+
Map.add(*args)
|
775
|
+
end
|
776
|
+
|
777
|
+
def combine!(*args, &block)
|
778
|
+
add(*args, &block)
|
779
|
+
end
|
780
|
+
|
781
|
+
def combine(*args, &block)
|
782
|
+
dup.tap do |map|
|
783
|
+
map.combine!(*args, &block)
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
def leaf_for(key, options = {}, &block)
|
788
|
+
leaf = self
|
789
|
+
key = Array(key).flatten
|
790
|
+
k = key.first
|
791
|
+
|
792
|
+
key.each_cons(2) do |a, b|
|
793
|
+
exists = Map.collection_has?(leaf, a)
|
794
|
+
|
795
|
+
case b
|
796
|
+
when Numeric
|
797
|
+
if options[:autovivify]
|
798
|
+
Map.collection_set(leaf, a, Array.new) unless exists
|
799
|
+
end
|
800
|
+
|
801
|
+
when String, Symbol
|
802
|
+
if options[:autovivify]
|
803
|
+
Map.collection_set(leaf, a, Map.new) unless exists
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
leaf = Map.collection_key(leaf, a)
|
808
|
+
k = b
|
809
|
+
end
|
810
|
+
|
811
|
+
block ? block.call(leaf, k) : [leaf, k]
|
812
|
+
end
|
813
|
+
|
814
|
+
def rm(*args)
|
815
|
+
paths, path = args.partition{|arg| arg.is_a?(Array)}
|
816
|
+
paths.push(path)
|
817
|
+
|
818
|
+
paths.each do |path|
|
819
|
+
if path.size == 1
|
820
|
+
delete(*path)
|
821
|
+
next
|
822
|
+
end
|
823
|
+
|
824
|
+
branch, leaf = path[0..-2], path[-1]
|
825
|
+
collection = get(branch)
|
826
|
+
|
827
|
+
case collection
|
828
|
+
when Hash
|
829
|
+
key = leaf
|
830
|
+
collection.delete(key)
|
831
|
+
when Array
|
832
|
+
index = leaf
|
833
|
+
collection.delete_at(index)
|
834
|
+
else
|
835
|
+
raise(IndexError, "(#{ collection.inspect }).rm(#{ path.inspect })")
|
836
|
+
end
|
837
|
+
end
|
838
|
+
paths
|
839
|
+
end
|
840
|
+
|
841
|
+
def forcing(forcing=nil, &block)
|
842
|
+
@forcing ||= nil
|
843
|
+
|
844
|
+
if block
|
845
|
+
begin
|
846
|
+
previous = @forcing
|
847
|
+
@forcing = forcing
|
848
|
+
block.call()
|
849
|
+
ensure
|
850
|
+
@forcing = previous
|
851
|
+
end
|
852
|
+
else
|
853
|
+
@forcing
|
854
|
+
end
|
855
|
+
end
|
856
|
+
|
857
|
+
def forcing?(forcing=nil)
|
858
|
+
@forcing ||= nil
|
859
|
+
@forcing == forcing
|
860
|
+
end
|
861
|
+
|
862
|
+
def apply(other)
|
863
|
+
Map.for(other).depth_first_each do |keys, value|
|
864
|
+
set(keys => value) unless !get(keys).nil?
|
865
|
+
end
|
866
|
+
self
|
867
|
+
end
|
868
|
+
|
869
|
+
def Map.alphanumeric_key_for(key)
|
870
|
+
return key if key.is_a?(Numeric)
|
871
|
+
|
872
|
+
digity, stringy, digits = %r/^(~)?(\d+)$/iomx.match(key).to_a
|
873
|
+
|
874
|
+
digity ? stringy ? String(digits) : Integer(digits) : key
|
875
|
+
end
|
876
|
+
|
877
|
+
def alphanumeric_key_for(key)
|
878
|
+
Map.alphanumeric_key_for(key)
|
879
|
+
end
|
880
|
+
|
881
|
+
def self.key_for(*keys)
|
882
|
+
return keys.flatten
|
883
|
+
end
|
884
|
+
|
885
|
+
def key_for(*keys)
|
886
|
+
self.class.key_for(*keys)
|
887
|
+
end
|
888
|
+
|
889
|
+
## TODO - technically this returns only leaves so the name isn't *quite* right. re-factor for 3.0
|
890
|
+
#
|
891
|
+
def Map.depth_first_each(enumerable, path = [], accum = [], &block)
|
892
|
+
Map.pairs_for(enumerable) do |key, val|
|
893
|
+
path.push(key)
|
894
|
+
if((val.is_a?(Hash) or val.is_a?(Array)) and not val.empty?)
|
895
|
+
Map.depth_first_each(val, path, accum)
|
896
|
+
else
|
897
|
+
accum << [path.dup, val]
|
898
|
+
end
|
899
|
+
path.pop()
|
900
|
+
end
|
901
|
+
if block
|
902
|
+
accum.each{|keys, val| block.call(keys, val)}
|
903
|
+
else
|
904
|
+
accum
|
905
|
+
end
|
906
|
+
end
|
907
|
+
|
908
|
+
def Map.depth_first_keys(enumerable, path = [], accum = [], &block)
|
909
|
+
accum = Map.depth_first_each(enumerable, path = [], accum = [], &block)
|
910
|
+
accum.map!{|kv| kv.first}
|
911
|
+
accum
|
912
|
+
end
|
913
|
+
|
914
|
+
def Map.depth_first_values(enumerable, path = [], accum = [], &block)
|
915
|
+
accum = Map.depth_first_each(enumerable, path = [], accum = [], &block)
|
916
|
+
accum.map!{|kv| kv.last}
|
917
|
+
accum
|
918
|
+
end
|
919
|
+
|
920
|
+
def Map.pairs_for(enumerable, *args, &block)
|
921
|
+
if block.nil?
|
922
|
+
pairs, block = [], lambda{|*pair| pairs.push(pair)}
|
923
|
+
else
|
924
|
+
pairs = false
|
925
|
+
end
|
926
|
+
|
927
|
+
result =
|
928
|
+
case enumerable
|
929
|
+
when Hash
|
930
|
+
enumerable.each_pair(*args, &block)
|
931
|
+
when Array
|
932
|
+
enumerable.each_with_index(*args) do |val, key|
|
933
|
+
block.call(key, val)
|
934
|
+
end
|
935
|
+
else
|
936
|
+
enumerable.each_pair(*args, &block)
|
937
|
+
end
|
938
|
+
|
939
|
+
pairs ? pairs : result
|
940
|
+
end
|
941
|
+
|
942
|
+
def Map.breadth_first_each(enumerable, accum = [], &block)
|
943
|
+
levels = []
|
944
|
+
|
945
|
+
keys = Map.depth_first_keys(enumerable)
|
946
|
+
|
947
|
+
keys.each do |key|
|
948
|
+
key.size.times do |i|
|
949
|
+
k = key.slice(0, i + 1)
|
950
|
+
level = k.size - 1
|
951
|
+
levels[level] ||= Array.new
|
952
|
+
last = levels[level].last
|
953
|
+
levels[level].push(k) unless last == k
|
954
|
+
end
|
955
|
+
end
|
956
|
+
|
957
|
+
levels.each do |level|
|
958
|
+
level.each do |key|
|
959
|
+
val = enumerable.get(key)
|
960
|
+
block ? block.call(key, val) : accum.push([key, val])
|
961
|
+
end
|
962
|
+
end
|
963
|
+
|
964
|
+
block ? enumerable : accum
|
965
|
+
end
|
966
|
+
|
967
|
+
def Map.keys_for(enumerable)
|
968
|
+
keys = enumerable.respond_to?(:keys) ? enumerable.keys : Array.new(enumerable.size){|i| i}
|
969
|
+
end
|
970
|
+
|
971
|
+
def depth_first_each(*args, &block)
|
972
|
+
Map.depth_first_each(enumerable=self, *args, &block)
|
973
|
+
end
|
974
|
+
|
975
|
+
def depth_first_keys(*args, &block)
|
976
|
+
Map.depth_first_keys(enumerable=self, *args, &block)
|
977
|
+
end
|
978
|
+
|
979
|
+
def depth_first_values(*args, &block)
|
980
|
+
Map.depth_first_values(enumerable=self, *args, &block)
|
981
|
+
end
|
982
|
+
|
983
|
+
def breadth_first_each(*args, &block)
|
984
|
+
Map.breadth_first_each(enumerable=self, *args, &block)
|
985
|
+
end
|
986
|
+
|
987
|
+
def contains(other)
|
988
|
+
other = other.is_a?(Hash) ? Map.coerce(other) : other
|
989
|
+
breadth_first_each{|key, value| return true if value == other}
|
990
|
+
return false
|
991
|
+
end
|
992
|
+
alias_method 'contains?', 'contains'
|
993
|
+
|
994
|
+
## for rails' extract_options! compat
|
995
|
+
#
|
996
|
+
def extractable_options?
|
997
|
+
true
|
998
|
+
end
|
999
|
+
|
1000
|
+
## for mongoid type system support
|
1001
|
+
#
|
1002
|
+
def serialize(object)
|
1003
|
+
::Map.for(object)
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
def deserialize(object)
|
1007
|
+
::Map.for(object)
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
def Map.demongoize(object)
|
1011
|
+
Map.for(object)
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
def Map.evolve(object)
|
1015
|
+
Map.for(object)
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
def mongoize
|
1019
|
+
self
|
1020
|
+
end
|
1021
|
+
end
|
1022
|
+
|
1023
|
+
module Kernel
|
1024
|
+
private
|
1025
|
+
def Map(*args, &block)
|
1026
|
+
Map.new(*args, &block)
|
1027
|
+
end
|
1028
|
+
end
|