index.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. var Traverse = require('traverse');
  2. var EventEmitter = require('events').EventEmitter;
  3. module.exports = Chainsaw;
  4. function Chainsaw (builder) {
  5. var saw = Chainsaw.saw(builder, {});
  6. var r = builder.call(saw.handlers, saw);
  7. if (r !== undefined) saw.handlers = r;
  8. saw.record();
  9. return saw.chain();
  10. };
  11. Chainsaw.light = function ChainsawLight (builder) {
  12. var saw = Chainsaw.saw(builder, {});
  13. var r = builder.call(saw.handlers, saw);
  14. if (r !== undefined) saw.handlers = r;
  15. return saw.chain();
  16. };
  17. Chainsaw.saw = function (builder, handlers) {
  18. var saw = new EventEmitter;
  19. saw.handlers = handlers;
  20. saw.actions = [];
  21. saw.chain = function () {
  22. var ch = Traverse(saw.handlers).map(function (node) {
  23. if (this.isRoot) return node;
  24. var ps = this.path;
  25. if (typeof node === 'function') {
  26. this.update(function () {
  27. saw.actions.push({
  28. path : ps,
  29. args : [].slice.call(arguments)
  30. });
  31. return ch;
  32. });
  33. }
  34. });
  35. process.nextTick(function () {
  36. saw.emit('begin');
  37. saw.next();
  38. });
  39. return ch;
  40. };
  41. saw.pop = function () {
  42. return saw.actions.shift();
  43. };
  44. saw.next = function () {
  45. var action = saw.pop();
  46. if (!action) {
  47. saw.emit('end');
  48. }
  49. else if (!action.trap) {
  50. var node = saw.handlers;
  51. action.path.forEach(function (key) { node = node[key] });
  52. node.apply(saw.handlers, action.args);
  53. }
  54. };
  55. saw.nest = function (cb) {
  56. var args = [].slice.call(arguments, 1);
  57. var autonext = true;
  58. if (typeof cb === 'boolean') {
  59. var autonext = cb;
  60. cb = args.shift();
  61. }
  62. var s = Chainsaw.saw(builder, {});
  63. var r = builder.call(s.handlers, s);
  64. if (r !== undefined) s.handlers = r;
  65. // If we are recording...
  66. if ("undefined" !== typeof saw.step) {
  67. // ... our children should, too
  68. s.record();
  69. }
  70. cb.apply(s.chain(), args);
  71. if (autonext !== false) s.on('end', saw.next);
  72. };
  73. saw.record = function () {
  74. upgradeChainsaw(saw);
  75. };
  76. ['trap', 'down', 'jump'].forEach(function (method) {
  77. saw[method] = function () {
  78. throw new Error("To use the trap, down and jump features, please "+
  79. "call record() first to start recording actions.");
  80. };
  81. });
  82. return saw;
  83. };
  84. function upgradeChainsaw(saw) {
  85. saw.step = 0;
  86. // override pop
  87. saw.pop = function () {
  88. return saw.actions[saw.step++];
  89. };
  90. saw.trap = function (name, cb) {
  91. var ps = Array.isArray(name) ? name : [name];
  92. saw.actions.push({
  93. path : ps,
  94. step : saw.step,
  95. cb : cb,
  96. trap : true
  97. });
  98. };
  99. saw.down = function (name) {
  100. var ps = (Array.isArray(name) ? name : [name]).join('/');
  101. var i = saw.actions.slice(saw.step).map(function (x) {
  102. if (x.trap && x.step <= saw.step) return false;
  103. return x.path.join('/') == ps;
  104. }).indexOf(true);
  105. if (i >= 0) saw.step += i;
  106. else saw.step = saw.actions.length;
  107. var act = saw.actions[saw.step - 1];
  108. if (act && act.trap) {
  109. // It's a trap!
  110. saw.step = act.step;
  111. act.cb();
  112. }
  113. else saw.next();
  114. };
  115. saw.jump = function (step) {
  116. saw.step = step;
  117. saw.next();
  118. };
  119. };