UNPKG

4.42 kBJavaScriptView Raw
1
2/*!
3 * Module dependencies.
4 */
5
6'use strict';
7
8const utils = require('./utils');
9
10/*!
11 * StateMachine represents a minimal `interface` for the
12 * constructors it builds via StateMachine.ctor(...).
13 *
14 * @api private
15 */
16
17const StateMachine = module.exports = exports = function StateMachine() {
18};
19
20/*!
21 * StateMachine.ctor('state1', 'state2', ...)
22 * A factory method for subclassing StateMachine.
23 * The arguments are a list of states. For each state,
24 * the constructor's prototype gets state transition
25 * methods named after each state. These transition methods
26 * place their path argument into the given state.
27 *
28 * @param {String} state
29 * @param {String} [state]
30 * @return {Function} subclass constructor
31 * @private
32 */
33
34StateMachine.ctor = function() {
35 const states = utils.args(arguments);
36
37 const ctor = function() {
38 StateMachine.apply(this, arguments);
39 this.paths = {};
40 this.states = {};
41 this.stateNames = states;
42
43 let i = states.length,
44 state;
45
46 while (i--) {
47 state = states[i];
48 this.states[state] = {};
49 }
50 };
51
52 ctor.prototype = new StateMachine();
53
54 states.forEach(function(state) {
55 // Changes the `path`'s state to `state`.
56 ctor.prototype[state] = function(path) {
57 this._changeState(path, state);
58 };
59 });
60
61 return ctor;
62};
63
64/*!
65 * This function is wrapped by the state change functions:
66 *
67 * - `require(path)`
68 * - `modify(path)`
69 * - `init(path)`
70 *
71 * @api private
72 */
73
74StateMachine.prototype._changeState = function _changeState(path, nextState) {
75 const prevBucket = this.states[this.paths[path]];
76 if (prevBucket) delete prevBucket[path];
77
78 this.paths[path] = nextState;
79 this.states[nextState][path] = true;
80};
81
82/*!
83 * ignore
84 */
85
86StateMachine.prototype.clear = function clear(state) {
87 const keys = Object.keys(this.states[state]);
88 let i = keys.length;
89 let path;
90
91 while (i--) {
92 path = keys[i];
93 delete this.states[state][path];
94 delete this.paths[path];
95 }
96};
97
98/*!
99 * Checks to see if at least one path is in the states passed in via `arguments`
100 * e.g., this.some('required', 'inited')
101 *
102 * @param {String} state that we want to check for.
103 * @private
104 */
105
106StateMachine.prototype.some = function some() {
107 const _this = this;
108 const what = arguments.length ? arguments : this.stateNames;
109 return Array.prototype.some.call(what, function(state) {
110 return Object.keys(_this.states[state]).length;
111 });
112};
113
114/*!
115 * This function builds the functions that get assigned to `forEach` and `map`,
116 * since both of those methods share a lot of the same logic.
117 *
118 * @param {String} iterMethod is either 'forEach' or 'map'
119 * @return {Function}
120 * @api private
121 */
122
123StateMachine.prototype._iter = function _iter(iterMethod) {
124 return function() {
125 const numArgs = arguments.length;
126 let states = utils.args(arguments, 0, numArgs - 1);
127 const callback = arguments[numArgs - 1];
128
129 if (!states.length) states = this.stateNames;
130
131 const _this = this;
132
133 const paths = states.reduce(function(paths, state) {
134 return paths.concat(Object.keys(_this.states[state]));
135 }, []);
136
137 return paths[iterMethod](function(path, i, paths) {
138 return callback(path, i, paths);
139 });
140 };
141};
142
143/*!
144 * Iterates over the paths that belong to one of the parameter states.
145 *
146 * The function profile can look like:
147 * this.forEach(state1, fn); // iterates over all paths in state1
148 * this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2
149 * this.forEach(fn); // iterates over all paths in all states
150 *
151 * @param {String} [state]
152 * @param {String} [state]
153 * @param {Function} callback
154 * @private
155 */
156
157StateMachine.prototype.forEach = function forEach() {
158 this.forEach = this._iter('forEach');
159 return this.forEach.apply(this, arguments);
160};
161
162/*!
163 * Maps over the paths that belong to one of the parameter states.
164 *
165 * The function profile can look like:
166 * this.forEach(state1, fn); // iterates over all paths in state1
167 * this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2
168 * this.forEach(fn); // iterates over all paths in all states
169 *
170 * @param {String} [state]
171 * @param {String} [state]
172 * @param {Function} callback
173 * @return {Array}
174 * @private
175 */
176
177StateMachine.prototype.map = function map() {
178 this.map = this._iter('map');
179 return this.map.apply(this, arguments);
180};