sombrero 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Rakefile +1 -2
  4. data/app/.gitignore +6 -0
  5. data/app/.pryrc +1 -0
  6. data/app/Gemfile +6 -0
  7. data/app/Rakefile +1 -0
  8. data/app/base/base_controller.rb +3 -0
  9. data/app/base/boot.rb +2 -0
  10. data/app/base/helpers/application_helpers.rb +1 -0
  11. data/app/base/load_controllers.rb +2 -0
  12. data/app/base/rtcp_controller.rb +22 -0
  13. data/app/config/config.rb +11 -0
  14. data/app/config/config.yml +11 -0
  15. data/app/config/env/development.yml +0 -0
  16. data/app/config/env/production.yml +0 -0
  17. data/app/config/env/stage.yml +0 -0
  18. data/app/config/env/test.yml +0 -0
  19. data/app/config.ru +3 -0
  20. data/app/core/Gemfile +5 -0
  21. data/app/core/boot.rb +17 -0
  22. data/app/core/client/activity_observer.coffee +37 -0
  23. data/app/core/client/api.coffee +248 -0
  24. data/app/core/client/channels.coffee +37 -0
  25. data/app/core/client/load.coffee +20 -0
  26. data/app/core/client/page.coffee +68 -0
  27. data/app/core/client/polyfills/array.compact.coffee +4 -0
  28. data/app/core/client/polyfills/array.compact_join.coffee +4 -0
  29. data/app/core/client/polyfills/number.to_money.coffee +3 -0
  30. data/app/core/client/polyfills/string.capitalize.coffee +4 -0
  31. data/app/core/client/polyfills/string.strip.coffee +5 -0
  32. data/app/core/client/polyfills.coffee +6 -0
  33. data/app/core/client/render.coffee +57 -0
  34. data/app/core/client/util/alert.coffee +50 -0
  35. data/app/core/client/util/datetime.coffee +47 -0
  36. data/app/core/client/util.coffee +38 -0
  37. data/app/core/generate_controllers_map.rb +4 -0
  38. data/app/core/generate_webpack_setup.rb +4 -0
  39. data/app/core/load.rb +5 -0
  40. data/app/core/load_controllers.rb +16 -0
  41. data/app/package.json +5 -0
  42. data/app/webpack.config.js +51 -0
  43. data/bin/sombrero +5 -0
  44. data/docker/Dockerfile +5 -0
  45. data/docker/base/Dockerfile +3 -0
  46. data/docker/base/build +10 -0
  47. data/docker/base/build.sh +25 -0
  48. data/docker/cleanup +7 -0
  49. data/docker/run +68 -0
  50. data/docker/skel/build.sh +1 -0
  51. data/docker/skel/config.yml +24 -0
  52. data/docker/skel/prepare_build.sh +5 -0
  53. data/docker/skel/start.sh +1 -0
  54. data/docker/start +7 -0
  55. data/lib/sombrero/{version.rb → app.rb} +3 -1
  56. data/lib/sombrero/base_controller.rb +5 -0
  57. data/lib/sombrero/cli/app/install.rb +38 -0
  58. data/lib/sombrero/cli/app/update.rb +20 -0
  59. data/lib/sombrero/cli/app.rb +10 -0
  60. data/lib/sombrero/cli/docker/build.rb +170 -0
  61. data/lib/sombrero/cli/docker/install.rb +23 -0
  62. data/lib/sombrero/cli/docker.rb +14 -0
  63. data/lib/sombrero/cli.rb +128 -0
  64. data/lib/sombrero/rtcp_controller.rb +93 -0
  65. data/lib/sombrero.rb +110 -2
  66. data/sombrero.gemspec +6 -1
  67. metadata +91 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: af202b91c5e9642c9c273a24d5efd257f84a1040
4
- data.tar.gz: bc18c9a59cdbecb2a8a69e97165d203e62a660ab
3
+ metadata.gz: 7e63221dd6689f29ae064f7b74182f25a1953226
4
+ data.tar.gz: 80792a8594b681d53d42e9c2a001aef3b9507e2d
5
5
  SHA512:
6
- metadata.gz: 3de1aadd490934eecef1ae4173a7a8dffef21561e3701eb78131a5769fdb9a2cd49b2100ec8f467c5326e44430f8b1f6eeccdb3e2ea8d336b77517edb31687b4
7
- data.tar.gz: 95acee3df9f7ab331098ceb59becfc2bb972cc256899a9f9abd837ffae842248f2fdce1ccfb993465664d5e026bd94f881c7fd88e1cb09b632b355feb26939ae
6
+ metadata.gz: 9dc87a61ae7064de27c0e339eb4efee3933a73eda9060ec79a7bc860791bb7eceac2cd35d3459f5c7c3f8487ad7cd6cc85bd40c35b5b455041ad294cc0efb7c2
7
+ data.tar.gz: 7217715f408adbeb81d1bc30e4b9807e354837c9e518b74a664396ce0c3b999396fe3a13c293b607ba851107b1568d2f779dc80d3c5bfcb1089aca764ee31c5d
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ .DS_Store
data/Rakefile CHANGED
@@ -1,2 +1 @@
1
- require "bundler/gem_tasks"
2
- task :default => :spec
1
+ require 'bundler/gem_tasks'
data/app/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .git
2
+ node_modules
3
+ var
4
+ tmp
5
+ Gemfile.lock
6
+ __tmpbuildir__
data/app/.pryrc ADDED
@@ -0,0 +1 @@
1
+ require File.expand_path('../core/load', __FILE__)
data/app/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # do NOT remove this
4
+ eval File.read(File.expand_path('../core/Gemfile', __FILE__))
5
+
6
+ # add gems here
data/app/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require File.expand_path('../core/boot', __FILE__)
@@ -0,0 +1,3 @@
1
+ class BaseController < Sombrero::BaseController
2
+ map Cfg.baseurl
3
+ end
data/app/base/boot.rb ADDED
@@ -0,0 +1,2 @@
1
+ # first file to be loaded when app starts.
2
+ # it is loaded on bare metal, e.g. before any gems, configs, helpers.
@@ -0,0 +1 @@
1
+ # this file will be loaded before other files found in helpers/ folder
@@ -0,0 +1,2 @@
1
+ # All "official" controllers are loaded by core/load_controllers.rb
2
+ # This file is intended to load any "custom" controllers, ones that are not seen by default loader.
@@ -0,0 +1,22 @@
1
+ class RTCPController < BaseController
2
+ map Cfg.baseurl + '__rtcp__'
3
+
4
+ private
5
+ # called after socket connection established.
6
+ def connected
7
+ end
8
+
9
+ # data sent to client after connection established.
10
+ def initialization_data
11
+ {}
12
+ end
13
+
14
+ # merged into original env when calling a controller
15
+ def rtcp_env
16
+ {}
17
+ end
18
+
19
+ # called when socket connection closed
20
+ def disconnected
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ # === Cfg is a required reserved hard-coded constant. === #
2
+ # === If you change it the entire app will broke. === #
3
+
4
+ # loading .yml configs from current directory. config.yml will be loaded first.
5
+ Cfg = Sombrero.load_config(File.expand_path('..', __FILE__))
6
+
7
+ # it is highly recommended to freeze configs so they stay unaltered on runtime.
8
+ Cfg.freeze
9
+
10
+ # loading any .rb files in current dir. this one will be skipped automatically.
11
+ Dir[File.expand_path('../**/*.rb', __FILE__)].each {|f| require(f)}
@@ -0,0 +1,11 @@
1
+ # module SystemRequiredConfigs # Do NOT Remove!
2
+ baseurl: /
3
+ app:
4
+ url:
5
+ development: /app
6
+ production: /app
7
+ dir: ./public
8
+ # end
9
+
10
+ # put here configs that will be the same on all environments.
11
+ # environment-aware configs should be set in config/env/ files.
File without changes
File without changes
File without changes
File without changes
data/app/config.ru ADDED
@@ -0,0 +1,3 @@
1
+ require File.expand_path('../core/load', __FILE__)
2
+
3
+ run RocketIO::Application.new
data/app/core/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # DO NOT EDIT THIS FILE, all updates will be lost on next update.
2
+
3
+ gem 'sombrero'
4
+ gem 'rocketio'
5
+ gem 'tubesock'
data/app/core/boot.rb ADDED
@@ -0,0 +1,17 @@
1
+ # DO NOT EDIT THIS FILE, all updates will be lost on next update.
2
+ # edit base/boot.rb instead.
3
+
4
+ Dir.chdir File.expand_path('../..', __FILE__) do
5
+ require './base/boot'
6
+
7
+ require 'bundler/setup'
8
+ Bundler.require(:default)
9
+ Bundler.require(RocketIO.environment)
10
+
11
+ require './config/config'
12
+
13
+ require './base/helpers/application_helpers'
14
+ Dir['./base/helpers/**/*.rb'].each {|f| require(f)}
15
+
16
+ require './base/load'
17
+ end
@@ -0,0 +1,37 @@
1
+ _ =
2
+ debounce: require('lodash/debounce')
3
+ isFunction: require('lodash/isFunction')
4
+ #end
5
+
6
+ ONLINE = 'online'
7
+ AWAY = 'away'
8
+ OFFLINE = 'offline'
9
+
10
+ module.exports = (consider_idle_after, report_status = ->) ->
11
+ is_idle = false
12
+ idle_time = 0
13
+
14
+ if report_status && !_.isFunction(report_status)
15
+ throw new Error('activity_observer expects second argument to be a function')
16
+
17
+ activity_detected = ->
18
+ report_status(ONLINE) if is_idle
19
+ is_idle = false
20
+ idle_time = 0
21
+ true
22
+ #end
23
+
24
+
25
+ tictac = ->
26
+ return if is_idle
27
+ idle_time += 1
28
+ is_idle = idle_time >= consider_idle_after
29
+ if is_idle
30
+ report_status(AWAY)
31
+ #end
32
+
33
+
34
+ window.onload = document.onmousemove = document.onkeypress = _.debounce(activity_detected, 500)
35
+ window.onunload = -> report_status(OFFLINE)
36
+ window.setInterval(tictac, 1000)
37
+ #end
@@ -0,0 +1,248 @@
1
+ _ =
2
+ isObject: require('lodash/isObject')
3
+ last: require('lodash/last')
4
+ has: require('lodash/has')
5
+ isFunction: require('lodash/isFunction')
6
+ isArray: require('lodash/isArray')
7
+ forEach: require('lodash/forEach')
8
+ #end
9
+
10
+ ReconnectingWebSocket = require('reconnectingwebsocket')
11
+ QueryString = require('qs')
12
+
13
+ Page = require('./page')
14
+ Load = require('./load')
15
+ Channels = require('./channels')
16
+ Controllers = require('../../controllers.json')
17
+
18
+ App =
19
+ connected: false
20
+ listeners: {}
21
+ call_on_reconnect: []
22
+ rtcp_serial: 0
23
+ #end
24
+
25
+ App.urlify = (url) ->
26
+ fn = (args...) ->
27
+ args.unshift(url)
28
+ q = ''
29
+ if _.isObject(_.last(args))
30
+ q = '?' + QueryString.stringify(args.pop())
31
+ '/' + args.join('/').replace(/^\/+/, '') + q
32
+ fn.toString = -> url
33
+ fn
34
+ #end
35
+
36
+
37
+ App.controller_loader = (controller) ->
38
+ ->
39
+
40
+ url = [App.app_url, controller.path].join('/') + '.js'
41
+ window.exports = {}
42
+ window.module = {url: url, exports: window.exports}
43
+
44
+ done = ->
45
+ unless api = window.module.exports
46
+ throw new Error("Looks like #{url} does not contain a CommonJS module")
47
+
48
+ unless window.module && window.module.url == url
49
+ throw new Error("Looks like #{url} exports was overridden while downloading the script")
50
+
51
+ Core.on_controller_loaded(controller, api)
52
+ #end
53
+
54
+ failed = ->
55
+ console.log("Error loading #{controller.name}")
56
+ #end
57
+
58
+ Load.script(url, done, failed)
59
+ #end
60
+
61
+
62
+ App.controllers = ->
63
+ controllers = {}
64
+
65
+ Controllers.forEach (controller) ->
66
+
67
+ controller.url = App.urlify(controller.url)
68
+ controller.load = App.controller_loader(controller)
69
+
70
+ if _.isArray(controller.api)
71
+ api = {}
72
+
73
+ _.forEach controller.api, (meth) ->
74
+ api[meth] = (args...) ->
75
+ cb = if _.isFunction(_.last(args)) then args.pop() else null
76
+ App.send_rtcp_message(controller.url, meth, args, cb)
77
+ #end
78
+
79
+ controller.api = api
80
+ #end
81
+
82
+ Page.Api controller.url_pattern, (context, next) ->
83
+ controller.load()
84
+ #end
85
+
86
+ if controller.name
87
+ controllers[controller.name] = controller
88
+ #end
89
+
90
+ controllers
91
+ #end
92
+
93
+
94
+ App.onmessage = (evt) ->
95
+ msg = JSON.parse(evt.data)
96
+
97
+ unless typeof msg == 'object'
98
+ throw new Error("Wrong message format, #{typeof msg} given, Object expected")
99
+
100
+ if msg.error
101
+ throw new Error(msg.error)
102
+
103
+ if msg.channel
104
+ return Core.on_channel_message(msg)
105
+
106
+ if msg.controller
107
+ return App.handle_rpc_message(msg)
108
+
109
+ unless typeof msg.serial == 'number'
110
+ throw new Error('Received a message without or with a wrong serial')
111
+
112
+ if msg.serial < 0
113
+ throw new Error('A subzero serial received')
114
+
115
+ unless _.has(msg, 'data')
116
+ throw new Error('Received a message without data')
117
+
118
+ if msg.data.__rtcp_error__
119
+ return App.handle_standard_message(msg)
120
+
121
+ if msg.serial == 0
122
+ return App.handle_initialization_message(msg)
123
+
124
+ return App.handle_standard_message(msg)
125
+ #end
126
+
127
+
128
+ App.handle_initialization_message = (msg) ->
129
+
130
+ # avoid repetitive bootstraping on reconnects
131
+ return if App.initialized
132
+ App.initialized = true
133
+ App.app_url = msg.data.app_url
134
+
135
+ Core.on_initialize(msg)
136
+
137
+ Page.Api.start()
138
+ #end
139
+
140
+
141
+ App.handle_standard_message = (msg) ->
142
+ return unless listener = App.listeners[msg.serial]
143
+
144
+ delete App.listeners[msg.serial]
145
+
146
+ return unless _.isFunction(listener)
147
+
148
+ return listener(msg.data.__rtcp_error__) if msg.data.__rtcp_error__
149
+
150
+ if _.isArray(msg.data)
151
+ listener(null, msg.data...)
152
+ else
153
+ listener(null, msg.data)
154
+ #end
155
+ #end
156
+
157
+
158
+ App.handle_rpc_message = (msg) ->
159
+ unless Core.controllers[msg.controller]
160
+ throw new Error('Unknown controller called')
161
+
162
+ Core.controllers[msg.controller].load (api) ->
163
+
164
+ unless method = api[msg.method]
165
+ throw new Error('Unknown method called')
166
+
167
+ if args = msg.arguments
168
+ args = [args] unless _.isArray(args)
169
+ else
170
+ args = []
171
+
172
+ method.apply(api, args)
173
+ #end
174
+
175
+
176
+ App.send_rtcp_message = (controller, method, args, callback) ->
177
+ unless App.connected
178
+ App.call_on_reconnect.push(-> App.send_rtcp_message(controller, method, args, callback))
179
+ Core.connect() if App.explicitly_disconnected
180
+ return
181
+ #end
182
+
183
+ if _.isFunction(controller)
184
+ controller = controller()
185
+ #end
186
+
187
+ App.rtcp_serial += 1
188
+ data =
189
+ controller: controller
190
+ method: method
191
+ arguments: args
192
+ serial: App.rtcp_serial
193
+ #end
194
+
195
+ if callback
196
+ App.listeners[App.rtcp_serial] = callback
197
+ data.reply = true
198
+ #end
199
+
200
+ App.socket.send(JSON.stringify(data), binary: true)
201
+ #end
202
+
203
+
204
+ Core =
205
+ on_socket_open: ->
206
+ on_socket_close: ->
207
+ on_initialize: ->
208
+ on_controller_loaded: ->
209
+
210
+ on_channel_message: Channels.on_message
211
+ subscribe: Channels.subscribe
212
+ unsubscribe: Channels.unsubscribe
213
+
214
+ controllers: App.controllers()
215
+ send_rtcp_message: App.send_rtcp_message
216
+
217
+ connect: (url = '__rtcp__')->
218
+
219
+ return if typeof window.WebSocket == 'undefined'
220
+ protocol = if window.location.protocol.toLowerCase() == 'https:' then 'wss' else 'ws'
221
+ App.socket = new ReconnectingWebSocket("#{protocol}://#{window.location.host}/#{url}")
222
+
223
+ App.socket.onopen = ->
224
+ App.connected = true
225
+ _.forEach App.call_on_reconnect, (fn) ->
226
+ try
227
+ fn()
228
+ #end
229
+ App.call_on_reconnect = []
230
+ Core.on_socket_open(App.socket)
231
+ #end
232
+
233
+ App.socket.onerror = App.socket.onclose = ->
234
+ App.connected = false
235
+ Core.on_socket_close()
236
+ #end
237
+
238
+ App.socket.onmessage = App.onmessage
239
+ #end
240
+
241
+ disconnect: ->
242
+ App.explicitly_disconnected = true
243
+ App.socket.close()
244
+ #end
245
+
246
+ #end
247
+
248
+ module.exports = Core
@@ -0,0 +1,37 @@
1
+ _ =
2
+ isRegExp: require('lodash/isRegExp')
3
+ #end
4
+
5
+ subscriptions = {}
6
+ awaiting_subscriptions = {}
7
+
8
+ module.exports =
9
+ on_message: (msg) ->
10
+ # if there is a handler, call it
11
+ if handler = subscriptions[msg.channel]
12
+ delete subscriptions[msg.channel] if msg.unsubscribe
13
+ handler(msg.data) if msg.data
14
+ else
15
+ # otherwise store message for when handler defined to handle it straight away
16
+ awaiting_subscriptions[msg.channel] = msg.data
17
+ #end
18
+
19
+
20
+ subscribe: (channel, handler) ->
21
+ # if there is a awaiting subscription handle it right away
22
+ if msg = awaiting_subscriptions[channel]
23
+ delete awaiting_subscriptions[channel]
24
+ return handler(msg)
25
+ # otherwise subscribe
26
+ subscriptions[channel] = handler
27
+ #end
28
+
29
+
30
+ unsubscribe: (channel) ->
31
+ if _.isRegExp(channel)
32
+ for c in Object.keys(subscriptions).filter((c) -> channel.text(c))
33
+ delete subscriptions[c]
34
+ else
35
+ delete subscriptions[channel]
36
+ #end
37
+ #end
@@ -0,0 +1,20 @@
1
+ module.exports =
2
+
3
+ script: (url, done, failed) ->
4
+ script = document.createElement('script')
5
+ script.async = true
6
+ script.onload = done
7
+ script.onerror = failed
8
+ script.src = url
9
+ document.getElementsByTagName('head')[0].appendChild(script)
10
+ #end
11
+
12
+
13
+ stylesheet: (url, done) ->
14
+ link = document.createElement('link')
15
+ link.onload = done
16
+ link.type = 'text/css'
17
+ link.rel = 'stylesheet'
18
+ link.href = url
19
+ document.getElementsByTagName('head')[0].appendChild(link)
20
+ #end
@@ -0,0 +1,68 @@
1
+ _ =
2
+ isFunction: require('lodash/isFunction')
3
+ isObject: require('lodash/isObject')
4
+ isArray: require('lodash/isArray')
5
+ has: require('lodash/has')
6
+ #end
7
+
8
+ QueryString = require('qs')
9
+ Api = require('page')
10
+
11
+ page = ->
12
+
13
+ this.navigate = (path, options) ->
14
+ if _.isFunction(path)
15
+ path = path()
16
+
17
+ if !path || _.isObject(path)
18
+ options = path
19
+ path = window.location.pathname
20
+
21
+ options = {} unless _.isObject(options)
22
+
23
+ if _.isArray(options.path)
24
+ path = '/' + options.path
25
+ .filter((x) -> x)
26
+ .map((x) -> String(x))
27
+ .join('/')
28
+ .replace(/\/+/, '/')
29
+ .replace(/^\//, '')
30
+
31
+ if _.isObject(options.params)
32
+ path = path + '?' + QueryString.stringify(options.params)
33
+
34
+ if options.reload
35
+ Api.stop()
36
+ window.location = path
37
+
38
+ parser = document.createElement('a')
39
+ parser.href = path
40
+ if parser.hostname
41
+ unless parser.hostname == location.hostname
42
+ throw new Error('Guess what! Someone tries to navigate out!')
43
+
44
+ args = [
45
+ path,
46
+ (if _.has(options, 'state') then options.state else {}),
47
+ (if _.has(options, 'dispatch') then options.dispatch else true),
48
+ (if _.has(options, 'push') then options.push else true)
49
+ ]
50
+ Api.show.apply(this, args)
51
+
52
+ this.update_params()
53
+ #end
54
+
55
+
56
+ this.update_params = ->
57
+ this.params = QueryString.parse(window.location.search.slice(1))
58
+ this.path_params = window.location.pathname.replace(/^\/+|\/+$/g, '').split(/\/+/).filter((x) -> x.length > 0)
59
+ #end
60
+
61
+ this.update_params()
62
+
63
+ this
64
+ #end
65
+
66
+ page.Api = Api
67
+
68
+ module.exports = page
@@ -0,0 +1,4 @@
1
+
2
+ Array.prototype.compact = ->
3
+ this.filter (x) -> x
4
+ #end
@@ -0,0 +1,4 @@
1
+
2
+ Array.prototype.compact_join = (glue) ->
3
+ (this.filter (x) -> x).join(glue)
4
+ #end
@@ -0,0 +1,3 @@
1
+ Number.prototype.to_money = (n = 2, x = 3) ->
2
+ re = '(\\d)(?=(\\d{' + x + '})+' + (n > 0 ? '\\.' : '$') + ')'
3
+ this.toFixed(Math.max(0, ~~n)).replace(new RegExp(re, 'g'), '$1,')
@@ -0,0 +1,4 @@
1
+
2
+ String.prototype.capitalize = ->
3
+ this.charAt(0).toUpperCase() + this.slice(1)
4
+ #end
@@ -0,0 +1,5 @@
1
+
2
+
3
+ String.prototype.strip = ->
4
+ this.replace(/^\s+|\s+$/g, '')
5
+ #end
@@ -0,0 +1,6 @@
1
+
2
+ require './polyfills/array.compact.coffee'
3
+ require './polyfills/array.compact_join.coffee'
4
+ require './polyfills/number.to_money.coffee'
5
+ require './polyfills/string.capitalize.coffee'
6
+ require './polyfills/string.strip.coffee'
@@ -0,0 +1,57 @@
1
+ _ =
2
+ extend: require('lodash/extend')
3
+ has: require('lodash/has')
4
+ isFunction: require('lodash/isFunction')
5
+ #end
6
+
7
+ extend = (data) ->
8
+ _.extend({}, data || {}, api.global_data || {})
9
+ #end
10
+
11
+ api = (opts) ->
12
+ api.default_el || throw new Error('Render.default_el not set')
13
+ opts.data = extend(opts.data)
14
+ opts.el = opts.el || api.default_el
15
+ opts.magic = if _.has(opts, 'magic') then opts.magic else true
16
+
17
+ ractive = new api.Ractive(opts)
18
+
19
+ for h,f of api.global_handlers || {}
20
+ ractive.on(h, f)
21
+
22
+ ractive
23
+ #end
24
+
25
+ api.Ractive = require('ractive')
26
+
27
+ api.global_data = {}
28
+ api.global_handlers = {}
29
+ api.default_el = null
30
+
31
+ api.component = (opts) ->
32
+ data = opts.data
33
+ opts.data = ->
34
+ if _.isFunction(data)
35
+ extend(data())
36
+ else
37
+ extend(data)
38
+ #end
39
+
40
+ opts.magic = if _.has(opts, 'magic') then opts.magic else true
41
+
42
+ oninit = opts.oninit
43
+ opts.oninit = ->
44
+ for h,f of api.global_handlers || {}
45
+ this.on(h, f)
46
+ oninit?()
47
+ #end
48
+ api.Ractive.extend(opts)
49
+ #end
50
+
51
+
52
+ api.string = (opts = {}) ->
53
+ opts.data = extend(opts.data)
54
+ new api.Ractive(opts).toHTML()
55
+ #end
56
+
57
+ module.exports = api