spade-core 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,47 @@
1
+ module Spade
2
+ module Core
3
+ end
4
+
5
+ JSPATH = File.expand_path("../js/spade.js", __FILE__)
6
+ SPADE_DIR = '.spade'
7
+
8
+ # find the current path with a package.json or .packages or cur_path
9
+ def self.discover_root(cur_path)
10
+ ret = File.expand_path(cur_path)
11
+ while ret != '/' && ret != '.'
12
+ return ret if File.exists?(File.join(ret,'package.json')) || File.exists?(File.join(ret,'.spade'))
13
+ ret = File.dirname ret
14
+ end
15
+
16
+ return cur_path
17
+ end
18
+
19
+ # The next 5 methods should maybe be in Runtime
20
+
21
+ def self.current_context
22
+ @current_context
23
+ end
24
+
25
+ def self.current_context=(ctx)
26
+ @current_context = ctx
27
+ end
28
+
29
+ def self.exports=(klass)
30
+ exports(klass, nil)
31
+ end
32
+
33
+ def self.exports(klass, path = nil)
34
+ path = @current_path if path.nil?
35
+ @exports ||= {}
36
+ @exports[path] = klass
37
+ end
38
+
39
+ def self.exports_for(path)
40
+ @current_path = path
41
+ require path
42
+ @current_path = nil
43
+
44
+ @exports ||= {}
45
+ @exports[path]
46
+ end
47
+ end
@@ -0,0 +1,5 @@
1
+ module Spade
2
+ module Core
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,1130 @@
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
+ // Make this work when loaded from browser or from node.js
42
+ var spade ;
43
+ (function() {
44
+
45
+ var Spade, Tp, Sandbox, Sp,
46
+ Loader, Lp, K, Compiler, Cp;
47
+
48
+ // defining these types here will allow the minifier the compact them
49
+ if ('undefined' !== typeof spade) return ; // nothing to do
50
+
51
+ K = function() {}; // noop
52
+
53
+ // assume id is already normalized
54
+ function packageIdFor(normalizedId) {
55
+ return normalizedId.slice(0, normalizedId.indexOf('/'));
56
+ }
57
+
58
+ function remap(id, contextPkg) {
59
+ var mappings = contextPkg ? contextPkg.mappings : null;
60
+ if (!mappings) return id;
61
+
62
+ var packageId = packageIdFor(id);
63
+ if (mappings[packageId]) {
64
+ id = mappings[packageId] + id.slice(id.indexOf('/'));
65
+ }
66
+ return id;
67
+ }
68
+
69
+ function normalize(id, contextId, contextPkg, _asPackage) {
70
+ // slice separator off the end since it is not used...
71
+ if (id[id.length-1]==='/') id = id.slice(0,-1);
72
+
73
+ // need to walk if there is a .
74
+ if (id.indexOf('.')>=0) {
75
+ var parts = contextId && (id[0]==='.') ? contextId.split('/') : [],
76
+ idx = 0,
77
+ len = id.length,
78
+ part, next,
79
+ packageName = parts[0],
80
+ useTilde = false;
81
+
82
+ if (contextPkg && contextPkg.main && contextId === packageName+'/main') {
83
+ // If we're requiring from main we need to handle relative requires specially
84
+ useTilde = true;
85
+ parts = contextPkg.main.replace(/^\.?\//, '').split('/');
86
+ }
87
+
88
+ parts.pop(); // get rid of the last path element since it is a module.
89
+
90
+ while(idx<len) {
91
+ next = id.indexOf('/', idx);
92
+ if (next<0) next = len;
93
+ part = id.slice(idx, next);
94
+ if (part==='..') parts.pop();
95
+ else if (part!=='.' && part!=='' && part!==null) parts.push(part);
96
+ // skip .., empty, and null.
97
+ idx = next+1;
98
+ }
99
+
100
+ id = parts.join('/');
101
+
102
+ // Make the path into a root relative path
103
+ if (useTilde) { id = packageName+'/~'+id; }
104
+
105
+ // else, just slice off beginning '/' if needed
106
+ } else if (id[0]==='/') id = id.slice(1);
107
+
108
+ // if we end up with no separators, make this a pkg
109
+ if (id.indexOf('/')<0) id = id+(_asPackage ? '/~package' : '/main');
110
+ // may need to walk if there is a separator...
111
+ if (id.indexOf('/')>0 || id.indexOf('.')>0) {
112
+ }
113
+ // slice separators off begin and end
114
+ if (id[0]==='/') id = id.slice(1);
115
+
116
+ // Convert root relative paths to normal paths where possible
117
+ if (contextPkg && contextPkg.directories) {
118
+ var libPath = contextPkg.directories['lib'];
119
+ if (libPath) { id = id.replace('~'+libPath.replace(/^\.?\//, '')+'/', ''); }
120
+ }
121
+
122
+ return remap(id, contextPkg);
123
+ }
124
+
125
+ function parseIdFormats(id, formats) {
126
+ // Handle requires with extension
127
+ var formatRE = new RegExp("^(.+)\\.("+formats.join('|')+")$"), match, format;
128
+ if (id.substring(0,5) !== "file:" && (match = id.match(formatRE))) {
129
+ id = match[1];
130
+ format = match[2];
131
+ formats = [format];
132
+ }
133
+
134
+ return {
135
+ id: id,
136
+ format: format,
137
+ formats: formats
138
+ };
139
+ }
140
+
141
+ // ..........................................................
142
+ // PLATFORM
143
+ //
144
+ // Detect important platform properties. Mostly for determining code
145
+ // that can't run one way or the other.
146
+ var SPADE_PLATFORM;
147
+ if (('undefined'===typeof ENV) || !ENV.SPADE_PLATFORM) {
148
+ SPADE_PLATFORM = { ENGINE: 'browser' };
149
+ } else {
150
+ SPADE_PLATFORM = ENV.SPADE_PLATFORM;
151
+ }
152
+
153
+ var LANG;
154
+ if ('undefined'!==typeof ENV) LANG = ENV.LANG;
155
+ if (!LANG && 'undefined'!==typeof navigator) LANG = navigator.language;
156
+ if (!LANG) LANG = 'en-US';
157
+
158
+
159
+ // ..........................................................
160
+ // Sandbox - you could make a secure version if you want...
161
+ //
162
+
163
+ // runs a factory within context and returns exports...
164
+ function execFactory(id, factory, sandbox, spade) {
165
+ var require, mod;
166
+
167
+ var pkg = spade.package(id),
168
+ filename = factory.filename,
169
+ format = factory.format,
170
+ ARGV = sandbox.ARGV,
171
+ ENV = sandbox.ENV,
172
+ fullId = id+'.'+format;
173
+
174
+ require = function(moduleId) {
175
+ return sandbox.require(moduleId, id, pkg);
176
+ };
177
+
178
+ // make the require 'object' have the same API as sandbox and spade.
179
+ require.require = require;
180
+
181
+ require.exists = function(moduleId) {
182
+ return sandbox.exists(normalize(moduleId, id, pkg));
183
+ };
184
+
185
+ require.normalize = function(moduleId) {
186
+ return normalize(moduleId, id, pkg);
187
+ };
188
+
189
+ require.async = function(moduleId, callback) {
190
+ return sandbox.async(normalize(moduleId, id, pkg), callback);
191
+ };
192
+
193
+ require.sandbox = function(name, isolate) {
194
+ return spade.sandbox(name, isolate);
195
+ };
196
+
197
+ require.url = function(moduleId, ext) {
198
+ return sandbox.url(normalize(moduleId, id, pkg), ext);
199
+ };
200
+
201
+ require.runCommand = function(command, args){
202
+ return sandbox.runCommand(command, args, id, pkg);
203
+ };
204
+
205
+ require.id = id; // so you can tell one require from another
206
+
207
+ sandbox._modules[id] = mod = {
208
+ id: id,
209
+ exports: {},
210
+ sandbox: sandbox
211
+ };
212
+
213
+ factory = factory.data; // extract the raw module body
214
+
215
+ // compile if needed - use cache so we only do it once per sandbox
216
+ if ('string' === typeof factory) {
217
+ if (sandbox._factories[fullId]) {
218
+ factory = sandbox._factories[fullId];
219
+ } else {
220
+ sandbox._loading[id] = sandbox._loading[fullId] = true;
221
+ factory = sandbox.compileFormat(factory, filename, format, pkg);
222
+ factory = sandbox.compilePreprocessors(factory, filename, pkg, id);
223
+ factory = sandbox.compile('(function(require, exports, __module, ARGV, ENV, __filename) {'+factory+';\n}) //@ sourceURL='+filename+'\n', filename);
224
+ sandbox._factories[fullId] = factory;
225
+ sandbox._loading[id] = sandbox._loading[fullId] = false;
226
+ }
227
+ }
228
+
229
+ if ('function' === typeof factory) {
230
+ var ret = factory(require, mod.exports, mod, ARGV, ENV, filename);
231
+ if (ret !== undefined) mod.exports = ret; // allow return exports
232
+ } else {
233
+ mod.exports = factory;
234
+ }
235
+
236
+ return mod.exports;
237
+ }
238
+
239
+ /**
240
+ @constructor
241
+
242
+ Sandbox provides an isolated context for loading and running modules.
243
+ You can create new sandboxes anytime you want. If you pass true for the
244
+ isolate flag, then the sandbox will be created in a separate context if
245
+ supported on the platform. Otherwise it will share globals with the
246
+ default sandbox context.
247
+
248
+ Note that isolated sandboxes are not the same as secure sandboxes. For
249
+ example in the browser, a isolated sandbox is created using an iframe
250
+ which still exposes access to the DOM and parent environment.
251
+
252
+ Isolated sandboxes are mostly useful for testing and sharing plugin code
253
+ that might want to use different versions of packages.
254
+
255
+ @param {Spade} spade
256
+ The spade instance
257
+
258
+ @param {Boolean} isolate
259
+ Set to true if you want to isolate it
260
+
261
+ @returns {Sandbox} instance
262
+ */
263
+ Sandbox = function(spade, name, isolate) {
264
+ if (typeof name !== 'string') {
265
+ isolate = name;
266
+ name = null;
267
+ }
268
+
269
+ if (!name) name = '(anonymous)';
270
+
271
+ this.spade = spade;
272
+ this.name = name;
273
+ this.isIsolated = !!isolate;
274
+ this._factories = {}; // compiled factories
275
+ this._loading = {}; // list of loading modules
276
+ this._modules = {}; // cached export results
277
+ this._used = {}; // to detect circular references
278
+ };
279
+
280
+ // alias this to help minifier make the page a big smaller.
281
+ Sp = Sandbox.prototype;
282
+
283
+ Sp.toString = function() {
284
+ return '[Sandbox '+this.name+']';
285
+ };
286
+
287
+ /**
288
+ Evaluate the passed string in the Sandbox context, returning the result.
289
+ This is the primitive used to compile string-encoded factories into
290
+ modules that can execute within a specific context.
291
+ */
292
+ Sp.compile = function(code, filename) {
293
+ if (this.isDestroyed) throw new Error("Sandbox destroyed");
294
+ if (!this._compilerInited) {
295
+ this._compilerInited = true;
296
+ this.spade.compiler.setup(this);
297
+ }
298
+ return this.spade.compiler.compile(code, this, filename);
299
+ };
300
+
301
+ function findPlugins(sandbox, pkg, type, key, single) {
302
+ var plugins = [], ret = [], found;
303
+
304
+ found = pkg && pkg['plugin:'+type];
305
+ if (key) { found = found && found[key]; }
306
+ if (found) {
307
+ if (single) { return found; }
308
+ plugins = plugins.concat(found);
309
+ }
310
+
311
+ // look in immediate dependencies only, if we wanted to have formats we would have required them
312
+ var deps = pkg && pkg.dependencies;
313
+ if (deps) {
314
+ for(var packageId in deps) {
315
+ pkg = sandbox.spade.package(packageId);
316
+ found = pkg && pkg['plugin:'+type];
317
+ if (key) { found = found && found[key]; }
318
+ if (found) plugins = plugins.concat(found);
319
+ }
320
+ }
321
+
322
+ // Remove duplicates
323
+ for (var i=0,len=plugins.length; i < len; i++) {
324
+ if (ret.indexOf(plugins[i]) < 0) { ret.push(plugins[i]); }
325
+ }
326
+
327
+ if (single) {
328
+ if (ret.length > 1) { console.warn("Found multiple plugins when expecting one for type: '"+type+"' and key: '"+key+"'"); }
329
+ return ret[0] || null;
330
+ } else {
331
+ return ret;
332
+ }
333
+ }
334
+
335
+ Sp.compileFormat = function(code, filename, format, pkg) {
336
+ var plugin = findPlugins(this, pkg, 'formats', format, true);
337
+ if (plugin) { plugin = this.require(plugin); }
338
+ return plugin ? plugin.compileFormat(code, this, filename, format, pkg) : code;
339
+ };
340
+
341
+ Sp.compilePreprocessors = function(code, filename, pkg, id) {
342
+ var plugins = findPlugins(this, pkg, 'preprocessors'), plugin;
343
+ for (var i=0, len=plugins.length; i < len; i++) {
344
+ // Avoid trying to process self
345
+ if (plugins[i] === id) { continue; }
346
+ plugin = this.require(plugins[i]);
347
+ if (!this._loading[id] || plugin.compilePreprocessor) {
348
+ code = plugin.compilePreprocessor(code, this, filename, pkg);
349
+ }
350
+ }
351
+ return code;
352
+ };
353
+
354
+ /**
355
+ Sandbox-specific require. This is actually the most primitive form of
356
+ require.
357
+ */
358
+ Sp.require = function(id, callingId) {
359
+ var pkg = callingId ? this.spade.package(callingId) : null;
360
+ id = normalize(id, callingId, pkg);
361
+
362
+ var ret = this._modules[id];
363
+ if (ret) ret = ret.exports;
364
+
365
+ if (ret) {
366
+ if (!this._used[id]) this._used[id] = ret;
367
+ return ret ;
368
+
369
+ } else {
370
+ var factory = this.spade.loadFactory(this.spade.resolve(id, this));
371
+ if (!factory) throw new Error('Module '+id+' not found');
372
+
373
+ var spade = this.spade;
374
+ if (!this.ENV) this.ENV = spade.env(); // get at the last minute
375
+ if (!this.ARGV) this.ARGV = spade.argv();
376
+
377
+ ret = execFactory(id, factory, this, spade);
378
+
379
+ // detect circular references...
380
+ if (this._used[id] && (this._used[id] !== ret)) {
381
+ throw new Error("Circular require detected for module "+id);
382
+ }
383
+ }
384
+
385
+ return ret ;
386
+ };
387
+
388
+ /**
389
+ Sandbox-specific test to determine if the named module exists or not.
390
+ This property only reflects what is immediately available through the
391
+ sync-loader. Using the async loader may change the return value of this
392
+ call.
393
+ */
394
+ Sp.exists = function(id, callingId) {
395
+ var pkg = callingId ? this.spade.package(callingId) : null;
396
+ id = normalize(id, callingId, pkg);
397
+ if (this._modules[id]) return true;
398
+ return this.spade.factoryExists(this.spade.resolve(id, this));
399
+ };
400
+
401
+ /**
402
+ Sandbox-specific async load. This is actually the most primitive form of
403
+ require.
404
+ */
405
+ Sp.async = function(id, callback, callingId) {
406
+ var spade = this.spade, pkg;
407
+
408
+ pkg = callingId ? this.spade.package(callingId) : null;
409
+ id = spade.resolve(normalize(id, callingId, pkg), this);
410
+ return spade.loadFactory(id, callback);
411
+ };
412
+
413
+ Sp.url = function(id, ext, callingId) {
414
+ var ret, pkg;
415
+
416
+ pkg = callingId ? this.spade.package(callingId) : null;
417
+ id = normalize(id, callingId, pkg);
418
+
419
+ pkg = this.spade.package(id);
420
+ if (!pkg) {
421
+ var packageId = packageIdFor(id)+'/~package';
422
+ if (this.spade.exists(packageId)) this.spade.require(packageId);
423
+ pkg = this.spade.package(id);
424
+ }
425
+
426
+ if (!pkg) {
427
+ throw new Error("Can't get url for non-existent package "+id);
428
+ }
429
+
430
+ if (!pkg.root) {
431
+ throw new Error('Package for '+id+' does not support urls');
432
+ }
433
+
434
+ ret = pkg.root + id.slice(id.indexOf('/'));
435
+ if (ext) ret = ret+'.'+ext;
436
+ return ret ;
437
+ };
438
+
439
+ Sp.runCommand = function(command, args, callerId, pkg){
440
+ var xhr = new XMLHttpRequest(),
441
+ url = "_spade/command",
442
+ params = [];
443
+
444
+ args.command = command;
445
+ args.callerId = callerId;
446
+ args.pkgRoot = pkg && pkg.root;
447
+
448
+ for (var arg in args){
449
+ params.push(encodeURIComponent(arg)+"="+encodeURIComponent(args[arg]));
450
+ }
451
+ params = params.join('&');
452
+
453
+ xhr.open('POST', url, false);
454
+
455
+ try {
456
+ xhr.send(params);
457
+ } catch(e) {
458
+ throw new Error('unable to run command `'+command+'`, most likely either this browser or server does not support this');
459
+ }
460
+
461
+ if (xhr.status === 200) {
462
+ return xhr.responseText;
463
+ // Safari returns -1100 for file:// not found
464
+ } else if (xhr.status === 404 || xhr.status === -1100) {
465
+ throw new Error('unable to run command `'+command+'`, most likely you are running without a supported server');
466
+ } else {
467
+ throw new Error('running command `'+command+'` return status '+xhr.status+': '+xhr.responseText);
468
+ }
469
+ };
470
+
471
+ Sp.isDestroyed = false;
472
+
473
+ Sp.destroy = function() {
474
+ if (!this.isDestroyed) {
475
+ this.isDestroyed = true;
476
+ this.spade.compiler.teardown(this);
477
+ }
478
+ return this;
479
+ };
480
+
481
+ // ..........................................................
482
+ // LOADER
483
+ //
484
+
485
+ /**
486
+ The built-in loader object knows how to load whole packages as long as
487
+ you have registered an external reference to the package. This is pkg
488
+ info that contains:
489
+
490
+ {
491
+ extern: true, // this is not a real package yet
492
+ src: 'http://example.com/bar', // URL to load
493
+ expects: ['foo', 'bar', 'baz'] // optional modules to expect
494
+ }
495
+ */
496
+ Loader = function() {
497
+ this._loading = {};
498
+ };
499
+
500
+ Lp = Loader.prototype;
501
+
502
+ function syncLoad(spade, id, url, format, force) {
503
+ if (force) url = url+'?'+Date.now();
504
+
505
+ var xhr = new XMLHttpRequest();
506
+ xhr.open('GET', url, false);
507
+ try {
508
+ xhr.send(null);
509
+ } catch(e) {
510
+ throw new Error('unable to fetch '+url+', most likely this browser is not supported');
511
+ }
512
+
513
+ // successful file:// requests return 0 in Safari and Firefox
514
+ if (xhr.status === 200 || xhr.status === 0) {
515
+ var body = xhr.responseText;
516
+ if (body.slice(0,2) === '#!') body = body.slice(body.indexOf('\n'));
517
+
518
+ if (!format) format = 'js';
519
+ spade.register(id, body, { format: format });
520
+ return true;
521
+
522
+ // Safari returns -1100 for file:// not found
523
+ } else if (xhr.status === 404 || xhr.status === -1100) {
524
+ return false;
525
+
526
+ } else {
527
+ throw new Error('fetching '+url+' return status '+xhr.status+': '+xhr.responseText);
528
+ }
529
+ }
530
+
531
+ function resolveUrl(id, extern) {
532
+ var dirname, idx,
533
+ packageUrl = extern.root,
534
+ url;
535
+
536
+ id = normalize(id);
537
+ id = id.slice(id.indexOf('/')+1); // slide off pkg
538
+
539
+ // get directory
540
+ if (id[0]==='~') {
541
+ idx = id.indexOf('/');
542
+ dirname = idx>=0 ? id.slice(0, idx) : id;
543
+ id = dirname.length>=id.length ? null : id.slice(dirname.length+1);
544
+ dirname = dirname.slice(1); // get rid of ~
545
+ } else dirname = 'lib';
546
+
547
+ // map to directories
548
+ if (extern.directories && extern.directories[dirname]) {
549
+ dirname = extern.directories[dirname];
550
+ }
551
+
552
+ // combine elements to form URL
553
+ url = packageUrl;
554
+ if (url === '.') url = null;
555
+ if (dirname && dirname !== '.') url = url ? url+'/'+dirname : dirname;
556
+ if (id && id !== '.') url = url ? url+'/'+id : id;
557
+
558
+ // Clean up '.' and '..'
559
+ var parts = url.split('/');
560
+ for(var i=0,len=parts.length; i<len;) {
561
+ if (parts[i] === '.') {
562
+ parts = parts.slice(0,i).concat(parts.slice(i+1)); // Remove item
563
+ // Retry at same index
564
+ } else if (parts[i] === '..') {
565
+ if (i === 0) {
566
+ console.warn("Can't resolve leading '..'");
567
+ i++; // Go to next
568
+ } else {
569
+ parts = parts.slice(0,i-1).concat(parts.slice(i+1)); // Remove previous and current item
570
+ i--; // Retry at previous index
571
+ }
572
+ } else {
573
+ i++; // Go to next
574
+ }
575
+ }
576
+ url = parts.join('/');
577
+
578
+ return url;
579
+ }
580
+ Lp.resolveUrl = resolveUrl;
581
+
582
+ function syncLoadFormats(spade, id, extern, formats) {
583
+ var packageUrl = extern.root,
584
+ url = resolveUrl(id, extern);
585
+
586
+ // If we know about a file we want to try to load it first
587
+ if (extern.files) {
588
+ var relativeUrl = url.replace(packageUrl+'/', '');
589
+ for (var i=0,len=formats.length; i<len; i++) {
590
+ // See if we know about a match
591
+ if (extern.files.indexOf(relativeUrl+'.'+formats[i]) > -1) {
592
+ // Move format to the front
593
+ formats = [formats[i]].concat(formats.slice(0,i)).concat(formats.slice(i+1));
594
+ break;
595
+ }
596
+ }
597
+ }
598
+
599
+ function tryFormat(format) {
600
+ return syncLoad(spade, id, url+'.'+format, format, true);
601
+ }
602
+
603
+ return !!formats.some(tryFormat);
604
+ }
605
+
606
+ function verifyInBrowser(id, done) {
607
+ if ('undefined'===typeof document) {
608
+ var err = new Error("Cannot load package "+id+" outside of browser");
609
+ if (done) done(err);
610
+ else throw err;
611
+ return false;
612
+ }
613
+
614
+ return true;
615
+ }
616
+
617
+ Lp.loadFactory = function(spade, id, formats, done) {
618
+
619
+ var url, loaded, packageId,
620
+ extern = spade.package(id), that = this;
621
+
622
+ // loader only works for sync requests if the package info permits sync
623
+ // loading. In production mode, normally it should not.
624
+ if (!done && (!extern || !extern.sync)) return this;
625
+
626
+ // this loader only works in the browser
627
+ if (!verifyInBrowser(id, done)) return this;
628
+
629
+ if (!done) {
630
+ url = extern.src;
631
+ if (!url) loaded = syncLoadFormats(spade, id, extern, formats);
632
+ else loaded = syncLoad(spade, id, url);
633
+ if (!loaded) throw new Error('fetching '+id+' not found');
634
+ // not actually loadable
635
+ } else if (!extern || !extern.extern) {
636
+ done(new Error('Module '+id+' not found'));
637
+
638
+ } else {
639
+
640
+ // now do actual load of src
641
+ if (!extern.src) {
642
+ throw new Error("Cannot load package "+id+" without a src URL");
643
+ }
644
+
645
+ // if already loading, just add to queue
646
+ packageId = packageIdFor(normalize(id));
647
+ if (this._loading[packageId]) {
648
+ this._loading[packageId].push(done);
649
+ } else {
650
+ this._loading[packageId] = [done];
651
+ this.loadURL(extern.src, function() { that.didLoad(packageId); });
652
+ // TODO: Load dependencies
653
+ }
654
+ }
655
+ return this;
656
+ };
657
+
658
+ Lp.exists = function(spade, id, formats) {
659
+
660
+ var extern = spade.package(id);
661
+
662
+ // loader only works for sync requests if the package info permits sync
663
+ // loading. In production mode, normally it should not.
664
+ if (!extern || !extern.sync || !extern.root) return false;
665
+
666
+ // this loader only works in the browser
667
+ if (!verifyInBrowser(id)) return false;
668
+ return syncLoadFormats(spade, id, extern, formats, true);
669
+ };
670
+
671
+ Lp.didLoad = function(packageId) {
672
+ // TODO: verify/load dependencies
673
+ var callbacks = this._loading[packageId];
674
+ delete this._loading[packageId];
675
+ if (callbacks) callbacks.forEach(function(done) { done(); });
676
+ };
677
+
678
+ // actually create a script tag and load it
679
+ Lp.loadURL = function(url, callback) {
680
+ var el, head;
681
+
682
+ el = document.createElement('script');
683
+ el.src = url;
684
+ el.type = 'text/javascript';
685
+ el.onload = callback;
686
+
687
+ head = document.head || document.body;
688
+ head.appendChild(el);
689
+ head = el = null;
690
+ };
691
+
692
+ // NOTE: On ready stuff mostly stolen from jQuery 1.4. Need to incl here
693
+ // because spade will often be used to load jQuery.
694
+ // Will only be invoked once. Just be prepared to call it
695
+ Lp.scheduleReady = function(callback) {
696
+
697
+ // handle case where ready is invoked AFTER the document is already ready
698
+ if ( document.readyState === "complete" ) return setTimeout(callback, 1);
699
+
700
+ var handler, handled = false;
701
+
702
+ // The DOM ready check for Internet Explorer
703
+ function doScrollCheck() {
704
+ if (handled) return;
705
+
706
+ try {
707
+ // If IE is used, use the trick by Diego Perini
708
+ // http://javascript.nwbox.com/IEContentLoaded/
709
+ document.documentElement.doScroll("left");
710
+ } catch(e) {
711
+ setTimeout( doScrollCheck, 1 );
712
+ return;
713
+ }
714
+
715
+ // and execute any waiting functions
716
+ handler();
717
+ }
718
+
719
+ // Mozilla, Opera and webkit nightlies currently support this event
720
+ if (document.addEventListener) {
721
+
722
+ handler = function() {
723
+ if (handled) return;
724
+ handled = true;
725
+ document.removeEventListener("DOMContentLoaded", handler, false);
726
+ window.removeEventListener('load', handler, false);
727
+ callback();
728
+ };
729
+
730
+ document.addEventListener( "DOMContentLoaded", handler, false);
731
+
732
+ // A fallback to window.onload, that will always work
733
+ window.addEventListener( "load", handler, false );
734
+
735
+ // If IE event model is used
736
+ } else if ( document.attachEvent ) {
737
+
738
+ handler = function() {
739
+ if (!handled && document.readyState === "complete") {
740
+ handled = true;
741
+ document.detachEvent( "onreadystatechange", handler );
742
+ window.detachEvent('onload', handler);
743
+ callback();
744
+ }
745
+ };
746
+
747
+ // ensure firing before onload,
748
+ // maybe late but safe also for iframes
749
+ document.attachEvent("onreadystatechange", handler);
750
+
751
+ // A fallback to window.onload, that will always work
752
+ window.attachEvent( "onload", handler);
753
+
754
+ // If IE and not a frame
755
+ // continually check to see if the document is ready
756
+ var toplevel = false;
757
+
758
+ try {
759
+ toplevel = window.frameElement === null;
760
+ } catch(e) {}
761
+ if ( document.documentElement.doScroll && toplevel ) doScrollCheck();
762
+ }
763
+ };
764
+
765
+ // ..........................................................
766
+ // Compiler Class
767
+ //
768
+
769
+ Compiler = function() {};
770
+ Cp = Compiler.prototype;
771
+
772
+ Cp.setup = function(sandbox) {
773
+ if (sandbox.isIsolated) throw new Error("Isolated Sandbox not supported");
774
+ };
775
+
776
+ Cp.compile = function(text, sandbox, filename) {
777
+ return eval(text);
778
+ };
779
+
780
+ Cp.teardown = function(sandbox) {
781
+ // noop by default
782
+ };
783
+
784
+ // ..........................................................
785
+ // Spade Class - defined so we can recreate
786
+ //
787
+
788
+ Spade = function() {
789
+ this.loader = new this.Loader(this);
790
+ this.compiler = new this.Compiler(this);
791
+ this.defaultSandbox = this.sandbox();
792
+ this._factories = {};
793
+ this._packages = {};
794
+
795
+ // register this instance as the result of the spade package.
796
+ var inst = this;
797
+ this.register('spade', { "name": "spade", "version": this.VERSION });
798
+ this.register('spade/main', function(r, e, m) { m.exports = inst; });
799
+ };
800
+
801
+ Tp = Spade.prototype;
802
+
803
+ Tp.VERSION = "0.1.0";
804
+
805
+ // expose the classes. We do it this way so that you can create a new
806
+ // Spade instance and treat it like the spade module
807
+ Tp.Spade = Spade;
808
+ Tp.Sandbox = Sandbox;
809
+ Tp.Loader = Loader;
810
+ Tp.Compiler = Compiler;
811
+
812
+ Tp.env = function() {
813
+ if (!this.ENV) this.ENV = 'undefined' !== typeof ENV ? ENV : {};
814
+ if (!this.ENV.SPADE_PLATFORM) this.ENV.SPADE_PLATFORM = SPADE_PLATFORM;
815
+ if (!this.ENV.LANG) this.ENV.LANG = LANG;
816
+ return this.ENV;
817
+ };
818
+
819
+ Tp.argv = function() {
820
+ if (!this.ARGV) this.ARGV = 'undefined' !== typeof ARGV ? ARGV : [];
821
+ return this.ARGV;
822
+ };
823
+
824
+ /**
825
+ Expose the spade require methods to the global context. This should allow
826
+ your global code to access spade in the same way that normal modules
827
+ would.
828
+ */
829
+ Tp.globalize = function() {
830
+ var spade = this;
831
+
832
+ // save old info for conflict...
833
+ this._conflict = {
834
+ require: 'undefined' !== typeof require ? require : undefined,
835
+ ENV: 'undefined' !== typeof ENV ? ENV : undefined,
836
+ ARGV: 'undefined' !== typeof ARGV ? ARGV : undefined
837
+ };
838
+
839
+ require = function() { return spade.require.apply(spade,arguments); };
840
+ ['async', 'sandbox', 'exists', 'url'].forEach(function(key) {
841
+ require[key] = function() { return spade[key].apply(spade, arguments);};
842
+ });
843
+
844
+ ENV = this.env();
845
+ ARGV = this.argv();
846
+ return this;
847
+ };
848
+
849
+ /**
850
+ Restores original values after a call to globalize(). If you call this
851
+ method more than once it will have no effect.
852
+ */
853
+ Tp.noConflict = function() {
854
+ var c = this._conflict;
855
+ if (c) {
856
+ delete this._conflict;
857
+ require = c.require;
858
+ ENV = c.ENV;
859
+ ARGV = c.ARGV;
860
+ }
861
+ return this;
862
+ };
863
+
864
+ /**
865
+ Returns a new sandbox instance attached to the current spade instance.
866
+ Can isolate if preferred.
867
+
868
+ @param {Boolean} isolate
869
+ true if you want the sandbox to be isolated. Throws exception if
870
+ platform cannot isolate.
871
+
872
+ @returns {Sandbox} sandbox instance
873
+ */
874
+ Tp.sandbox = function(name, isolate) {
875
+ return new this.Sandbox(this, name, isolate);
876
+ };
877
+
878
+ /**
879
+ Register a module or package information. You can pass one of the
880
+ following:
881
+
882
+ 'module/id', 'module body string'
883
+ 'module/id', function() { module func }
884
+ 'module/id', { exports: 'foo' }
885
+ 'module/id' - just register module id and no body to indicate presence
886
+
887
+ Note also that if you pass just a packageId, it will be normalized to
888
+ packageId/~package. This is how you register a package.
889
+
890
+ @param {String} id
891
+ The module or package id
892
+
893
+ @param {String|Function|Hash} data
894
+ A module function, module body (as string), or hash of exports to use.
895
+
896
+ @param {String} opts
897
+ Additional metadata only if you are registering a module factory. Known
898
+ keys include 'filename' and 'format' (for compilation of DSLs).
899
+
900
+ */
901
+ Tp.register = function(id, data, opts) {
902
+ if (!data) data = K ;
903
+ var t = typeof data, isExtern, factory, isPkg;
904
+
905
+ id = normalize(id, null, null, true);
906
+ isPkg = id.slice(-9) === '/~package';
907
+
908
+ // register - note packages can only accept hashes
909
+ if (isPkg && 'object'!==typeof data) {
910
+ throw new Error('You can only register hashes for packages');
911
+ }
912
+
913
+ factory = { data: data };
914
+ factory.filename = opts && opts.filename ? opts.filename : id;
915
+ factory.format = opts && opts.format ? opts.format : 'js';
916
+
917
+ // Store with generic id if none, or if JS
918
+ if (!this._factories[id] || factory.format === 'js') {
919
+ this._factories[id] = factory;
920
+ }
921
+
922
+ // Store with format
923
+ if (!isPkg) {
924
+ this._factories[id+'.'+factory.format] = factory;
925
+ }
926
+
927
+ return this;
928
+ };
929
+
930
+ /**
931
+ Efficient way to register external packages. Pass a hash of packageIds
932
+ and source URLs. If the package is already registered, the extern will
933
+ not replace it so this is safe to call multiple times.
934
+ */
935
+ Tp.externs = function(externs, extern) {
936
+ var tmp, packages = this._packages;
937
+
938
+ // normalize method call.
939
+ if ('string' === typeof externs) {
940
+ tmp = {};
941
+ tmp[externs] = extern;
942
+ externs = tmp;
943
+ extern = null;
944
+ }
945
+
946
+ for(var packageId in externs) {
947
+ if (!externs.hasOwnProperty(packageId)) continue;
948
+ if (packages[packageId] && !packages[packageId].extern) continue;
949
+
950
+ extern = externs[packageId];
951
+ if ('string' === typeof extern) extern = {name: packageId, src: extern};
952
+ extern.extern = true;
953
+ this.register(packageId, extern);
954
+ }
955
+ };
956
+
957
+ /**
958
+ Require a module from the default sandbox.
959
+
960
+ @param {String} id
961
+ The module id.
962
+
963
+ @returns {Hash} module exports
964
+ */
965
+ Tp.require = function(id) {
966
+ return this.defaultSandbox.require(id, this.defaultSandbox.callerId);
967
+ };
968
+
969
+
970
+ /**
971
+ Async load a module if it is not already a registered factory. Invoke
972
+ the passed callback with an optional error object when the module is
973
+ ready to load.
974
+ */
975
+ Tp.async = function(id, callback) {
976
+ return this.defaultSandbox.async(id, callback);
977
+ };
978
+
979
+ Tp.exists = function(id) {
980
+ return this.defaultSandbox.exists(id);
981
+ };
982
+
983
+ Tp.url = function(id, ext) {
984
+ return this.defaultSandbox.url(id, ext);
985
+ };
986
+
987
+ function _collectFormats(spade, ret, pkg) {
988
+
989
+ function extractFormats(formats) {
990
+ if (formats) {
991
+ Object.keys(formats).forEach(function(key) {
992
+ if (ret.indexOf(key)<0) ret.unshift(key); // new formats go first
993
+ });
994
+ }
995
+ }
996
+
997
+ extractFormats(pkg['plugin:formats']);
998
+
999
+ var deps = pkg.dependencies;
1000
+ if (!deps) return ret;
1001
+
1002
+ for(var packageId in deps) {
1003
+ pkg = spade.package(packageId);
1004
+ if (pkg) extractFormats(pkg['plugin:formats']);
1005
+ }
1006
+
1007
+ return ret ;
1008
+ }
1009
+
1010
+ /**
1011
+ Called by the sandbox to get a factory object for the named moduleId
1012
+ */
1013
+ Tp.loadFactory = function(id, callback) {
1014
+ var pkg, formats, format, data, ret;
1015
+
1016
+ // find any formats the current package might know about. Note that for
1017
+ // lazy-loaders this may not be entirely up to date (since not all pkgs
1018
+ // are registered right away)
1019
+ pkg = this.package(id);
1020
+ formats = pkg ? _collectFormats(this, ['js'], pkg) : ['js'];
1021
+
1022
+ data = parseIdFormats(id, formats);
1023
+ id = data.id;
1024
+ format = data.format;
1025
+ formats = data.formats;
1026
+
1027
+ ret = format ? this._factories[id+'.'+format] : this._factories[id];
1028
+
1029
+ if (callback) {
1030
+ if (!ret) {
1031
+ if (this.loader && this.loader.loadFactory) {
1032
+ this.loader.loadFactory(this, id, formats, callback);
1033
+ } else callback(new Error('Module '+id+' not found'));
1034
+ } else callback();
1035
+
1036
+ } else if (!ret && this.loader && this.loader.loadFactory) {
1037
+ this.loader.loadFactory(this, id, formats);
1038
+ ret = format ? this._factories[id+'.'+format] : this._factories[id];
1039
+ }
1040
+
1041
+ return ret ;
1042
+ };
1043
+
1044
+ /**
1045
+ Called by the sandbox to determine if the named id exists on the system.
1046
+ The id should already be normalized. If the id is not yet registered, the
1047
+ loader will also be consulted.
1048
+ */
1049
+ Tp.factoryExists = function(id) {
1050
+ if (this._factories[id]) return true;
1051
+ if (!this.loader || !this.loader.exists) return false;
1052
+
1053
+ var pkg = this.package(id),
1054
+ formats = pkg ? _collectFormats(this, ['js'], pkg) : ['js'],
1055
+ data = parseIdFormats(id, formats);
1056
+
1057
+ return this.loader.exists(this, data.id, data.formats);
1058
+ };
1059
+
1060
+ /**
1061
+ Returns the package info, if any, for the named module or packageId
1062
+ */
1063
+ Tp.package = function(id) {
1064
+ id = packageIdFor(normalize(id))+'/~package';
1065
+ var ret = this._factories[id];
1066
+ return ret ? ret.data : null;
1067
+ };
1068
+
1069
+ /**
1070
+ Normalize a moduleId, expanding it if needed.
1071
+ */
1072
+ Tp.normalize = function(id, contextId) {
1073
+ return normalize(id, contextId);
1074
+ };
1075
+
1076
+ // maps the passed ID to a potentially location specific ID. This gives
1077
+ // the loader a way to vary the factory function returned for a given id
1078
+ // per sandbox
1079
+ Tp.resolve = function(id, sandbox) {
1080
+ if (sandbox && this.loader && this.loader.resolve) {
1081
+ return this.loader.resolve(id, sandbox);
1082
+ } else return id;
1083
+ };
1084
+
1085
+ // uses the loader to invoke when the app is ready. For the browser this
1086
+ // is on the ready event.
1087
+ Tp.ready = function(callback) {
1088
+ switch(this.readyState) {
1089
+ case 'ready':
1090
+ callback();
1091
+ break;
1092
+
1093
+ case 'scheduled':
1094
+ this._readyQueue.push(callback);
1095
+ break;
1096
+
1097
+ default:
1098
+ this._readyQueue = [callback];
1099
+ this.readyState = 'scheduled';
1100
+ if (this.loader && this.loader.scheduleReady) {
1101
+ var that = this;
1102
+ this.loader.scheduleReady(function() {
1103
+ var queue = that._readyQueue, len = queue ? queue.length : 0;
1104
+ that._readyQueue = null;
1105
+ that.readyState = 'ready';
1106
+ for(var idx=0;idx<len;idx++) queue[idx]();
1107
+ });
1108
+
1109
+ } else {
1110
+ throw new Error('Loader does not support activate on ready state');
1111
+ }
1112
+ }
1113
+ };
1114
+
1115
+ // instantiate spade and also attach class for testing
1116
+ spade = new Spade();
1117
+
1118
+ // in the browser - if ENV and ARGS are not defined, just create some
1119
+ // reasonable defaults. We assume that when loading strobe from the CLI
1120
+ // these will already be setup.
1121
+ if (SPADE_PLATFORM.engine === 'browser') spade.globalize();
1122
+
1123
+ // make this work when called as a module
1124
+ if ('undefined' !== typeof require) {
1125
+ if ('undefined' !== typeof __module) __module.exports = spade;
1126
+ else if ('undefined' !== typeof module) module.exports = spade;
1127
+ }
1128
+
1129
+ })();
1130
+
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib/', __FILE__)
3
+ $:.unshift lib unless $:.include?(lib)
4
+
5
+ require 'spade/core/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "spade-core"
9
+ s.version = Spade::Core::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ["Charles Jolley", "Peter Wagenet"]
12
+ s.email = ["charles@sproutcore.com", "peterw@strobecorp.com"]
13
+ s.homepage = "http://github.com/strobecorp/spade-runtime"
14
+ s.summary = s.description = "Core Libraries for Spade Package Manager and Runtime"
15
+
16
+ s.add_development_dependency "rspec"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+
21
+ s.require_paths = ["lib"]
22
+ end
23
+
24
+
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spade-core
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Charles Jolley
9
+ - Peter Wagenet
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2011-06-13 00:00:00 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rspec
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :development
26
+ version_requirements: *id001
27
+ description: Core Libraries for Spade Package Manager and Runtime
28
+ email:
29
+ - charles@sproutcore.com
30
+ - peterw@strobecorp.com
31
+ executables: []
32
+
33
+ extensions: []
34
+
35
+ extra_rdoc_files: []
36
+
37
+ files:
38
+ - lib/spade/core.rb
39
+ - lib/spade/core/version.rb
40
+ - lib/spade/js/spade.js
41
+ - spade-core.gemspec
42
+ homepage: http://github.com/strobecorp/spade-runtime
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ requirements: []
63
+
64
+ rubyforge_project:
65
+ rubygems_version: 1.8.5
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: Core Libraries for Spade Package Manager and Runtime
69
+ test_files: []
70
+