Martin Thorsen Ranang avatar Martin Thorsen Ranang committed 8a9e51f

Comments (0)

Files changed (54)

daemon/orbited/jsio/base.js

 exports.log = jsio.__env.log;
 exports.GLOBAL = jsio.__env.global;
 
+var SLICE = Array.prototype.slice;
+
+exports.isArray = function(obj) {
+	return Object.prototype.toString.call(obj) === '[object Array]';
+}
+
 exports.bind = function(context, method /*, VARGS*/) {
 	if(arguments.length > 2) {
-		var args = Array.prototype.slice.call(arguments, 2);
+		var args = SLICE.call(arguments, 2);
 		return typeof method == 'string'
-			? function() {
+			? function __bound() {
 				if (context[method]) {
-					return context[method].apply(context, args.concat(Array.prototype.slice.call(arguments, 0)));
+					return context[method].apply(context, args.concat(SLICE.call(arguments, 0)));
 				} else {
 					throw logger.error('No method:', method, 'for context', context);
 				}
 			}
-			: function() { return method.apply(context, args.concat(Array.prototype.slice.call(arguments, 0))); }
+			: function __bound() { return method.apply(context, args.concat(SLICE.call(arguments, 0))); }
 	} else {
 		return typeof method == 'string'
-			? function() {
+			? function __bound() {
 				if (context[method]) {
 					return context[method].apply(context, arguments);
 				} else {
 					throw logger.error('No method:', method, 'for context', context);
 				}
 			}
-			: function() { return method.apply(context, arguments); }
+			: function __bound() { return method.apply(context, arguments); }
 	}
 }
 
 exports.Class = function(parent, proto) {
-	if(!parent) { throw new Error('parent or prototype not provided'); }
-	if(!proto) { proto = parent; parent = null; }
-	else if(parent instanceof Array) { // multiple inheritance, use at your own risk =)
+	if (typeof parent == 'string') {
+		var name = arguments[0],
+			parent = arguments[1],
+			proto = arguments[2],
+			logger = exports.logging.get(name);
+	}
+	
+	if (!parent) { throw new Error('parent or prototype not provided'); }
+	if (!proto) { proto = parent; parent = null; }
+	else if (exports.isArray(parent)) { // multiple inheritance, use at your own risk =)
 		proto.prototype = {};
 		for(var i = 0, p; p = parent[i]; ++i) {
-			for(var item in p.prototype) {
-				if(!(item in proto.prototype)) {
+			if (p == Error && ErrorParentClass) { p = ErrorParentClass; }
+			for (var item in p.prototype) {
+				if (!(item in proto.prototype)) {
 					proto.prototype[item] = p.prototype[item];
 				}
 			}
 		}
 		parent = parent[0]; 
 	} else {
+		if (parent == Error && ErrorParentClass) { parent = ErrorParentClass; }
 		proto.prototype = parent.prototype;
 	}
-
-	var cls = function() { if(this.init) { return this.init.apply(this, arguments); }}
-	cls.prototype = new proto(parent ? function(context, method, args) {
-		var args = args || [];
-		var target = proto;
-		while(target = target.prototype) {
-			if(target[method]) {
-				return target[method].apply(context, args);
-			}
-		}
-		throw new Error('method ' + method + ' does not exist');
-	} : null);
+	
+	var cls = function() { if (this.init) { return this.init.apply(this, arguments); }},
+		supr = parent ? function(context, method, args) {
+			var f = parent.prototype[method];
+			if (!f) { throw new Error('method ' + method + ' does not exist'); }
+			return f.apply(context, args || []);
+		} : null;
+	
+	cls.prototype = new proto(logger || supr, logger && supr);
 	cls.prototype.constructor = cls;
+	cls.prototype.__parentClass__ = parent;
+	if (name) { cls.prototype.__class__ = name; }
 	return cls;
 }
 
-exports.$setTimeout = function(f, t/*, VARGS */) {
-	var args = Array.prototype.slice.call(arguments, 2);
-	return setTimeout(function() {
-		try {
-			f.apply(this, args);
-		} catch(e) {
-			// log?
+var ErrorParentClass = exports.Class(Error, function() {
+	this.init = function() {
+		var err = Error.prototype.constructor.apply(this, arguments);
+		for (var prop in err) {
+			if (err.hasOwnProperty(prop)) {
+				this[prop] = err[prop];
+			}
 		}
-	}, t)
+	}
+});
+
+exports.Class.defaults = 
+exports.merge = function(base, extra) {
+	base = base || {};
+	
+	for (var i = 1, len = arguments.length; i < len; ++i) {
+		var copyFrom = arguments[i];
+		for (var key in copyFrom) {
+			if (copyFrom.hasOwnProperty(key) && !base.hasOwnProperty(key)) {
+				base[key] = copyFrom[key];
+			}
+		}
+	}
+	
+	return base;
 }
 
-exports.$setInterval = function(f, t/*, VARGS */) {
-	var args = Array.prototype.slice.call(arguments, 2);
-	return setInterval(function() {
-		try {
-			f.apply(this, args);
-		} catch(e) {
-			// log?
+exports.Class.ctor = function(proto, supr, defaults, post) {
+	if (!supr) {
+		supr = function(ctx, method, args) {
+			ctx._opts = args[0];
 		}
-	}, t)
+	}
+
+	if (post) {
+		proto.init = function(opts) {
+			supr(this, 'init', [opts = exports.merge(opts, defaults)]);
+			post.apply(this, [opts].concat(SLICE.call(arguments, 1)));
+		}
+	} else {
+		proto.init = function(opts) {
+			supr(this, 'init', [exports.merge(opts, defaults)]);
+		}
+	}
 }
 
-// node doesn't let you call clearTimeout(null)
-exports.$clearTimeout = function (timer) { return timer ? clearTimeout(timer) : null; };
-exports.$clearInterval = function (timer) { return timer ? clearInterval(timer) : null; };
-
 // keep logging local variables out of other closures in this file!
 exports.logging = (function() {
 	
 		},
 		loggers = {}, // effectively globals - all loggers and a global production state
 		production = false;
-
+	var gPrefix = '';
+	logging.setPrefix = function(prefix) { gPrefix = prefix + ' '; }
 	logging.setProduction = function(prod) { production = !!prod; }
 	logging.get = function(name) {
 		return loggers.hasOwnProperty(name) ? loggers[name]
 		
 		this.setLevel = function(level) { this._level = level; }
 	
-		var SLICE = Array.prototype.slice;
-		
 		function makeLogFunction(level, type) {
 			return function() {
 				if (!production && level >= this._level) {
-					return this._listener.apply(this._listener, [type, this._name].concat(SLICE.call(arguments)));
+					var prefix = type + ' ' + gPrefix + this._name;
+					if (this._listener) {
+						return this._listener.apply(this._listener, [prefix].concat(SLICE.call(arguments)));
+					} else { 
+						return function() {}
+					}
 				}
 				return arguments[0];
 			}
 		}
 	
-		this.setListener = function(listener) { log = listener; }
+		this.setListener = function(listener) { this._listener = listener; }
 		this.debug = makeLogFunction(logging.DEBUG, "DEBUG");
 		this.log = makeLogFunction(logging.LOG, "LOG");
 		this.info = makeLogFunction(logging.INFO, "INFO");

daemon/orbited/jsio/jsio.js

-// jsio/browser.js
+// Copyright (c) 2010
+// Michael Carter (cartermichael@gmail.com)
+// Martin Hunt (mghunt@gmail.com)
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Initialization of js.io occurs in a closure, preventing local variables
+// from entering the global scope.  During execution, the method `jsio` is
+// added to the global scope.
 
 ;(function() {
-	var ENV, sourceCache = {
-		// Insert pre-loaded modules here...
+	function init(envClone) {
+		// We expect this code to be minified before production use, so we may
+		// write code slightly more verbosely than we otherwise would.
+	
+		// Should we parse syntax errors in the browser?
+		var DEBUG = true;
+	
+		// Store a reference to the slice function for converting objects of
+		// type arguments to type array.
+		var SLICE = Array.prototype.slice;
+	
+		// js.io supports multiple JavaScript environments such as node.js and
+		// most web browsers (IE, Firefox, WebKit).  The ENV object wraps 
+		// any utility functions that contain environment-specific code (e.g.
+		// reading a file using node's `fs` library or a browser's
+		// `XMLHttpRequest`).  Running js.io in other JavaScript environments
+		// is as easy as implementing an environment object that conforms to 
+		// the abstract interface for an environment (provided below) and 
+		// calling `jsio.setEnv()`.
+		var ENV;
+	
+		// Checks if the last character in a string is `/`.
+		var rexpEndSlash = /\/$/;
+	
+		// Creates an object containing metadata about a module.
+		function makeModuleDef(path, baseMod, basePath) {
+			var def = util.splitPath(path + '.js');
+			if (baseMod) {
+				def.baseMod = baseMod;
+				def.basePath = basePath;
+			}
+			return def;
+		}
+	
+		// Utility functions
+		var util = {
+				// `util.bind` returns a function that, when called, will execute
+				// the method passed in with the provided context and any additional
+				// arguments passed to `util.bind`.
+				//       util.bind(obj, 'f', a) -> function() { return obj.f(a); }
+				//       util.bind(obj, g, a, b, c) -> function() { return g.call(g, a, b, c); }
+				bind: function(context, method/*, args... */) {
+					var args = SLICE.call(arguments, 2);
+					return function() {
+						method = (typeof method == 'string' ? context[method] : method);
+						return method.apply(context, args.concat(SLICE.call(arguments, 0)));
+					};
+				},
+			
+				// `util.addEndSlash` accepts a string.  That string is returned with a `/`
+				// appended if the string did not already end in a `/`.
+				addEndSlash: function(str) {
+					return rexpEndSlash.test(str) ? str : str + '/';
+				},
+			
+				// `util.removeEndSlash` accepts a string.  It removes a trailing `/` if
+				// one is found.
+				removeEndSlash: function(str) {
+					return str.replace(rexpEndSlash, '');
+				},
+			
+				// `util.makeRelativePath` accepts two paths (strings) and returns the first path
+				// made relative to the second.  Note: this function needs some work.  It currently
+				// handles the most common use cases, but may fail in unexpected edge cases.
+				// 
+				//  - Simple case: if `path` starts with `relativeTo`, then we can strip `path` 
+				// off the `relativeTo` part and we're done.
+				//
+				//         util.makeRelativePath('abc/def/', 'abc') -> 'def'
+				//
+				//  - Harder case: `path` starts with some substring of `relativeTo`.  We want to remove this substring and then add `../` for each remaining segment of `relativeTo`.
+				//
+				//         util.makeRelativePath('abc/def/', 'abc/hij') -> '../def'
+				//
+				makeRelativePath: function(path, relativeTo) {
+					var len = relativeTo.length;
+					if (path.substring(0, len) == relativeTo) {
+						/* Note: we're casting a boolean to an int by adding len to it */
+						return path.slice((path.charAt(len) == '/') + len);
+					}
+				
+					var sA = util.removeEndSlash(path).split('/'),
+						sB = util.removeEndSlash(relativeTo).split('/'),
+						i = 0;
+				
+					/* Count how many segments match. */
+					while(sA[i] == sB[i]) { ++i; }
+				
+					if (i) {
+						/* If at least some segments matched, remove them.  The result is our new path. */
+						path = sA.slice(i).join('/');
+					
+						/* Prepend `../` for each segment remaining in `relativeTo`. */
+						for (var j = sB.length - i; j > 0; --j) { path = '../' + path; }
+					}
+				
+					return path;
+				},
+			
+				// `buildPath` accepts an arbitrary number of string arguments to concatenate into a path.
+				//     util.buildPath('a', 'b', 'c/', 'd/') -> 'a/b/c/d/'
+				buildPath: function() {
+					return util.resolveRelativePath(Array.prototype.join.call(arguments, '/'));
+				},
+			
+				// `resolveRelativePath` removes relative path indicators.  For example:
+				//     util.resolveRelativePath('a/../b') -> b
+				resolveRelativePath: function(path) {
+					/* If the path starts with a protocol, store it and remove it (add it
+					   back later) so we don't accidently modify it. */
+					var protocol = path.match(/^(\w+:\/\/)(.*)$/);
+					if (protocol) { path = protocol[2]; }
+				
+					/* Remove multiple slashes and trivial dots (`/./ -> /`). */
+					path = path.replace(/\/+/g, '/').replace(/\/\.\//g, '/');
+				
+					/* Loop to collapse instances of `../` in the path by matching a previous
+					   path segment.  Essentially, we find substrings of the form `/abc/../`
+					   where abc is not `.` or `..` and replace the substrings with `/`.
+					   We loop until the string no longer changes since after collapsing all
+					   possible instances once, we may have created more instances that can
+					   be collapsed.
+					*/
+					var o;
+					while((o = path) != (path = path.replace(/(^|\/)(?!\.?\.\/)([^\/]+)\/\.\.\//g, '$1'))) {}
+					/* Don't forget to prepend any protocol we might have removed earlier. */
+					return protocol ? protocol[1] + path : path;
+				},
+			
+				resolveRelativeModule: function(modulePath, directory) {
+					var result = [],
+						parts = modulePath.split('.'),
+						len = parts.length,
+						relative = (len > 1 && !parts[0]),
+						i = relative ? 0 : -1;
+				
+					while(++i < len) { result.push(parts[i] ? parts[i] : '..'); }
+					return util.buildPath(relative ? directory : '', result.join('/'));
+				},
+				resolveModulePath: function(modulePath, directory) {
+					// resolve relative paths
+					if(modulePath.charAt(0) == '.') {
+						return [makeModuleDef(util.resolveRelativeModule(modulePath, directory))];
+					}
+				
+					// resolve absolute paths with respect to jsio packages/
+					var pathSegments = modulePath.split('.'),
+						baseMod = pathSegments[0],
+						pathString = pathSegments.join('/');
+				
+					if (jsio.path.cache.hasOwnProperty(baseMod)) {
+						return [makeModuleDef(util.buildPath(jsio.path.cache[baseMod], pathString))];
+					}
+				
+					var out = [],
+						paths = jsio.path.get(),
+						len = paths.length;
+					for (var i = 0; i < len; ++i) {
+						out.push(makeModuleDef(util.buildPath(paths[i], pathString), baseMod, paths[i]));
+					}
+					return out;
+				},
+				splitPath: function(path) {
+					var i = path.lastIndexOf('/') + 1;
+					return {
+						path: path,
+						directory: path.substring(0, i),
+						filename: path.substring(i)
+					};
+				}
+			};
+	
+		var jsio = util.bind(this, importer, null, null, null);
+		jsio.__util = util;
+		jsio.__init__ = init;
+	
+		// explicitly use jsio.__srcCache to avoid obfuscation with closure compiler
+		var sourceCache = jsio.__srcCache = {};
+
+		(function() {
+			this.__filename = 'jsio.js';
+			this.__preprocessors = {};
+			this.__cmds = [];
+			this.__jsio = this;
+			this.__importer = importer;
+			this.__modules = {preprocessors:{}};
 		
-	};
+			this.path = {
+				set: function(path) { this.value = (typeof path == 'string' ? [path] : path); },
+				get: function() { return this.value.slice(0); },
+				add: function(path) {
+					var v = this.value, len = v.length;
+					for (var i = 0; i < len; ++i) {
+						if (v[i] == path) { return; }
+					}
+					v.push(path);
+				},
+				remove: function(path) {
+					var v = this.value, len = v.length;
+					for (var i = 0; i < len; ++i) {
+						if (v[i] == path) {
+							v.splice(i, 1);
+						}
+					}
+				},
+				value: [],
+				cache: {}
+			};
+		
+			this.addPath = util.bind(this.path, 'add');
+		
+			this.setCachedSrc = function(path, src) { sourceCache[path] = { path: path, src: src }; }
+			this.getCachedSrc = function(path) { return sourceCache[path]; }
+		
+			this.addPreprocessor = function(name, preprocessor) { this.__preprocessors[name] = preprocessor; }
+			this.addCmd = function(processor) { this.__cmds.push(processor); }
+		
+			this.setEnv = function(envCtor) {
+				if (!envCtor && envClone) {
+					ENV = envClone;
+				} else if (typeof envCtor == 'string') {
+					switch(envCtor) {
+						case 'node':
+							ENV = new ENV_node(util);
+							break;
+						case 'browser':
+						default:
+							ENV = new ENV_browser(util);
+							break;
+					}
+				} else {
+					ENV = new envCtor(util);
+				}
+			
+				this.__env = ENV;
+				this.__dir = ENV.getCwd();
+				this.path.set(ENV.getPath());
+			}
+		}).call(jsio);
 	
-	function bind(context, method/*, args... */) {
-		var args = Array.prototype.slice.call(arguments, 2);
-		return function(){
-			method = (typeof method == 'string' ? context[method] : method);
-			return method.apply(context, args.concat(Array.prototype.slice.call(arguments, 0)));
+		if (envClone) {
+			jsio.setEnv();
+		} else if (typeof process !== 'undefined' && process.version) {
+			jsio.setEnv('node');
+		} else if (typeof XMLHttpRequest != 'undefined' || typeof ActiveXObject != 'undefined') {
+			jsio.setEnv('browser');
 		}
-	}
+
+		/*
+		function ENV_abstract() {
+			this.global = null;
+			this.getCwd = function() {};
+			this.getPath = function() {};
+			this.eval = function(code, path) {};
+			this.fetch = function(path) { return contentsOfPath; };
+			this.log = function(args...) {};
+		}
+		*/
 	
-	jsio = bind(this, importer, null, '');
-	jsio.__filename = 'jsio.js';
-	jsio.modules = [];
-	jsio.setCachedSrc = function(pkg, filePath, src) {
-		sourceCache[pkg] = { filePath: filePath, src: src };
-	}
-	jsio.getCachedSrc = function(pkg) { return sourceCache[pkg]; }
-	jsio.path = {};
-	jsio.setPath = function(path) { jsio.path.__default__ = typeof path == 'string' ? [path] : path; }
-	jsio.setEnv = function(env) {
-		if(ENV && (env == ENV || env == ENV.name)) { return; }
+		function ENV_node() {
+			var fs = require('fs'),
+				sys = require('sys');
 		
-		if(typeof env == 'string') {
-			switch(env) {
-				case 'node':
-					ENV = new ENV_node();
-					break;
-				case 'browser':
-				default:
-					ENV = new ENV_browser();
-					break;
+			this.name = 'node';
+			this.global = GLOBAL;
+			this.getCwd = process.cwd;
+			this.log = function() {
+				var msg;
+				try {
+					sys.error(msg = Array.prototype.map.call(arguments, function(a) {
+						if ((a instanceof Error) && a.message) {
+							return 'Error:' + a.message + '\nStack:' + a.stack + '\nArguments:' + a.arguments;
+						}
+						return typeof a == 'string' ? a : JSON.stringify(a);
+					}).join(' '));
+				} catch(e) {
+					sys.error(msg = Array.prototype.join.call(arguments, ' ') + '\n');
+				}
+				return msg;
 			}
-			ENV.name = env;
-		} else {
-			ENV = env;
+		
+			this.getPath = function() {
+				var segments = __filename.split('/');
+				segments.pop();
+				return util.makeRelativePath(segments.join('/') || '.', this.getCwd());
+			}
+			this.eval = process.compile;
+		
+			this.fetch = function(path) {
+				try { return fs.readFileSync(path, 'utf8'); } catch(e) {}
+				return false;
+			}
+		
+			this.require = require;
 		}
+	
+		function ENV_browser() {
+			var XHR = window.XMLHttpRequest || function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
+				cwd = null,
+				path = null;
 		
-		jsio.__env = ENV;
-		jsio.__dir = ENV.getCwd();
-		if(!jsio.path.__default__) { jsio.setPath(ENV.getPath()); }
-	}
-	
-	if (typeof process !== 'undefined' && process.version) {
-		jsio.setEnv('node');
-	} else if (typeof XMLHttpRequest != 'undefined' || typeof ActiveXObject != 'undefined') {
-		jsio.setEnv('browser');
-	}
-	
-	// DONE
-	
-	/*
-	function ENV_abstract() {
-		this.global = null;
-		this.getCwd = function() {};
-		this.getPath = function() {};
-		this.eval = function(code, path) {};
-		this.findModule = function(pathString) {};
-		this.log = function(args...) {};
-	}
-	*/
-	
-	function ENV_node() {
-		var fs = require('fs'),
-			sys = require('sys');
+			this.name = 'browser';
+			this.global = window;
+			this.global.jsio = jsio;
 		
-		this.global = GLOBAL;
-		this.getCwd = process.cwd;
-		this.log = function() {
-			var msg;
-			try {
-				sys.error(msg = Array.prototype.map.call(arguments, function(a) {
-					if ((a instanceof Error) && a.message) {
-						return 'Error:' + a.message + '\nStack:' + a.stack + '\nArguments:' + a.arguments;
+			this.log = function() {
+				var args = SLICE.call(arguments, 0);
+				if (typeof console != 'undefined' && console.log) {
+					if (console.log.apply) {
+						console.log.apply(console, arguments);
+					} else { // IE doesn't support log.apply, and the argument cannot be arguments - it must be an array
+						console.log(args);
 					}
-					return typeof a == 'string' ? a : JSON.stringify(a);
-				}).join(' '));
-			} catch(e) {
-				sys.error(msg = Array.prototype.join.call(arguments, ' ') + '\n');
+				}
+				return args.join(' ');
 			}
-			return msg;
-		}
 		
-		this.getPath = function() {
-			var segments = __filename.split('/');
-			segments.pop();
-			return segments.join('/') || '.';
-		}
-		this.eval = process.compile;
-		this.findModule = function(possibilities) {
-			for (var i = 0, possible; possible = possibilities[i]; ++i) {
+			this.getCwd = function() {
+				if(!cwd) {
+					var loc = window.location, path = loc.pathname;
+					cwd = loc.protocol + '//' + loc.host + path.substring(0, path.lastIndexOf('/') + 1);
+				}
+				return cwd;
+			}
+		
+			this.getPath = function() {
+				if(!path) {
+					try {
+						var filename = new RegExp('(.*?)' + jsio.__filename + '(\\?.*)?$'),
+							scripts = document.getElementsByTagName('script');
+					
+						for (var i = 0, script; script = scripts[i]; ++i) {
+							var result = script.src.match(filename);
+							if (result) {
+								path = result[1];
+								if (/^[A-Za-z]*:\/\//.test(path)) { path = util.makeRelativePath(path, this.getCwd()); }
+								break;
+							}
+						}
+					} catch(e) {}
+				
+					if(!path) { path = '.'; }
+				}
+				return path;
+			}
+		
+			this.debugPath = function(path) { return path; }
+
+			// IE6 won't return an anonymous function from eval, so use the function constructor instead
+			var rawEval = typeof eval('(function(){})') == 'undefined'
+				? function(src, path) { return (new Function('return ' + src))(); }
+				: function(src, path) { var src = src + '\n//@ sourceURL=' + path; return window.eval(src); };
+
+			// provide an eval with reasonable debugging
+			this.eval = function(code, path, origCode) {
 				try {
-					possible.src = fs.readFileSync(possible.filePath);
-					return possible;
+					return rawEval(code, this.debugPath(path));
 				} catch(e) {
+					if(e instanceof SyntaxError) {
+						ENV.log("a syntax error is preventing execution of " + path);
+						if (DEBUG && this.checkSyntax) {
+							this.checkSyntax(origCode, path);
+						}
+					}
+					throw e;
 				}
 			}
-			return false;
-		}
-
-		this.require = require;
-		this.include = include;
-	}
-	
-	function ENV_browser() {
-		var XHR = window.XMLHttpRequest || function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
-			SLICE = Array.prototype.slice,
-			cwd = null,
-			path = null;
 		
-		this.global = window;
-		this.global.jsio = jsio;
+			this.checkSyntax = function(code, path) {
+				try {
+					var syntax = jsio('import util.syntax', {suppressErrors: true, dontExport: true}),
+						result = syntax(code);
+					syntax.display(result, path);
+				} catch(e) {}
+			}
 		
-		this.log = function() {
-			var args = SLICE.call(arguments, 0);
-			if (typeof console != 'undefined' && console.log) {
-				if (console.log.apply) {
-					console.log.apply(console, arguments);
-				} else { // IE doesn't support log.apply, and the argument cannot be arguments - it must be an array
-					console.log(args);
-				}
-			}
-			return args.join(' ');
-		}
-		
-		this.getCwd = function() {
-			if(!cwd) {
-				var location = window.location.toString().split('#')[0];
-				cwd = location.substring(0, location.lastIndexOf('/') + 1);
-			}
-			return cwd;
-		}
-		
-		this.getPath = function() {
-			if(!path) {
-				try {
-					var filename = new RegExp('(.*?)' + jsio.__filename + '(\\?.*)?$'),
-						scripts = document.getElementsByTagName('script');
-					
-					for (var i = 0, script; script = scripts[i]; ++i) {
-						var result = script.src.match(filename);
-						if (result) {
-							path = result[1];
-							if (/^[A-Za-z]*:\/\//.test(path)) { path = makeRelativePath(path, this.getCwd()); }
-							break;
-						}
-					}
-				} catch(e) {}
-				
-				if(!path) { path = '.'; }
-			}
-			return path;
-		}
-
-		// IE6 won't return an anonymous function from eval, so use the function constructor instead
-		var rawEval = typeof eval('(function(){})') == 'undefined'
-			? function(src, path) { return (new Function('return ' + src))(); }
-			: function(src, path) { var src = src + '\n//@ sourceURL=' + path; return window.eval(src); }
-
-		// provide an eval with reasonable debugging
-		this.eval = function(code, path) {
-			try { return rawEval(code, path); } catch(e) {
-				if(e instanceof SyntaxError) {
-					e.message = "a syntax error is preventing execution of " + path;
-					e.type = "syntax_error";
-					try {
-						var cb = function() {
-							var el = document.createElement('iframe');
-							el.style.cssText = "position:absolute;top:-999px;left:-999px;width:1px;height:1px;visibility:hidden";
-							el.src = 'javascript:document.open();document.write("<scr"+"ipt src=\'' + path + '\'></scr"+"ipt>")';
-							setTimeout(function() {try{document.body.appendChild(el)}catch(e){}}, 0);
-						};
-						if (document.body) { cb(); }
-						else { window.addEventListener('load', cb, false); }
-					} catch(f) {}
-				}
-				throw e;
-			}
-		}
-		
-		this.findModule = function(possibilities) {
-			for (var i = 0, possible; possible = possibilities[i]; ++i) {
+			this.fetch = function(path) {
 				var xhr = new XHR();
 				try {
-					xhr.open('GET', possible.filePath, false);
+					xhr.open('GET', path, false);
 					xhr.send(null);
 				} catch(e) {
 					ENV.log('e:', e);
-					continue; // firefox file://
+					return false; // firefox file://
 				}
-				
+			
 				if (xhr.status == 404 || // all browsers, http://
 					xhr.status == -1100 || // safari file://
 					// XXX: We have no way to tell in opera if a file exists and is empty, or is 404
 					//(!failed && xhr.status == 0 && !xhr.responseText && EXISTS)) // opera
 					false)
 				{
-					continue;
+					return false;
 				}
-				
-				possible.src = xhr.responseText;
-				return possible;
+			
+				return xhr.responseText;
 			}
+		};
+	
+		var preprocessorCheck = /^"use (.*?)"\s*;\s*\n/,
+			preprocessorFunc = /^(.+)\(.+\)$/,
+			failedFetch = {};
+	
+		function findModule(possibilities, opts) {
+			var src;
+			for (var i = 0, possible; possible = possibilities[i]; ++i) {
+				var path = possible.path,
+					cachedVersion = sourceCache[path];
 			
+				if (cachedVersion) {
+					possible.src = cachedVersion.src;
+					return possible;
+				}
+			
+				/*if (/^\.\//.test(path)) {
+					// remove one path segment for each dot from the cwd 
+					path = addEndSlash(ENV.getCwd()) + path;
+				}*/
+			
+				src = ENV.fetch(path);
+			
+				if (src !== false) {
+					possible.src = src;
+					return possible;
+				} else {
+					failedFetch[path] = true;
+				}
+			}
+		
 			return false;
 		}
-	};
 	
-	function ensureHasTrailingSlash(str) { return str.length && str.replace(/([^\/])$/, '$1/') || str; }
-	function removeTrailingSlash(str) { return str.replace(/\/$/,''); }
+		// load a module from a file
+		function loadModule(fromDir, fromFile, modulePath, opts) {
+			var possibilities = util.resolveModulePath(modulePath, fromDir);
+			for (var i = 0, p; p = possibilities[i]; ++i) {
+				var path = possibilities[i].path;
+				if (!opts.reload && (path in jsio.__modules)) {
+					return possibilities[i];
+				}
+				if (path in failedFetch) { possibilities.splice(i--, 1); }
+			}
+		
+			if (!possibilities.length) {
+				if (opts.suppressErrors) { return false; }
+				var e = new Error('Module failed to load (again)');
+				e.jsioLogged = true;
+				throw e;
+			}
+		
+			var moduleDef = findModule(possibilities, opts),
+				match;
+		
+			if (!moduleDef) {
+				if (opts.suppressErrors) { return false; }
+				var paths = [];
+				for (var i = 0, p; p = possibilities[i]; ++i) { paths.push(p.path); }
+				throw new Error(fromDir + fromFile + ": \n\tcurrent directory: " + ENV.getCwd() + "\n\tlooked in:\n\t\t" + paths.join('\n\t\t') + '\n\tImport Stack:\n\t\t' + importStack.join('\n\t\t') + "\n\tError: requested import (" + modulePath + ") not found.");
+			}
+		
+			moduleDef.friendlyPath = modulePath;
+		
+			if (moduleDef.baseMod && !(moduleDef.baseMod in jsio.path.cache)) {
+				jsio.path.cache[moduleDef.baseMod] = moduleDef.basePath;
+			}
+		
+			// the order here is somewhat arbitrary and might be overly restrictive (... or overly powerful)
+			while (moduleDef.src.charAt(0) == '"' && (match = moduleDef.src.match(preprocessorCheck))) {
+				moduleDef.src = moduleDef.src.substring(match[0].length - 1);
+				applyPreprocessors(fromDir, moduleDef, match[1].split(','), opts);
+			}
+		
+			if (opts.preprocessors) {
+				applyPreprocessors(fromDir, moduleDef, opts.preprocessors, opts);
+			}
+		
+			return moduleDef;
+		}
 	
-	function guessModulePath(pathString) {
-		// resolve relative paths
-		if(pathString.charAt(0) == '.') {
-			// count the number of dots
-			var i = 0;
-			while(pathString.charAt(i + 1) == '.') { ++i; }
-
-			// remove one path segment for each dot from the cwd 
-			var prefix = removeTrailingSlash(ENV.getCwd());
-			if (i) { prefix = prefix.split('/').slice(0, -i).join('/'); }
-			
-			return [{filePath: prefix + '/' + pathString.substring(i + 1).split('.').join('/') + '.js'}];
-		}
-		
-		// resolve absolute paths with respect to jsio packages/
-		var pathSegments = pathString.split('.'),
-			baseMod = pathSegments[0],
-			modPath = pathSegments.join('/');
-		
-		if (baseMod in jsio.path) {
-			return [{filePath: ensureHasTrailingSlash(jsio.path[baseMod]) + modPath + '.js'}];
-		}
-		
-		var out = [];
-		var paths = typeof jsio.path.__default__ == 'string' ? [jsio.path.__default__] : jsio.path.__default__;
-		for (var i = 0, len = paths.length; i < len; ++i) {
-			var path = ensureHasTrailingSlash(paths[i]);
-			out.push({filePath: path + modPath + '.js', baseMod: baseMod, basePath: path});
-		}
-		return out;
-	}
-	
-	// load a module from a file
-	function loadModule(pathString) {
-		var possibilities = guessModulePath(pathString),
-			module = ENV.findModule(possibilities);
-		if(!module) {
-			var paths = [];
-			for (var i = 0, p; p = possibilities[i]; ++i) { paths.push(p.filePath); }
-			throw new Error("Module not found: " + pathString + " (looked in " + paths.join(', ') + ")");
-		}
-		
-		if (!(module.baseMod in jsio.path)) {
-			jsio.path[module.baseMod] = module.basePath;
-		}
-		
-		return module;
-	}
-	
-	function execModule(context, module) {
-		var code = "(function(_){with(_){delete _;(function(){" + module.src + "\n}).call(this)}})";
-		var fn = ENV.eval(code, module.filePath);
-		try {
-			fn.call(context.exports, context);
-		} catch(e) {
-			if(e.type == "syntax_error") {
-				throw new Error("error importing module: " + e.message);
-			} else if (!e.jsioLogged) {
-				e.jsioLogged = true;
-				if (e.type == "stack_overflow") {
-					ENV.log("Stack overflow in", module.filePath, ':', e);
-				} else {
-					ENV.log("ERROR LOADING", module.filePath, ':', e);
+		function applyPreprocessors(path, moduleDef, names, opts) {
+			for (var i = 0, len = names.length; i < len; ++i) {
+				p = getPreprocessor(names[i]);
+				if (p) {
+					p(path, moduleDef, opts);
 				}
 			}
-			throw e;
 		}
-	};
 	
-	function resolveRelativePath(pkg, path, pathSep) {
-		// does the pkg need to be resolved, i.e. is it a relative path?
-		if(!path || (pathSep = pathSep || '.') != pkg.charAt(0)) { return pkg; }
+		function getPreprocessor(name) {
+			return typeof name == 'function'
+				? name
+				: (jsio.__modules['preprocessors.' + name] 
+					|| jsio('import preprocessors.' + name, {dontExport: true}));
+		}
+	
+		function execModuleDef(context, moduleDef) {
+			var code = "(function(_){with(_){delete _;return function $$" + moduleDef.friendlyPath.replace(/[\/.]/g, '_') + "(){" + moduleDef.src + "\n}}})";
+			var fn = ENV.eval(code, moduleDef.path, moduleDef.src);
+			fn = fn(context);
+			fn.call(context.exports);
+		};
+	
+		function resolveImportRequest(context, request, opts) {
+			var cmds = jsio.__cmds,
+				imports = [],
+				result = false;
 		
-		var i = 1;
-		while(pkg.charAt(i) == pathSep) { ++i; }
-		path = path.split(pathSep).slice(0, -i);
-		if(path.length) {
-			path = path.join(pathSep);
-			if(path.charAt(path.length - 1) != pathSep) { path += pathSep; }
-		}
-		return path + pkg.substring(i);
-	}
+			for (var i = 0, imp; imp = cmds[i]; ++i) {
+				if ((result = imp(context, request, opts, imports))) { break; }
+			}
+		
+			if (result !== true) {
+				throw new (typeof SyntaxError != 'undefined' ? SyntaxError : Error)(String(result || 'invalid jsio command: jsio(\'' + request + '\')'));
+			}
+		
+			return imports;
+		};
 	
-	function resolveImportRequest(path, request) {
-		var match, imports = [];
-		if((match = request.match(/^(from|external)\s+([\w.$]+)\s+import\s+(.*)$/))) {
+		function makeContext(ctx, modulePath, moduleDef, dontAddBase) {
+			if (!ctx) { ctx = {}; }
+			if (!ctx.exports) { ctx.exports = {}; }
 
-			imports[0] = {
-				from: resolveRelativePath(match[2], path),
-				external: match[1] == 'external',
-				'import': {}
+			ctx.jsio = util.bind(this, importer, ctx, moduleDef.directory, moduleDef.filename);
+			ctx.require = function(request, opts) {
+				if (!opts) { opts = {}; }
+				opts.dontExport = true;
+				opts.suppressErrors = true;
+			
+				try {
+					var ret = ctx.jsio(request, opts);
+					if (ret === false) {
+						// need this to trigger require attempt due to suppresserrors = true
+						throw "module failed to load";
+					} else {
+						return ret;
+					}
+				} catch(e) {
+					try {
+						return require(request);
+					} catch(e2) {
+						ENV.log('Error loading request ' + request + ':');
+						ENV.log(e);
+
+						ENV.log('Also could not load using standard CommonJS');
+						ENV.log(e2);
+
+						throw e;
+					}
+				}
 			};
+		
+			ctx.module = {id: modulePath};
+			if (!dontAddBase && modulePath != 'base') {
+				ctx.jsio('from base import *');
+				ctx.logging.__create(modulePath, ctx);
+			}
+		
+			// TODO: FIX for "trailing ." case
+			ctx.jsio.__jsio = jsio;
+			ctx.jsio.__env = jsio.__env;
+			ctx.jsio.__dir = moduleDef.directory;
+			ctx.jsio.__filename = moduleDef.filename;
+			ctx.jsio.__path = modulePath;
+			ctx.jsio.path = jsio.path;
+			return ctx;
+		};
+		
+		var importStack = [];
+		function importer(boundContext, fromDir, fromFile, request, opts) {
+			opts = opts || {};
+			fromDir = fromDir || './';
+			fromFile = fromFile || '<initial file>';
+		
+			// importer is bound to a module's (or global) context -- we can override this
+			// by using opts.exportInto
+			var exportInto = opts.exportInto || boundContext || ENV.global;
+		
+			// parse the import request(s)
+			var imports = resolveImportRequest(exportInto, request, opts),
+				numImports = imports.length,
+				retVal = numImports > 1 ? {} : null;
+		
+			// import each requested item
+			for(var i = 0; i < numImports; ++i) {
+				var item = imports[i],
+					modulePath = item.from,
+					modules = jsio.__modules;
 			
-			match[3].replace(/\s*([\w.$*]+)(?:\s+as\s+([\w.$]+))?/g, function(_, item, as) {
-				imports[0]['import'][item] = as || item;
-			});
-		} else if((match = request.match(/^import\s+(.*)$/))) {
-			match[1].replace(/\s*([\w.$]+)(?:\s+as\s+([\w.$]+))?,?/g, function(_, pkg, as) {
-				fullPkg = resolveRelativePath(pkg, path);
-				imports[imports.length] = as ? {from: fullPkg, as: as} : {from: fullPkg, as: pkg};
-			});
-		} else if((match = request.match(/[\w.0-9$\/]/))) { // CommonJS syntax
-			var req = match[0],
-				isAbsolute = req.charAt(0) == '/';
-			
-			req = req.replace(/^\//, '') // leading slash not needed
-				.replace(/\.\.?\//g, '.') // replace relative path indicators with dots
-				.replace(/\//g, '.'); // any remaining slashes are path separators
-			
-			// isAbsolute handles the edge case where the path looks like /../foo
-			imports[0] = {
-				from: isAbsolute ? req : resolveRelativePath(req, path),
-				as: req
-			};
-		} else {
-			var msg = 'Invalid jsio request: jsio(\'' + request + '\')';
-			throw SyntaxError ? new SyntaxError(msg) : new Error(msg);
-		}
-		return imports;
-	};
-	
-	function makeContext(pkgPath, filePath) {
-		var ctx = {
-				exports: {},
-				global: ENV.global
-			},
-			cwd = ENV.getCwd(),
-			i = filePath.lastIndexOf('/'),
-			isRelative = i > 0;
-		
-		ctx.require = ctx.jsio = bind(this, importer, ctx, pkgPath);
-		ctx.module = {id: pkgPath};
-		
-		if (pkgPath != 'base') {
-			ctx.jsio('from base import *');
-			ctx.logging.__create(pkgPath, ctx);
-		}
-		
-		// TODO: FIX for "trailing ." case
-		ctx.jsio.__jsio = jsio;
-		ctx.jsio.__env = jsio.__env;
-		ctx.jsio.__dir = isRelative ? makeRelativePath(filePath.substring(0, i), cwd) : '';
-		ctx.jsio.__filename = isRelative ? filePath.substring(i) : filePath;
-		ctx.jsio.__path = pkgPath;
-		return ctx;
-	};
-	
-	function makeRelativePath(path, relativeTo) {
-		var i = path.match('^' + relativeTo);
-		if (i && i[0] == relativeTo) {
-			var offset = path[relativeTo.length] == '/' ? 1 : 0
-			return path.slice(relativeTo.length + offset);
-		}
-		return path;
-	};
-	
-	function importer(context, path, request, altContext) {
-		// importer is bound to a module's (or global) context -- we can override this
-		// by using altContext as in jsio('import foo', obj) --> obj.foo
-		context = altContext || context || ENV.global;
-		
-		// parse the import request(s)
-		var imports = resolveImportRequest(path, request),
-			numImports = imports.length,
-			retVal = numImports > 1 ? {} : null;
-		
-		// import each requested item
-		for(var i = 0; i < numImports; ++i) {
-			var item = imports[i],
-				pkg = item.from,
-				modules = jsio.modules;
-			
-			// eval any packages that we don't know about already
-			if(!(pkg in modules)) {
 				try {
-					var module = sourceCache[pkg] || loadModule(pkg);
+					var moduleDef = loadModule(fromDir, fromFile, modulePath, opts);
+					if (moduleDef === false) { return false; }
 				} catch(e) {
-					ENV.log('\nError executing \'', request, '\': could not load module', pkg, '\n\tpath:', path, '\n\trequest:', request, '\n');
+					if (!e.jsioLogged) {
+						ENV.log('\nError loading module:\n\trequested:', modulePath, '\n\tfrom:', fromDir + fromFile, '\n\tfull request:', request, '\n');
+						e.jsioLogged = true;
+					}
 					throw e;
 				}
 				
-				if(!item.external) {
-					var newContext = makeContext(pkg, module.filePath);
-					modules[pkg] = newContext.exports;
-					execModule(newContext, module);
-					modules[pkg] = newContext.exports;
-				} else {
-					var newContext = modules[pkg] = {};
+				importStack.push(importStack.length + ' : ' + moduleDef.friendlyPath + ' (' + moduleDef.path + ')');
+				
+				// eval any packages that we don't know about already
+				var path = moduleDef.path;
+				if(!(path in modules)) {
+					var newContext = makeContext(opts.context, modulePath, moduleDef, item.dontAddBase);
+					modules[path] = newContext.exports;
+					if(item.dontUseExports) {
+						var src = [';(function(){'], k = 1;
+						for (var j in item['import']) {
+							newContext.exports[j] = undefined;
+							src[k++] = 'if(typeof '+j+'!="undefined"&&exports.'+j+'==undefined)exports.'+j+'='+j+';';
+						}
+						src[k] = '})();';
+						moduleDef.src += src.join('');
+					}
+					execModuleDef(newContext, moduleDef);
+					modules[path] = newContext.exports;
+				}
+				
+				importStack.pop();
+			
+				var module = modules[path];
+			
+				// return the module if we're only importing one module
+				if (numImports == 1) { retVal = module; }
+			
+				if (!opts.dontExport) {
+					// add the module to the current context
+					if (item.as) {
+						// remove trailing/leading dots
+						var as = item.as.match(/^\.*(.*?)\.*$/)[1],
+							segments = as.split('.'),
+							kMax = segments.length - 1,
+							c = exportInto;
+				
+						// build the object in the context
+						for(var k = 0; k < kMax; ++k) {
+							var segment = segments[k];
+							if (!segment) continue;
+							if (!c[segment]) { c[segment] = {}; }
+							c = c[segment];
+						}
 					
-					// capture the requested objects so they don't escape into the global scope
-					for(var j in item['import']) { newContext[j] = undefined; }
-					
-					execModule(newContext, module);
-					for(var j in item['import']) {
-						if(newContext[j] === undefined) {
-							newContext[j] = ENV.global[j];
+						c[segments[kMax]] = module;
+				
+						// there can be multiple module imports with this syntax (import foo, bar)
+						if (numImports > 1) {
+							retVal[as] = module;
+						}
+					} else if(item['import']) {
+						// there can only be one module import with this syntax 
+						// (from foo import bar), so retVal will already be set here
+						if(item['import']['*']) {
+							for(var k in modules[path]) { exportInto[k] = module[k]; }
+						} else {
+							try {
+								for(var k in item['import']) { exportInto[item['import'][k]] = module[k]; }
+							} catch(e) {
+								ENV.log('module: ', modules);
+								throw e;
+							}
 						}
 					}
 				}
 			}
+		
+			return retVal;
+		}
+	
+		// DEFINE SYNTAX FOR JSIO('cmd')
+	
+		// from myPackage import myFunc
+		// external myPackage import myFunc
+		jsio.addCmd(function(context, request, opts, imports) {
+			var match = request.match(/^\s*(from|external)\s+([\w.$]+)\s+(import|grab)\s+(.*)$/);
+			if(match) {
+				imports.push({
+					from: match[2],
+					dontAddBase: match[1] == 'external',
+					dontUseExports: match[3] == 'grab' || match[1] == 'external',
+					'import': {}
+				});
 			
-			var module = modules[pkg];
+				match[4].replace(/\s*([\w.$*]+)(?:\s+as\s+([\w.$]+))?/g, function(_, item, as) {
+					imports[0]['import'][item] = as || item;
+				});
+				return true;
+			}
+		});
+
+		// import myPackage
+		jsio.addCmd(function(context, request, opts, imports) {
+			var match = request.match(/^\s*import\s+(.*)$/);
+			if (match) {
+				match[1].replace(/\s*([\w.$]+)(?:\s+as\s+([\w.$]+))?,?/g, function(_, fullPath, as) {
+					imports.push(
+						as ? {
+							from: fullPath,
+							as: as
+						} : {
+							from: fullPath,
+							as: fullPath
+						});
+				});
+				return true;
+			}
+		});
+
+		// CommonJS syntax
+		jsio.addCmd(function(context, request, opts, imports) {
+		
+			//		./../b -> ..b
+			// 		../../b -> ...b
+			// 		../b -> ..b
+			// 		./b -> .b
+		
+			var match = request.match(/^\s*[\w.0-9$\/]+\s*$/);
+			if (match) {
 			
-			// return the module if we're only importing one module
-			if (numImports == 1) { retVal = module; }
+				var req = util.resolveRelativePath(match[0]),
+					isRelative = req.charAt(0) == '.';
 			
-			// add the module to the current context
-			if (item.as) {
-				// remove trailing/leading dots
-				var as = item.as.match(/^\.*(.*?)\.*$/)[1],
-					segments = as.split('.'),
-					kMax = segments.length - 1,
-					c = context;
-				
-				// build the object in the context
-				for(var k = 0; k < kMax; ++k) {
-					var segment = segments[k];
-					if (!segment) continue;
-					if (!c[segment]) { c[segment] = {}; }
-					c = c[segment];
-				}
-				
-				c[segments[kMax]] = module;
-				
-				// there can be multiple module imports with this syntax (import foo, bar)
-				if (numImports > 1) {
-					retVal[as] = module;
-				}
-			} else if(item['import']) {
-				// there can only be one module import with this syntax 
-				// (from foo import bar), so retVal will already be set here
-				if(item['import']['*']) {
-					for(var k in modules[pkg]) { context[k] = module[k]; }
-				} else {
-					try {
-						for(var k in item['import']) { context[item['import'][k]] = module[k]; }
-					} catch(e) {
-						ENV.log('module: ', modules);
-						throw e;
-					}
-				}
+				req = req	
+					// .replace(/^\//, '') // remove any leading slash
+					.replace(/\.\.\//g, '.') // replace relative path indicators with dots
+					.replace(/\.\//g, '')
+					.replace(/\//g, '.'); // any remaining slashes are path separators
+
+				imports[0] = { from: (isRelative ? '.' : '') + req };
+				return true;
 			}
+		});
+	
+		jsio.install = function(){
+			jsio('from base import *');
+			GLOBAL['logger'] = logging.get('jsiocore');
+		};
+	
+		jsio.clone = function() {
+			var copy = jsio.__init__(ENV);
+			if (ENV.name == 'browser') { window.jsio = jsio; }
+			return copy;
 		}
-		
-		return retVal;
+
+		return jsio;
 	}
+	jsio = init();
 })();

daemon/orbited/jsio/lib/Callback.js

+"use import";
+
+import std.js as JS;
+
+exports = Class(function() {
+	this.init = function() {
+		this._fired = false;
+		this._run = [];
+		this._id = 0;
+		this._pending = 0;
+		this._stat = {};
+	}
+	
+	this.fired = function() { return this._fired; } 
+	this.reset = function() { this._args = []; this._fired = false; }
+	this.clear = function() { this.reset(); this._run = []; }
+	this.forward = function(arguments) { this.run.apply(this, arguments); }
+	this.run = function(ctx, method) {
+		var f = method ? bind.apply(this, arguments) : ctx;
+		if (f) {
+			if (this._fired) {
+				f.apply(this, this._args);
+			} else {
+				this._run.push(f);
+			}
+		}
+		return this;
+	}
+	
+	this.fire = function() {
+		if (this._fired) { return; }
+		this._fired = true;
+		
+		var cbs = this._run;
+		this._run = [];
+		this._args = arguments;
+		for(var i = 0, len = cbs.length; i < len; ++i) {
+			if (cbs[i]) { cbs[i].apply(this, arguments); }
+		}
+	}
+	
+	this.chain = function(id) {
+		++this._pending;
+		this.reset();
+		return bind(this, '_deferred', id || (this._id++));
+	}
+
+	this._deferred = function(id) {
+		if (this._stat.hasOwnProperty(id)) { return; }
+
+		this._stat[id] = JS.vargs(arguments, 1);
+		if (this._pending) { --this._pending; }
+		if (!this._pending) { this.fire(this._stat); }
+	}
+});

daemon/orbited/jsio/lib/Hash.js

 /**
- * basic Hash/Set class
+ * Summary: a basic Hash/Set class for number and string values.
+ * Methods:
+ *  - init(args...) - if args is a single JS object, this will be used to define
+ *      the keys and values for the Hash.  
+ *  - contains(value) 
+ * Example:
  *  var h = new Hash('a', 'b', 'c');
  *  h.contains('a') ==> true
  *
  */
 exports = Class(function() {
 	this.init = function() {
-		this.m_keys = {};
-		this.m_dict = {};
-		this.m_values = {};
+		this._keys = {};
+		this._dict = {};
+		this._values = {};
 		if (arguments.length == 1 && typeof arguments == 'object') {
 			var dict = arguments[0];
 			for (var i in dict) {
 				if (dict.hasOwnProperty(i)) {
-					this.m_keys[i] = true;
-					this.m_values[i] = dict[i];
+					this._keys[i] = true;
+					this._values[i] = dict[i];
 				}
 			}
 		} else {
 			for (var i = 0, len = arguments.length; i < len; i++) {
-				this.m_keys[arguments[i]] = true;
-				this.m_values[arguments[i]] = true;
+				this._keys[arguments[i]] = true;
+				this._values[arguments[i]] = true;
 			};
 		}
 	}
 	
-	this.contains = function(val) { return this.m_values.hasOwnProperty(val); }
-	this.hasKey = this.containsKey = function(key) { return this.m_keys.hasOwnProperty(key); }
+	this.contains = function(val) { return this._values.hasOwnProperty(val); }
+	this.hasKey = this.containsKey = function(key) { return this._keys.hasOwnProperty(key); }
 	this.each = function(f, ctx) {
 		for (var i in keys) {
-			if (this.m_keys.hasOwnProperty(i)) {
-				f.call(ctx || GLOBAL, i, this.m_values[i], this);
+			if (this._keys.hasOwnProperty(i)) {
+				f.call(ctx || GLOBAL, i, this._values[i], this);
 			}
 		}
 	}

daemon/orbited/jsio/lib/Iterator.js

+"use import";
+
+/**
+ * Summary: Provides an object for iterating over the keys and values of
+ * an object or array.  
+ * Methods: 
+ *  - init(src) - src is the object to iterate over
+ *  - next(): returns the current value and advances the iterator to the next value
+ *  - loop(cb): iterate over all items immediately, calling cb with each item
+ *  - asyncLoop(cb): iterate over all items asynchronously.  First argument to
+ *     the callback is the item.  Second argument is a function `nextItem` that, 
+ *     when called, will cause the iterator to advance to the next element and 
+ *     call cb again.
+ * Usage notes: asyncLoop is implemented to not be vulnerable to stack overflows.
+ *     If cb immediately calls the nextItem function, it will not immediately 
+ *     result in a call to cb -- the stack will unwind to the asyncLoop call 
+ *     before continuing.
+ */
+
+import std.js as JS;
+
+exports = Class(function() {
+	this.init = function(src) {
+		this._src = src;
+		this._i = 0;
+		
+		// a call count prevents a stack overflow if the callback in
+		// an aysncloop is called repeatedly for large arrays
+		this._calls = 0;
+		if (JS.isArray(src)) {
+			this._isArray = true;
+		} else if (Object.keys) {
+			this._keys = Object.keys(src);
+		} else {
+			var k = this._keys = [];
+			for (var i in src) { if (src.hasOwnProperty(i)) { k.push(i); } }
+		}
+	}
+	
+	this.nextKey = function() {
+		return this._keys[this._i++];
+	}
+	
+	this.next = function() {
+		if (this._isArray) {
+			return this._src[this._i++] || exports.END_OF_LOOP;
+		} else {
+			var key = this._keys[this._i++];
+			return key ? this._src[key] : exports.END_OF_LOOP;
+		}
+	}
+	
+	this.loop = function(cb) {
+		if (arguments.length > 1) { cb = bind.apply(this, arguments); }
+		var next;
+		if (this._isArray) {
+			while((next = this.next())) {
+				cb(next);
+			}
+		} else {
+			while((next = this.nextKey())) {
+				cb(this._src[next], next);
+			}
+		}
+	}
+	
+	this.asyncLoop = function(cb) {
+		if (arguments.length > 1) { cb = bind.apply(this, arguments); }
+		this._next = bind(this, '_onReturn', cb);
+		this._calls++;
+		this._asyncLoop(cb);
+	}
+	
+	this._asyncLoop = function(cb) {
+		this._inLoop = true;
+		while (this._calls) {
+			--this._calls;
+			cb(this.next(), this._next);
+		}
+		this._inLoop = false;
+	}
+	
+	this._onReturn = function(cb) {
+		this._calls++;
+		if (!this._inLoop) { this._asyncLoop(cb); }
+	}
+});
+
+exports.END_OF_LOOP = new Error('jsio.Iterator.END_OF_LOOP');

daemon/orbited/jsio/lib/LogClass.js

+exports = function(name, parent, proto) {
+	if (!proto) { proto = parent; parent = null; }
+	var protoRef = {},
+		loggingProto = bind(this, LogClassProto, name, proto, logging.get(name), protoRef);
+	protoRef.proto = loggingProto;
+	return parent ? Class(parent, loggingProto) : Class(loggingProto);
+}
+
+function LogClassProto(name, proto, logger, protoRef, supr) {
+	proto.prototype = protoRef.proto.prototype;
+	var p = new proto(logger, supr);
+	p.__class__ = name;
+	return p;
+}
+

daemon/orbited/jsio/lib/PubSub.js

+/**
+ * Summary: inherit from lib.PubSub if a class wants publish/subscribe ability
+ * Methods:
+ *  - publish(signal, args...) - all subscribers to signal will be called
+ *     with the list of arguments provided.
+ *  - subscribe(signal, ctx, method, args...) - register a bound method
+ *     to a signal.  Any args that are passed in will be the first args
+ *     when the method is invoked during a publish.
+ *  Usage notes: There is one special signal '__any'.  Any subscribers to
+ *     '__any' will be called on every publish with the first publish
+ *     argument being the signal itself (after any args passed in during
+ *     the corresponding subscribe). 
+ *     Calling the super constructor is not required for descendants of 
+ *     lib.PubSub. 
+ */
 var ctx = jsio.__env.global;
 
 exports = Class(function() {
+	this.init = function() {}
+	
 	this.publish = function(signal) {
 		if(this._subscribers) {
 			var args = Array.prototype.slice.call(arguments, 1);
 			if(this._subscribers.__any) {
-				var anyArgs = [signal].concat(args);
-				for(var i = 0, sub; sub = this._subscribers.__any[i]; ++i) {
-					sub.apply(ctx, args);
+				var anyArgs = [signal].concat(args),
+					subs = this._subscribers.__any.slice(0);
+				for(var i = 0, sub; sub = subs[i]; ++i) {
+					sub.apply(ctx, anyArgs);
 				}
 			}
-		
-			if(!this._subscribers[signal]) { return; }		
-			for(var i = 0, sub; sub = this._subscribers[signal][i]; ++i) {
+			
+			if(!this._subscribers[signal]) { return; }
+			
+			var subs = this._subscribers[signal].slice(0);
+			for(var i = 0, sub; sub = subs[i]; ++i) {
 				sub.apply(ctx, args);
 			}
 		}
 	
 	// if no method is specified, all subscriptions with a callback context of ctx will be removed
 	this.unsubscribe = function(signal, ctx, method) {
+		if (!this._subscribers || !this._subscribers[signal]) { return; }
 		var subs = this._subscribers[signal];
 		for (var i = 0, c; c = subs[i]; ++i) {
 			if (c._ctx == ctx && (!method || c._method == method)) {

daemon/orbited/jsio/lib/Sortable.js

+"use import";
+
+import .LogClass;
+
+exports = LogClass('lib.Sortable', function(logger) {
+	
+	this.toStringPush = function(indexer) {
+		if (!this._toString || !this._toString.push) {
+			this._toString = [this.toString];
+		} else {
+			this._toString.push(this.toString);
+		}
+		
+		this.toString = indexer;
+	}
+	
+	this.toStringPop = function() {
+		this.toString = this._toString.pop();
+	}
+});
+
+var zeroPadding = [];
+function ensurePadding(n) {
+	for (var i = zeroPadding.length; i < n; ++i) {
+		var str = [];
+		for (var j = 0; j < i; ++j) { str.push('0'); }
+		zeroPadding[i] = str.join('');
+	}
+}
+
+/**
+ * Here we handle arbitrary sorting indexes transparently converting numbers to strings
+ * for efficient sorting with toString.  Unfortunately, this does not work for large floating
+ * point values, but that functionality could theoretically be added if desired.
+ */
+function sortIndex(i) { return this[i]; }
+
+exports.sort = function(arr, indexer) {
+	
+	var len = arr.length,
+		index = new Array(len),
+		result = new Array(len),
+		toString = new Array(len),
+		indexers = Array.prototype.slice.call(arguments, 1),
+		haveMultiple = !!indexers[1];
+	
+	if (haveMultiple) {
+		for (var i = 0; i < len; ++i) {
+			result[i] = [];
+		}
+	}
+	
+	for (var k = 0, indexer; indexer = indexers[k]; ++k) {
+		for (var i = 0; i < len; ++i) {
+			index[i] = indexer.call(arr[i], i);
+		}
+		
+		if (typeof index[0] == 'number') {
+			// we do two passes here:
+			//  1: find the max and min numerical indices
+			//  2: convert the indices to strings with appropriate zero-padding
+			var largest = index[0],
+				smallest = index[0];
+
+			for (var i = 1; i < len; ++i) {
+				if (index[i] > largest) {
+					largest = index[i];
+				} else if (index[i] < smallest) {
+					smallest = index[i];
+				}
+			}
+
+			// we have to be very careful here - large floating point numbers will break the
+			// string padding code
+			var paddingPositive = String(Math.floor(largest)).length,
+				paddingNegative = String(Math.floor(smallest)).length;
+
+			ensurePadding(Math.max(paddingPositive, paddingNegative));
+
+			var strLen;
+			for (var i = 0; i < len; ++i) {
+				var val = index[i];
+				if (val < 0) {
+					val = -(smallest - val);
+					strLen = ('' + Math.floor(val)).length;
+					index[i] = '-' + zeroPadding[paddingNegative - strLen] + val;
+				} else {
+					strLen = ('' + Math.floor(val)).length;
+					index[i] = zeroPadding[paddingPositive - strLen] + val;
+				}
+			}
+		}
+		
+		if (haveMultiple) {
+			for (var i = 0; i < len; ++i) {
+				result[i].push(index[i]);
+			}
+		} else {
+			result = index;
+		}
+	}
+	
+	for (var i = 0; i < len; ++i) {
+		if (haveMultiple) {
+			result[i] = result[i].join('|');
+		}
+		
+		toString[i] = arr[i].toString;
+		arr[i].toString = bind(result, sortIndex, i);
+	}
+	
+	Array.prototype.sort.apply(arr);
+	
+	for (var i = 0; i < len; ++i) {
+		arr[i].toString = toString[i];
+	}
+}

daemon/orbited/jsio/math/arrays.js

+jsio('import .util');
+
+exports.weightedAverage = function (a, w, n) {
+	n = n || a.length;
+	var s = 0;
+	for (var i = n - 1; i >= 0; --i) {
+		s += a[i] * w;
+	}
+	return s / n;
+}
+
+exports.subtract = function(a, b) {
+	var length = a.length,
+		diff = new Array(length);
+	for (var i = 0; i < length; ++i) {
+		diff[i] = b[i] - a[i];
+	}
+	return diff;
+}
+
+exports.average = function (a, n) {
+	n = n || a.length;
+	var s = 0;
+	for (var i = n - 1; i >= 0; --i) {
+		s += a[i];
+	}
+	return s / n;
+}
+
+exports.stddev = function (a, n) {
+	var avg = exports.average(a, n);
+	n = n || a.length;
+	var s = 0;
+	for (var i = n - 1; i >= 0; --i) {
+		var diff = (a[i] - avg);
+		s += diff * diff;
+	}
+	return Math.sqrt(sum / (1 - n));
+}
+
+exports.shuffle = function(a, randGen) {
+	var len = a.length;
+	for (var i = 0; i < len; ++i) {
+		var j = util.random(i, len, randGen),
+			temp = a[j];
+		a[j] = a[i];
+		a[i] = temp;
+	}
+	return a;
+}
+
+exports.rotate = function(a, count) {
+	var len = a.length,
+		b = new Array(len),
+		j = count % len;
+	
+	if (j < 0) {
+		j = j % len;
+		if (j) { j += len; }
+	}
+	
+	for (var i = 0; i < len; ++i) {
+		b[i] = a[j];
+		j = (j + 1) % len;
+	}
+	
+	return b;
+}

daemon/orbited/jsio/math/util.js

+jsio('import lib.Enum as Enum');
+
+exports.interpolate = function(a, b, x) { return a * (1 - x) + b * x; }
+
+exports.random = function(a, b, rand) { return a + ((rand || Math.random)() * (b - a) | 0); }
+exports.rand = Math.random;
+exports.int = exports.truncate = function(a) { return a | 0; }
+
+exports.clip = function(num, min, max) { return Math.max(Math.min(num, max), min); }
+
+var round = exports.round = function(a, precision, method) {
+	if (!method || method == round.ROUND_HALF_AWAY_FROM_ZERO) {
+		return a.toFixed(precision);
+	}
+	
+	if(!precision) {
+		if (method == round.ROUND_HALF_UP) { Math.round(a); }
+		
+		var int = a | 0,
+			frac = a - int,
+			half = frac == 0.5 || frac == -0.5;
+		if (!half) { return Math.round(a); }
+		
+		var sign = a < 0 ? -1 : 1;
+		switch(method) {
+			case round.ROUND_HALF_TO_EVEN:
+				return int % 2 ? int + sign : int;
+			case round.ROUND_HALF_TO_ODD:
+				return int % 2 ? int : int + sign;
+			case round.ROUND_HALF_STOCHASTIC:
+				return Math.random() < 0.5 ? int + sign : int;
+			case round.ROUND_HALF_ALTERNATE:
+				return (round.alt = !round.alt) ? int + sign : int;
+		}
+	}
+	
+	var int = a | 0,
+		frac = a - int,
+		p = Math.pow(10, precision);
+	return (int + round(frac * p, 0, method) / p).toFixed(precision);
+}
+
+round.alt = true;
+
+Enum.call(round, 'ROUND_HALF_UP', 'ROUND_HALF_AWAY_FROM_ZERO', 'ROUND_HALF_TO_EVEN', 'ROUND_HALF_STOCHASTIC', 'ROUND_HALF_ALTERNATE');

daemon/orbited/jsio/math2D/Circle.js

+"use import";
+
+import .Point;
+
+exports = Class(Point, function(supr) {
+	this.init = function(a, b, c) {
+		switch(arguments.length) {
+			case 0:
+				this.x = 0;
+				this.y = 0;
+				this.radius = 0;
+				break;
+			case 1:
+			case 2:
+				this.x = a.x || 0;
+				this.y = a.y || 0;
+				this.radius = a.radius || 0;
+				break;
+			case 3:
+				this.x = a;
+				this.y = b;
+				this.radius = c;
+				break;
+		}
+	}
+	
+	this.scale = function(s) {
+		supr(this, 'scale', arguments);
+		this.radius *= s;
+		return this;
+	}
+});

daemon/orbited/jsio/math2D/Line.js

+"use import";
+
+import .Point;
+
+exports = Class(function() {
+	this.init = function(a, b, c, d) {
+		switch(arguments.length) {
+			case 0:
+				this.start = new Point();
+				this.end = new Point();
+				break;
+			case 1:
+				this.start = new Point(a.start);
+				this.end = new Point(a.end);
+				break;
+			case 2:
+				this.start = new Point(a);
+				this.end = new Point(b);
+				break;
+			case 3:
+				this.start = new Point(a);
+				this.end = new Point(b, c);
+				break;
+			case 4:
+			default:
+				this.start = new Point(a, b);
+				this.end = new Point(c, d);
+				break;
+		}
+	}
+	
+	this.getMagnitude = 
+	this.getLength = function() {
+		var dx = this.end.x - this.start.x,
+			dy = this.end.y - this.start.y;
+		
+		return Math.sqrt(dx * dx + dy * dy);
+	}
+});

daemon/orbited/jsio/math2D/Point.js

+var Point = exports = Class(function() {
+	this.init = function(a, b) {
+		switch(arguments.length) {
+			case 0:
+				this.x = 0;
+				this.y = 0;
+				break;
+			case 1:
+				this.x = a.x || 0;
+				this.y = a.y || 0;
+				break;
+			case 2:
+				this.x = a || 0;
+				this.y = b || 0;
+				break;
+		}
+	}
+	
+	this.rotate = function(r) {
+		var x = this.x,
+			y = this.y,
+			cosr = Math.cos(r),
+			sinr = Math.sin(r);
+		
+		this.x = x * cosr - y * sinr;
+		this.y = x * sinr + y * cosr;
+		
+		return this;
+	}
+	
+	this.translate = 
+	this.add = function(x, y) {
+		if (typeof x == 'number') {
+			this.x += x;
+			this.y += y;
+		} else {
+			this.x += x.x;
+			this.y += x.y;
+		}
+		return this;
+	}
+	
+	this.subtract = function(x, y) {
+		if (typeof x == 'number') {
+			this.x -= x;
+			this.y -= y;
+		} else {
+			this.x -= x.x;
+			this.y -= x.y;
+		}
+		return this;
+	}
+
+	this.scale = function(s) {
+		this.x *= s;
+		this.y *= s;
+		return this;
+	}
+	
+	this.setMagnitude = function(m) {
+		var theta = Math.atan2(this.y, this.x);
+		this.x = m * Math.cos(theta);
+		this.y = m * Math.sin(theta);
+		return this;
+	}
+	
+	this.normalize = function() {
+		var m = this.getMagnitude();
+		this.x /= m;
+		this.y /= m;
+		return this;
+	}
+	
+	this.addMagnitude = function(m) { return this.setMagnitude(this.getMagnitude() + m); }
+	this.getMagnitude = function() { return Math.sqrt(this.x * this.x + this.y * this.y); }
+	this.getSquaredMagnitude = function() { return this.x * this.x + this.y * this.y; }
+	this.getDirection =
+	this.getAngle = function() { return Math.atan2(this.y, this.x); }
+	
+});
+
+Point.getPolarR = function(x, y) { 
+	throw "notImplemented";
+}
+
+Point.getPolarTheta = function(x, y) { 
+	var val = Math.atan2(y,x) + (Math.PI * 2); 
+	return val > Math.PI * 2 ? val % (Math.PI * 2) : val;
+}
+
+Point.add = Point.translate = function(a, b, c, d) {
+	switch(arguments.length) {
+		case 2: return new Point(a).add(b);
+		case 3: return new Point(a).add(b, c);
+		case 4: return new Point(a, b).add(c, d);
+	}
+}
+
+Point.subtract = function(a, b, c, d) {
+	switch(arguments.length) {
+		case 2: return new Point(a).subtract(b);
+		case 3: return new Point(a).subtract(b, c);
+		case 4: return new Point(a, b).subtract(c, d);
+	}
+}
+
+Point.scale = function(a, b, c) {
+	switch(arguments.length) {
+		case 2: return new Point(a).scale(b);
+		case 3: return new Point(a, b).scale(c);
+	}
+}
+
+Point.setMagnitude = function(a, b, c) {
+	switch(arguments.length) {
+		case 2: return new Point(a).setMagnitude(c);
+		case 3: return new Point(a, b).setMagnitude(c);
+	}
+}
+
+Point.addMagnitude = function(a, b, c) {
+	switch(arguments.length) {
+		case 2: pt = new Point(a); break;
+		case 3: pt = new Point(a, b); b = c; break;
+	}
+	
+	return pt.addMagnitude(b);
+}
+
+Point.getMagnitude = function(a, b) { return new Point(a, b).getMagnitude(); }
+
+Point.rotate = function(a, b, c) {
+	switch(arguments.length) {
+		case 2: return new Point(a).rotate(b);
+		case 3: return new Point(a, b).rotate(c);
+	}
+}

daemon/orbited/jsio/math2D/Rect.js

+"use import";
+
+import lib.Enum;
+import .Point;
+import .Line;
+
+var Rect = exports = Class(function() {
+	this.init = function(a, b, c, d) {
+		switch(arguments.length) {
+			case 0: // init
+				this.width = this.height = this.x = this.y = 0;
+				break;
+			case 1: // copy
+				this.width = a.width;
+				this.height = a.height;
+				this.x = a.x;
+				this.y = a.y;
+				break;
+			case 2: // (x, y), (width, height)