atbar
Version:
Async callback manager for javascript in nodejs and browser
416 lines • 12.8 kB
JavaScript
/*
coffee -cb /opt/node/bb/dev/node_modules/atbar/atbar.cof
*/var AtbarNode, atbarVersion, _;
var __hasProp = Object.prototype.hasOwnProperty, __slice = Array.prototype.slice;
atbarVersion = "0.4.1";
_ = require('underscore');
_.mixin(require('underscore.string'));
_.mixin(require('underscore.inspector'));
AtbarNode = (function() {
function AtbarNode(parent, label, options, func) {
var attr, _ref, _ref2, _ref3;
this.parent = parent;
this.label = label;
this.options = options;
this.func = func;
this.root = (_ref = (_ref2 = this.parent) != null ? _ref2.root : void 0) != null ? _ref : this;
if (this.parent) {
_ref3 = this.parent;
for (attr in _ref3) {
if (!__hasProp.call(_ref3, attr)) continue;
if (attr[0] === '_') {
this[attr] = this.parent[attr];
}
}
}
this.children = [];
this.activeCallbacks = {};
this.callbackResults = [];
if (this.parent) {
if (this.parent.id !== 'root') {
this.id = this.parent.id + '-' + label;
} else {
this.id = label;
}
} else {
this.id = 'root';
}
}
AtbarNode.prototype._ = function() {
var arg1, args, func, label, options;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
label = this.children.length;
options = null;
switch (args.length) {
case 1:
func = args[0];
break;
case 2:
arg1 = args[0], func = args[1];
switch (typeof arg1) {
case 'string':
label = arg1;
break;
case 'object':
options = arg1;
if (options.label) {
label = options.label;
}
break;
default:
this.execErr('first @_ arg has invalid type');
return;
}
break;
default:
this.execErr('@_ has wrong number of arguments');
return;
}
if (typeof func !== 'function') {
this.execErr('last @_ arg not a function');
return;
}
if (label === 'atbar' || label === 'root' || label === 'first' || label === 'loop' || label === 'next' || label === 'throw' || label === 'enter' || label === 'exit') {
this.execErr(label + ' is a reserved word');
return;
}
return this.children.push(new AtbarNode(this, label, options, func));
};
AtbarNode.prototype.$ = function() {
var arg, args, callbackInfo, data, destNode, destSpec, firstChild, i, level, levelNode, node, seqNum, srcLabel, strings, _i, _len, _ref;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
level = 0;
strings = [];
data = null;
for (_i = 0, _len = args.length; _i < _len; _i++) {
arg = args[_i];
switch (typeof arg) {
case 'number':
level = arg;
break;
case 'string':
strings.unshift(arg);
break;
case 'object':
data = arg;
break;
default:
this.execErr('invalid arg type for callback generator: ' + typeof arg);
return function() {};
}
}
destSpec = strings[0], srcLabel = strings[1];
if (destSpec === 'first') {
level++;
}
levelNode = this;
for (i = 0; 0 <= level ? i < level : i > level; 0 <= level ? i++ : i--) {
levelNode = levelNode != null ? levelNode.parent : void 0;
}
if (!levelNode) {
this.execErr('callback level param above top level');
return function() {};
}
destNode = levelNode;
if (destSpec != null) {
destSpec;
} else {
destSpec = 'next';
};
if (destSpec === 'throw') {
destSpec = 'catch';
}
if (destSpec !== 'loop') {
switch (destSpec) {
case 'root':
destNode = this.root.children[0];
break;
case 'enter':
case 'first':
if (!(firstChild = destNode.children[0])) {
return function() {};
}
destNode = firstChild;
break;
case 'next':
destNode = destNode.nextNode();
break;
case 'exit':
destNode = this.root;
break;
default:
while ((destNode = destNode.nextNode())) {
if (destNode.label === destSpec || destNode === this.root) {
break;
}
}
}
}
if (destNode === this.root) {
node = this;
if (destSpec === 'catch') {
return (function(node) {
return function(err) {
return node.exit(err, null);
};
})(node);
} else if (destSpec !== 'next' && destSpec !== 'root' && destSpec !== 'first' && destSpec !== 'loop' && destSpec !== 'enter' && destSpec !== 'exit') {
this.execErr('Destination for ' + destSpec + ' not found');
return function() {};
}
}
seqNum = this.root.nextCbSeqNum++;
callbackInfo = {
seqNum: seqNum,
srcNode: this,
destNode: destNode,
level: level,
srcLabel: srcLabel,
destSpec: destSpec,
data: data
};
if (!((_ref = destNode.options) != null ? _ref.nojoin : void 0)) {
destNode.activeCallbacks[seqNum] = callbackInfo;
this.root.totalActiveCallbacks++;
}
return (function(callbackInfo) {
return function() {
var cbArgs;
cbArgs = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
callbackInfo.cbArgs = cbArgs;
return callbackInfo.destNode.call(callbackInfo);
};
})(callbackInfo);
};
AtbarNode.prototype.call = function(callbackInfo) {
var cbArgs, data, destNode, destSpec, idx, param, seqNum, srcLabel, srcNode, tpi, _i, _len, _len2, _ref, _ref2, _ref3;
seqNum = callbackInfo.seqNum, srcNode = callbackInfo.srcNode, srcLabel = callbackInfo.srcLabel, destNode = callbackInfo.destNode, destSpec = callbackInfo.destSpec, cbArgs = callbackInfo.cbArgs, data = callbackInfo.data;
if (this.root.finished) {
if (this._trace) {
this.printNode(srcNode.id, destSpec, true, false, 'callback after atbar finished');
}
return;
}
if (!((_ref = this.options) != null ? _ref.nojoin : void 0)) {
if (!this.activeCallbacks[seqNum]) {
if (this._trace) {
this.printNode(srcNode.id, destSpec, true, false, 'unregistered or duplicate callback');
}
return;
}
this.root.totalActiveCallbacks--;
delete this.activeCallbacks[seqNum];
}
if (this.callbacksComplete) {
this.callbackResults = [];
}
this.data = data;
this.callbackResults.push(callbackInfo);
this.callbacksComplete = _.size(this.activeCallbacks) === 0;
if (this === this.root && destSpec === 'exit') {
this.exit(null, this.callbackResults);
return;
}
if (cbArgs.length) {
if (!this.throwParamIndexes) {
this.throwParamIndexes = [];
_ref2 = this.func.toString().match(/^\s*function\s*\(([^)]*)\)/i)[1].split(',');
for (idx = 0, _len = _ref2.length; idx < _len; idx++) {
param = _ref2[idx];
if (_.trim(param).toLowerCase() === '_throw') {
this.throwParamIndexes.push(idx);
}
}
}
_ref3 = this.throwParamIndexes;
for (_i = 0, _len2 = _ref3.length; _i < _len2; _i++) {
tpi = _ref3[_i];
if (cbArgs[tpi]) {
this["throw"](cbArgs[tpi]);
return;
}
}
}
if (!this.callbacksComplete) {
if (this._trace) {
this.printNode(srcNode.id, destSpec, true, false, 'Callback ignored, waiting for ' + _.size(this.activeCallbacks) + ' more');
}
return;
}
if (this === this.root && destSpec !== 'loop') {
this.exitIfIdle(null, cbArgs, srcNode.id);
return;
}
if (this._trace) {
if (srcLabel === 'atbar') {
console.log("Starting atbar trace (Version " + atbarVersion + ") ...");
} else {
this.printNode(srcNode.id, destSpec, true, false);
}
}
try {
this.func.apply(this, cbArgs);
} catch (e) {
if (this.root.finished) {
throw e;
}
this.$('throw')(e);
}
if (this.children.length) {
return this.$('enter')();
}
};
AtbarNode.prototype.nextNode = function() {
var idx, nextNode, node, parent, sibling, siblings, _len, _ref;
node = this;
while (node) {
siblings = (_ref = node.parent) != null ? _ref.children : void 0;
if (siblings) {
for (idx = 0, _len = siblings.length; idx < _len; idx++) {
sibling = siblings[idx];
if (sibling === node && (nextNode = siblings[idx + 1])) {
return nextNode;
}
}
}
if ((parent = node.parent)) {
return parent.nextNode();
} else {
break;
}
}
return this.root;
};
AtbarNode.prototype.exit = function(err, resp) {
this.root.finished = true;
this.listing();
if (this.root.exitCallback) {
return this.root.exitCallback(err, resp);
} else if (err) {
throw err;
}
};
AtbarNode.prototype.exitIfIdle = function(err, resp, id) {
if (this.root.totalActiveCallbacks === 0) {
if (this._trace && id) {
this.printNode(id, null, true, false, 'Nothing to do, exiting ...');
}
return this.exit(err, resp);
}
};
AtbarNode.prototype["throw"] = function(e) {
return this.$('throw')(e);
};
AtbarNode.prototype.execErr = function(msg) {
msg = "atbar execution error at " + this.id + ": " + msg;
return this.$('throw')(msg);
};
AtbarNode.prototype.log = function() {
var arg, args, dump, msg, _i, _len;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
if (this._debug) {
msg = '';
if (typeof args[0] === 'string') {
msg = args.shift();
}
dump = '';
for (_i = 0, _len = args.length; _i < _len; _i++) {
arg = args[_i];
dump += '\n' + _.inspect(arg, 1);
}
return console.log(("\natbar log at " + this.id + ": ") + msg + dump);
}
};
AtbarNode.prototype.logx = function() {};
AtbarNode.prototype.printNode = function(src, label, trace, returnText, comment) {
var code, line, time;
if (src == null) {
src = '';
}
if (comment) {
code = comment;
} else {
code = this.func.toString().replace(/\s+/g, ' ').replace(/^\s*function\s*/, '');
if (code.length > 80) {
code = code.slice(0, 80) + ' ...';
}
}
src = src + (label ? '(' + label + ')' : '');
if (trace) {
time = (+(new Date) - this.root.startTime) / 1e3;
line = _.sprintf('%8.3f %-3d %12s -> %-7s %s', time, this.root.totalActiveCallbacks, src, this.id, code);
} else {
line = _.sprintf('%-10s %s', this.id, code);
}
if (returnText) {
return line + '\n';
} else {
return console.log(line);
}
};
AtbarNode.prototype.treeText = function() {
var text, treeTextRecurse;
text = '';
treeTextRecurse = function(node, depth) {
var child, i, pad, _i, _len, _ref;
if (node.label !== 'root') {
pad = '';
for (i = 0; 0 <= depth ? i < depth : i > depth; 0 <= depth ? i++ : i--) {
pad = ' ' + pad;
}
text += pad + node.printNode(null, null, false, true);
}
_ref = node.children;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
child = _ref[_i];
treeTextRecurse(child, depth + 1);
}
return null;
};
treeTextRecurse(this.root, 1);
return text;
};
AtbarNode.prototype.listing = function() {
if (this._listing) {
console.log('\nListing of visited atbar functions ...');
return console.log(this.treeText());
}
};
return AtbarNode;
})();
exports.run = function() {
var arg, args, exitCallback, func, funcs, opt, options, root, _i, _j, _len, _len2, _ref;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
options = _.trim((_ref = exports.options) != null ? _ref : '').split(/\W+/);
funcs = [];
for (_i = 0, _len = args.length; _i < _len; _i++) {
arg = args[_i];
switch (typeof arg) {
case 'function':
funcs.unshift(arg);
break;
case 'string':
options.push(arg);
}
}
if (funcs.length > 1) {
func = funcs[1];
exitCallback = funcs[0];
} else {
func = funcs[0];
}
if (func) {
root = new AtbarNode(null, 'root', null, func);
root.nextCbSeqNum = 0;
root.totalActiveCallbacks = 0;
root.exitCallback = exitCallback;
for (_j = 0, _len2 = options.length; _j < _len2; _j++) {
opt = options[_j];
if (opt) {
root['_' + opt] = true;
}
}
root.startTime = +(new Date);
return root.$('atbar', 'loop')();
}
};