statemachine.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /*!
  2. * Module dependencies.
  3. */
  4. var utils = require('./utils');
  5. /*!
  6. * StateMachine represents a minimal `interface` for the
  7. * constructors it builds via StateMachine.ctor(...).
  8. *
  9. * @api private
  10. */
  11. var StateMachine = module.exports = exports = function StateMachine () {
  12. }
  13. /*!
  14. * StateMachine.ctor('state1', 'state2', ...)
  15. * A factory method for subclassing StateMachine.
  16. * The arguments are a list of states. For each state,
  17. * the constructor's prototype gets state transition
  18. * methods named after each state. These transition methods
  19. * place their path argument into the given state.
  20. *
  21. * @param {String} state
  22. * @param {String} [state]
  23. * @return {Function} subclass constructor
  24. * @private
  25. */
  26. StateMachine.ctor = function () {
  27. var states = utils.args(arguments);
  28. var ctor = function () {
  29. StateMachine.apply(this, arguments);
  30. this.paths = {};
  31. this.states = {};
  32. this.stateNames = states;
  33. var i = states.length
  34. , state;
  35. while (i--) {
  36. state = states[i];
  37. this.states[state] = {};
  38. }
  39. };
  40. ctor.prototype = new StateMachine();
  41. states.forEach(function (state) {
  42. // Changes the `path`'s state to `state`.
  43. ctor.prototype[state] = function (path) {
  44. this._changeState(path, state);
  45. }
  46. });
  47. return ctor;
  48. };
  49. /*!
  50. * This function is wrapped by the state change functions:
  51. *
  52. * - `require(path)`
  53. * - `modify(path)`
  54. * - `init(path)`
  55. *
  56. * @api private
  57. */
  58. StateMachine.prototype._changeState = function _changeState (path, nextState) {
  59. var prevBucket = this.states[this.paths[path]];
  60. if (prevBucket) delete prevBucket[path];
  61. this.paths[path] = nextState;
  62. this.states[nextState][path] = true;
  63. }
  64. /*!
  65. * ignore
  66. */
  67. StateMachine.prototype.clear = function clear (state) {
  68. var keys = Object.keys(this.states[state])
  69. , i = keys.length
  70. , path
  71. while (i--) {
  72. path = keys[i];
  73. delete this.states[state][path];
  74. delete this.paths[path];
  75. }
  76. }
  77. /*!
  78. * Checks to see if at least one path is in the states passed in via `arguments`
  79. * e.g., this.some('required', 'inited')
  80. *
  81. * @param {String} state that we want to check for.
  82. * @private
  83. */
  84. StateMachine.prototype.some = function some () {
  85. var self = this;
  86. var what = arguments.length ? arguments : this.stateNames;
  87. return Array.prototype.some.call(what, function (state) {
  88. return Object.keys(self.states[state]).length;
  89. });
  90. }
  91. /*!
  92. * This function builds the functions that get assigned to `forEach` and `map`,
  93. * since both of those methods share a lot of the same logic.
  94. *
  95. * @param {String} iterMethod is either 'forEach' or 'map'
  96. * @return {Function}
  97. * @api private
  98. */
  99. StateMachine.prototype._iter = function _iter (iterMethod) {
  100. return function () {
  101. var numArgs = arguments.length
  102. , states = utils.args(arguments, 0, numArgs-1)
  103. , callback = arguments[numArgs-1];
  104. if (!states.length) states = this.stateNames;
  105. var self = this;
  106. var paths = states.reduce(function (paths, state) {
  107. return paths.concat(Object.keys(self.states[state]));
  108. }, []);
  109. return paths[iterMethod](function (path, i, paths) {
  110. return callback(path, i, paths);
  111. });
  112. };
  113. }
  114. /*!
  115. * Iterates over the paths that belong to one of the parameter states.
  116. *
  117. * The function profile can look like:
  118. * this.forEach(state1, fn); // iterates over all paths in state1
  119. * this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2
  120. * this.forEach(fn); // iterates over all paths in all states
  121. *
  122. * @param {String} [state]
  123. * @param {String} [state]
  124. * @param {Function} callback
  125. * @private
  126. */
  127. StateMachine.prototype.forEach = function forEach () {
  128. this.forEach = this._iter('forEach');
  129. return this.forEach.apply(this, arguments);
  130. }
  131. /*!
  132. * Maps over the paths that belong to one of the parameter states.
  133. *
  134. * The function profile can look like:
  135. * this.forEach(state1, fn); // iterates over all paths in state1
  136. * this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2
  137. * this.forEach(fn); // iterates over all paths in all states
  138. *
  139. * @param {String} [state]
  140. * @param {String} [state]
  141. * @param {Function} callback
  142. * @return {Array}
  143. * @private
  144. */
  145. StateMachine.prototype.map = function map () {
  146. this.map = this._iter('map');
  147. return this.map.apply(this, arguments);
  148. }