/*! * 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); }