agt 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.
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,64 @@
1
+ namespace('agt.mixins')
2
+ # Public: A `Memoizable` object can store data resulting of heavy methods
3
+ # in order to speed up further call to that method.
4
+ #
5
+ # The invalidation of the memoized data is defined using a `memoizationKey`.
6
+ # That key should be generated based on the data that may induce changes
7
+ # in the functions's results.
8
+ #
9
+ # ```coffeescript
10
+ # class Dummy
11
+ # @include agt.mixins.Memoizable
12
+ #
13
+ # constructor: (@p1, @p2) ->
14
+ # # ...
15
+ #
16
+ # heavyMethod: (arg) ->
17
+ # key = "heavyMethod-#{arg}"
18
+ # return @memoFor key if @memoized key
19
+ #
20
+ # # do costly computation
21
+ # @memoize key, result
22
+ #
23
+ # memoizationKey: -> "#{p1};#{p2}"
24
+ # ```
25
+ class agt.mixins.Memoizable
26
+ # Public: Returns `true` if data are available for the given `prop`.
27
+ #
28
+ # When the current state of the object don't match the stored
29
+ # memoization key, the whole data stored in the memo are cleared.
30
+ #
31
+ # prop - The {String} name of a property.
32
+ #
33
+ # Retuns a {Boolean} of whether the value of the propery is memoized or not.
34
+ memoized: (prop) ->
35
+ if @memoizationKey() is @__memoizationKey__
36
+ @__memo__?[ prop ]?
37
+ else
38
+ @__memo__ = {}
39
+ false
40
+
41
+ # Public: Returns the memoized data for the given `prop`.
42
+ #
43
+ # prop - The {String} name of a property.
44
+ #
45
+ # Returns the memoized data for the given prop
46
+ memoFor: (prop) -> @__memo__[ prop ]
47
+
48
+ # Public: Register a memo in the current object for the given `prop`.
49
+ # The memoization key is updated with the current state of the
50
+ # object.
51
+ memoize: (prop, value) ->
52
+ @__memo__ ||= {}
53
+ @__memoizationKey__ = @memoizationKey()
54
+ @__memo__[ prop ] = value
55
+
56
+ # Public Abstract: Generates the memoization key for this
57
+ # instance's state.
58
+ #
59
+ # By default the memoization key of an object is the return of its `toString`
60
+ # method. **You SHOULD redefine the memoization key generation in the class
61
+ # including the `Memoizable` mixin.**
62
+ #
63
+ # Returns a {String} that identify the state of the current instance.
64
+ memoizationKey: -> @toString()
@@ -0,0 +1,101 @@
1
+ namespace('agt.mixins')
2
+ # Public: A `Parameterizable` object provides a class and an instance
3
+ # method to convert an arbitrary {Object} or a series of values into
4
+ # an instance of the class receiving the mixin.
5
+ #
6
+ # ```coffeescript
7
+ # class Dummy
8
+ # @include agt.mixins.Parameterizable('dummyFrom', {
9
+ # x: 0
10
+ # y: 0
11
+ # name: 'Untitled'
12
+ # })
13
+ #
14
+ # constructor: (@x, @y, @name) ->
15
+ #
16
+ # instance = new Dummy
17
+ #
18
+ # Dummy.instanceFrom(1,2,'foo')
19
+ # instance.instanceFrom(1,2,'foo')
20
+ # ```
21
+ #
22
+ # The methods can be used either with an object or with a list
23
+ # of values whose order is the order of the `parameters` object keys:
24
+ #
25
+ # ``` coffeescript
26
+ # Dummy.instanceFrom(1,2,'Foo') # [Dummy(x=1,y=2,name='Foo')]
27
+ # Dummy.instanceFrom(x: 1, y: 2, name: 'Foo') # [Dummy(x=1,y=2,name='Foo')]
28
+ # ```
29
+ #
30
+ # The last `instanceFrom` argument controls can be a {Boolean} of whether
31
+ # the method raises an exception when the creation arguments types mismatch:
32
+ #
33
+ # ``` coffeescript
34
+ # Dummy.instanceFrom(1,'foo','bar' true) # value for y doesn't match type number
35
+ # Dummy.instanceFrom(x: 1, y: 'foo', name: 'bar', true) # value for y doesn't match type number
36
+ # ```
37
+ #
38
+ # The only edge case is that if you have yourself defined a {Boolean} as last
39
+ # argument you'll always have to pass the `strict` argument when calling the
40
+ # methods.
41
+ #
42
+ # The `allowPartial` argument, when set to `true`, will prevent the method
43
+ # to fallback on the default values provided in the `parameters` object
44
+ # leaving the class constructor to deal with it.
45
+ #
46
+ # method - The {String} name of the method to create.
47
+ # parameters - An {Object} describing the arguments name, type and order.
48
+ # allowPartial - A {Boolean} of
49
+ agt.mixins.Parameterizable = (method, parameters, allowPartial=false) ->
50
+
51
+ # Public: The concrete mixin as returned by the
52
+ # [Parameterizable](../files/mixins/parameterizable.coffee.html) generator.
53
+ class ConcreteParameterizable
54
+
55
+ # Internal:
56
+ @included: (klass) ->
57
+ f = (args..., strict)->
58
+ (args.push(strict); strict = false) if typeof strict isnt 'boolean'
59
+ output = {}
60
+
61
+ o = arguments[ 0 ]
62
+ n = 0
63
+ firstArgumentIsObject = o? and typeof o is 'object'
64
+
65
+ for k,v of parameters
66
+ value = if firstArgumentIsObject then o[ k ] else arguments[ n++ ]
67
+ parameterType = typeof v
68
+
69
+ if strict
70
+ if typeof value is parameterType
71
+ output[ k ] = value
72
+ else
73
+ if parameterType is 'number'
74
+ value = parseFloat value
75
+
76
+ if isNaN value
77
+ throw new Error "value for #{ k } doesn't match type #{ parameterType}"
78
+ else
79
+ output[k] = value
80
+ else
81
+ throw new Error "value for #{ k } doesn't match type #{ parameterType}"
82
+
83
+ else
84
+ if value?
85
+ if parameterType is 'number'
86
+ value = parseFloat value
87
+
88
+ if isNaN value
89
+ output[ k ] = v unless allowPartial
90
+ else
91
+ output[ k ] = value
92
+ else
93
+ output[ k ] = value
94
+ else
95
+ output[ k ] = v unless allowPartial
96
+
97
+
98
+ output
99
+
100
+ klass[method] = f
101
+ klass::[method] = f
@@ -0,0 +1,62 @@
1
+ namespace('agt.mixins')
2
+ # Public: A `Poolable` class has the ability to manage a pool of instances
3
+ # and prevent the further creation of instances as long as unused ones
4
+ # are still present.
5
+ class agt.mixins.Poolable
6
+
7
+ # Internal: The two objects stores are created in the extended hook to avoid
8
+ # that all the class extending `Poolable` shares the same instances.
9
+ @extended: (klass) ->
10
+ klass.usedInstances = []
11
+ klass.unusedInstances = []
12
+
13
+ # Public: Resets all the instances pools.
14
+ @resetPools: ->
15
+ @usedInstances = []
16
+ @unusedInstances = []
17
+
18
+ # Public: The `get` method returns an instance of the class.
19
+ # If the class defines an `init` method, it will be called with the
20
+ # passed-in `options` {Object}.
21
+ #
22
+ # options - The option {Object} to use to setup the created instance.
23
+ #
24
+ # Returns an instance of the current class.
25
+ @get: (options={}) ->
26
+ # Either retrieve or create the instance.
27
+ if @unusedInstances.length > 0
28
+ instance = @unusedInstances.shift()
29
+ else
30
+ instance = new this
31
+
32
+ # Stores the instance in the used pool.
33
+ @usedInstances.push instance
34
+
35
+ # Init the instance and return it.
36
+ instance.init(options)
37
+ instance
38
+
39
+ # Public: The `release` method takes an instance and move
40
+ # it from the the used pool to the unused pool.
41
+ #
42
+ # instance - The instance of the current class.
43
+ @release: (instance) ->
44
+ return unless instance in @usedInstances
45
+
46
+ # The instance is removed from the used instances pool.
47
+ index = @usedInstances.indexOf(instance)
48
+ @usedInstances.splice(index, 1)
49
+
50
+ # And then moved to the unused instances one.
51
+ @unusedInstances.push instance
52
+
53
+ # Public: Default `init` implementation, just copy all the options
54
+ # in the instance.
55
+ #
56
+ # options - The setup {Object} for this instance.
57
+ init: (options={}) -> @[ k ] = v for k,v of options
58
+
59
+ # Public: Default `dispose` implementation, call the `release` method
60
+ # on the instance constructor. A proper implementation should
61
+ # take care of removing/cleaning all the instance properties.
62
+ dispose: -> @constructor.release(this)
@@ -0,0 +1,45 @@
1
+ namespace('agt.mixins')
2
+ # Public: A `Sourcable` object is an object that can return the source code
3
+ # to re-create it by code.
4
+ #
5
+ # ```coffeescript
6
+ # class Dummy
7
+ # @include agt.mixins.Sourcable('agt.geom.Dummy', 'p1', 'p2')
8
+ #
9
+ # constructor: (@p1, @p2) ->
10
+ #
11
+ # dummy = new Dummy(10,'foo')
12
+ # dummy.toSource() # "new agt.geom.Dummy(10,'foo')"
13
+ # ```
14
+ #
15
+ # name - The {String} path to the current class.
16
+ # signature - A list of {String} name of properties
17
+ agt.mixins.Sourcable = (name, signature...) ->
18
+
19
+ # Public: A concrete class is generated and returned by
20
+ # [Sourcable](../files/mixins/sourcable.coffee.html).
21
+ class ConcreteSourcable
22
+
23
+ # Internal: Generates the source for a property's value.
24
+ sourceFor = (value) ->
25
+ switch typeof value
26
+ when 'object'
27
+ isArray = Object::toString.call(value).indexOf('Array') isnt -1
28
+ if isArray
29
+ "[#{ value.map (el) -> sourceFor el }]"
30
+ else
31
+ if value.toSource?
32
+ value.toSource()
33
+ else
34
+ value
35
+ when 'string'
36
+ "'#{ value.replace "'", "\\'" }'"
37
+ else value
38
+
39
+ # Public: Returns the source code corresponding to the current instance.
40
+ #
41
+ # Returns a {String} with the source of the instance.
42
+ toSource: ->
43
+ args = (@[ arg ] for arg in signature).map (o) -> sourceFor o
44
+
45
+ "new #{ name }(#{ args.join ',' })"
@@ -0,0 +1,47 @@
1
+ namespace('agt.mixins')
2
+ class agt.mixins.StateMachine
3
+ @initial: (state) -> @initialState = state
4
+
5
+ @event: (name, block) ->
6
+ @::[name] = block
7
+
8
+ @state: (name, defines) ->
9
+ @states ?= {}
10
+ @stateProperties ?= []
11
+
12
+ @states[name] = defines
13
+ @::[name] = -> @state is name
14
+ @stateProperties.push(k) for k,v of defines when k not in @stateProperties
15
+
16
+ this
17
+
18
+ initializeStateMachine: -> @setState(@constructor.initialState)
19
+
20
+ transition: (options) ->
21
+ throw new Error("From option is mandatory") unless options.from?
22
+ throw new Error("To option is mandatory") unless options.to?
23
+
24
+ {from, to} = options
25
+ if from is 'all'
26
+ from = Object.keys(@constructor.states)
27
+ else
28
+ from = from.split(' ')
29
+
30
+ to = options.to
31
+
32
+ for f in from
33
+ unless @constructor.states[f]
34
+ throw new Error("From state '#{f}' does not match any defined state")
35
+
36
+ unless @constructor.states[to]
37
+ throw new Error("To state '#{to}' does not match any defined state")
38
+
39
+ if @state in from
40
+ @setState(to)
41
+ else
42
+ throw new Error "Can't transition from #{@state} to #{to}"
43
+
44
+ setState: (state) ->
45
+ @state = state
46
+ state = @constructor.states[@state]
47
+ @[key] = state[key] for key in @constructor.stateProperties
@@ -0,0 +1,165 @@
1
+ namespace('agt.net')
2
+ # Public: The router class allow to trigger view changes on url changes.
3
+ # Typically the url changes may be handled by the history API or a url hash
4
+ # handler.
5
+ # The routes the router will support are defined in the callback passed to
6
+ # the router constructor as in the example below:
7
+ #
8
+ # ```coffee
9
+ # router = new Router ->
10
+ # @match '/posts', ->
11
+ # # render the post index
12
+ #
13
+ # @match '/posts/:id', ({id}) ->
14
+ # # render a single post identified with an :id
15
+ # ```
16
+ class agt.net.Router
17
+ @extend agt.mixins.Aliasable
18
+
19
+ ### Public ###
20
+
21
+ # Creates a new router instance. The constructor takes a {Function}
22
+ # and call it in the constructor this the router as the context object
23
+ # of the call. This is generally inside the block {Function} that you'll
24
+ # define the route to support.
25
+ #
26
+ # block - The initialization {Function}.
27
+ constructor: (block) ->
28
+ @routes = {}
29
+ @beforeFilters = []
30
+ @afterFilters = []
31
+
32
+ block.call(this)
33
+ @buildRoutesHandlers()
34
+
35
+ # Registers a route on this router.
36
+ #
37
+ # A route is defined with a `path` {String}, starting with a `/` and that
38
+ # can contains variables patterns for dynamic routes.
39
+ #
40
+ # For instance, the following path `'/posts/:post_id/comments/:id'` sets
41
+ # that the route defines two variables `post_id` and `id` that can contains
42
+ # any value. It means that this route will match both `'/posts/1/comments/2'`
43
+ # or `'/posts/foo/comments/bar'`.
44
+ #
45
+ # To limit the legal values for a variable, you can pass a string
46
+ # in the option object with the name of the variable as key, such as in:
47
+ #
48
+ # ```coffee
49
+ # @match '/posts/:id', id: '(\d+)', ({id}) ->
50
+ # ```
51
+ #
52
+ # In the example above the route can only be matched if the content for the
53
+ # `id` variable in the path contains only numbers. It means that
54
+ # `'/posts/1/comments/2'` will be matched by the route but
55
+ # `'/posts/foo/comments/bar'` won't.
56
+ #
57
+ # path - The path {String} to support.
58
+ # options - An option {Object} that can contain properties matching the
59
+ # path variables and containing strings that will be used to
60
+ # construct the regexp String to test paths.
61
+ match: (path, options={}, handle) ->
62
+ [options, handle] = [{}, options] if typeof options is 'function'
63
+ path = path.replace /^\/|\/$/g, ''
64
+ @routes[path] = {handle, options}
65
+
66
+ @alias 'match', 'get'
67
+
68
+ # Registers a filter to be called before the route handler.
69
+ #
70
+ # ```coffee
71
+ # @beforeFilter (path, router) ->
72
+ # # ...
73
+ # ```
74
+ #
75
+ # filter - The {Function} to invoke before the route handler.
76
+ beforeFilter: (filter) -> @beforeFilters.push filter
77
+
78
+ # Registers a filter to be called after the route handler.
79
+ #
80
+ # ```coffee
81
+ # @afterFilter (path, router) ->
82
+ # # ...
83
+ # ```
84
+ #
85
+ # filter - The {Function} to invoke after the route handler.
86
+ afterFilter: (filter) -> @afterFilters.push filter
87
+
88
+ # Defines a handler for paths that doesn't match any routes.
89
+ #
90
+ # notFoundHandle - The {Function} to call for route not found.
91
+ notFound: (@notFoundHandle) ->
92
+
93
+ # Triggers a change of route with the passed-in path.
94
+ #
95
+ # A `route:changed` event is dispatched after all the filters and the
96
+ # handler was called.
97
+ #
98
+ # path - The path {String} to match.
99
+ goto: (path) ->
100
+ path = '/' if path is '.'
101
+ path = path.replace(/^\./, '')
102
+ path = path.replace(/\/$/, '') unless path is '/'
103
+ path = "/#{path}" if path.indexOf('/') isnt 0
104
+
105
+ handler = @findRoute(path)
106
+
107
+ @beforeFilters.forEach (filter) => filter(path, this)
108
+
109
+ if handler?
110
+ handler(path)
111
+ else
112
+ @notFoundHandle?({path})
113
+
114
+ @afterFilters.forEach (filter) => filter(path, this)
115
+
116
+ document.dispatchEvent agt.domEvent('route:changed', {path}) if document?
117
+
118
+ # Internal: Returns the {Function} to handle the passed-in `path`.
119
+ #
120
+ # path - The path {String} to match.
121
+ #
122
+ # Returns a {Function}.
123
+ findRoute: (path) ->
124
+ for k, {test, handle} of @routes
125
+ return handle if test(path)
126
+
127
+ # Internal: Builds the route handlers.
128
+ buildRoutesHandlers: ->
129
+ for path, data of @routes
130
+ @routes[path] = @buildRouteHandler(path, data)
131
+
132
+ # Internal: Build a single route handler using the information provided.
133
+ #
134
+ # path - A {String} of the route path pattern.
135
+ # options - An object with the following properties:
136
+ # :handle - The {Function} that will handle the route.
137
+ # :options - An {Object} that contains the path variables patterns.
138
+ buildRouteHandler: (path, {handle, options}) ->
139
+ pathArray = path.split '/'
140
+ pathRe = []
141
+ pathParams = []
142
+
143
+ for part in pathArray
144
+ params_re = /^:(.+)$/
145
+ if res = params_re.exec(part)
146
+ param_name = res[1]
147
+ pathRe.push options[param_name] ? '([^/]+)'
148
+ pathParams.push param_name
149
+ else
150
+ pathRe.push part
151
+
152
+ re = new RegExp('^/' + pathRe.join('/') + '$')
153
+
154
+ {
155
+ options
156
+ test: (path) -> re.test(path)
157
+ handle: (path) ->
158
+ params = {path: path}
159
+ res = re.exec(path)
160
+ if res? and res.length > 1
161
+ for pname,i in pathParams
162
+ params[pname] = decodeURI(res[i+1])
163
+
164
+ handle(params)
165
+ }