tape-six 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +11 -0
- package/README.md +9 -0
- package/bin/tape6-server.js +196 -0
- package/bin/tape6.js +187 -0
- package/index.js +74 -0
- package/package.json +57 -0
- package/src/State.js +93 -0
- package/src/TTYReporter.js +270 -0
- package/src/TapReporter.js +126 -0
- package/src/Tester.js +411 -0
- package/src/deep6/env.js +174 -0
- package/src/deep6/index.js +21 -0
- package/src/deep6/traverse/assemble.js +158 -0
- package/src/deep6/traverse/clone.js +109 -0
- package/src/deep6/traverse/deref.js +104 -0
- package/src/deep6/traverse/preprocess.js +112 -0
- package/src/deep6/traverse/walk.js +262 -0
- package/src/deep6/unifiers/matchCondition.js +16 -0
- package/src/deep6/unifiers/matchInstanceOf.js +16 -0
- package/src/deep6/unifiers/matchString.js +33 -0
- package/src/deep6/unifiers/matchTypeOf.js +16 -0
- package/src/deep6/unifiers/ref.js +21 -0
- package/src/deep6/unify.js +446 -0
- package/src/deep6/utils/replaceVars.js +24 -0
- package/src/node/TestWorker.js +37 -0
- package/src/node/config.js +56 -0
- package/src/node/listing.js +78 -0
- package/src/test.js +187 -0
- package/src/utils/Deferred.js +10 -0
- package/src/utils/EventServer.js +78 -0
- package/src/utils/box.js +101 -0
- package/src/utils/defer.js +33 -0
- package/src/utils/fileSets.js +113 -0
- package/src/utils/formatters.js +30 -0
- package/src/utils/timeout.js +3 -0
- package/src/utils/timer.js +24 -0
- package/src/utils/yamlFormatter.js +150 -0
package/src/test.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {selectTimer} from './utils/timer.js';
|
|
2
|
+
import State, {StopTest} from './State.js';
|
|
3
|
+
import Tester from './Tester.js';
|
|
4
|
+
import Deferred from './utils/Deferred.js';
|
|
5
|
+
import timeout from './utils/timeout.js';
|
|
6
|
+
import {formatTime} from './utils/formatters.js';
|
|
7
|
+
|
|
8
|
+
let tests = [],
|
|
9
|
+
reporter = null,
|
|
10
|
+
testCounter = 0,
|
|
11
|
+
isConfigured = false;
|
|
12
|
+
|
|
13
|
+
export const getConfiguredFlag = () => isConfigured;
|
|
14
|
+
export const setConfiguredFlag = value => (isConfigured = !!value);
|
|
15
|
+
|
|
16
|
+
const processArgs = (name, options, testFn) => {
|
|
17
|
+
// normalize arguments
|
|
18
|
+
if (typeof name == 'function') {
|
|
19
|
+
testFn = name;
|
|
20
|
+
options = null;
|
|
21
|
+
name = '';
|
|
22
|
+
} else if (typeof name == 'object') {
|
|
23
|
+
testFn = options;
|
|
24
|
+
options = name;
|
|
25
|
+
name = null;
|
|
26
|
+
}
|
|
27
|
+
if (typeof options == 'function') {
|
|
28
|
+
testFn = options;
|
|
29
|
+
options = null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// normalize options
|
|
33
|
+
options = {...options};
|
|
34
|
+
if (name && typeof name == 'string') {
|
|
35
|
+
options.name = name;
|
|
36
|
+
}
|
|
37
|
+
if (testFn && typeof testFn == 'function') {
|
|
38
|
+
options.testFn = testFn;
|
|
39
|
+
}
|
|
40
|
+
if (!options.name && typeof options.testFn == 'function' && options.testFn.name) {
|
|
41
|
+
options.name = options.testFn.name;
|
|
42
|
+
}
|
|
43
|
+
if (!options.name) {
|
|
44
|
+
options.name = '(anonymous)';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return options;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
let isTimerSet = false;
|
|
51
|
+
export const test = async (name, options, testFn) => {
|
|
52
|
+
options = processArgs(name, options, testFn);
|
|
53
|
+
if (!isTimerSet) {
|
|
54
|
+
await selectTimer();
|
|
55
|
+
isTimerSet = true;
|
|
56
|
+
}
|
|
57
|
+
const deferred = new Deferred();
|
|
58
|
+
tests.push({options, deferred});
|
|
59
|
+
return deferred.promise;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const getTests = () => tests;
|
|
63
|
+
export const clearTests = () => (tests = []);
|
|
64
|
+
|
|
65
|
+
export const getReporter = () => reporter;
|
|
66
|
+
export const setReporter = newReporter => (reporter = newReporter);
|
|
67
|
+
|
|
68
|
+
export const runTests = async (rootState, tests) => {
|
|
69
|
+
for (let i = 0; i < tests.length; ++i) {
|
|
70
|
+
const {options, deferred} = tests[i],
|
|
71
|
+
testNumber = ++testCounter,
|
|
72
|
+
state = new State(rootState, options),
|
|
73
|
+
tester = new Tester(state, testNumber);
|
|
74
|
+
if (state.skip) {
|
|
75
|
+
tester.comment('SKIP test: ' + options.name);
|
|
76
|
+
} else {
|
|
77
|
+
try {
|
|
78
|
+
state.emit({type: 'test', name: options.name, test: testNumber, time: state.timer.now()});
|
|
79
|
+
if (options.skip) {
|
|
80
|
+
state.emit({type: 'comment', name: 'SKIP test: ' + options.name, test: testNumber, time: state.timer.now()});
|
|
81
|
+
} else if (options.testFn) {
|
|
82
|
+
if (options.timeout && !isNaN(options.timeout) && options.timeout > 0) {
|
|
83
|
+
const result = options.testFn(tester);
|
|
84
|
+
if (result && typeof result == 'object' && typeof result.then == 'function') {
|
|
85
|
+
const timedOut = await Promise.race([result.then(() => false), timeout(options.timeout).then(() => true)]);
|
|
86
|
+
if (timedOut) {
|
|
87
|
+
state.emit({
|
|
88
|
+
type: 'comment',
|
|
89
|
+
name: 'TIMED OUT after ' + formatTime(options.timeout) + ', test: ' + options.name,
|
|
90
|
+
test: testNumber,
|
|
91
|
+
time: state.timer.now()
|
|
92
|
+
});
|
|
93
|
+
await result;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
await options.testFn(tester);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (!(error instanceof StopTest)) {
|
|
102
|
+
state.emit({
|
|
103
|
+
name: 'UNEXPECTED EXCEPTION: ' + String(error),
|
|
104
|
+
test: testNumber,
|
|
105
|
+
marker: new Error(),
|
|
106
|
+
time: state.timer.now(),
|
|
107
|
+
operator: 'error',
|
|
108
|
+
fail: true,
|
|
109
|
+
data: {
|
|
110
|
+
actual: error
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
state.emit({type: 'end', name: options.name, test: testNumber, time: state.timer.now(), fail: state.failed > 0, data: state});
|
|
116
|
+
state.updateParent();
|
|
117
|
+
}
|
|
118
|
+
deferred && deferred.resolve(state);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
test.skip = function skip(name, options, testFn) {
|
|
123
|
+
options = processArgs(name, options, testFn);
|
|
124
|
+
return test({...options, skip: true});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
test.todo = function todo(name, options, testFn) {
|
|
128
|
+
options = processArgs(name, options, testFn);
|
|
129
|
+
return test({...options, todo: true});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
test.asPromise = function asPromise(name, options, testFn) {
|
|
133
|
+
options = processArgs(name, options, testFn);
|
|
134
|
+
if (options.testFn) {
|
|
135
|
+
const testFn = options.testFn;
|
|
136
|
+
options.testFn = tester => new Promise((resolve, reject) => {
|
|
137
|
+
try {
|
|
138
|
+
testFn(tester, resolve, reject)
|
|
139
|
+
} catch (error) {
|
|
140
|
+
reject(error);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return test(options);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// test() (an embedded test runner) is added here to ./Tester.js to avoid circular dependencies
|
|
148
|
+
|
|
149
|
+
Tester.prototype.test = async function test(name, options, testFn) {
|
|
150
|
+
options = processArgs(name, options, testFn);
|
|
151
|
+
if (this.state.skip) {
|
|
152
|
+
this.comment('SKIP test: ' + options.name);
|
|
153
|
+
} else {
|
|
154
|
+
await runTests(this.state, [{options}]);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
Tester.prototype.skip = async function skip(name, options, testFn) {
|
|
159
|
+
options = processArgs(name, options, testFn);
|
|
160
|
+
this.comment('SKIP test: ' + options.name);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
Tester.prototype.todo = async function todo(name, options, testFn) {
|
|
164
|
+
options = processArgs(name, options, testFn);
|
|
165
|
+
if (this.state.skip) {
|
|
166
|
+
this.comment('SKIP test: ' + options.name);
|
|
167
|
+
} else {
|
|
168
|
+
await runTests(this.state, [{options: {...options, todo: true}}]);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
Tester.prototype.asPromise = async function asPromise(name, options, testFn) {
|
|
173
|
+
options = processArgs(name, options, testFn);
|
|
174
|
+
if (options.testFn) {
|
|
175
|
+
const testFn = options.testFn;
|
|
176
|
+
options.testFn = tester => new Promise((resolve, reject) => {
|
|
177
|
+
try {
|
|
178
|
+
testFn(tester, resolve, reject)
|
|
179
|
+
} catch (error) {
|
|
180
|
+
reject(error);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
await runTests(this.state, [{options}]);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export default test;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import defer from './defer.js';
|
|
2
|
+
|
|
3
|
+
export default class EventServer {
|
|
4
|
+
constructor(reporter, numberOfTasks = 1, options = {}) {
|
|
5
|
+
this.reporter = reporter;
|
|
6
|
+
this.numberOfTasks = numberOfTasks;
|
|
7
|
+
this.options = options;
|
|
8
|
+
|
|
9
|
+
this.totalTasks = 0;
|
|
10
|
+
this.fileQueue = [];
|
|
11
|
+
this.passThroughId = null;
|
|
12
|
+
this.backlog = {};
|
|
13
|
+
this.closed = {};
|
|
14
|
+
}
|
|
15
|
+
report(id, event) {
|
|
16
|
+
if (this.passThroughId === null) this.passThroughId = id;
|
|
17
|
+
if (this.passThroughId === id) return this.reporter(event);
|
|
18
|
+
const events = this.backlog[id];
|
|
19
|
+
if (Array.isArray(events)) {
|
|
20
|
+
events.push(event);
|
|
21
|
+
} else {
|
|
22
|
+
this.backlog[id] = [event];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
close(id) {
|
|
26
|
+
this.destroyTask(id);
|
|
27
|
+
--this.totalTasks;
|
|
28
|
+
if (this.fileQueue.length) {
|
|
29
|
+
++this.totalTasks;
|
|
30
|
+
const nextFile = this.fileQueue.shift();
|
|
31
|
+
defer(() => {
|
|
32
|
+
const id = this.makeTask(nextFile);
|
|
33
|
+
this.report(id, {type: 'comment', name: 'FILE: /' + nextFile, test: 0});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (this.passThroughId === id) {
|
|
37
|
+
this.passThroughId = null;
|
|
38
|
+
Object.keys(this.closed).forEach(id => {
|
|
39
|
+
const events = this.backlog[id];
|
|
40
|
+
if (!events) return;
|
|
41
|
+
events.forEach(event => this.reporter(event));
|
|
42
|
+
delete this.backlog[id];
|
|
43
|
+
});
|
|
44
|
+
this.closed = {};
|
|
45
|
+
const ids = Object.keys(this.backlog);
|
|
46
|
+
if (ids.length) {
|
|
47
|
+
const id = (this.passThroughId = ids[0]),
|
|
48
|
+
events = this.backlog[id];
|
|
49
|
+
if (events) {
|
|
50
|
+
events.forEach(event => this.reporter(event));
|
|
51
|
+
delete this.backlog[id];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
this.closed[id] = 1;
|
|
56
|
+
}
|
|
57
|
+
!this.totalTasks && this.done && this.done();
|
|
58
|
+
}
|
|
59
|
+
createTask(fileName) {
|
|
60
|
+
if (this.totalTasks < this.numberOfTasks) {
|
|
61
|
+
++this.totalTasks;
|
|
62
|
+
const id = this.makeTask(fileName);
|
|
63
|
+
this.report(id, {type: 'comment', name: 'FILE: /' + fileName, test: 0});
|
|
64
|
+
} else {
|
|
65
|
+
this.fileQueue.push(fileName);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
execute(files) {
|
|
69
|
+
files.forEach(fileName => this.createTask(fileName));
|
|
70
|
+
}
|
|
71
|
+
makeTask(fileName) {
|
|
72
|
+
// TBD in children
|
|
73
|
+
// should return a task id as a string
|
|
74
|
+
}
|
|
75
|
+
destroyTask(id) {
|
|
76
|
+
// TBD in children
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/utils/box.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export const stringRep = (n, str = ' ') => {
|
|
2
|
+
n = Math.max(0, Math.floor(n));
|
|
3
|
+
if (!n) return '';
|
|
4
|
+
let s = str,
|
|
5
|
+
buffer = '';
|
|
6
|
+
for (;;) {
|
|
7
|
+
n & 1 && (buffer += s);
|
|
8
|
+
n >>= 1;
|
|
9
|
+
if (!n) break;
|
|
10
|
+
s += s;
|
|
11
|
+
}
|
|
12
|
+
return buffer;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const findEscSequence = /\x1B(?:\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]|[\x20-\x2F]*[\x30-\x7E])/g;
|
|
16
|
+
export const getLength = str => String(str).replace(findEscSequence, '').length;
|
|
17
|
+
|
|
18
|
+
export const normalizeBox = (strings, symbol = ' ', align = 'right') => {
|
|
19
|
+
const maxLength = strings.reduce((acc, s) => Math.max(acc, getLength(s)), 0);
|
|
20
|
+
return strings.map(s => {
|
|
21
|
+
const padding = stringRep(maxLength - getLength(s), symbol);
|
|
22
|
+
switch (align) {
|
|
23
|
+
case 'left':
|
|
24
|
+
return padding + s;
|
|
25
|
+
case 'center':
|
|
26
|
+
const half = padding.length >> 1;
|
|
27
|
+
return padding.substr(0, half) + s + padding.substr(half);
|
|
28
|
+
}
|
|
29
|
+
return s + padding;
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const padBoxRight = (strings, n, symbol = ' ') => {
|
|
34
|
+
const padding = stringRep(n, symbol);
|
|
35
|
+
return strings.map(s => s + padding);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const padBoxLeft = (strings, n, symbol = ' ') => {
|
|
39
|
+
const padding = stringRep(n, symbol);
|
|
40
|
+
return strings.map(s => padding + s);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const padBoxTop = (strings, n, symbol = ' ') => {
|
|
44
|
+
const string = stringRep(getLength(strings[0]), symbol),
|
|
45
|
+
result = [];
|
|
46
|
+
for (; n > 0; --n) result.push(string);
|
|
47
|
+
strings.forEach(s => result.push(s));
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const padBoxBottom = (strings, n, symbol = ' ') => {
|
|
52
|
+
const string = stringRep(getLength(strings[strings.length - 1]), symbol),
|
|
53
|
+
result = [...strings];
|
|
54
|
+
for (; n > 0; --n) result.push(string);
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const padBox = (strings, t, r, b, l, symbol) => {
|
|
59
|
+
if (typeof r != 'number') {
|
|
60
|
+
symbol = r;
|
|
61
|
+
r = b = l = t;
|
|
62
|
+
} else if (typeof b != 'number') {
|
|
63
|
+
symbol = b;
|
|
64
|
+
l = r;
|
|
65
|
+
b = t;
|
|
66
|
+
} else if (typeof l != 'number') {
|
|
67
|
+
symbol = l;
|
|
68
|
+
l = r;
|
|
69
|
+
}
|
|
70
|
+
if (typeof symbol != 'string') symbol = ' ';
|
|
71
|
+
if (t > 0) strings = padBoxTop(strings, t, symbol);
|
|
72
|
+
if (b > 0) strings = padBoxBottom(strings, b, symbol);
|
|
73
|
+
if (r > 0) strings = padBoxRight(strings, r, symbol);
|
|
74
|
+
if (l > 0) strings = padBoxLeft(strings, l, symbol);
|
|
75
|
+
return strings;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const drawBox = strings => {
|
|
79
|
+
const maxLength = strings.reduce((acc, s) => Math.max(acc, getLength(s)), 0),
|
|
80
|
+
line = stringRep(maxLength, '\u2500'),
|
|
81
|
+
result = ['\u256D' + line + '\u256E'];
|
|
82
|
+
strings.forEach(s => result.push('\u2502' + s + '\u2502'));
|
|
83
|
+
result.push('\u2570' + line + '\u256F');
|
|
84
|
+
return result;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const stackVertically = (a, b) => [...a, ...b];
|
|
88
|
+
|
|
89
|
+
export const stackHorizontally = (a, b, symbol = ' ') => {
|
|
90
|
+
const n = Math.min(a.length, b.length),
|
|
91
|
+
result = [];
|
|
92
|
+
for (let i = 0; i < n; ++i) result.push(a[i] + b[i]);
|
|
93
|
+
if (a.length < b.length) {
|
|
94
|
+
const maxLength = a.reduce((acc, s) => Math.max(acc, getLength(s)), 0),
|
|
95
|
+
string = stringRep(maxLength, symbol);
|
|
96
|
+
for (let i = n; i < b.length; ++i) result.push(string + b[i]);
|
|
97
|
+
} else {
|
|
98
|
+
for (let i = n; i < a.length; ++i) result.push(a[i]);
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
let deferImplementation;
|
|
2
|
+
|
|
3
|
+
// initialize the variable
|
|
4
|
+
if (typeof setImmediate == 'function') {
|
|
5
|
+
deferImplementation = setImmediate;
|
|
6
|
+
} else if (typeof window == 'object' && typeof window.postMessage == 'function' && typeof window.addEventListener == 'function') {
|
|
7
|
+
let buffer = [];
|
|
8
|
+
window.addEventListener(
|
|
9
|
+
'message',
|
|
10
|
+
evt => {
|
|
11
|
+
const src = evt.source;
|
|
12
|
+
if ((src === window || src === null) && evt.data === 'tape6-process-tick') {
|
|
13
|
+
evt.stopPropagation();
|
|
14
|
+
if (buffer.length) {
|
|
15
|
+
const tasks = buffer.slice(0);
|
|
16
|
+
buffer = [];
|
|
17
|
+
tasks.forEach(fn => fn());
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
true
|
|
22
|
+
);
|
|
23
|
+
deferImplementation = fn => {
|
|
24
|
+
buffer.push(fn);
|
|
25
|
+
window.postMessage('tape6-process-tick', '*');
|
|
26
|
+
};
|
|
27
|
+
} else {
|
|
28
|
+
deferImplementation = setTimeout;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const defer = fn => deferImplementation(fn);
|
|
32
|
+
|
|
33
|
+
export default defer;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// file sets are represented as sorted name lists with no duplicated items
|
|
2
|
+
|
|
3
|
+
export const normalize = fileSet => fileSet.sort().filter((name, i, list) => !i || list[i - 1] !== name);
|
|
4
|
+
|
|
5
|
+
export const union = (a, b) => {
|
|
6
|
+
if (!a.length) return b.slice(0);
|
|
7
|
+
if (!b.length) return a.slice(0);
|
|
8
|
+
|
|
9
|
+
let i = 0,
|
|
10
|
+
j = 0,
|
|
11
|
+
x = a[0],
|
|
12
|
+
y = b[0];
|
|
13
|
+
|
|
14
|
+
const result = [];
|
|
15
|
+
for (;;) {
|
|
16
|
+
if (x < y) {
|
|
17
|
+
result.push(x);
|
|
18
|
+
++i;
|
|
19
|
+
if (i >= a.length) break;
|
|
20
|
+
x = a[i];
|
|
21
|
+
} else if (x > y) {
|
|
22
|
+
result.push(y);
|
|
23
|
+
++j;
|
|
24
|
+
if (j >= b.length) break;
|
|
25
|
+
y = b[j];
|
|
26
|
+
} else {
|
|
27
|
+
result.push(x);
|
|
28
|
+
++i;
|
|
29
|
+
++j;
|
|
30
|
+
if (i >= a.length || j >= b.length) break;
|
|
31
|
+
x = a[i];
|
|
32
|
+
y = b[j];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (i < a.length) {
|
|
37
|
+
result.push(...a.slice(i));
|
|
38
|
+
} else if (j < b.length) {
|
|
39
|
+
result.push(...b.slice(j));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const intersection = (a, b) => {
|
|
46
|
+
if (!a.length || !b.length) return [];
|
|
47
|
+
|
|
48
|
+
let i = 0,
|
|
49
|
+
j = 0,
|
|
50
|
+
x = a[0],
|
|
51
|
+
y = b[0];
|
|
52
|
+
|
|
53
|
+
const result = [];
|
|
54
|
+
for (;;) {
|
|
55
|
+
if (x < y) {
|
|
56
|
+
++i;
|
|
57
|
+
if (i >= a.length) break;
|
|
58
|
+
x = a[i];
|
|
59
|
+
} else if (x > y) {
|
|
60
|
+
++j;
|
|
61
|
+
if (j >= b.length) break;
|
|
62
|
+
y = b[j];
|
|
63
|
+
} else {
|
|
64
|
+
result.push(x);
|
|
65
|
+
++i;
|
|
66
|
+
++j;
|
|
67
|
+
if (i >= a.length || j >= b.length) break;
|
|
68
|
+
x = a[i];
|
|
69
|
+
y = b[j];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const difference = (a, b) => {
|
|
77
|
+
if (!a.length) return [];
|
|
78
|
+
if (!b.length) return a.slice(0);
|
|
79
|
+
|
|
80
|
+
let i = 0,
|
|
81
|
+
j = 0,
|
|
82
|
+
x = a[0],
|
|
83
|
+
y = b[0];
|
|
84
|
+
|
|
85
|
+
const result = [];
|
|
86
|
+
for (;;) {
|
|
87
|
+
if (x < y) {
|
|
88
|
+
result.push(x);
|
|
89
|
+
++i;
|
|
90
|
+
if (i >= a.length) break;
|
|
91
|
+
x = a[i];
|
|
92
|
+
} else if (x > y) {
|
|
93
|
+
++j;
|
|
94
|
+
if (j >= b.length) break;
|
|
95
|
+
y = b[j];
|
|
96
|
+
} else {
|
|
97
|
+
++i;
|
|
98
|
+
++j;
|
|
99
|
+
if (i >= a.length || j >= b.length) break;
|
|
100
|
+
x = a[i];
|
|
101
|
+
y = b[j];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (i < a.length) {
|
|
106
|
+
result.push(...a.slice(i));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return result;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const filter = (a, re) => a.filter(item => re.test(item));
|
|
113
|
+
export const exclude = (a, re) => a.filter(item => !re.test(item));
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const formatNumber = (n, precision = 0) => {
|
|
2
|
+
const s = Number(Math.abs(n)).toFixed(precision),
|
|
3
|
+
[i, f] = precision ? s.split('.') : [s];
|
|
4
|
+
if (!f && i.length <= 3) return n < 0 ? '-' + s : s;
|
|
5
|
+
const parts = [];
|
|
6
|
+
let start = i.length % 3;
|
|
7
|
+
start && parts.push(i.substr(0, start));
|
|
8
|
+
for (; start < i.length; start += 3) parts.push(i.substr(start, 3));
|
|
9
|
+
let result = parts.join(',');
|
|
10
|
+
f && !/^0*$/.test(f) && (result += '.' + f.replace(/0+$/, ''));
|
|
11
|
+
return n < 0 ? '-' + result : result;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const SEC = 1000,
|
|
15
|
+
MIN = 60 * SEC,
|
|
16
|
+
HOUR = 60 * MIN,
|
|
17
|
+
DAY = 24 * HOUR;
|
|
18
|
+
|
|
19
|
+
const omit = (value, suffix, expected = '0') => (value === expected ? '' : value + suffix);
|
|
20
|
+
|
|
21
|
+
export const formatTime = ms => {
|
|
22
|
+
if (ms < 10) return formatNumber(ms, 3) + 'ms';
|
|
23
|
+
if (ms < 100) return formatNumber(ms, 2) + 'ms';
|
|
24
|
+
if (ms < SEC) return formatNumber(ms, 1) + 'ms';
|
|
25
|
+
if (ms < 10000) return formatNumber(ms / SEC, 3) + 's';
|
|
26
|
+
if (ms < MIN) return formatNumber(ms / SEC, 2) + 's';
|
|
27
|
+
if (ms < HOUR) return formatNumber(Math.floor(ms / MIN), 0) + 'm' + omit(formatNumber(Math.floor((ms % MIN) / SEC), 0), 's');
|
|
28
|
+
if (ms < DAY) return formatNumber(Math.floor(ms / HOUR), 0) + 'h' + omit(formatNumber(Math.floor((ms % HOUR) / MIN), 0), 'm');
|
|
29
|
+
return formatNumber(Math.floor(ms / DAY), 0) + 'd' + omit(formatNumber(Math.floor((ms % DAY) / HOUR), 0), 'h');
|
|
30
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
let timer = Date;
|
|
2
|
+
|
|
3
|
+
export const getTimer = () => timer;
|
|
4
|
+
export const setTimer = newTimer => (timer = newTimer);
|
|
5
|
+
|
|
6
|
+
export const selectTimer = async () => {
|
|
7
|
+
// set HR timer
|
|
8
|
+
if (typeof performance == 'object' && performance && typeof performance.now == 'function') {
|
|
9
|
+
// browser or Deno
|
|
10
|
+
setTimer(performance);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (typeof process == 'object' && typeof process.exit == 'function') {
|
|
14
|
+
// Node
|
|
15
|
+
try {
|
|
16
|
+
const {performance} = await import('perf_hooks');
|
|
17
|
+
setTimer(performance);
|
|
18
|
+
return;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
// squelch
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
setTimer(Date);
|
|
24
|
+
};
|