spade 0.1.0 → 0.1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/.gitignore +0 -11
  2. data/.gitmodules +3 -6
  3. data/Gemfile +10 -0
  4. data/bin/spade +3 -1
  5. data/lib/spade.rb +70 -0
  6. data/lib/spade/bundle.rb +180 -0
  7. data/lib/spade/cli.rb +1 -17
  8. data/lib/spade/cli/base.rb +182 -0
  9. data/lib/spade/console.rb +39 -0
  10. data/lib/spade/context.rb +107 -0
  11. data/lib/spade/evaluator.rb +34 -0
  12. data/lib/spade/exports.rb +70 -0
  13. data/lib/spade/loader.rb +208 -0
  14. data/lib/spade/package/.gitignore +1 -0
  15. data/lib/spade/package/Gemfile +15 -0
  16. data/lib/spade/package/lib/spade.js +1283 -0
  17. data/lib/spade/package/lib/wrapper.js +15 -0
  18. data/lib/spade/package/package.json +17 -0
  19. data/lib/spade/package/spec/javascript/async-test.js +123 -0
  20. data/lib/spade/package/spec/javascript/compiler/javascript.js +13 -0
  21. data/lib/spade/package/spec/javascript/compiler/ruby.js +14 -0
  22. data/lib/spade/package/spec/javascript/loader-test.js +64 -0
  23. data/lib/spade/package/spec/javascript/normalize-test.js +73 -0
  24. data/lib/spade/package/spec/javascript/packages-test.js +50 -0
  25. data/lib/spade/package/spec/javascript/relative-require-test.js +72 -0
  26. data/lib/spade/package/spec/javascript/require-test.js +117 -0
  27. data/lib/spade/package/spec/javascript/sandbox/creation.js +44 -0
  28. data/lib/spade/package/spec/javascript/sandbox/evaluate.js +37 -0
  29. data/lib/spade/package/spec/javascript/sandbox/format.js +79 -0
  30. data/lib/spade/package/spec/javascript/sandbox/misc.js +58 -0
  31. data/lib/spade/package/spec/javascript/sandbox/preprocessor.js +81 -0
  32. data/lib/spade/package/spec/javascript/sandbox/require.js +48 -0
  33. data/lib/spade/package/spec/javascript/sandbox/run-command.js +21 -0
  34. data/lib/spade/package/spec/javascript/spade/externs.js +14 -0
  35. data/lib/spade/package/spec/javascript/spade/load-factory.js +15 -0
  36. data/lib/spade/package/spec/javascript/spade/misc.js +23 -0
  37. data/lib/spade/package/spec/javascript/spade/ready.js +12 -0
  38. data/lib/spade/package/spec/javascript/spade/register.js +13 -0
  39. data/lib/spade/package/spec/javascript_spec.rb +7 -0
  40. data/lib/spade/package/spec/spec_helper.rb +3 -0
  41. data/lib/spade/package/spec/support/core_test.rb +67 -0
  42. data/lib/spade/reactor.rb +159 -0
  43. data/lib/spade/server.rb +66 -0
  44. data/lib/spade/shell.rb +85 -0
  45. data/lib/spade/version.rb +1 -1
  46. data/spade.gemspec +15 -4
  47. data/spec/cli/update_spec.rb +65 -0
  48. data/spec/spec_helper.rb +22 -0
  49. data/spec/support/cli.rb +103 -0
  50. data/spec/support/matchers.rb +12 -0
  51. data/spec/support/path.rb +66 -0
  52. metadata +146 -78
  53. data/.rspec +0 -1
  54. data/Buildfile +0 -18
  55. data/README.md +0 -152
  56. data/Rakefile +0 -9
  57. data/examples/format-app/lib/hello.coffee +0 -1
  58. data/examples/format-app/lib/main.js +0 -6
  59. data/examples/format-app/package.json +0 -10
  60. data/examples/format-app/resources/README.txt +0 -1
  61. data/examples/format-app/resources/config.json +0 -3
  62. data/examples/path-test/lib/hello.js +0 -1
  63. data/examples/path-test/lib/main.js +0 -1
  64. data/examples/path-test/package.json +0 -5
  65. data/examples/sc-app/index.html +0 -13
  66. data/examples/sc-app/lib/main.js +0 -24
  67. data/examples/sc-app/package.json +0 -8
  68. data/examples/single-file.js +0 -22
  69. data/examples/todos/index.html +0 -11
  70. data/examples/todos/lib/main.js +0 -11
  71. data/examples/todos/lib/todos.js +0 -93
  72. data/examples/todos/package.json +0 -10
  73. data/examples/todos/resources/stylesheets/todos.css +0 -162
  74. data/examples/todos/resources/templates/todos.handlebars +0 -31
  75. data/examples/web-app/README.md +0 -83
  76. data/examples/web-app/index.html +0 -12
  77. data/examples/web-app/lib/main.js +0 -3
  78. data/examples/web-app/package.json +0 -6
  79. data/examples/web-app/tests.html +0 -12
  80. data/examples/web-app/tests/ct-example-test.js +0 -39
  81. data/examples/web-app/tests/qunit-test.js +0 -23
@@ -0,0 +1 @@
1
+ Gemfile.lock
@@ -0,0 +1,15 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem "rspec"
4
+
5
+ if ENV["BPM_PATH"]
6
+ gem 'bpm', :path => ENV["BPM_PATH"]
7
+ else
8
+ gem 'bpm', :git => "git://github.com/sproutcore/bpm"
9
+ end
10
+
11
+ if ENV["SPADE_PATH"]
12
+ gem 'spade', :path => ENV["SPADE_PATH"]
13
+ else
14
+ gem 'spade', :git => "git://github.com/sproutcore/spade-ruby"
15
+ end
@@ -0,0 +1,1283 @@
1
+ // ==========================================================================
2
+ // Project: Spade - CommonJS Runtime
3
+ // Copyright: ©2010 Strobe Inc. All rights reserved.
4
+ // License: Licened under MIT license (see __preamble__.js)
5
+ // ==========================================================================
6
+ /*jslint evil:true */
7
+ /*globals ARGS ARGV ENV __module */
8
+
9
+ /*! @license
10
+ ==========================================================================
11
+ Spade 2.0 CommonJS Runtime
12
+ copyright 2010 Strobe Inc.
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a
15
+ copy of this software and associated documentation files (the "Software"),
16
+ to deal in the Software without restriction, including without limitation
17
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
18
+ and/or sell copies of the Software, and to permit persons to whom the
19
+ Software is furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in
22
+ all copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30
+ DEALINGS IN THE SOFTWARE.
31
+
32
+ Spade is part of the SproutCore project.
33
+
34
+ SproutCore and the SproutCore logo are trademarks of Sprout Systems, Inc.
35
+
36
+ For more information visit http://www.sproutcore.com/spade
37
+
38
+ ==========================================================================
39
+ @license */
40
+
41
+
42
+ // Compatibility backports
43
+
44
+ if (!Array.prototype.some) {
45
+ Array.prototype.some = function(fun /*, thisp*/) {
46
+ var len = this.length;
47
+ if (typeof fun != "function") { throw new TypeError(); }
48
+
49
+ var thisp = arguments[1];
50
+ for (var i = 0; i < len; i++) {
51
+ if (i in this && fun.call(thisp, this[i], i, this)) { return true; }
52
+ }
53
+ return false;
54
+ };
55
+ }
56
+
57
+ if (!Array.prototype.indexOf) {
58
+ Array.prototype.indexOf = function (obj, fromIndex) {
59
+ if (fromIndex == null) { fromIndex = 0; }
60
+ else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); }
61
+ for (var i = fromIndex, j = this.length; i < j; i++) {
62
+ if (this[i] === obj) { return i; }
63
+ }
64
+ return -1;
65
+ };
66
+ }
67
+
68
+
69
+
70
+ // Make this work when loaded from browser or from node.js
71
+ var spade ;
72
+ (function() {
73
+
74
+ var Spade, Tp, Sandbox, Sp,
75
+ Loader, Lp, K, Evaluator, Ep, Compiler, Cp;
76
+
77
+ // defining these types here will allow the minifier the compact them
78
+ if ('undefined' !== typeof spade) { return; } // nothing to do
79
+
80
+ K = function() {}; // noop
81
+
82
+ // assume id is already normalized
83
+ function packageIdFor(normalizedId) {
84
+ return normalizedId.slice(0, normalizedId.indexOf('/'));
85
+ }
86
+
87
+ function remap(id, contextPkg) {
88
+ var mappings = contextPkg ? contextPkg.mappings : null;
89
+ if (!mappings) { return id; }
90
+
91
+ var packageId = packageIdFor(id);
92
+ if (mappings[packageId]) {
93
+ id = mappings[packageId] + id.slice(id.indexOf('/'));
94
+ }
95
+ return id;
96
+ }
97
+
98
+ function normalize(id, contextId, contextPkg, _asPackage) {
99
+ var idx, len;
100
+
101
+ // slice separator off the end since it is not used...
102
+ if (id[id.length-1]==='/') { id = id.slice(0,-1); }
103
+
104
+ // need to walk if there is a .
105
+ if (id.indexOf('.')>=0) {
106
+ var parts = contextId && (id.charAt(0) ==='.') ? contextId.split('/') : [],
107
+ part, next,
108
+ packageName = parts[0],
109
+ needsCleanup = false;
110
+
111
+ idx = 0;
112
+ len = id.length;
113
+
114
+ if (contextPkg && contextPkg.main && contextId === packageName+'/main') {
115
+ // If we're requiring from main we need to handle relative requires specially
116
+ needsCleanup = true;
117
+ parts = contextPkg.main.replace(/^\.?\//, '').split('/');
118
+ }
119
+
120
+ parts.pop(); // get rid of the last path element since it is a module.
121
+
122
+ while(idx<len) {
123
+ next = id.indexOf('/', idx);
124
+ if (next<0) { next = len; }
125
+ part = id.slice(idx, next);
126
+ if (part==='..') { parts.pop(); }
127
+ else if (part!=='.' && part!=='' && part!==null) { parts.push(part); }
128
+ // skip .., empty, and null.
129
+ idx = next+1;
130
+ }
131
+
132
+ id = parts.join('/');
133
+
134
+ if (needsCleanup) {
135
+ var libPaths = contextPkg.directories.lib;
136
+ for (idx=0,len=libPaths.length; idx<len; idx++){
137
+ id = id.replace(libPaths[idx].replace(/^\.?\//, '')+'/', '');
138
+ }
139
+ id = packageName+'/'+id;
140
+ }
141
+
142
+ // else, just slice off beginning '/' if needed
143
+ } else if (id[0]==='/') { id = id.slice(1); }
144
+
145
+ // if we end up with no separators, make this a pkg
146
+ if (id.indexOf('/')<0) { id = id+(_asPackage ? '/~package' : '/main'); }
147
+
148
+ // slice separators off begin and end
149
+ if (id[0]==='/') { id = id.slice(1); }
150
+
151
+ // Remove unnecessary ~lib references
152
+ id = id.replace('~lib/', '');
153
+
154
+ return remap(id, contextPkg);
155
+ }
156
+
157
+ function parseIdFormats(id, formats) {
158
+ // Handle requires with extension
159
+ var formatRE = new RegExp("^(.+)\\.("+formats.join('|')+")$"), match, format;
160
+ if (id.substring(0,5) !== "file:" && (match = id.match(formatRE))) {
161
+ id = match[1];
162
+ format = match[2];
163
+ formats = [format];
164
+ }
165
+
166
+ return {
167
+ id: id,
168
+ format: format,
169
+ formats: formats
170
+ };
171
+ }
172
+
173
+ // ..........................................................
174
+ // PLATFORM
175
+ //
176
+ // Detect important platform properties. Mostly for determining code
177
+ // that can't run one way or the other.
178
+ var SPADE_PLATFORM;
179
+ if (('undefined'===typeof ENV) || !ENV.SPADE_PLATFORM) {
180
+ SPADE_PLATFORM = { ENGINE: 'browser' };
181
+ } else {
182
+ SPADE_PLATFORM = ENV.SPADE_PLATFORM;
183
+ }
184
+
185
+ var LANG;
186
+ if ('undefined'!==typeof ENV) { LANG = ENV.LANG; }
187
+ if (!LANG && 'undefined'!==typeof navigator) { LANG = navigator.language; }
188
+ if (!LANG) { LANG = 'en-US'; }
189
+
190
+
191
+ function processFactory(id, factory, sandbox) {
192
+ var factoryData = factory.data,
193
+ spade = sandbox.spade,
194
+ pkg = spade.package(id);
195
+ if (typeof factoryData === 'string') {
196
+ factoryData = sandbox.compileFormat(factoryData, factory.filename, factory.format, pkg);
197
+ factoryData = sandbox.compilePreprocessors(factoryData, factory.filename, pkg, id);
198
+ }
199
+ return factoryData;
200
+ }
201
+
202
+ // ..........................................................
203
+ // Sandbox - you could make a secure version if you want...
204
+ //
205
+
206
+ // runs a factory within context and returns exports...
207
+ function execFactory(id, factory, sandbox, spade) {
208
+ var require, mod;
209
+
210
+ var pkg = spade.package(id),
211
+ filename = factory.filename,
212
+ format = factory.format,
213
+ skipPreprocess = factory.skipPreprocess,
214
+ ARGV = sandbox.ARGV,
215
+ ENV = sandbox.ENV,
216
+ fullId = id+'.'+format;
217
+
218
+ require = function(moduleId) {
219
+ return sandbox.require(moduleId, id, pkg);
220
+ };
221
+
222
+ // make the require 'object' have the same API as sandbox and spade.
223
+ require.require = require;
224
+
225
+ require.exists = function(moduleId) {
226
+ return sandbox.exists(normalize(moduleId, id, pkg));
227
+ };
228
+
229
+ require.normalize = function(moduleId) {
230
+ return normalize(moduleId, id, pkg);
231
+ };
232
+
233
+ require.async = function(moduleId, callback) {
234
+ return sandbox.async(normalize(moduleId, id, pkg), callback);
235
+ };
236
+
237
+ require.sandbox = function(name, isolate) {
238
+ return spade.sandbox(name, isolate);
239
+ };
240
+
241
+ require.url = function(moduleId, ext) {
242
+ return sandbox.url(normalize(moduleId, id, pkg), ext);
243
+ };
244
+
245
+ require.runCommand = function(command, args){
246
+ return sandbox.runCommand(command, args, id, pkg);
247
+ };
248
+
249
+ require.id = id; // so you can tell one require from another
250
+
251
+ sandbox._modules[id] = mod = {
252
+ id: id,
253
+ exports: {},
254
+ sandbox: sandbox
255
+ };
256
+
257
+ factoryData = factory.data; // extract the raw module body
258
+
259
+ // evaluate if needed - use cache so we only do it once per sandbox
260
+ if ('string' === typeof factoryData) {
261
+ if (sandbox._factories[fullId]) {
262
+ factoryData = sandbox._factories[fullId];
263
+ } else {
264
+ sandbox._loading[id] = sandbox._loading[fullId] = true;
265
+
266
+ if (!skipPreprocess) {
267
+ factoryData = processFactory(id, factory, sandbox);
268
+ }
269
+
270
+ // The __evalFunc keeps IE 9 happy since it doesn't like
271
+ // unassigned anonymous functions
272
+ factoryData = sandbox.evaluate('__evalFunc = function(require, exports, __module, ARGV, ENV, __filename) {'+factoryData+'} //@ sourceURL='+filename+'\n', filename);
273
+ sandbox._factories[fullId] = factoryData;
274
+ sandbox._loading[id] = sandbox._loading[fullId] = false;
275
+ }
276
+ }
277
+
278
+ if ('function' === typeof factoryData) {
279
+ var ret = factoryData(require, mod.exports, mod, ARGV, ENV, filename);
280
+ if (ret !== undefined) { mod.exports = ret; } // allow return exports
281
+ } else {
282
+ mod.exports = factoryData;
283
+ }
284
+
285
+ return mod.exports;
286
+ }
287
+
288
+ /**
289
+ @constructor
290
+
291
+ Sandbox provides an isolated context for loading and running modules.
292
+ You can create new sandboxes anytime you want. If you pass true for the
293
+ isolate flag, then the sandbox will be created in a separate context if
294
+ supported on the platform. Otherwise it will share globals with the
295
+ default sandbox context.
296
+
297
+ Note that isolated sandboxes are not the same as secure sandboxes. For
298
+ example in the browser, a isolated sandbox is created using an iframe
299
+ which still exposes access to the DOM and parent environment.
300
+
301
+ Isolated sandboxes are mostly useful for testing and sharing plugin code
302
+ that might want to use different versions of packages.
303
+
304
+ @param {Spade} spade
305
+ The spade instance
306
+
307
+ @param {Boolean} isolate
308
+ Set to true if you want to isolate it
309
+
310
+ @returns {Sandbox} instance
311
+ */
312
+ Sandbox = function(spade, name, isolate) {
313
+ if (typeof name !== 'string') {
314
+ isolate = name;
315
+ name = null;
316
+ }
317
+
318
+ if (!name) { name = '(anonymous)'; }
319
+
320
+ this.spade = spade;
321
+ this.name = name;
322
+ this.isIsolated = !!isolate;
323
+ this._factories = {}; // evaluated factories
324
+ this._loading = {}; // list of loading modules
325
+ this._modules = {}; // cached export results
326
+ this._used = {}; // to detect circular references
327
+ };
328
+
329
+ // alias this to help minifier make the page a big smaller.
330
+ Sp = Sandbox.prototype;
331
+
332
+ Sp.toString = function() {
333
+ return '[Sandbox '+this.name+']';
334
+ };
335
+
336
+ /**
337
+ Evaluate the passed string in the Sandbox context, returning the result.
338
+ This is the primitive used to evalute string-encoded factories into
339
+ modules that can execute within a specific context.
340
+ */
341
+ Sp.evaluate = function(code, filename) {
342
+ if (this.isDestroyed) { throw new Error("Sandbox destroyed"); }
343
+ if (!this._evaluatorInited) {
344
+ this._evaluatorInited = true;
345
+ this.spade.evaluator.setup(this);
346
+ }
347
+ return this.spade.evaluator.evaluate(code, this, filename);
348
+ };
349
+
350
+ function findPlugins(sandbox, pkg, type, options) {
351
+ if (!options) { options = {}; }
352
+
353
+ var plugins = [], ret = [], found,
354
+ single = options.single,
355
+ key = options.key,
356
+ searchSelf = (options.searchSelf != false) ? true : options.searchSelf;
357
+
358
+ if (searchSelf) {
359
+ found = pkg && pkg['plugin:'+type];
360
+ if (key) { found = found && found[key]; }
361
+ if (found) {
362
+ if (single) { return found; }
363
+ plugins = plugins.concat(found);
364
+ }
365
+ }
366
+
367
+ // look in immediate dependencies only, if we wanted to have formats we would have required them
368
+ var deps = pkg && pkg.dependencies;
369
+ if (deps) {
370
+ for(var packageId in deps) {
371
+ pkg = sandbox.spade.package(packageId);
372
+ found = pkg && pkg['plugin:'+type];
373
+ if (key) { found = found && found[key]; }
374
+ if (found) { plugins = plugins.concat(found); }
375
+ }
376
+ }
377
+
378
+ // Remove duplicates
379
+ for (var i=0,len=plugins.length; i < len; i++) {
380
+ if (ret.indexOf(plugins[i]) < 0) { ret.push(plugins[i]); }
381
+ }
382
+
383
+ if (single) {
384
+ if (ret.length > 1) { console.warn("Found multiple plugins when expecting one for type: '"+type+"' and key: '"+key+"'"); }
385
+ return ret[0] || null;
386
+ } else {
387
+ return ret;
388
+ }
389
+ }
390
+
391
+ Sp.compileFormat = function(code, filename, format, pkg) {
392
+ var plugin = findPlugins(this, pkg, 'formats', { key: format, single: true });
393
+ if (plugin) { plugin = this.require(plugin); }
394
+ return plugin ? plugin.compileFormat(code, this, filename, format, pkg) : code;
395
+ };
396
+
397
+ Sp.compilePreprocessors = function(code, filename, pkg, id) {
398
+ var plugins = findPlugins(this, pkg, 'preprocessors'), plugin;
399
+ for (var i=0, len=plugins.length; i < len; i++) {
400
+ // Avoid trying to process self
401
+ if (plugins[i] === id) { continue; }
402
+ plugin = this.require(plugins[i]);
403
+ if (!this._loading[id] || plugin.compilePreprocessor) {
404
+ code = plugin.compilePreprocessor(code, this, filename, pkg);
405
+ }
406
+ }
407
+ return code;
408
+ };
409
+
410
+ Sp.compileWrapper = function(code, filename, pkg, id) {
411
+ var plugin = findPlugins(this, pkg, 'wrapper', { single: true, searchSelf: false });
412
+ if (plugin) { plugin = this.require(plugin); }
413
+ return plugin ? plugin.compileWrapper(code, this, filename, pkg, id) : code;
414
+ };
415
+
416
+ /**
417
+ Sandbox-specific require. This is actually the most primitive form of
418
+ require.
419
+ */
420
+ Sp.require = function(id, callingId) {
421
+ var pkg = callingId ? this.spade.package(callingId) : null;
422
+ id = normalize(id, callingId, pkg);
423
+
424
+ var ret = this._modules[id];
425
+ if (ret) { ret = ret.exports; }
426
+
427
+ var factory;
428
+ if (ret) {
429
+ if (!this._used[id]) { this._used[id] = ret; }
430
+ return ret ;
431
+
432
+ } else {
433
+ factory = this.spade.loadFactory(this.spade.resolve(id, this));
434
+ if (!factory) { throw new Error('Module '+id+' not found'); }
435
+
436
+ var spade = this.spade;
437
+ if (!this.ENV) { this.ENV = spade.env(); } // get at the last minute
438
+ if (!this.ARGV) { this.ARGV = spade.argv(); }
439
+
440
+ ret = execFactory(id, factory, this, spade);
441
+
442
+ // detect circular references...
443
+ if (this._used[id] && (this._used[id] !== ret)) {
444
+ throw new Error("Circular require detected for module "+id);
445
+ }
446
+ }
447
+
448
+ return ret ;
449
+ };
450
+
451
+ /**
452
+ Sandbox-specific test to determine if the named module exists or not.
453
+ This property only reflects what is immediately available through the
454
+ sync-loader. Using the async loader may change the return value of this
455
+ call.
456
+ */
457
+ Sp.exists = function(id, callingId) {
458
+ var pkg = callingId ? this.spade.package(callingId) : null;
459
+ id = normalize(id, callingId, pkg);
460
+ if (this._modules[id]) { return true; }
461
+ return this.spade.factoryExists(this.spade.resolve(id, this));
462
+ };
463
+
464
+ /**
465
+ Sandbox-specific async load. This is actually the most primitive form of
466
+ require.
467
+ */
468
+ Sp.async = function(id, callback, callingId) {
469
+ var spade = this.spade, pkg;
470
+
471
+ pkg = callingId ? this.spade.package(callingId) : null;
472
+ id = spade.resolve(normalize(id, callingId, pkg), this);
473
+ return spade.loadFactory(id, callback);
474
+ };
475
+
476
+ Sp.url = function(id, ext, callingId) {
477
+ var ret, pkg;
478
+
479
+ pkg = callingId ? this.spade.package(callingId) : null;
480
+ id = normalize(id, callingId, pkg);
481
+
482
+ pkg = this.spade.package(id);
483
+ if (!pkg) {
484
+ var packageId = packageIdFor(id)+'/~package';
485
+ if (this.spade.exists(packageId)) { this.spade.require(packageId); }
486
+ pkg = this.spade.package(id);
487
+ }
488
+
489
+ if (!pkg) {
490
+ throw new Error("Can't get url for non-existent package "+id);
491
+ }
492
+
493
+ if (!pkg.root) {
494
+ throw new Error('Package for '+id+' does not support urls');
495
+ }
496
+
497
+ ret = pkg.root + id.slice(id.indexOf('/'));
498
+ if (ext) { ret = ret+'.'+ext; }
499
+ return ret ;
500
+ };
501
+
502
+ Sp.runCommand = function(command, args, callerId, pkg){
503
+ var xhr;
504
+ if (window.XMLHttpRequest) {
505
+ xhr = new XMLHttpRequest();
506
+ } else { // IE
507
+ xhr = new ActiveXObject("Microsoft.XMLHTTP");
508
+ }
509
+ var url = "_spade/command",
510
+ params = [];
511
+
512
+ args.command = command;
513
+ args.callerId = callerId;
514
+ args.pkgRoot = pkg && pkg.root;
515
+
516
+ for (var arg in args){
517
+ params.push(encodeURIComponent(arg)+"="+encodeURIComponent(args[arg]));
518
+ }
519
+ params = params.join('&');
520
+
521
+ xhr.open('POST', url, false);
522
+
523
+ try {
524
+ xhr.send(params);
525
+ } catch(e) {
526
+ throw new Error('unable to run command `'+command+'`, most likely either this browser or server does not support this');
527
+ }
528
+
529
+ if (xhr.status === 200) {
530
+ return xhr.responseText;
531
+ // Safari returns -1100 for file:// not found
532
+ } else if (xhr.status === 404 || xhr.status === -1100) {
533
+ throw new Error('unable to run command `'+command+'`, most likely you are running without a supported server');
534
+ } else {
535
+ throw new Error('running command `'+command+'` return status '+xhr.status+': '+xhr.responseText);
536
+ }
537
+ };
538
+
539
+ Sp.isDestroyed = false;
540
+
541
+ Sp.destroy = function() {
542
+ if (!this.isDestroyed) {
543
+ this.isDestroyed = true;
544
+ this.spade.evaluator.teardown(this);
545
+ }
546
+ return this;
547
+ };
548
+
549
+ // ..........................................................
550
+ // LOADER
551
+ //
552
+
553
+ /**
554
+ The built-in loader object knows how to load whole packages as long as
555
+ you have registered an external reference to the package. This is pkg
556
+ info that contains:
557
+
558
+ {
559
+ extern: true, // this is not a real package yet
560
+ src: 'http://example.com/bar', // URL to load
561
+ expects: ['foo', 'bar', 'baz'] // optional modules to expect
562
+ }
563
+ */
564
+ Loader = function() {
565
+ this._loading = {};
566
+ };
567
+
568
+ Lp = Loader.prototype;
569
+
570
+ function syncLoad(spade, id, url, format, force) {
571
+ if (force) { url = url+'?'+(+ new Date()); }
572
+
573
+ if (window.XMLHttpRequest) {
574
+ xhr = new XMLHttpRequest();
575
+ } else { // IE
576
+ xhr = new ActiveXObject("Microsoft.XMLHTTP");
577
+ }
578
+
579
+ xhr.open('GET', url, false);
580
+ try {
581
+ xhr.send(null);
582
+ } catch(e) {
583
+ throw new Error('unable to fetch '+url+', most likely this browser is not supported');
584
+ }
585
+
586
+ // successful file:// requests return 0 in Safari and Firefox
587
+ if (xhr.status === 200 || xhr.status === 0) {
588
+ var body = xhr.responseText;
589
+ if (body.slice(0,2) === '#!') { body = body.slice(body.indexOf('\n')); }
590
+
591
+ if (!format) { format = 'js'; }
592
+ spade.register(id, body, { format: format });
593
+ return true;
594
+
595
+ // Safari returns -1100 for file:// not found
596
+ } else if (xhr.status === 404 || xhr.status === -1100) {
597
+ return false;
598
+
599
+ } else {
600
+ throw new Error('fetching '+url+' return status '+xhr.status+': '+xhr.responseText);
601
+ }
602
+ }
603
+
604
+ function resolveUrl(id, extern) {
605
+ var dirname, dir, dirs, idx,
606
+ packageUrl = extern.root,
607
+ url, urls = [];
608
+
609
+ id = normalize(id);
610
+ id = id.slice(id.indexOf('/')+1); // slide off pkg
611
+
612
+ // get directory
613
+ if (id.charAt(0) === '~') {
614
+ idx = id.indexOf('/');
615
+ dirname = idx>=0 ? id.slice(0, idx) : id;
616
+ id = dirname.length>=id.length ? null : id.slice(dirname.length+1);
617
+ dirname = dirname.slice(1); // get rid of ~
618
+ } else { dirname = 'lib'; }
619
+
620
+ // map to directories
621
+ dirs = extern.directories && extern.directories[dirname];
622
+ if (!dirs) {
623
+ throw new Error("Can't require '"+id+"' when '"+dirname+"' is not a known directory.");
624
+ }
625
+ if (typeof dirs === 'string') { dirs = [dirs]; }
626
+
627
+ for(var idx=0,len=dirs.length; idx < len; idx++){
628
+ dir = dirs[idx];
629
+ // combine elements to form URL
630
+ url = packageUrl;
631
+ if (url === '.') { url = null; }
632
+ if (dir && dir !== '.') { url = url ? url+'/'+dir : dir; }
633
+ if (id && id !== '.') { url = url ? url+'/'+id : id; }
634
+
635
+ // Clean up '.' and '..'
636
+ var parts = url.split('/');
637
+ for(var i=0,plen=parts.length; i<plen;) {
638
+ if (parts[i] === '.') {
639
+ parts = parts.slice(0,i).concat(parts.slice(i+1)); // Remove item
640
+ // Retry at same index
641
+ } else if (parts[i] === '..') {
642
+ if (i === 0) {
643
+ console.warn("Can't resolve leading '..'");
644
+ i++; // Go to next
645
+ } else {
646
+ parts = parts.slice(0,i-1).concat(parts.slice(i+1)); // Remove previous and current item
647
+ i--; // Retry at previous index
648
+ }
649
+ } else {
650
+ i++; // Go to next
651
+ }
652
+ }
653
+ url = parts.join('/');
654
+ urls.push(url);
655
+ }
656
+
657
+ return urls;
658
+ }
659
+ Lp.resolveUrl = resolveUrl;
660
+
661
+ function syncLoadFormats(spade, id, extern, formats) {
662
+ var packageUrl = extern.root,
663
+ urls = resolveUrl(id, extern),
664
+ tryUrls = [];
665
+
666
+ for (var urlIdx=0,urlsLen=urls.length; urlIdx<urlsLen; urlIdx++) {
667
+ for (var formatIdx=0,formatsLen=formats.length; formatIdx<formatsLen; formatIdx++) {
668
+ tryUrls.push(urls[urlIdx]+'.'+formats[formatIdx]);
669
+ }
670
+ }
671
+
672
+ // If we know about a file we want to try to load it first
673
+ // We still will try others incase it doesn't exist
674
+ if (extern.files) {
675
+ for (var idx=0,len=tryUrls.length; idx<len; idx++) {
676
+ var relativeUrl = tryUrls[idx].replace(packageUrl+'/', '');
677
+ if (extern.files.indexOf(relativeUrl) > -1) {
678
+ // Move url to the front
679
+ tryUrls = [tryUrls[idx]].concat(tryUrls.slice(0,idx)).concat(tryUrls.slice(idx+1));
680
+ break;
681
+ }
682
+ }
683
+ }
684
+
685
+ function tryUrl(url) {
686
+ var format = url.match(/\.([^\.]+)$/)[1]; // this is a touch hacky
687
+ return syncLoad(spade, id, url, format, true);
688
+ }
689
+
690
+ return !!tryUrls.some(tryUrl);
691
+ }
692
+
693
+ function verifyInBrowser(id, done) {
694
+ if ('undefined'===typeof document) {
695
+ var err = new Error("Cannot load package "+id+" outside of browser");
696
+ if (done) { done(err); }
697
+ else { throw err; }
698
+ return false;
699
+ }
700
+
701
+ return true;
702
+ }
703
+
704
+ Lp.loadFactory = function(spade, id, formats, done) {
705
+
706
+ var url, loaded, packageId,
707
+ extern = spade.package(id),
708
+ that = this;
709
+
710
+ // loader only works for sync requests if the package info permits sync
711
+ // loading. In production mode, normally it should not.
712
+ if (!done && (!extern || !extern.sync)) { return this; }
713
+
714
+ // this loader only works in the browser
715
+ if (!verifyInBrowser(id, done)) { return this; }
716
+
717
+ if (!done) {
718
+ url = extern.src;
719
+ if (!url) { loaded = syncLoadFormats(spade, id, extern, formats); }
720
+ else { loaded = syncLoad(spade, id, url); }
721
+ if (!loaded) { throw new Error('fetching '+id+' not found'); }
722
+ // not actually loadable
723
+ } else if (!extern || !extern.extern) {
724
+ done(new Error('Module '+id+' not found'));
725
+
726
+ } else {
727
+
728
+ // now do actual load of src
729
+ if (!extern.src) {
730
+ throw new Error("Cannot load package "+id+" without a src URL");
731
+ }
732
+
733
+ // if already loading, just add to queue
734
+ packageId = packageIdFor(normalize(id));
735
+ if (this._loading[packageId]) {
736
+ this._loading[packageId].push(done);
737
+ } else {
738
+ this._loading[packageId] = [done];
739
+ this.loadURL(extern.src, function() { that.didLoad(packageId); });
740
+ // TODO: Load dependencies
741
+ }
742
+ }
743
+ return this;
744
+ };
745
+
746
+ Lp.exists = function(spade, id, formats) {
747
+
748
+ var extern = spade.package(id);
749
+
750
+ // loader only works for sync requests if the package info permits sync
751
+ // loading. In production mode, normally it should not.
752
+ if (!extern || !extern.sync || !extern.root) { return false; }
753
+
754
+ // this loader only works in the browser
755
+ if (!verifyInBrowser(id)) { return false; }
756
+ return syncLoadFormats(spade, id, extern, formats, true);
757
+ };
758
+
759
+ Lp.didLoad = function(packageId) {
760
+ // TODO: verify/load dependencies
761
+ var callbacks = this._loading[packageId];
762
+ delete this._loading[packageId];
763
+ if (callbacks) { callbacks.forEach(function(done) { done(); }); }
764
+ };
765
+
766
+ // actually create a script tag and load it
767
+ Lp.loadURL = function(url, callback) {
768
+ var el, head, didCallback = false;
769
+
770
+ el = document.createElement('script');
771
+ el.src = url;
772
+ el.type = 'text/javascript';
773
+
774
+ el.onload = callback;
775
+ // IE doesn't support onload for scripts
776
+ el.onreadystatechange = function(){
777
+ if (el.readyState === 'loaded' || el.readyState === 'complete') {
778
+ callback();
779
+ el = null; // Avoid memory leaks in IE
780
+ }
781
+ };
782
+
783
+ head = document.head || document.body;
784
+ head.appendChild(el);
785
+ head = null; // Avoid memory leaks in IE
786
+ };
787
+
788
+ // NOTE: On ready stuff mostly stolen from jQuery 1.4. Need to incl here
789
+ // because spade will often be used to load jQuery.
790
+ // Will only be invoked once. Just be prepared to call it
791
+ Lp.scheduleReady = function(callback) {
792
+
793
+ // handle case where ready is invoked AFTER the document is already ready
794
+ if ( document.readyState === "complete" ) { return setTimeout(callback, 1); }
795
+
796
+ var handler, handled = false;
797
+
798
+ // The DOM ready check for Internet Explorer
799
+ function doScrollCheck() {
800
+ if (handled) { return; }
801
+
802
+ try {
803
+ // If IE is used, use the trick by Diego Perini
804
+ // http://javascript.nwbox.com/IEContentLoaded/
805
+ document.documentElement.doScroll("left");
806
+ } catch(e) {
807
+ setTimeout( doScrollCheck, 1 );
808
+ return;
809
+ }
810
+
811
+ // and execute any waiting functions
812
+ handler();
813
+ }
814
+
815
+ // Mozilla, Opera and webkit nightlies currently support this event
816
+ if (document.addEventListener) {
817
+
818
+ handler = function() {
819
+ if (handled) { return; }
820
+ handled = true;
821
+ document.removeEventListener("DOMContentLoaded", handler, false);
822
+ window.removeEventListener('load', handler, false);
823
+ callback();
824
+ };
825
+
826
+ document.addEventListener( "DOMContentLoaded", handler, false);
827
+
828
+ // A fallback to window.onload, that will always work
829
+ window.addEventListener( "load", handler, false );
830
+
831
+ // If IE event model is used
832
+ } else if ( document.attachEvent ) {
833
+
834
+ handler = function() {
835
+ if (!handled && document.readyState === "complete") {
836
+ handled = true;
837
+ document.detachEvent( "onreadystatechange", handler );
838
+ window.detachEvent('onload', handler);
839
+ callback();
840
+ }
841
+ };
842
+
843
+ // ensure firing before onload,
844
+ // maybe late but safe also for iframes
845
+ document.attachEvent("onreadystatechange", handler);
846
+
847
+ // A fallback to window.onload, that will always work
848
+ window.attachEvent( "onload", handler);
849
+
850
+ // If IE and not a frame
851
+ // continually check to see if the document is ready
852
+ var toplevel = false;
853
+
854
+ try {
855
+ toplevel = window.frameElement === null;
856
+ } catch(e) {}
857
+ if ( document.documentElement.doScroll && toplevel ) { doScrollCheck(); }
858
+ }
859
+ };
860
+
861
+ // ..........................................................
862
+ // Evaluator Class
863
+ //
864
+
865
+ Evaluator = function() {};
866
+ Ep = Evaluator.prototype;
867
+
868
+ Ep.setup = function(sandbox) {
869
+ if (sandbox.isIsolated) { throw new Error("Isolated Sandbox not supported"); }
870
+ };
871
+
872
+ Ep.evaluate = function(text, sandbox, filename) {
873
+ return eval(text);
874
+ };
875
+
876
+ Ep.teardown = function(sandbox) {
877
+ // noop by default
878
+ };
879
+
880
+ // ..........................................................
881
+ // Compiler Class
882
+ //
883
+
884
+ Compiler = function(spade) {
885
+ this.spade = spade;
886
+ };
887
+ Cp = Compiler.prototype;
888
+
889
+ Cp.compile = function(id, callingId) {
890
+ var pkg = callingId ? this.spade.package(callingId) : null;
891
+ var sandbox = this.spade.defaultSandbox;
892
+ id = normalize(id, callingId, pkg);
893
+
894
+ // Don't show the wrapper
895
+ if ((pkg && pkg['plugin:wrapper']) === id) return null;
896
+
897
+ factory = this.spade.loadFactory(this.spade.resolve(id, sandbox));
898
+ if (!factory) { throw new Error('Module '+id+' not found'); }
899
+
900
+ var ret = processFactory(id, factory, sandbox);
901
+ if (typeof ret !== 'string') { ret = String(ret); }
902
+ ret = sandbox.compileWrapper(ret, factory.filename, pkg, id);
903
+
904
+ return "/* --------------- "+id+" --------------- */\n"+ret;
905
+ };
906
+
907
+ // ..........................................................
908
+ // Spade Class - defined so we can recreate
909
+ //
910
+
911
+ Spade = function() {
912
+ this.loader = new this.Loader(this);
913
+ this.evaluator = new this.Evaluator(this);
914
+ this.defaultSandbox = this.sandbox();
915
+ this.compiler = new this.Compiler(this);
916
+ this._factories = {};
917
+ this._packages = {};
918
+ };
919
+
920
+ Tp = Spade.prototype;
921
+
922
+ Tp.VERSION = "0.1.1";
923
+
924
+ // expose the classes. We do it this way so that you can create a new
925
+ // Spade instance and treat it like the spade module
926
+ Tp.Spade = Spade;
927
+ Tp.Sandbox = Sandbox;
928
+ Tp.Loader = Loader;
929
+ Tp.Evaluator = Evaluator;
930
+ Tp.Compiler = Compiler;
931
+
932
+ Tp.env = function() {
933
+ if (!this.ENV) { this.ENV = 'undefined' !== typeof ENV ? ENV : {}; }
934
+ if (!this.ENV.SPADE_PLATFORM) { this.ENV.SPADE_PLATFORM = SPADE_PLATFORM; }
935
+ if (!this.ENV.LANG) { this.ENV.LANG = LANG; }
936
+ return this.ENV;
937
+ };
938
+
939
+ Tp.argv = function() {
940
+ if (!this.ARGV) { this.ARGV = 'undefined' !== typeof ARGV ? ARGV : []; }
941
+ return this.ARGV;
942
+ };
943
+
944
+ /**
945
+ Expose the spade require methods to the global context. This should allow
946
+ your global code to access spade in the same way that normal modules
947
+ would.
948
+ */
949
+ Tp.globalize = function() {
950
+ var spade = this;
951
+
952
+ // save old info for conflict...
953
+ this._conflict = {
954
+ require: 'undefined' !== typeof require ? require : undefined,
955
+ ENV: 'undefined' !== typeof ENV ? ENV : undefined,
956
+ ARGV: 'undefined' !== typeof ARGV ? ARGV : undefined
957
+ };
958
+
959
+ require = function() { return spade.require.apply(spade,arguments); };
960
+ ['async', 'sandbox', 'exists', 'url'].forEach(function(key) {
961
+ require[key] = function() { return spade[key].apply(spade, arguments);};
962
+ });
963
+
964
+ ENV = this.env();
965
+ ARGV = this.argv();
966
+ return this;
967
+ };
968
+
969
+ /**
970
+ Restores original values after a call to globalize(). If you call this
971
+ method more than once it will have no effect.
972
+ */
973
+ Tp.noConflict = function() {
974
+ var c = this._conflict;
975
+ if (c) {
976
+ delete this._conflict;
977
+ require = c.require;
978
+ ENV = c.ENV;
979
+ ARGV = c.ARGV;
980
+ }
981
+ return this;
982
+ };
983
+
984
+ /**
985
+ Returns a new sandbox instance attached to the current spade instance.
986
+ Can isolate if preferred.
987
+
988
+ @param {Boolean} isolate
989
+ true if you want the sandbox to be isolated. Throws exception if
990
+ platform cannot isolate.
991
+
992
+ @returns {Sandbox} sandbox instance
993
+ */
994
+ Tp.sandbox = function(name, isolate) {
995
+ return new this.Sandbox(this, name, isolate);
996
+ };
997
+
998
+ /**
999
+ Register a module or package information. You can pass one of the
1000
+ following:
1001
+
1002
+ 'module/id', 'module body string'
1003
+ 'module/id', function() { module func }
1004
+ 'module/id', { exports: 'foo' }
1005
+ 'module/id' - just register module id and no body to indicate presence
1006
+
1007
+ Note also that if you pass just a packageId, it will be normalized to
1008
+ packageId/~package. This is how you register a package.
1009
+
1010
+ @param {String} id
1011
+ The module or package id
1012
+
1013
+ @param {String|Function|Hash} data
1014
+ A module function, module body (as string), or hash of exports to use.
1015
+
1016
+ @param {String} opts
1017
+ Additional metadata only if you are registering a module factory. Known
1018
+ keys include 'filename' and 'format' (for compilation of DSLs).
1019
+
1020
+ */
1021
+ Tp.register = function(id, data, opts) {
1022
+ if (!data) { data = K ; }
1023
+ var t = typeof data, isExtern, factory, isPkg;
1024
+
1025
+ id = normalize(id, null, null, true);
1026
+ isPkg = id.slice(-9) === '/~package';
1027
+
1028
+ // register - note packages can only accept hashes
1029
+ if (isPkg && 'object'!==typeof data) {
1030
+ throw new Error('You can only register hashes for packages');
1031
+ }
1032
+
1033
+ // Set some package defaults
1034
+ if (isPkg) {
1035
+ if (!data.directories) { data.directories = {}; }
1036
+ if (!data.directories.lib) {
1037
+ data.directories.lib = ['lib'];
1038
+ } else if (typeof data.directories.lib === 'string') {
1039
+ data.directories.lib = [data.directories.lib];
1040
+ }
1041
+ if (!data.directories.tests) {
1042
+ data.directories.tests = 'tests';
1043
+ }
1044
+ }
1045
+
1046
+ factory = { data: data };
1047
+ factory.filename = opts && opts.filename ? opts.filename : id;
1048
+ factory.format = opts && opts.format ? opts.format : 'js';
1049
+ factory.skipPreprocess = !!(opts && opts.skipPreprocess); // Force boolean
1050
+
1051
+ // Store with generic id if none, or if JS
1052
+ if (!this._factories[id] || factory.format === 'js') {
1053
+ this._factories[id] = factory;
1054
+ }
1055
+
1056
+ // Store with format
1057
+ if (!isPkg) {
1058
+ this._factories[id+'.'+factory.format] = factory;
1059
+ }
1060
+
1061
+ return this;
1062
+ };
1063
+
1064
+ /**
1065
+ Efficient way to register external packages. Pass a hash of packageIds
1066
+ and source URLs. If the package is already registered, the extern will
1067
+ not replace it so this is safe to call multiple times.
1068
+ */
1069
+ Tp.externs = function(externs, extern) {
1070
+ var tmp, packages = this._packages;
1071
+
1072
+ // normalize method call.
1073
+ if ('string' === typeof externs) {
1074
+ tmp = {};
1075
+ tmp[externs] = extern;
1076
+ externs = tmp;
1077
+ extern = null;
1078
+ }
1079
+
1080
+ for(var packageId in externs) {
1081
+ if (!externs.hasOwnProperty(packageId)) { continue; }
1082
+ if (packages[packageId] && !packages[packageId].extern) { continue; }
1083
+
1084
+ extern = externs[packageId];
1085
+ if ('string' === typeof extern) { extern = {name: packageId, src: extern}; }
1086
+ extern.extern = true;
1087
+ this.register(packageId, extern);
1088
+ }
1089
+ };
1090
+
1091
+ /**
1092
+ Require a module from the default sandbox.
1093
+
1094
+ @param {String} id
1095
+ The module id.
1096
+
1097
+ @returns {Hash} module exports
1098
+ */
1099
+ Tp.require = function(id) {
1100
+ return this.defaultSandbox.require(id, this.defaultSandbox.callerId);
1101
+ };
1102
+
1103
+ /**
1104
+ Compile a file.
1105
+
1106
+ @param {String} id
1107
+ The module id
1108
+
1109
+ @returns {String} the compiled file
1110
+ */
1111
+ Tp.compile = function(id, callingId) {
1112
+ return this.compiler.compile(id, callingId || this.defaultSandbox.callerId);
1113
+ };
1114
+
1115
+ /**
1116
+ Async load a module if it is not already a registered factory. Invoke
1117
+ the passed callback with an optional error object when the module is
1118
+ ready to load.
1119
+ */
1120
+ Tp.async = function(id, callback) {
1121
+ return this.defaultSandbox.async(id, callback);
1122
+ };
1123
+
1124
+ Tp.exists = function(id) {
1125
+ return this.defaultSandbox.exists(id);
1126
+ };
1127
+
1128
+ Tp.url = function(id, ext) {
1129
+ return this.defaultSandbox.url(id, ext);
1130
+ };
1131
+
1132
+ function _collectFormats(spade, ret, pkg) {
1133
+
1134
+ function extractFormats(formats) {
1135
+ if (formats) {
1136
+ for (var key in formats) {
1137
+ if (ret.indexOf(key)<0) { ret.unshift(key); } // new formats go first
1138
+ };
1139
+ }
1140
+ }
1141
+
1142
+ extractFormats(pkg['plugin:formats']);
1143
+
1144
+ var deps = pkg.dependencies;
1145
+ if (!deps) { return ret; }
1146
+
1147
+ for(var packageId in deps) {
1148
+ pkg = spade.package(packageId);
1149
+ if (pkg) { extractFormats(pkg['plugin:formats']); }
1150
+ }
1151
+
1152
+ return ret ;
1153
+ }
1154
+
1155
+ /**
1156
+ Gets a list of known formats for a package
1157
+ */
1158
+ Tp.listFormats = function(pkg){
1159
+ if (typeof pkg === 'string') { pkg = this.package(pkg); }
1160
+ return pkg ? _collectFormats(this, ['js'], pkg) : ['js'];
1161
+ }
1162
+
1163
+ /**
1164
+ Called by the sandbox to get a factory object for the named moduleId
1165
+ */
1166
+ Tp.loadFactory = function(id, callback) {
1167
+ var pkg, formats, format, data, ret;
1168
+
1169
+ // find any formats the current package might know about. Note that for
1170
+ // lazy-loaders this may not be entirely up to date (since not all pkgs
1171
+ // are registered right away)
1172
+ pkg = this.package(id);
1173
+ formats = this.listFormats(pkg);
1174
+
1175
+ data = parseIdFormats(id, formats);
1176
+ id = data.id;
1177
+ format = data.format;
1178
+ formats = data.formats;
1179
+
1180
+ ret = format ? this._factories[id+'.'+format] : this._factories[id];
1181
+
1182
+ if (callback) {
1183
+ if (!ret) {
1184
+ if (this.loader && this.loader.loadFactory) {
1185
+ this.loader.loadFactory(this, id, formats, callback);
1186
+ } else { callback(new Error('Module '+id+' not found')); }
1187
+ } else { callback(); }
1188
+
1189
+ } else if (!ret && this.loader && this.loader.loadFactory) {
1190
+ this.loader.loadFactory(this, id, formats);
1191
+ ret = format ? this._factories[id+'.'+format] : this._factories[id];
1192
+ }
1193
+
1194
+ return ret ;
1195
+ };
1196
+
1197
+ /**
1198
+ Called by the sandbox to determine if the named id exists on the system.
1199
+ The id should already be normalized. If the id is not yet registered, the
1200
+ loader will also be consulted.
1201
+ */
1202
+ Tp.factoryExists = function(id) {
1203
+ if (this._factories[id]) { return true; }
1204
+ if (!this.loader || !this.loader.exists) { return false; }
1205
+
1206
+ var pkg = this.package(id),
1207
+ formats = pkg ? _collectFormats(this, ['js'], pkg) : ['js'],
1208
+ data = parseIdFormats(id, formats);
1209
+
1210
+ return this.loader.exists(this, data.id, data.formats);
1211
+ };
1212
+
1213
+ /**
1214
+ Returns the package info, if any, for the named module or packageId
1215
+ */
1216
+ Tp.package = function(id) {
1217
+ id = packageIdFor(normalize(id))+'/~package';
1218
+ var ret = this._factories[id];
1219
+ return ret ? ret.data : null;
1220
+ };
1221
+
1222
+ /**
1223
+ Normalize a moduleId, expanding it if needed.
1224
+ */
1225
+ Tp.normalize = function(id, contextId) {
1226
+ return normalize(id, contextId);
1227
+ };
1228
+
1229
+ // maps the passed ID to a potentially location specific ID. This gives
1230
+ // the loader a way to vary the factory function returned for a given id
1231
+ // per sandbox
1232
+ Tp.resolve = function(id, sandbox) {
1233
+ if (sandbox && this.loader && this.loader.resolve) {
1234
+ return this.loader.resolve(id, sandbox);
1235
+ } else { return id; }
1236
+ };
1237
+
1238
+ // uses the loader to invoke when the app is ready. For the browser this
1239
+ // is on the ready event.
1240
+ Tp.ready = function(callback) {
1241
+ switch(this.readyState) {
1242
+ case 'ready':
1243
+ callback();
1244
+ break;
1245
+
1246
+ case 'scheduled':
1247
+ this._readyQueue.push(callback);
1248
+ break;
1249
+
1250
+ default:
1251
+ this._readyQueue = [callback];
1252
+ this.readyState = 'scheduled';
1253
+ if (this.loader && this.loader.scheduleReady) {
1254
+ var that = this;
1255
+ this.loader.scheduleReady(function() {
1256
+ var queue = that._readyQueue, len = queue ? queue.length : 0;
1257
+ that._readyQueue = null;
1258
+ that.readyState = 'ready';
1259
+ for(var idx=0;idx<len;idx++) { queue[idx](); }
1260
+ });
1261
+
1262
+ } else {
1263
+ throw new Error('Loader does not support activate on ready state');
1264
+ }
1265
+ }
1266
+ };
1267
+
1268
+ // instantiate spade and also attach class for testing
1269
+ spade = new Spade();
1270
+
1271
+ // in the browser - if ENV and ARGS are not defined, just create some
1272
+ // reasonable defaults. We assume that when loading strobe from the CLI
1273
+ // these will already be setup.
1274
+ if (SPADE_PLATFORM.engine === 'browser') { spade.globalize(); }
1275
+
1276
+ // make this work when called as a module
1277
+ if ('undefined' !== typeof require) {
1278
+ if ('undefined' !== typeof __module) { __module.exports = spade; }
1279
+ else if ('undefined' !== typeof module) { module.exports = spade; }
1280
+ }
1281
+
1282
+ })();
1283
+