brancusi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/Rakefile +13 -0
  2. data/lib/assets/javascripts/brancusi/application/application.js.coffee +114 -0
  3. data/lib/assets/javascripts/brancusi/application/application_controller.js.coffee +17 -0
  4. data/lib/assets/javascripts/brancusi/application/application_module.js.coffee +29 -0
  5. data/lib/assets/javascripts/brancusi/application/bootstrapper.js.coffee +18 -0
  6. data/lib/assets/javascripts/brancusi/application/index.js.coffee +1 -0
  7. data/lib/assets/javascripts/brancusi/application/sandbox.js.coffee +14 -0
  8. data/lib/assets/javascripts/brancusi/container/container.js.coffee +139 -0
  9. data/lib/assets/javascripts/brancusi/container/dependent_module.js.coffee +30 -0
  10. data/lib/assets/javascripts/brancusi/container/dependent_object.js.coffee +9 -0
  11. data/lib/assets/javascripts/brancusi/container/index.js.coffee +1 -0
  12. data/lib/assets/javascripts/brancusi/events/event_object.js.coffee +10 -0
  13. data/lib/assets/javascripts/brancusi/events/events_module.js.coffee +14 -0
  14. data/lib/assets/javascripts/brancusi/events/index.js.coffee +1 -0
  15. data/lib/assets/javascripts/brancusi/events/mediator.js.coffee +70 -0
  16. data/lib/assets/javascripts/brancusi/index.js.coffee +2 -0
  17. data/lib/assets/javascripts/brancusi/namespace.js.coffee +13 -0
  18. data/lib/assets/javascripts/brancusi/object_model/base_object.js.coffee +27 -0
  19. data/lib/assets/javascripts/brancusi/object_model/decorate.js.coffee +14 -0
  20. data/lib/assets/javascripts/brancusi/object_model/extend.js.coffee +10 -0
  21. data/lib/assets/javascripts/brancusi/object_model/include.js.coffee +13 -0
  22. data/lib/assets/javascripts/brancusi/object_model/index.js.coffee +1 -0
  23. data/lib/assets/javascripts/brancusi/renderer/index.js.coffee +1 -0
  24. data/lib/assets/javascripts/brancusi/renderer/region_manager.js.coffee +30 -0
  25. data/lib/assets/javascripts/brancusi/renderer/renderer.js.coffee +41 -0
  26. data/lib/assets/javascripts/brancusi/renderer/template_manager.js.coffee +0 -0
  27. data/lib/assets/javascripts/brancusi/routes/dispatcher.js.coffee +10 -0
  28. data/lib/assets/javascripts/brancusi/routes/index.js.coffee +1 -0
  29. data/lib/assets/javascripts/brancusi/routes/mapper.js.coffee +41 -0
  30. data/lib/assets/javascripts/brancusi/routes/router.js.coffee +15 -0
  31. data/lib/assets/javascripts/brancusi/support/davis_router.js.coffee +19 -0
  32. data/lib/assets/javascripts/brancusi/support/knockout_renderer.js.coffee +12 -0
  33. data/lib/assets/javascripts/davis.js +1838 -0
  34. data/lib/assets/javascripts/knockout.js +3583 -0
  35. data/lib/assets/javascripts/underscore.js +1221 -0
  36. data/lib/assets/javascripts/underscore.string.js +600 -0
  37. data/lib/brancusi.rb +4 -0
  38. data/lib/brancusi/engine.rb +4 -0
  39. data/lib/brancusi/version.rb +3 -0
  40. metadata +282 -0
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'guard/jasmine/task'
4
+
5
+ Guard::JasmineTask.new('jasmine:specs') do |task|
6
+ task.options = "-s thin -p 3001"
7
+ end
8
+
9
+ Guard::JasmineTask.new('jasmine:stories') do |task|
10
+ task.options = "-s thin -p 3001 -u http://localhost:3001/jasmine-stories"
11
+ end
12
+
13
+ task 'jasmine:all' => ['guard:jasmine:specs', 'guard:jasmine:stories']
@@ -0,0 +1,114 @@
1
+ #= require brancusi/events
2
+ #= require brancusi/routes/mapper
3
+
4
+ namespace "brancusi"
5
+
6
+ class brancusi.Application extends brancusi.EventObject
7
+
8
+ @dependency mediator: "Mediator"
9
+
10
+ # Default configuration options, which may be overridden by instances
11
+ @config:
12
+ bootstrapper: brancusi.Bootstrapper
13
+
14
+ # Module classes for the application
15
+ @Modules: {}
16
+
17
+ # Module instances
18
+ modules: {}
19
+
20
+ # Controller classes for the application
21
+ @Controllers: {}
22
+
23
+ # Controller instances
24
+ controllers: {}
25
+
26
+ # Model classes for the application
27
+ @Models: {}
28
+
29
+ @routes: new brancusi.routes.Mapper
30
+
31
+ # Instantiates the application and bootstrapper, and resolves any dependencies, modules and controllers.
32
+ #
33
+ # @return [Application] an instance of the application.
34
+ #
35
+ @create: ->
36
+ @instance = new @
37
+ bootstrapper = new @Bootstrapper
38
+ @instance.resolve(bootstrapper)
39
+
40
+ # Resolves, initializes, and runs the application.
41
+ #
42
+ # @return [Application] an instance of the application.
43
+ #
44
+ @run: ->
45
+ @create().initialize().run()
46
+
47
+ # Creates and assigns the application container with the bootstrapper, then resolves dependencies, modules and controllers.
48
+ #
49
+ # @return [Application] the application instance.
50
+ #
51
+ resolve: (bootstrapper) ->
52
+ @container = bootstrapper.configure_container(@)
53
+ @container.resolve(@)
54
+ @_resolve_modules()
55
+ @_resolve_controllers()
56
+ @
57
+
58
+ # Binds event handlers on the modules and controllers, then publishes the application.initialize event.
59
+ #
60
+ # @return [Application] the application instance.
61
+ #
62
+ initialize: ->
63
+ @_bind_events()
64
+ @mediator.publish "application.initialize", @
65
+ @
66
+
67
+ # Publishes the application.ready event.
68
+ #
69
+ # @return [Application] the application instance.
70
+ #
71
+ run: (bootstrapper) ->
72
+ @mediator.publish "application.ready"
73
+ @
74
+
75
+ # @private
76
+ # Instantiates and resolves the application modules.
77
+ #
78
+ _resolve_modules: ->
79
+ # @modules.router = @router if @router?
80
+ # @modules.renderer = @renderer if @renderer?
81
+ module_regex = /(.*)Module/ # e.g. AuthModule
82
+ for klass_name, klass of @constructor.Modules when matches = module_regex.exec(klass_name)
83
+ module_name = _.string.underscored(matches[1]) # e.g. "auth"
84
+ module = @container.resolve(new klass(module_name))
85
+ # module.sandbox.bind_subscriptions(module)
86
+ @modules[module_name] = module
87
+
88
+ # @private
89
+ # Instantiates and resolves the application controllers.
90
+ #
91
+ _resolve_controllers: ->
92
+ controller_regex = /(.*)Controller/ # e.g. HomeController
93
+ for klass_name, klass of @constructor.Controllers when matches = controller_regex.exec(klass_name)
94
+ controller_name = _.string.underscored(matches[1]) # e.g. home
95
+ controller = @container.resolve(new klass(controller_name))
96
+ # controller.sandbox.bind_subscriptions(controller)
97
+ @controllers[controller_name] = controller
98
+
99
+ # @private
100
+ # Binds event handlers on the modules and controllers.
101
+ #
102
+ _bind_events: ->
103
+ @router.sandbox.bind_subscriptions(@router)
104
+ @renderer.sandbox.bind_subscriptions(@renderer)
105
+
106
+ for module_name, module of @modules
107
+ module.sandbox.bind_subscriptions(module)
108
+
109
+ for controller_name, controller of @controllers
110
+ controller.sandbox.bind_subscriptions(controller)
111
+
112
+ # route_mapper = new @container.resolve 'RouteMapper'
113
+ # route_mapper.draw(@constructor.routes.( config?.routes || -> )
114
+ # @router?.initialize?()
@@ -0,0 +1,17 @@
1
+ #= require ./application_module
2
+
3
+ namespace "brancusi"
4
+
5
+ class brancusi.ApplicationController extends brancusi.ApplicationModule
6
+ @dependency renderer: 'Renderer'
7
+
8
+ begin_request: (action_name) ->
9
+ @request =
10
+ action: action_name
11
+
12
+ render: (args...) ->
13
+ if args.length > 0
14
+ @renderer.render_page(args...)
15
+ else
16
+ @renderer.render_page("#{@name}/#{@request.action}")
17
+
@@ -0,0 +1,29 @@
1
+ #= require brancusi/events
2
+
3
+ namespace "brancusi"
4
+
5
+ class brancusi.ApplicationModule extends brancusi.EventObject
6
+ constructor: (@name) ->
7
+
8
+ @dependency sandbox: (container) ->
9
+ container.resolve "Sandbox", [@name]
10
+
11
+ @dependency container: (container) ->
12
+ container.child().register_instance "Sandbox", @sandbox
13
+
14
+ publish: (args...) ->
15
+ @sandbox.publish(args...)
16
+
17
+ # TODO: maybe bind subscriptions automatically after resolving any EventObject (or anything with subscriptions)
18
+ # e.g.
19
+ # resolve: (ref, opts) ->
20
+ # resolution = @container.resolve(ref, opts)
21
+ # @sandbox.bind_scriptions(resolution)
22
+ # resolution.publish = @sandbox.publish
23
+ # resolution
24
+ # bind_subscriptions: (target) ->
25
+ # @sandbox.bind_subscriptions.apply( @sandbox, [target] )
26
+ # target.publish = @sandbox.publish
27
+
28
+ # create_model: ( class_name, opts ) ->
29
+ # @env.create( class_name, opts )
@@ -0,0 +1,18 @@
1
+ #= require brancusi/container
2
+
3
+ namespace "brancusi"
4
+
5
+ class brancusi.Bootstrapper
6
+
7
+ configure_container: ( application ) ->
8
+ container = new brancusi.Container()
9
+
10
+ # TODO: do we need these?
11
+ container.register_instance "Application", application
12
+ container.register_instance "Container", container
13
+
14
+ container.register_class "Sandbox", brancusi.Sandbox
15
+ container.register_class "Mediator", brancusi.Mediator, singleton: true
16
+ container.register_class "RegionManager", brancusi.renderer.RegionManager, singleton: true
17
+
18
+ container
@@ -0,0 +1 @@
1
+ #= require_tree .
@@ -0,0 +1,14 @@
1
+ #= require brancusi/container
2
+
3
+ namespace "brancusi"
4
+
5
+ class brancusi.Sandbox extends brancusi.DependentObject
6
+ @dependency mediator: "Mediator"
7
+
8
+ constructor: (@scope) ->
9
+
10
+ publish: (event, args...) =>
11
+ @mediator.publish_scoped(event, @scope, args...)
12
+
13
+ bind_subscriptions: (target) =>
14
+ @mediator.bind_subscriptions(target, @scope)
@@ -0,0 +1,139 @@
1
+ namespace "brancusi"
2
+
3
+ # Implementation of a DI container.
4
+ #
5
+ class brancusi.Container
6
+
7
+ # Creates an instance of a container.
8
+ #
9
+ # @param parent [brancusi.Container] a parent container. When provided, if resolution of a dependency fails then resolution will be attempted with the parent container instead.
10
+ #
11
+ constructor: (@parent) ->
12
+ @_mappings = {}
13
+
14
+ # Registers a class mapping with the container.
15
+ #
16
+ # @param name [String] the name of the mapping to the class.
17
+ # @param klass [Class] the class the dependency should resolve with.
18
+ # @option opts [Boolean] singleton indicates whether the resolved dependency should be memoized.
19
+ # @return [Container] the container.
20
+ #
21
+ register_class: (name, klass, opts = {}) ->
22
+ @_register_mapping name, klass, "class", opts
23
+ @
24
+
25
+
26
+ # Registers an instance mapping with the container.
27
+ #
28
+ # @param name [String] the name of the mapping to the instance.
29
+ # @param obj [Object] the instance the dependency should resolve to.
30
+ # @return [Container] the container.
31
+ #
32
+ register_instance: (name, obj) ->
33
+ @_register_mapping name, obj, "instance"
34
+ @
35
+
36
+
37
+ # Registers a factory function for resolving dependencies
38
+ #
39
+ # @param name [String] the name of the mapping
40
+ # @param fn [Function] the factory function to resolve the mapping
41
+ # @return [Container] the container
42
+ #
43
+ register_factory: (name, fn) ->
44
+ @_register_mapping name, fn, "factory"
45
+ @
46
+
47
+
48
+ # Returns an instance of the given dependency, resolving any child dependencies.
49
+ #
50
+ # @overload resolve(name)
51
+ # Resolves the dependency according to the name of the mapping.
52
+ # @param name [String] the name of the dependency mapping.
53
+ # @return [Object] the fully resolved dependency.
54
+ #
55
+ # @overload resolve(target, opts)
56
+ # Resolves any unresolved dependencies on a given object.
57
+ # @param target [Object] the object to resolve dependencies for.
58
+ # @return [Object] target.
59
+ #
60
+ resolve: (ref, opts) ->
61
+ if typeof ref == "string"
62
+ resolution = @_resolve_string(ref, opts)
63
+ else if typeof ref == "function"
64
+ resolution = @_resolve_function(ref, opts)
65
+ else if typeof ref == "object"
66
+ resolution = @_resolve_object(ref)
67
+
68
+ if resolution?
69
+ resolution
70
+ else if @parent?
71
+ @parent.resolve ref, opts unless resolution?
72
+ else
73
+ throw new Error("Unable to resolve dependency: #{ref}") unless resolution?
74
+
75
+
76
+ # Creates a child container.
77
+ # @return [Container] a new child container.
78
+ #
79
+ child: ->
80
+ new Container(@)
81
+
82
+
83
+ # Helper method to set up a mapping. Merges in the given options to the definition.
84
+ #
85
+ # @private
86
+ # @param name [String] the name of the mapping
87
+ # @param [Class|Object|Function] the dependency to map too
88
+ # @param [String] the kind of mapping. Either 'class', 'object' or 'factory'
89
+ #
90
+ _register_mapping: (name, ref, kind, opts = {}) ->
91
+ @_mappings[name] = _.defaults({ kind: kind, ref: ref }, opts)
92
+
93
+
94
+ # Resolves a dependency by name, passing the given options (if provided) to the dependency when initialized.
95
+ #
96
+ # @private
97
+ # @param name [String] the name of the dependency.
98
+ # @param opts [Array] optional array of arguments to pass to the dependency
99
+ #
100
+ _resolve_string: (name, opts) ->
101
+ mapping = @_mappings[name]
102
+ return null unless mapping?
103
+
104
+ if mapping.kind == "instance"
105
+ mapping.ref
106
+ else if mapping.kind == "class" and mapping.singleton == true
107
+ mapping.instance = @resolve( mapping.ref, opts ) unless mapping.instance?
108
+ mapping.instance
109
+ else if mapping.kind == "factory"
110
+ mapping.ref.apply(null, opts)
111
+ else
112
+ @resolve(mapping.ref, opts)
113
+
114
+
115
+ # Resolves a dependency specified by a function.
116
+ #
117
+ # @private
118
+ # @param fn [Function] the function to invoke
119
+ # @param opts [Array] an optional array of arguments to pass to the function
120
+ #
121
+ _resolve_function: (fn, opts) ->
122
+ opts ?= []
123
+ obj = new fn(opts...)
124
+ @resolve obj
125
+
126
+
127
+ # Resolves dependencies on the given object.
128
+ #
129
+ # @private
130
+ # @param target [Object] the target object.
131
+ #
132
+ _resolve_object: (target) ->
133
+ for name, args of target.constructor.dependencies
134
+ [dependency, dependency_args] = args
135
+ if typeof dependency == "function"
136
+ target[name] = dependency.apply(target, [@])
137
+ else
138
+ target[name] = @resolve dependency, dependency_args
139
+ target
@@ -0,0 +1,30 @@
1
+ namespace "brancusi"
2
+
3
+ # A module to facilitate the specification of dependencies. Dependencies are defined on the
4
+ # 'dependencies' property of the target's prototype, in the format:
5
+ #
6
+ # {attr1: ['dep1', [<args1>]], attr2: ['dep2', [<args2>]]}
7
+ #
8
+ # @mixin
9
+ #
10
+ class brancusi.DependentModule
11
+
12
+ # Adds the named dependencies to the object's ::dependencies property
13
+ #
14
+ # @overload dependency(dependency, args...)
15
+ # Defines the given dependency on the subject class
16
+ # @param dependency [Object] an object with a single field in the format {<attribute>: '<dependency name>'}
17
+ # @param args [Array] optional an array of arguments to store with the dependency, to be passed as arguments on resolution
18
+ #
19
+ # @overload dependency(dependencies)
20
+ # Defines the given dependencies on the subject class
21
+ # @param dependencies [Object] an object naming a number of dependencies
22
+ #
23
+ @dependency: (dependencies, args...) ->
24
+ @dependencies ?= {}
25
+
26
+ if @__super__? and @__super__.constructor.dependencies == @dependencies
27
+ @dependencies = _.clone(@__super__.constructor.dependencies)
28
+
29
+ for dependency_name, dependency_type of dependencies
30
+ @dependencies[dependency_name] = [dependency_type, args]
@@ -0,0 +1,9 @@
1
+ #= require brancusi/object_model
2
+ #= require ./dependent_module
3
+
4
+ namespace "brancusi"
5
+
6
+ # Base class for dependent classes.
7
+ #
8
+ class brancusi.DependentObject extends brancusi.BaseObject
9
+ @include brancusi.DependentModule
@@ -0,0 +1 @@
1
+ #= require_tree .
@@ -0,0 +1,10 @@
1
+ #= require ./events_module
2
+ #= require ../container/dependent_object
3
+
4
+ namespace "brancusi"
5
+
6
+ # Base class for AER classes.
7
+ #
8
+ class brancusi.EventObject extends brancusi.DependentObject
9
+ @include brancusi.EventsModule
10
+
@@ -0,0 +1,14 @@
1
+ namespace "brancusi"
2
+
3
+ # Provides convenience methods @on and publish for subscribing and publishing to application events with AER.
4
+ #
5
+ class brancusi.EventsModule
6
+
7
+ # Generates a named event handler for AER.
8
+ #
9
+ # @param event [String] the name of the event.
10
+ # @param handler [Function] the event handler.
11
+ #
12
+ @on: (event, handler) ->
13
+ @::["@#{event}"] = handler
14
+
@@ -0,0 +1 @@
1
+ #= require_tree .
@@ -0,0 +1,70 @@
1
+ namespace "brancusi"
2
+
3
+ # An implementation of the mediator pattern. This class should not be referred to directly, as the AER pattern is preferred.
4
+ #
5
+ class brancusi.Mediator
6
+
7
+ constructor: ->
8
+ @subscribers = {}
9
+
10
+ # Subscribes the given handler to the content for the specified event.
11
+ #
12
+ # @param event [String] the (fully qualified) name of the event.
13
+ # @param handler [Function] the event handler.
14
+ # @param context [Object] optional the context to invoke the handler against
15
+ #
16
+ subscribe: (event, handler, context) ->
17
+ @subscribers[event] ?= []
18
+ @subscribers[event].push(-> handler.apply(context, arguments))
19
+
20
+ # Invokes all handlers for the given event.
21
+ #
22
+ # @param event [String] the (fully qualified) name of the event.
23
+ # @param args... the arguments to forward to the handler.
24
+ #
25
+ publish: (event, args...) ->
26
+ @publish_scoped(event, null, args...)
27
+
28
+ # Invokes all handlers for the given event.
29
+ #
30
+ # @param event [String] the name of the event (either qualified or unqualified)
31
+ # @param scope [String] the name of the scope for unqualified event names.
32
+ # @param args... the arguments to forward to the handler.
33
+ #
34
+ publish_scoped: (event, scope, args...) ->
35
+ event = @_scoped_name(event, scope, true) if scope?
36
+ subscribers = (@subscribers?[event] || {})
37
+ for handler in subscribers
38
+ handler(args...)
39
+
40
+ # Binds all subscriptions on the target object (optionally using the given scope).
41
+ #
42
+ # @param target [Object] the target object.
43
+ # @param scope [String] optional the name of the scope of the object.
44
+ #
45
+ bind_subscriptions: (target, scope) ->
46
+ for name, handler of target when matches = /@(.*)/.exec(name)
47
+ event = matches[1]
48
+ event = @_scoped_name(event, scope) if scope?
49
+ @subscribe(event, handler, target)
50
+
51
+ # @private
52
+ # Returns the fully qualified name of the event, and optionally validates the inpute.
53
+ #
54
+ # @param input [String] the input to qualify (and maybe validate).
55
+ # @param scope [String] optional the name of the scope for unqualified event names.
56
+ # @param validate [Boolean] optional whether or not to error on invalid event names.
57
+ #
58
+ _scoped_name: (input, scope, validate) =>
59
+ regex = /^((\w+)\.)?(\w+)$/
60
+
61
+ if input.match(regex)
62
+ [_, _, event_scope, event] = regex.exec(input)
63
+ if scope?
64
+ event_scope ?= scope
65
+ "#{event_scope}.#{event}"
66
+ else
67
+ event
68
+ else if validate?
69
+ throw new Error("Invalid event name: #{input}")
70
+