You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					181 lines
				
				4.3 KiB
			
		
		
			
		
	
	
					181 lines
				
				4.3 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								
							 | 
						||
| 
								 | 
							
								/*!
							 | 
						||
| 
								 | 
							
								 * Module dependencies.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								'use strict';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const utils = require('./utils');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/*!
							 | 
						||
| 
								 | 
							
								 * StateMachine represents a minimal `interface` for the
							 | 
						||
| 
								 | 
							
								 * constructors it builds via StateMachine.ctor(...).
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @api private
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const StateMachine = module.exports = exports = function StateMachine() {
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/*!
							 | 
						||
| 
								 | 
							
								 * 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() {
							 | 
						||
| 
								 | 
							
								  const states = utils.args(arguments);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const ctor = function() {
							 | 
						||
| 
								 | 
							
								    StateMachine.apply(this, arguments);
							 | 
						||
| 
								 | 
							
								    this.paths = {};
							 | 
						||
| 
								 | 
							
								    this.states = {};
							 | 
						||
| 
								 | 
							
								    this.stateNames = states;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    let i = states.length,
							 | 
						||
| 
								 | 
							
								        state;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    while (i--) {
							 | 
						||
| 
								 | 
							
								      state = states[i];
							 | 
						||
| 
								 | 
							
								      this.states[state] = {};
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  ctor.prototype = new StateMachine();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  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) {
							 | 
						||
| 
								 | 
							
								  const 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) {
							 | 
						||
| 
								 | 
							
								  const keys = Object.keys(this.states[state]);
							 | 
						||
| 
								 | 
							
								  let i = keys.length;
							 | 
						||
| 
								 | 
							
								  let 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() {
							 | 
						||
| 
								 | 
							
								  const _this = this;
							 | 
						||
| 
								 | 
							
								  const what = arguments.length ? arguments : this.stateNames;
							 | 
						||
| 
								 | 
							
								  return Array.prototype.some.call(what, function(state) {
							 | 
						||
| 
								 | 
							
								    return Object.keys(_this.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() {
							 | 
						||
| 
								 | 
							
								    const numArgs = arguments.length;
							 | 
						||
| 
								 | 
							
								    let states = utils.args(arguments, 0, numArgs - 1);
							 | 
						||
| 
								 | 
							
								    const callback = arguments[numArgs - 1];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!states.length) states = this.stateNames;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const _this = this;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const paths = states.reduce(function(paths, state) {
							 | 
						||
| 
								 | 
							
								      return paths.concat(Object.keys(_this.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);
							 | 
						||
| 
								 | 
							
								};
							 |