|
|
/*! * Module dependencies. */
var utils = require('./utils');
/*! * StateMachine represents a minimal `interface` for the * constructors it builds via StateMachine.ctor(...). * * @api private */
var StateMachine = module.exports = exports = function StateMachine () { this.paths = {}; this.states = {}; }
/*! * StateMachine.ctor('state1', 'state2', ...) * A factory method for subclassing StateMachine. * The arguments are a list of states. For each state, * the constructor's prototype gets state transition * methods named after each state. These transition methods * place their path argument into the given state. * * @param {String} state * @param {String} [state] * @return {Function} subclass constructor * @private */
StateMachine.ctor = function () { var states = utils.args(arguments);
var ctor = function () { StateMachine.apply(this, arguments); this.stateNames = states;
var i = states.length , state;
while (i--) { state = states[i]; this.states[state] = {}; } };
ctor.prototype.__proto__ = StateMachine.prototype;
states.forEach(function (state) { // Changes the `path`'s state to `state`.
ctor.prototype[state] = function (path) { this._changeState(path, state); } });
return ctor; };
/*! * This function is wrapped by the state change functions: * * - `require(path)` * - `modify(path)` * - `init(path)` * * @api private */
StateMachine.prototype._changeState = function _changeState (path, nextState) { var prevBucket = this.states[this.paths[path]]; if (prevBucket) delete prevBucket[path];
this.paths[path] = nextState; this.states[nextState][path] = true; }
/*! * ignore */
StateMachine.prototype.clear = function clear (state) { var keys = Object.keys(this.states[state]) , i = keys.length , path
while (i--) { path = keys[i]; delete this.states[state][path]; delete this.paths[path]; } }
/*! * Checks to see if at least one path is in the states passed in via `arguments` * e.g., this.some('required', 'inited') * * @param {String} state that we want to check for. * @private */
StateMachine.prototype.some = function some () { var self = this; var what = arguments.length ? arguments : this.stateNames; return Array.prototype.some.call(what, function (state) { return Object.keys(self.states[state]).length; }); }
/*! * This function builds the functions that get assigned to `forEach` and `map`, * since both of those methods share a lot of the same logic. * * @param {String} iterMethod is either 'forEach' or 'map' * @return {Function} * @api private */
StateMachine.prototype._iter = function _iter (iterMethod) { return function () { var numArgs = arguments.length , states = utils.args(arguments, 0, numArgs-1) , callback = arguments[numArgs-1];
if (!states.length) states = this.stateNames;
var self = this;
var paths = states.reduce(function (paths, state) { return paths.concat(Object.keys(self.states[state])); }, []);
return paths[iterMethod](function (path, i, paths) { return callback(path, i, paths); }); }; }
/*! * Iterates over the paths that belong to one of the parameter states. * * The function profile can look like: * this.forEach(state1, fn); // iterates over all paths in state1
* this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2
* this.forEach(fn); // iterates over all paths in all states
* * @param {String} [state] * @param {String} [state] * @param {Function} callback * @private */
StateMachine.prototype.forEach = function forEach () { this.forEach = this._iter('forEach'); return this.forEach.apply(this, arguments); }
/*! * Maps over the paths that belong to one of the parameter states. * * The function profile can look like: * this.forEach(state1, fn); // iterates over all paths in state1
* this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2
* this.forEach(fn); // iterates over all paths in all states
* * @param {String} [state] * @param {String} [state] * @param {Function} callback * @return {Array} * @private */
StateMachine.prototype.map = function map () { this.map = this._iter('map'); return this.map.apply(this, arguments); }
|