var Traverse = require('traverse'); var EventEmitter = require('events').EventEmitter; module.exports = Chainsaw; function Chainsaw (builder) { var saw = Chainsaw.saw(builder, {}); var r = builder.call(saw.handlers, saw); if (r !== undefined) saw.handlers = r; saw.record(); return saw.chain(); }; Chainsaw.light = function ChainsawLight (builder) { var saw = Chainsaw.saw(builder, {}); var r = builder.call(saw.handlers, saw); if (r !== undefined) saw.handlers = r; return saw.chain(); }; Chainsaw.saw = function (builder, handlers) { var saw = new EventEmitter; saw.handlers = handlers; saw.actions = []; saw.chain = function () { var ch = Traverse(saw.handlers).map(function (node) { if (this.isRoot) return node; var ps = this.path; if (typeof node === 'function') { this.update(function () { saw.actions.push({ path : ps, args : [].slice.call(arguments) }); return ch; }); } }); process.nextTick(function () { saw.emit('begin'); saw.next(); }); return ch; }; saw.pop = function () { return saw.actions.shift(); }; saw.next = function () { var action = saw.pop(); if (!action) { saw.emit('end'); } else if (!action.trap) { var node = saw.handlers; action.path.forEach(function (key) { node = node[key] }); node.apply(saw.handlers, action.args); } }; saw.nest = function (cb) { var args = [].slice.call(arguments, 1); var autonext = true; if (typeof cb === 'boolean') { var autonext = cb; cb = args.shift(); } var s = Chainsaw.saw(builder, {}); var r = builder.call(s.handlers, s); if (r !== undefined) s.handlers = r; // If we are recording... if ("undefined" !== typeof saw.step) { // ... our children should, too s.record(); } cb.apply(s.chain(), args); if (autonext !== false) s.on('end', saw.next); }; saw.record = function () { upgradeChainsaw(saw); }; ['trap', 'down', 'jump'].forEach(function (method) { saw[method] = function () { throw new Error("To use the trap, down and jump features, please "+ "call record() first to start recording actions."); }; }); return saw; }; function upgradeChainsaw(saw) { saw.step = 0; // override pop saw.pop = function () { return saw.actions[saw.step++]; }; saw.trap = function (name, cb) { var ps = Array.isArray(name) ? name : [name]; saw.actions.push({ path : ps, step : saw.step, cb : cb, trap : true }); }; saw.down = function (name) { var ps = (Array.isArray(name) ? name : [name]).join('/'); var i = saw.actions.slice(saw.step).map(function (x) { if (x.trap && x.step <= saw.step) return false; return x.path.join('/') == ps; }).indexOf(true); if (i >= 0) saw.step += i; else saw.step = saw.actions.length; var act = saw.actions[saw.step - 1]; if (act && act.trap) { // It's a trap! saw.step = act.step; act.cb(); } else saw.next(); }; saw.jump = function (step) { saw.step = step; saw.next(); }; };