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