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); | ||
|  | }; |