agt 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +7 -0
  3. data/README.md +5 -0
  4. data/app/assets/javascripts/agt/config.coffee +9 -0
  5. data/app/assets/javascripts/agt/dom.coffee +12 -0
  6. data/app/assets/javascripts/agt/function.coffee +317 -0
  7. data/app/assets/javascripts/agt/geom/circle.coffee +338 -0
  8. data/app/assets/javascripts/agt/geom/cubic_bezier.coffee +37 -0
  9. data/app/assets/javascripts/agt/geom/diamond.coffee +241 -0
  10. data/app/assets/javascripts/agt/geom/ellipsis.coffee +141 -0
  11. data/app/assets/javascripts/agt/geom/linear_spline.coffee +56 -0
  12. data/app/assets/javascripts/agt/geom/matrix.coffee +240 -0
  13. data/app/assets/javascripts/agt/geom/mixins/geometry.coffee +171 -0
  14. data/app/assets/javascripts/agt/geom/mixins/intersections.coffee +150 -0
  15. data/app/assets/javascripts/agt/geom/mixins/path.coffee +145 -0
  16. data/app/assets/javascripts/agt/geom/mixins/proxyable.coffee +9 -0
  17. data/app/assets/javascripts/agt/geom/mixins/spline.coffee +329 -0
  18. data/app/assets/javascripts/agt/geom/mixins/surface.coffee +55 -0
  19. data/app/assets/javascripts/agt/geom/mixins/triangulable.coffee +112 -0
  20. data/app/assets/javascripts/agt/geom/point.coffee +454 -0
  21. data/app/assets/javascripts/agt/geom/polygon.coffee +119 -0
  22. data/app/assets/javascripts/agt/geom/quad_bezier.coffee +43 -0
  23. data/app/assets/javascripts/agt/geom/quint_bezier.coffee +42 -0
  24. data/app/assets/javascripts/agt/geom/rectangle.coffee +599 -0
  25. data/app/assets/javascripts/agt/geom/spiral.coffee +90 -0
  26. data/app/assets/javascripts/agt/geom/transformation_proxy.coffee +43 -0
  27. data/app/assets/javascripts/agt/geom/triangle.coffee +442 -0
  28. data/app/assets/javascripts/agt/impulse.coffee +105 -0
  29. data/app/assets/javascripts/agt/index.coffee +56 -0
  30. data/app/assets/javascripts/agt/inflector/inflection.coffee +21 -0
  31. data/app/assets/javascripts/agt/inflector/inflections.coffee +75 -0
  32. data/app/assets/javascripts/agt/inflector/inflector.coffee +235 -0
  33. data/app/assets/javascripts/agt/inheritance.coffee +132 -0
  34. data/app/assets/javascripts/agt/math.coffee +45 -0
  35. data/app/assets/javascripts/agt/mixins/activable.coffee +31 -0
  36. data/app/assets/javascripts/agt/mixins/aliasable.coffee +25 -0
  37. data/app/assets/javascripts/agt/mixins/alternate_case.coffee +72 -0
  38. data/app/assets/javascripts/agt/mixins/cloneable.coffee +71 -0
  39. data/app/assets/javascripts/agt/mixins/delegation.coffee +90 -0
  40. data/app/assets/javascripts/agt/mixins/disposable.coffee +7 -0
  41. data/app/assets/javascripts/agt/mixins/equatable.coffee +37 -0
  42. data/app/assets/javascripts/agt/mixins/formattable.coffee +52 -0
  43. data/app/assets/javascripts/agt/mixins/globalizable.coffee +175 -0
  44. data/app/assets/javascripts/agt/mixins/has_ancestors.coffee +47 -0
  45. data/app/assets/javascripts/agt/mixins/has_collection.coffee +107 -0
  46. data/app/assets/javascripts/agt/mixins/has_nested_collection.coffee +51 -0
  47. data/app/assets/javascripts/agt/mixins/memoizable.coffee +64 -0
  48. data/app/assets/javascripts/agt/mixins/parameterizable.coffee +101 -0
  49. data/app/assets/javascripts/agt/mixins/poolable.coffee +62 -0
  50. data/app/assets/javascripts/agt/mixins/sourcable.coffee +45 -0
  51. data/app/assets/javascripts/agt/mixins/state_machine.coffee +47 -0
  52. data/app/assets/javascripts/agt/net/router.coffee +165 -0
  53. data/app/assets/javascripts/agt/object.coffee +9 -0
  54. data/app/assets/javascripts/agt/particles/actions/base_action.coffee +7 -0
  55. data/app/assets/javascripts/agt/particles/actions/die_on_surface.coffee +14 -0
  56. data/app/assets/javascripts/agt/particles/actions/force.coffee +11 -0
  57. data/app/assets/javascripts/agt/particles/actions/friction.coffee +14 -0
  58. data/app/assets/javascripts/agt/particles/actions/live.coffee +9 -0
  59. data/app/assets/javascripts/agt/particles/actions/macro_action.coffee +13 -0
  60. data/app/assets/javascripts/agt/particles/actions/move.coffee +11 -0
  61. data/app/assets/javascripts/agt/particles/actions/null_action.coffee +9 -0
  62. data/app/assets/javascripts/agt/particles/counters/by_rate.coffee +17 -0
  63. data/app/assets/javascripts/agt/particles/counters/fixed.coffee +9 -0
  64. data/app/assets/javascripts/agt/particles/counters/null_counter.coffee +9 -0
  65. data/app/assets/javascripts/agt/particles/emission.coffee +35 -0
  66. data/app/assets/javascripts/agt/particles/emitters/null_emitter.coffee +7 -0
  67. data/app/assets/javascripts/agt/particles/emitters/path.coffee +10 -0
  68. data/app/assets/javascripts/agt/particles/emitters/ponctual.coffee +9 -0
  69. data/app/assets/javascripts/agt/particles/emitters/surface.coffee +10 -0
  70. data/app/assets/javascripts/agt/particles/initializers/explosion.coffee +19 -0
  71. data/app/assets/javascripts/agt/particles/initializers/life.coffee +16 -0
  72. data/app/assets/javascripts/agt/particles/initializers/macro_initializer.coffee +10 -0
  73. data/app/assets/javascripts/agt/particles/initializers/null_initializer.coffee +7 -0
  74. data/app/assets/javascripts/agt/particles/initializers/particle_sub_system.coffee +12 -0
  75. data/app/assets/javascripts/agt/particles/initializers/stream.coffee +20 -0
  76. data/app/assets/javascripts/agt/particles/mixins/randomizable.coffee +10 -0
  77. data/app/assets/javascripts/agt/particles/particle.coffee +32 -0
  78. data/app/assets/javascripts/agt/particles/sub_system.coffee +11 -0
  79. data/app/assets/javascripts/agt/particles/system.coffee +103 -0
  80. data/app/assets/javascripts/agt/particles/timers/instant.coffee +11 -0
  81. data/app/assets/javascripts/agt/particles/timers/limited.coffee +19 -0
  82. data/app/assets/javascripts/agt/particles/timers/null_timer.coffee +11 -0
  83. data/app/assets/javascripts/agt/particles/timers/unlimited.coffee +9 -0
  84. data/app/assets/javascripts/agt/particles/timers/until_death.coffee +11 -0
  85. data/app/assets/javascripts/agt/promise.coffee +214 -0
  86. data/app/assets/javascripts/agt/random/random.coffee +100 -0
  87. data/app/assets/javascripts/agt/random/seeds/lagged_fibonnacci.coffee +53 -0
  88. data/app/assets/javascripts/agt/random/seeds/linear.coffee +16 -0
  89. data/app/assets/javascripts/agt/random/seeds/linear_congruential.coffee +23 -0
  90. data/app/assets/javascripts/agt/random/seeds/math_random.coffee +9 -0
  91. data/app/assets/javascripts/agt/random/seeds/mersenne_twister.coffee +42 -0
  92. data/app/assets/javascripts/agt/random/seeds/no_random.coffee +12 -0
  93. data/app/assets/javascripts/agt/random/seeds/paul_houle.coffee +19 -0
  94. data/app/assets/javascripts/agt/signal.coffee +272 -0
  95. data/app/assets/javascripts/agt/sprites/animation.coffee +13 -0
  96. data/app/assets/javascripts/agt/sprites/sprite.coffee +30 -0
  97. data/app/assets/javascripts/agt/widgets/hash.coffee +36 -0
  98. data/app/assets/javascripts/agt/widgets/widgets.coffee +194 -0
  99. data/app/assets/javascripts/agt/widgets/widgets/checked_input.coffee +7 -0
  100. data/app/assets/javascripts/agt/widgets/widgets/focus_bubbling.coffee +15 -0
  101. data/lib/agt.rb +8 -0
  102. data/lib/agt/version.rb +5 -0
  103. metadata +173 -0
@@ -0,0 +1,23 @@
1
+ namespace('agt.random')
2
+ # Public:
3
+ class agt.random.LinearCongruential
4
+ @include agt.mixins.Cloneable('seed')
5
+ @include agt.mixins.Sourcable('chancejs.LinearCongruential','seed')
6
+ @include agt.mixins.Formattable('LinearCongruential','seed')
7
+
8
+ ### Public ###
9
+
10
+ constructor: (@seed=1) ->
11
+ plantSeed: (@seed=1) ->
12
+
13
+ get: ->
14
+ tmp = @seed
15
+ q = tmp
16
+ q = q << 1
17
+ p = tmp << 32
18
+ m = p + q
19
+ if m & 0x80000000
20
+ m = m & 0x7fffffff
21
+ m++
22
+ @seed = m
23
+ m / 0x80000000
@@ -0,0 +1,9 @@
1
+ namespace('agt.random')
2
+ # Public:
3
+ class agt.random.MathRandom
4
+ @include agt.mixins.Cloneable()
5
+ @include agt.mixins.Sourcable('chancejs.MathRandom')
6
+ @include agt.mixins.Formattable('MathRandom')
7
+
8
+ # Public:
9
+ get: -> Math.random()
@@ -0,0 +1,42 @@
1
+ namespace('agt.random')
2
+ # Public:
3
+ class agt.random.MersenneTwister
4
+ @include agt.mixins.Cloneable('seed')
5
+ @include agt.mixins.Sourcable('chancejs.MersenneTwister','seed')
6
+ @include agt.mixins.Formattable('MersenneTwister','seed')
7
+
8
+ ### Public ###
9
+
10
+ constructor: (seed=0) ->
11
+ @mt = Array 623
12
+ @z = 0
13
+ @y = 0
14
+
15
+ @plantSeed seed
16
+
17
+ plantSeed: (seed=0) ->
18
+ @mt[0] = seed
19
+ @mt[i] = ((0x10dcd * @mt[i-1]) + 1) & 0xFFFFFFFF for i in [1..623]
20
+
21
+ get: ->
22
+ @generateNumbers() if @z >= 623
23
+ @extractNumber(@z++) / 0x80000000
24
+
25
+ ### Internal: ###
26
+
27
+ generateNumbers: ->
28
+ @z = 0
29
+ for i in [0..623]
30
+ @y = 0x80000000 & @mt[i] + 0x7FFFFFFF & (@mt[(i + 1) % 623])
31
+
32
+ if @y % 2 is 0
33
+ @mt[i] = @mt[(i + 397) % 623] ^ (@y >> 1)
34
+ else
35
+ @mt[i] = @mt[(i + 397) % 623] ^ (@y >> 1) ^ 0x9908B0DF
36
+
37
+ extractNumber: (i) ->
38
+ @y = @mt[i]
39
+ @y ^= (@y >> 11)
40
+ @y ^= (@y << 7) & 0x9d2c5680
41
+ @y ^= (@y << 15) & 0xefc60000
42
+ @y ^= (@y >> 18)
@@ -0,0 +1,12 @@
1
+ namespace('agt.random')
2
+ # Public:
3
+ class agt.random.NoRandom
4
+ @include agt.mixins.Cloneable('seed')
5
+ @include agt.mixins.Sourcable('chancejs.NoRandom','seed')
6
+ @include agt.mixins.Formattable('NoRandom','seed')
7
+
8
+ ### Public ###
9
+
10
+ constructor: (@seed=0) ->
11
+
12
+ get: -> @seed
@@ -0,0 +1,19 @@
1
+ namespace('agt.random')
2
+ # Original Implementation License:
3
+ #
4
+ # The Central Randomizer 1.3 (C) 1997 by Paul Houle (paul@honeylocust.com)
5
+ # See: http://www.honeylocust.com/javascript/randomizer.html
6
+
7
+ # Public:
8
+ class agt.random.PaulHoule
9
+ @include agt.mixins.Cloneable('seed')
10
+ @include agt.mixins.Sourcable('chancejs.PaulHoule','seed')
11
+ @include agt.mixins.Formattable('PaulHoule','seed')
12
+
13
+ ### Public ###
14
+
15
+ constructor: (@seed) ->
16
+
17
+ get: ->
18
+ @seed = (@seed * 9301 + 49297) % 233280
19
+ @seed / 233280.0
@@ -0,0 +1,272 @@
1
+ # Public: Use a `Signal` object wherever you need to dispatch an event.
2
+ # A `Signal` is a dispatcher that have only one channel.
3
+ #
4
+ # Signals are generally defined as property of an object. And
5
+ # their name generally end with a past tense verb, such as in:
6
+ #
7
+ # ```coffeescript
8
+ # myObject.somethingChanged = new Signal
9
+ # ```
10
+ class agt.Signal
11
+
12
+ ### Public ###
13
+
14
+ # Creates a new `Signal` instance.
15
+ # Optionally, a signal can have a specific signature.
16
+ # A signature is a collection of argument names such:
17
+ #
18
+ # ```coffeescript
19
+ # positionChanged = new Signal 'target', 'position'
20
+ # ```
21
+ #
22
+ # If a signature is passed to a signal, every listener
23
+ # added to the signal must then match the signature.
24
+ #
25
+ # ```coffeescript
26
+ # # will be registered
27
+ # positionChanged.add (target, position) ->
28
+ #
29
+ # # will be registered too
30
+ # positionChanged.add (target, position, callback) ->
31
+ #
32
+ # # will not be registered
33
+ # positionChanged.add () -> # will throw an error
34
+ # ```
35
+ #
36
+ # In the case of an asynchronous listener, the callback argument
37
+ # is not considered as being part of the signature.
38
+ constructor: (@signature...) ->
39
+ @listeners = []
40
+ # The `asyncListeners` property stores the number of asynchronous
41
+ # listeners to use a synchronous dispatch when equal to 0.
42
+ @asyncListeners = 0
43
+
44
+ # Registers a listener for this signal to be called with
45
+ # the provided context.
46
+ # The context is the object that can be accessed through `this`
47
+ # inside the listener function body.
48
+ #
49
+ # An optional `priority` argument allow you to force
50
+ # an order of dispatch for a listener.
51
+ #
52
+ # Signals listeners can be asynchronous, in that case the last
53
+ # argument of the listener must be named `callback`. An async
54
+ # listener blocks the dispatch loop until the callback function
55
+ # passed to the listener is triggered.
56
+ #
57
+ # ```coffeescript
58
+ # # sync listener
59
+ # signal.add (a, b, c) ->
60
+ #
61
+ # # async listener
62
+ # signal.add (a, b, c, callback) -> callback()
63
+ # ```
64
+ #
65
+ # A listener can be registered several times, but only
66
+ # if the context object is different each time.
67
+ #
68
+ # In other words, the following is possible:
69
+ #
70
+ # ```coffeescript
71
+ # listener = ->
72
+ # context = {}
73
+ # myObject.signal.add listener
74
+ # myObject.signal.add listener, context
75
+ # ```
76
+ #
77
+ # When the following is not:
78
+ #
79
+ # ```coffeescript
80
+ # listener = ->
81
+ # myObject.signal.add listener
82
+ # myObject.signal.add listener
83
+ # ```
84
+ #
85
+ # listener - The {Function} to call when a signal is emitted.
86
+ # context - The `this` {Object} to call the function with.
87
+ # priority - A priority {Number}, the higher the priority the sooner
88
+ # the listener will be called in the dispatch loop.
89
+ add: (listener, context, priority=0) ->
90
+ @validate listener
91
+
92
+ if not @registered listener, context
93
+ @listeners.push [listener, context, false, priority]
94
+ @asyncListeners++ if @isAsync listener
95
+
96
+ # Listeners are sorted according to their order each time
97
+ # a new listener is added.
98
+ @sortListeners()
99
+
100
+ # Registers a listener for only one call.
101
+ #
102
+ # All the others rules are the same. So you can't add
103
+ # the same listener/context couple twice through the two methods.
104
+ #
105
+ # listener - The {Function} to call when a signal is emitted.
106
+ # context - The `this` {Object} to call the function with.
107
+ # priority - A priority {Number}, the higher the priority the sooner
108
+ # the listener will be called in the dispatch loop.
109
+ addOnce: (listener, context, priority = 0) ->
110
+ @validate listener
111
+ if not @registered listener, context
112
+ @listeners.push [listener, context, true, priority]
113
+ @asyncListeners++ if @isAsync listener
114
+ @sortListeners()
115
+
116
+ # Removes a listener from this signal, but only with the context that
117
+ # was registered with it.
118
+ #
119
+ # In this regards, avoid to register listeners without a context.
120
+ # If later in the application a context is forgotten or invalid
121
+ # when removing a listener from this signal, the listener
122
+ # without context will end up being removed.
123
+ #
124
+ # listener - The {Function} to remove from this signal.
125
+ # context - The `this` {Object} that was registered with the listener.
126
+ remove: (listener, context) ->
127
+ if @registered listener, context
128
+ @asyncListeners-- if @isAsync listener
129
+ @listeners.splice @indexOf(listener, context), 1
130
+
131
+ # Removes all listeners at once.
132
+ #
133
+ # ```coffeescript
134
+ # signal.removeAll()
135
+ # ```
136
+ removeAll: ->
137
+ @listeners = []
138
+ @asyncListeners = 0
139
+
140
+ # Internal: `indexOf` returns the position of the listener/context couple
141
+ # in the listeners array.
142
+ indexOf: (listener, context) ->
143
+ return i for [l,c],i in @listeners when listener is l and context is c
144
+ -1
145
+
146
+ # Returns true if the passed-in listener have been registered with the
147
+ # specified context in this signal.
148
+ #
149
+ # listener - The listener {Function} to verify.
150
+ # context - The context {Object} registered with the listener.
151
+ #
152
+ # Returns a {Boolean}.
153
+ registered: (listener, context) ->
154
+ @indexOf(listener, context) isnt -1
155
+
156
+ # Returns true if the signal has listeners.
157
+ #
158
+ # Returns a {Boolean}.
159
+ hasListeners: -> @listeners.length isnt 0
160
+
161
+ # Internal: The listeners are sorted according to their `priority`.
162
+ # The higher the priority the lower the listener will be
163
+ # in the call order.
164
+ sortListeners: ->
165
+ return if @listeners.length <= 1
166
+ @listeners.sort (a, b) ->
167
+ [pA, pB] = [a[3], b[3]]
168
+
169
+ if pA < pB then 1 else if pB < pA then -1 else 0
170
+
171
+ # Internal: Throws an error if the passed-in listener's signature
172
+ # doesn't match the signal's one.
173
+ #
174
+ # ```coffeescript
175
+ # signal = new Signal 'a', 'b', 'c'
176
+ # signal.validate () -> # false
177
+ # signal.validate (a,b,c) -> # true
178
+ # signal.validate (a,b,c,callback) -> # true
179
+ # ```
180
+ validate: (listener) ->
181
+ if @signature.length > 0
182
+ re = /[^(]+\(([^)]+)\).*$/m
183
+ listenerSignature = Function::toString.call(listener).split('\n').shift()
184
+ signature = listenerSignature.replace(re, '$1')
185
+ args = signature.split /\s*,\s*/g
186
+
187
+ args.shift() if args[0] is ''
188
+ args.pop() if args[args.length-1] is 'callback'
189
+
190
+ s1 = @signature.join()
191
+ s2 = args.join()
192
+
193
+ m = "The listener #{listener} doesn't match the signal's signature #{s1}"
194
+ throw new Error m if s2 isnt s1
195
+
196
+ # Returns `true` if the passed-in `listener` is asynchronous.
197
+ #
198
+ # ```coffeescript
199
+ # signal.isAsync(->) # false
200
+ # signal.isAsync((callback) ->) # true
201
+ # ```
202
+ #
203
+ # listener - The listner {Function} to test.
204
+ #
205
+ # Returns a {Boolean}.
206
+ isAsync: (listener) ->
207
+ Function::toString.call(listener).indexOf('callback)') != -1
208
+
209
+ # Dispatch a signal to the signal listeners.
210
+ # Signals are dispatched to all the listeners. All the arguments
211
+ # passed to the dispatch become the signal's message.
212
+ #
213
+ # ```coffeescript
214
+ # signal.dispatch this, true
215
+ # ```
216
+ #
217
+ # Listeners registered for only one call will be removed after
218
+ # the call.
219
+ #
220
+ # Optionally you can pass a callback argument to the dispatch function.
221
+ # In that case, the callback must be the last argument passed to the
222
+ # `dispatch` function. This function will be called at the end
223
+ # of the dispatch, allowing to execute code after all listeners,
224
+ # even asynchronous, have been triggered.
225
+ #
226
+ # ```coffeescript
227
+ # signal.dispatch this, true, ->
228
+ # # all listeners have finish their execution
229
+ # ```
230
+ #
231
+ # **Note:** As the dispatch function will automatically consider
232
+ # the last argument as the callback if its type is `function`, you should
233
+ # avoid using function as the sole argument or as the last argument
234
+ # for a listener. If that case occurs, consider either re-arranging the
235
+ # arguments order or using a value object to carry the function.
236
+ #
237
+ # args - The arguments to dispatch with the signal.
238
+ # callback - A {Function} to callback when all listeners have been notified.
239
+ dispatch: (args..., callback)->
240
+ unless typeof callback is 'function'
241
+ args.push callback
242
+ callback = null
243
+
244
+ listeners = @listeners.concat()
245
+ # If at leat one listener is async, the whole dispatch process is async
246
+ # otherwise the fast route is used.
247
+ if @asyncListeners > 0
248
+ next = (callback) =>
249
+ if listeners.length
250
+ [listener, context, once, priority] = listeners.shift()
251
+
252
+ if @isAsync listener
253
+ listener.apply context, args.concat =>
254
+ @remove listener, context if once
255
+ next callback
256
+ else
257
+ listener.apply context, args
258
+ @remove listener, context if once
259
+ next callback
260
+ else
261
+ callback?()
262
+
263
+ next callback
264
+ else
265
+ # The fast route is just a loop over the listeners.
266
+ # At that point, if your listener do async stuff, it will
267
+ # not prevent the dispatching until it's done.
268
+ for [listener, context, once, priority] in listeners
269
+ listener.apply context, arguments
270
+ @remove listener, context if once
271
+
272
+ callback?()
@@ -0,0 +1,13 @@
1
+ namespace('agt.sprites')
2
+
3
+ # Public:
4
+ class agt.sprites.Animation
5
+ constructor: (@image,
6
+ @width, @height,
7
+ @rowStart=0, @rowEnd=0,
8
+ @colStart=0, @colEnd=0
9
+ @durations) ->
10
+ @durations = (1000 / 24 for i in [@colStart..@colEnd]) unless @durations?
11
+ @rowLength = @rowEnd - @rowStart + 1
12
+ @colLength = @colEnd - @colStart + 1
13
+ @length = @rowLength * @colLength
@@ -0,0 +1,30 @@
1
+ namespace('agt.sprites')
2
+
3
+ # Public:
4
+ class agt.sprites.Sprite
5
+ constructor: (@animation) ->
6
+ @position = new agt.geom.Point
7
+ @center = new agt.geom.Point
8
+ @frame = 0
9
+ @time = 0
10
+
11
+ animate: (bias) ->
12
+ duration = @animation.durations[@frame]
13
+ if @time > duration
14
+ @frame = (@frame + 1) % @animation.length
15
+ @time = @time % duration
16
+ @time += bias
17
+
18
+ render: (context) ->
19
+ animation = @animation
20
+ col = @frame % animation.colLength
21
+ row = (@frame - col) / animation.colLength
22
+ clipX = (animation.colStart + col) * animation.width
23
+ clipY = (animation.rowStart + row) * animation.height
24
+
25
+ x = @position.x - @center.x
26
+ y = @position.y - @center.y
27
+
28
+ context.drawImage animation.image,
29
+ clipX, clipY, animation.width, animation.height,
30
+ x, y, animation.width, animation.height
@@ -0,0 +1,36 @@
1
+ namespace('agt.widgets')
2
+
3
+ # Public:
4
+ class agt.widgets.Hash
5
+ constructor: ->
6
+ @clear()
7
+
8
+ clear: ->
9
+ @keys = []
10
+ @values = []
11
+
12
+ set: (key, value) ->
13
+ if @hasKey key
14
+ index = @keys.indexOf key
15
+ @keys[index] = key
16
+ @values[index] = value
17
+ else
18
+ @keys.push key
19
+ @values.push value
20
+
21
+ get: (key) -> @values[ @keys.indexOf key ]
22
+
23
+ getKey: (value) -> @keys[ @values.indexOf value ]
24
+
25
+ hasKey: (key) -> @keys.indexOf(key) > 0
26
+
27
+ unset: (key) ->
28
+ index = @keys.indexOf key
29
+ @keys.splice index, 1
30
+ @values.splice index, 1
31
+
32
+ each: (block) -> @values.forEach block
33
+
34
+ eachKey: (block) -> @keys.forEach block
35
+
36
+ eachPair: (block) -> @keys.forEach (key) => block? key, @get key