@amekusa/util.js 1.2.1 → 2.0.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/README.md +37 -20
- package/dist/{import/bundle.js → amekusa.util.br.es.js} +164 -177
- package/dist/amekusa.util.br.es.min.js +73 -0
- package/dist/{require/bundle.cjs → amekusa.util.br.js} +163 -188
- package/dist/amekusa.util.br.min.js +73 -0
- package/dist/amekusa.util.cjs +926 -0
- package/dist/amekusa.util.js +926 -0
- package/package.json +8 -7
|
@@ -0,0 +1,926 @@
|
|
|
1
|
+
import os from'node:os';import fs from'node:fs';import*as fsp from'node:fs/promises';import path from'node:path';import {Transform}from'node:stream';import {env}from'node:process';import {exec as exec$1}from'node:child_process';import assert from'node:assert';/*!
|
|
2
|
+
* === @amekusa/util.js/gen === *
|
|
3
|
+
* MIT License
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2024 Satoshi Soma
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
* copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
* SOFTWARE.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Coerces the given value into an array.
|
|
28
|
+
* @param {any} x
|
|
29
|
+
* @return {any[]}
|
|
30
|
+
*/
|
|
31
|
+
function arr(x) {
|
|
32
|
+
return Array.isArray(x) ? x : [x];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Checks the type of the given value matches with one of the given types.
|
|
37
|
+
* If a constructor is given to `types`, it checks if `x` is `instanceof` the constructor.
|
|
38
|
+
* @param {any} x
|
|
39
|
+
* @param {...string|function} types - Type or Constructor
|
|
40
|
+
* @return {boolean}
|
|
41
|
+
*/
|
|
42
|
+
function is(x, ...types) {
|
|
43
|
+
let t = typeof x;
|
|
44
|
+
for (let i = 0; i < types.length; i++) {
|
|
45
|
+
let v = types[i];
|
|
46
|
+
if (typeof v == 'string') {
|
|
47
|
+
if (v == 'array') {
|
|
48
|
+
if (Array.isArray(x)) return true;
|
|
49
|
+
} else if (t == v) return true;
|
|
50
|
+
} else if (x instanceof v) return true;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns whether the given value can be considered as "empty".
|
|
57
|
+
* @param {any} x
|
|
58
|
+
* @return {boolean}
|
|
59
|
+
*/
|
|
60
|
+
function isEmpty(x) {
|
|
61
|
+
if (Array.isArray(x)) return x.length == 0;
|
|
62
|
+
switch (typeof x) {
|
|
63
|
+
case 'string':
|
|
64
|
+
return !x;
|
|
65
|
+
case 'object':
|
|
66
|
+
for (let _ in x) return false;
|
|
67
|
+
return true;
|
|
68
|
+
case 'undefined':
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Returns whether the given value can be considered as "empty" or "falsy".
|
|
76
|
+
* Faster than {@link isEmpty}.
|
|
77
|
+
* @param {any} x
|
|
78
|
+
* @return {boolean}
|
|
79
|
+
*/
|
|
80
|
+
function isEmptyOrFalsy(x) {
|
|
81
|
+
if (!x) return true;
|
|
82
|
+
if (Array.isArray(x)) return x.length == 0;
|
|
83
|
+
if (typeof x == 'object') {
|
|
84
|
+
for (let _ in x) return false;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @function isEmptyOrFalsey
|
|
91
|
+
* Alias of {@link isEmptyOrFalsy}.
|
|
92
|
+
*/
|
|
93
|
+
const isEmptyOrFalsey = isEmptyOrFalsy;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Removes "empty" values from the given object or array.
|
|
97
|
+
* @param {object|any[]} x
|
|
98
|
+
* @param {number} recurse - Recursion limit
|
|
99
|
+
* @return {object|any[]} modified `x`
|
|
100
|
+
*/
|
|
101
|
+
function clean$1(x, recurse = 8) {
|
|
102
|
+
if (recurse) {
|
|
103
|
+
if (Array.isArray(x)) {
|
|
104
|
+
let r = [];
|
|
105
|
+
for (let i = 0; i < x.length; i++) {
|
|
106
|
+
let v = clean$1(x[i], recurse - 1);
|
|
107
|
+
if (!isEmpty(v)) r.push(v);
|
|
108
|
+
}
|
|
109
|
+
return r;
|
|
110
|
+
}
|
|
111
|
+
if (typeof x == 'object') {
|
|
112
|
+
let r = {};
|
|
113
|
+
for (let k in x) {
|
|
114
|
+
let v = clean$1(x[k], recurse - 1);
|
|
115
|
+
if (!isEmpty(v)) r[k] = v;
|
|
116
|
+
}
|
|
117
|
+
return r;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return x;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Merges the 2nd object into the 1st object recursively (deep-merge). The 1st object will be modified.
|
|
125
|
+
* @param {object} x - The 1st object
|
|
126
|
+
* @param {object} y - The 2nd object
|
|
127
|
+
* @param {object} [opts] - Options
|
|
128
|
+
* @param {number} opts.recurse=8 - Recurstion limit. Negative number means unlimited
|
|
129
|
+
* @param {boolean|string} opts.mergeArrays - How to merge arrays
|
|
130
|
+
* - `true`: merge x with y
|
|
131
|
+
* - 'push': push y elements to x
|
|
132
|
+
* - 'concat': concat x and y
|
|
133
|
+
* - other: replace x with y
|
|
134
|
+
* @return {object} The 1st object
|
|
135
|
+
*/
|
|
136
|
+
function merge$1(x, y, opts = {}) {
|
|
137
|
+
if (!('recurse' in opts)) opts.recurse = 8;
|
|
138
|
+
switch (Array.isArray(x) + Array.isArray(y)) {
|
|
139
|
+
case 0: // no array
|
|
140
|
+
if (opts.recurse && x && y && typeof x == 'object' && typeof y == 'object') {
|
|
141
|
+
opts.recurse--;
|
|
142
|
+
for (let k in y) x[k] = merge$1(x[k], y[k], opts);
|
|
143
|
+
opts.recurse++;
|
|
144
|
+
return x;
|
|
145
|
+
}
|
|
146
|
+
case 1: // 1 array
|
|
147
|
+
return y;
|
|
148
|
+
}
|
|
149
|
+
// 2 arrays
|
|
150
|
+
switch (opts.mergeArrays) {
|
|
151
|
+
case true:
|
|
152
|
+
for (let i = 0; i < y.length; i++) {
|
|
153
|
+
if (!x.includes(y[i])) x.push(y[i]);
|
|
154
|
+
}
|
|
155
|
+
return x;
|
|
156
|
+
case 'push':
|
|
157
|
+
x.push(...y);
|
|
158
|
+
return x;
|
|
159
|
+
case 'concat':
|
|
160
|
+
return x.concat(y);
|
|
161
|
+
}
|
|
162
|
+
return y;
|
|
163
|
+
}var gen=/*#__PURE__*/Object.freeze({__proto__:null,arr:arr,clean:clean$1,is:is,isEmpty:isEmpty,isEmptyOrFalsey:isEmptyOrFalsey,isEmptyOrFalsy:isEmptyOrFalsy,merge:merge$1});/*!
|
|
164
|
+
* === @amekusa/util.js/web === *
|
|
165
|
+
* MIT License
|
|
166
|
+
*
|
|
167
|
+
* Copyright (c) 2024 Satoshi Soma
|
|
168
|
+
*
|
|
169
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
170
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
171
|
+
* in the Software without restriction, including without limitation the rights
|
|
172
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
173
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
174
|
+
* furnished to do so, subject to the following conditions:
|
|
175
|
+
*
|
|
176
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
177
|
+
* copies or substantial portions of the Software.
|
|
178
|
+
*
|
|
179
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
180
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
181
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
182
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
183
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
184
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
185
|
+
* SOFTWARE.
|
|
186
|
+
*/
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Converts non-safe chars in the given string into HTML entities.
|
|
190
|
+
* @param {string} str
|
|
191
|
+
* @return {string}
|
|
192
|
+
*/
|
|
193
|
+
function escHTML(str) {
|
|
194
|
+
return `${str}`.replace(escHTML_find, escHTML_replace);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const escHtml = escHTML; // alias
|
|
198
|
+
|
|
199
|
+
const escHTML_map = {
|
|
200
|
+
'&': 'amp',
|
|
201
|
+
'"': 'quot',
|
|
202
|
+
"'": 'apos',
|
|
203
|
+
'<': 'lt',
|
|
204
|
+
'>': 'gt'
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const escHTML_find = new RegExp(`["'<>]|(&(?!${Object.values(escHTML_map).join('|')};))`, 'g');
|
|
208
|
+
// NOTE:
|
|
209
|
+
// - This avoids double-escaping '&' symbols
|
|
210
|
+
// - Regex negative match: (?!word)
|
|
211
|
+
|
|
212
|
+
const escHTML_replace = found => `&${escHTML_map[found]};`;var web=/*#__PURE__*/Object.freeze({__proto__:null,escHTML:escHTML,escHtml:escHtml});/*!
|
|
213
|
+
* === @amekusa/util.js/time === *
|
|
214
|
+
* MIT License
|
|
215
|
+
*
|
|
216
|
+
* Copyright (c) 2024 Satoshi Soma
|
|
217
|
+
*
|
|
218
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
219
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
220
|
+
* in the Software without restriction, including without limitation the rights
|
|
221
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
222
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
223
|
+
* furnished to do so, subject to the following conditions:
|
|
224
|
+
*
|
|
225
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
226
|
+
* copies or substantial portions of the Software.
|
|
227
|
+
*
|
|
228
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
229
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
230
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
231
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
232
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
233
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
234
|
+
* SOFTWARE.
|
|
235
|
+
*/
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Coerces the given value into a `Date` object.
|
|
239
|
+
* @param {...any} args - A `Date` object or args to pass to `Date()`
|
|
240
|
+
* @return {Date}
|
|
241
|
+
*/
|
|
242
|
+
function date(...args) {
|
|
243
|
+
if (!args.length || !args[0]) return new Date();
|
|
244
|
+
if (args[0] instanceof Date) return args[0];
|
|
245
|
+
return new Date(...args);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Coerces the given value into a number of milliseconds.
|
|
250
|
+
* @param {...args} args - A number or args to pass to `Date()`
|
|
251
|
+
* @return {number} milliseconds
|
|
252
|
+
*/
|
|
253
|
+
function ms(...args) {
|
|
254
|
+
if (!args.length || !args[0]) return Date.now();
|
|
255
|
+
let x = args[0];
|
|
256
|
+
if (typeof x == 'number') return x;
|
|
257
|
+
if (x instanceof Date) return x.getTime();
|
|
258
|
+
return (new Date(...args)).getTime();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Adds the given amount of time to a `Date` object.
|
|
263
|
+
* @param {Date} d - Date object to modify
|
|
264
|
+
* @param {number} amount - Millieconds to add
|
|
265
|
+
* @return {Date} modified Date
|
|
266
|
+
*/
|
|
267
|
+
function addTime(d, amount) {
|
|
268
|
+
d.setTime(d.getTime() + amount);
|
|
269
|
+
return d;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Subtracts the timezone offset from a `Date` object.
|
|
274
|
+
* @param {Date} d - Date object to modify
|
|
275
|
+
* @return {Date} modified Date
|
|
276
|
+
*/
|
|
277
|
+
function localize(d) {
|
|
278
|
+
d.setTime(d.getTime() - d.getTimezoneOffset() * 60000);
|
|
279
|
+
return d;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Quantizes a `Date` object with the given amount of time.
|
|
284
|
+
* @param {Date} d - Date object to modify
|
|
285
|
+
* @param {number} step - Quantization step size
|
|
286
|
+
* @param {string} [method='round'] - `Math` method to apply
|
|
287
|
+
* @return {Date} modified Date
|
|
288
|
+
*/
|
|
289
|
+
function quantize(d, step, method = 'round') {
|
|
290
|
+
d.setTime(Math[method](d.getTime() / step) * step);
|
|
291
|
+
return d;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Alias of `quantize(d, step, 'round')`.
|
|
296
|
+
*/
|
|
297
|
+
function round(d, step) {
|
|
298
|
+
return quantize(d, step, 'round');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Alias of `quantize(d, step, 'floor')`.
|
|
303
|
+
*/
|
|
304
|
+
function floor(d, step) {
|
|
305
|
+
return quantize(d, step, 'floor');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Alias of `quantize(d, step, 'ceil')`.
|
|
310
|
+
*/
|
|
311
|
+
function ceil(d, step) {
|
|
312
|
+
return quantize(d, step, 'ceil');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Returns `YYYY`, `MM`, and `DD` representations of a `Date` object.
|
|
317
|
+
* @param {Date} d - Date object
|
|
318
|
+
* @param {string|object} [format]
|
|
319
|
+
* - If omitted, the return value will be an array consists of the three parts.
|
|
320
|
+
* - If a string is passed, the three parts will be joined with the string as a separator.
|
|
321
|
+
* - If an object is passed, the three parts will be assigned as `Y`, `M`, and `D` properties.
|
|
322
|
+
* @return {string|string[]|object}
|
|
323
|
+
*/
|
|
324
|
+
function ymd(d, format = null) {
|
|
325
|
+
let r = [
|
|
326
|
+
d.getFullYear().toString(),
|
|
327
|
+
(d.getMonth() + 1).toString().padStart(2, '0'),
|
|
328
|
+
d.getDate().toString().padStart(2, '0'),
|
|
329
|
+
];
|
|
330
|
+
switch (typeof format) {
|
|
331
|
+
case 'string':
|
|
332
|
+
return r.join(format);
|
|
333
|
+
case 'object':
|
|
334
|
+
if (!format) return r;
|
|
335
|
+
format.Y = r[0];
|
|
336
|
+
format.M = r[1];
|
|
337
|
+
format.D = r[2];
|
|
338
|
+
return format;
|
|
339
|
+
default:
|
|
340
|
+
if (!format) return r;
|
|
341
|
+
throw `invalid type`;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Returns `hh`, `mm`, and `ss` representations of a `Date` object.
|
|
347
|
+
* @param {Date} d - Date object
|
|
348
|
+
* @param {string|object} [format]
|
|
349
|
+
* - If omited, the return value will be an array consists of the three parts.
|
|
350
|
+
* - If a string is passed, the three parts will be joined with the string as a separator.
|
|
351
|
+
* - If an object is passed, the three parts will be assigned as `h`, `m`, and `s` properties.
|
|
352
|
+
* @return {string|string[]|object}
|
|
353
|
+
*/
|
|
354
|
+
function hms(d, format = null) {
|
|
355
|
+
let r = [
|
|
356
|
+
d.getHours().toString().padStart(2, '0'),
|
|
357
|
+
d.getMinutes().toString().padStart(2, '0'),
|
|
358
|
+
d.getSeconds().toString().padStart(2, '0'),
|
|
359
|
+
];
|
|
360
|
+
switch (typeof format) {
|
|
361
|
+
case 'string':
|
|
362
|
+
return r.join(format);
|
|
363
|
+
case 'object':
|
|
364
|
+
if (!format) return r;
|
|
365
|
+
format.h = r[0];
|
|
366
|
+
format.m = r[1];
|
|
367
|
+
format.s = r[2];
|
|
368
|
+
return format;
|
|
369
|
+
default:
|
|
370
|
+
if (!format) return r;
|
|
371
|
+
throw `invalid type`;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Returns a string representation of the given `Date` in ISO 9075 format, which is standard for MySQL.
|
|
377
|
+
* @param {Date} d - Date object
|
|
378
|
+
* @return {string} a string like `YYYY-MM-DD hh:mm:ss`
|
|
379
|
+
*/
|
|
380
|
+
function iso9075(d) {
|
|
381
|
+
return ymd(d, '-') + ' ' + hms(d, ':');
|
|
382
|
+
}var time=/*#__PURE__*/Object.freeze({__proto__:null,addTime:addTime,ceil:ceil,date:date,floor:floor,hms:hms,iso9075:iso9075,localize:localize,ms:ms,quantize:quantize,round:round,ymd:ymd});/*!
|
|
383
|
+
* === @amekusa/util.js/sh === *
|
|
384
|
+
* MIT License
|
|
385
|
+
*
|
|
386
|
+
* Copyright (c) 2024 Satoshi Soma
|
|
387
|
+
*
|
|
388
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
389
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
390
|
+
* in the Software without restriction, including without limitation the rights
|
|
391
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
392
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
393
|
+
* furnished to do so, subject to the following conditions:
|
|
394
|
+
*
|
|
395
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
396
|
+
* copies or substantial portions of the Software.
|
|
397
|
+
*
|
|
398
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
399
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
400
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
401
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
402
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
403
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
404
|
+
* SOFTWARE.
|
|
405
|
+
*/
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Executes the given shell command, and returns a Promise that resolves the stdout
|
|
409
|
+
* @param {string} cmd
|
|
410
|
+
* @param {object} [opts]
|
|
411
|
+
* @return {Promise}
|
|
412
|
+
*/
|
|
413
|
+
function exec(cmd, opts = {}) {
|
|
414
|
+
opts = Object.assign({
|
|
415
|
+
dryRun: false,
|
|
416
|
+
}, opts);
|
|
417
|
+
return new Promise((resolve, reject) => {
|
|
418
|
+
if (opts.dryRun) {
|
|
419
|
+
console.log(`[DRYRUN] ${cmd}`);
|
|
420
|
+
return resolve();
|
|
421
|
+
}
|
|
422
|
+
exec$1(cmd, (err, stdout) => {
|
|
423
|
+
return err ? reject(err) : resolve(stdout);
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Converts the given objects to shell arguments in a string form
|
|
430
|
+
* @param {object} args
|
|
431
|
+
* @param {object} [opts]
|
|
432
|
+
* @return {string}
|
|
433
|
+
*/
|
|
434
|
+
function args(args, opts = {}) {
|
|
435
|
+
opts = Object.assign({
|
|
436
|
+
sep: ' ', // key-value separator
|
|
437
|
+
}, opts);
|
|
438
|
+
let r = [];
|
|
439
|
+
for (let key in args) {
|
|
440
|
+
let value = args[key];
|
|
441
|
+
if (isNaN(key)) { // non-numeric key
|
|
442
|
+
switch (typeof value) {
|
|
443
|
+
case 'boolean':
|
|
444
|
+
if (value) r.push(key);
|
|
445
|
+
break;
|
|
446
|
+
case 'number':
|
|
447
|
+
r.push(key + opts.sep + value);
|
|
448
|
+
break;
|
|
449
|
+
case 'string':
|
|
450
|
+
r.push(key + opts.sep + `"${value}"`);
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
} else { // numeric key
|
|
454
|
+
r.push(value);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return r.join(' ');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Returns if NODE_ENV is 'production'
|
|
462
|
+
* @param {any} [set]
|
|
463
|
+
* @return {bool}
|
|
464
|
+
*/
|
|
465
|
+
function prod(set = undefined) {
|
|
466
|
+
let value = 'production';
|
|
467
|
+
if (set != undefined) env.NODE_ENV = set ? value : '';
|
|
468
|
+
return env.NODE_ENV == value;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Returns if NODE_ENV is 'development'
|
|
473
|
+
* @param {any} [set]
|
|
474
|
+
* @return {bool}
|
|
475
|
+
*/
|
|
476
|
+
function dev(set = undefined) {
|
|
477
|
+
let value = 'development';
|
|
478
|
+
if (set != undefined) env.NODE_ENV = set ? value : '';
|
|
479
|
+
return env.NODE_ENV == value;
|
|
480
|
+
}var sh=/*#__PURE__*/Object.freeze({__proto__:null,args:args,dev:dev,exec:exec,prod:prod});/*!
|
|
481
|
+
* === @amekusa/util.js/io === *
|
|
482
|
+
* MIT License
|
|
483
|
+
*
|
|
484
|
+
* Copyright (c) 2024 Satoshi Soma
|
|
485
|
+
*
|
|
486
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
487
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
488
|
+
* in the Software without restriction, including without limitation the rights
|
|
489
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
490
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
491
|
+
* furnished to do so, subject to the following conditions:
|
|
492
|
+
*
|
|
493
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
494
|
+
* copies or substantial portions of the Software.
|
|
495
|
+
*
|
|
496
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
497
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
498
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
499
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
500
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
501
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
502
|
+
* SOFTWARE.
|
|
503
|
+
*/
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Alias of `os.homedir()`.
|
|
507
|
+
* @type {string}
|
|
508
|
+
*/
|
|
509
|
+
const home = os.homedir();
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Returns or overwrites the extension of the given file path.
|
|
513
|
+
* @param {string} file - File path
|
|
514
|
+
* @param {string} [set] - New extension
|
|
515
|
+
* @return {string} the extension, or a modified file path with the new extension
|
|
516
|
+
*/
|
|
517
|
+
function ext(file, set = null) {
|
|
518
|
+
let dot = file.lastIndexOf('.');
|
|
519
|
+
return typeof set == 'string'
|
|
520
|
+
? (dot < 0 ? (file + set) : (file.substring(0, dot) + set))
|
|
521
|
+
: (dot < 0 ? '' : file.substring(dot));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Searches the given file path in the given directories.
|
|
526
|
+
* @param {string} file - File to find
|
|
527
|
+
* @param {string[]} dirs - Array of directories to search
|
|
528
|
+
* @param {object} [opts] - Options
|
|
529
|
+
* @param {boolean} [opts.allowAbsolute=true] - If true, `file` can be an absolute path
|
|
530
|
+
* @return {string|boolean} found file path, or false if not found
|
|
531
|
+
*/
|
|
532
|
+
function find(file, dirs = [], opts = {}) {
|
|
533
|
+
let {allowAbsolute = true} = opts;
|
|
534
|
+
if (allowAbsolute && path.isAbsolute(file)) return fs.existsSync(file) ? file : false;
|
|
535
|
+
for (let i = 0; i < dirs.length; i++) {
|
|
536
|
+
let find = path.join(dirs[i], file);
|
|
537
|
+
if (fs.existsSync(find)) return find;
|
|
538
|
+
}
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Replaces the beginning `~` character with `os.homedir()`.
|
|
544
|
+
* @param {string} file - File path
|
|
545
|
+
* @param {string} [replace=os.homedir()] - Replacement
|
|
546
|
+
* @return {string} modified `file`
|
|
547
|
+
*/
|
|
548
|
+
function untilde(file, replace = home) {
|
|
549
|
+
if (!file.startsWith('~')) return file;
|
|
550
|
+
if (file.length == 1) return replace;
|
|
551
|
+
if (file.startsWith(path.sep, 1)) return replace + file.substring(1);
|
|
552
|
+
return file;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Deletes the files in the given directory.
|
|
557
|
+
* @param {string} dir - Directory to clean
|
|
558
|
+
* @param {string|RegExp} [pattern] - File pattern
|
|
559
|
+
* @param {object} [opts] - Options
|
|
560
|
+
* @param {boolean} [opts.recursive=false] - Searches recursively
|
|
561
|
+
* @param {object} [opts.types] - File types to delete
|
|
562
|
+
* @param {boolean} [opts.types.any=false] - Any type
|
|
563
|
+
* @param {boolean} [opts.types.file=true] - Regular file
|
|
564
|
+
* @param {boolean} [opts.types.dir=false] - Directory
|
|
565
|
+
* @param {boolean} [opts.types.symlink=false] - Symbolic link
|
|
566
|
+
* @return {Promise} a promise resolved with the deleted file paths
|
|
567
|
+
*/
|
|
568
|
+
function clean(dir, pattern = null, opts = {}) {
|
|
569
|
+
if (pattern && typeof pattern == 'string') pattern = new RegExp(pattern);
|
|
570
|
+
let {
|
|
571
|
+
recursive = false,
|
|
572
|
+
types = {file: true},
|
|
573
|
+
} = opts;
|
|
574
|
+
return fsp.readdir(dir, {recursive, withFileTypes: true}).then(files => {
|
|
575
|
+
let tasks = [];
|
|
576
|
+
for (let i = 0; i < files.length; i++) {
|
|
577
|
+
let f = files[i];
|
|
578
|
+
if (!types.any) {
|
|
579
|
+
if (f.isFile()) {
|
|
580
|
+
if (!types.file) continue;
|
|
581
|
+
} else if (f.isDirectory()) {
|
|
582
|
+
if (!types.dir) continue;
|
|
583
|
+
} else if (f.isSymbolicLink()) {
|
|
584
|
+
if (!types.symlink) continue;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
f = path.join(dir, f.name);
|
|
588
|
+
if (pattern && !f.match(pattern)) continue;
|
|
589
|
+
tasks.push(fsp.rm(f, {force: true, recursive: true}).then(() => f));
|
|
590
|
+
}
|
|
591
|
+
return tasks.length ? Promise.all(tasks) : false;
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Copies the given file(s) to another directory
|
|
597
|
+
* @param {string|object|string[]|object[]} src
|
|
598
|
+
* @param {string} dst Base destination directory
|
|
599
|
+
* @return {Promise}
|
|
600
|
+
*/
|
|
601
|
+
function copy(src, dst) {
|
|
602
|
+
return Promise.all((Array.isArray(src) ? src : [src]).map(item => {
|
|
603
|
+
let _src, _dst;
|
|
604
|
+
switch (typeof item) {
|
|
605
|
+
case 'object':
|
|
606
|
+
_src = item.src;
|
|
607
|
+
_dst = item.dst;
|
|
608
|
+
break;
|
|
609
|
+
case 'string':
|
|
610
|
+
_src = item;
|
|
611
|
+
break;
|
|
612
|
+
default:
|
|
613
|
+
throw 'invalid type';
|
|
614
|
+
}
|
|
615
|
+
_dst = path.join(dst, _dst || path.basename(_src));
|
|
616
|
+
return fsp.mkdir(path.dirname(_dst), {recursive: true}).then(fsp.copyFile(_src, _dst));
|
|
617
|
+
}));
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Returns a Transform stream object with the given function as its transform() method.
|
|
622
|
+
* `fn` must return a string which is to be the new content, or a Promise which resolves a string.
|
|
623
|
+
*
|
|
624
|
+
* @example
|
|
625
|
+
* return gulp.src(src)
|
|
626
|
+
* .pipe(modifyStream((data, enc) => {
|
|
627
|
+
* // do stuff
|
|
628
|
+
* return newData;
|
|
629
|
+
* }));
|
|
630
|
+
*
|
|
631
|
+
* @param {function} fn
|
|
632
|
+
* @return {Transform}
|
|
633
|
+
*/
|
|
634
|
+
function modifyStream(fn) {
|
|
635
|
+
return new Transform({
|
|
636
|
+
objectMode: true,
|
|
637
|
+
transform(file, enc, done) {
|
|
638
|
+
let r = fn(file.contents.toString(enc), enc);
|
|
639
|
+
if (r instanceof Promise) {
|
|
640
|
+
r.then(modified => {
|
|
641
|
+
file.contents = Buffer.from(modified, enc);
|
|
642
|
+
this.push(file);
|
|
643
|
+
done();
|
|
644
|
+
});
|
|
645
|
+
} else {
|
|
646
|
+
file.contents = Buffer.from(r, enc);
|
|
647
|
+
this.push(file);
|
|
648
|
+
done();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
}var io=/*#__PURE__*/Object.freeze({__proto__:null,clean:clean,copy:copy,ext:ext,find:find,home:home,modifyStream:modifyStream,untilde:untilde});const merge = Object.assign;
|
|
653
|
+
|
|
654
|
+
/*!
|
|
655
|
+
* === @amekusa/util.js/test === *
|
|
656
|
+
* MIT License
|
|
657
|
+
*
|
|
658
|
+
* Copyright (c) 2024 Satoshi Soma
|
|
659
|
+
*
|
|
660
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
661
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
662
|
+
* in the Software without restriction, including without limitation the rights
|
|
663
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
664
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
665
|
+
* furnished to do so, subject to the following conditions:
|
|
666
|
+
*
|
|
667
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
668
|
+
* copies or substantial portions of the Software.
|
|
669
|
+
*
|
|
670
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
671
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
672
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
673
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
674
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
675
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
676
|
+
* SOFTWARE.
|
|
677
|
+
*/
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* @private
|
|
681
|
+
*/
|
|
682
|
+
function invalid(...args) {
|
|
683
|
+
throw new InvalidTest(...args);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
class InvalidTest extends Error {
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function assertProps(obj, props, opts = {}) {
|
|
690
|
+
if (typeof props != 'object') invalid(`'props' must be an object`);
|
|
691
|
+
for (let k in props) {
|
|
692
|
+
let v = props[k];
|
|
693
|
+
if (!(k in obj)) assert.fail(`no such property as '${k}'`);
|
|
694
|
+
assertEqual(obj[k], v, merge({msg: `property '${k}' failed`}, opts));
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function assertEqual(actual, expected, opts = {}) {
|
|
699
|
+
let equal, deepEqual;
|
|
700
|
+
if (opts.strict) {
|
|
701
|
+
equal = assert.strictEqual;
|
|
702
|
+
deepEqual = assert.deepStrictEqual;
|
|
703
|
+
} else {
|
|
704
|
+
equal = assert.equal;
|
|
705
|
+
deepEqual = assert.deepEqual;
|
|
706
|
+
}
|
|
707
|
+
try {
|
|
708
|
+
if (expected) {
|
|
709
|
+
switch (typeof expected) {
|
|
710
|
+
case 'object':
|
|
711
|
+
let proto = Object.getPrototypeOf(expected);
|
|
712
|
+
if (proto === Object.prototype || proto === Array.prototype)
|
|
713
|
+
return deepEqual(actual, expected);
|
|
714
|
+
return equal(actual, expected);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return equal(actual, expected);
|
|
718
|
+
} catch (e) {
|
|
719
|
+
if (opts.msg) e.message = opts.msg + '\n' + e.message;
|
|
720
|
+
throw e;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function assertType(value, type, msg = '') {
|
|
725
|
+
try {
|
|
726
|
+
if (typeof type == 'string') assert.equal(typeof value, type);
|
|
727
|
+
else assert.ok(value instanceof type);
|
|
728
|
+
} catch (e) {
|
|
729
|
+
if (msg) e.message = msg + '\n' + e.message;
|
|
730
|
+
throw e;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* @param {function} fn
|
|
736
|
+
* @param {Array|object} cases
|
|
737
|
+
* @param {string|function} [assertFn]
|
|
738
|
+
*/
|
|
739
|
+
function testFn(fn, cases, opts = {}) {
|
|
740
|
+
let testCase = (c, title) => {
|
|
741
|
+
it(title, () => {
|
|
742
|
+
if (typeof c != 'object') invalid(`a test case must be an object`);
|
|
743
|
+
|
|
744
|
+
// ---- call function ----
|
|
745
|
+
let args = [];
|
|
746
|
+
if ('args' in c) { // args to pass
|
|
747
|
+
if (!Array.isArray(c.args)) invalid(`'args' must be an array`);
|
|
748
|
+
args = c.args;
|
|
749
|
+
delete c.args;
|
|
750
|
+
}
|
|
751
|
+
let r = fn(...args);
|
|
752
|
+
|
|
753
|
+
// ---- check the result ----
|
|
754
|
+
let check = {
|
|
755
|
+
returnType() {
|
|
756
|
+
assertType(r, c.returnType, `return type failed`);
|
|
757
|
+
},
|
|
758
|
+
return() {
|
|
759
|
+
assertEqual(r, c.return, merge({msg: `return value failed`}, opts));
|
|
760
|
+
},
|
|
761
|
+
test() {
|
|
762
|
+
if (typeof c.test != 'function') invalid(`'test' must be a function`);
|
|
763
|
+
c.test(r, ...args);
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
for (let k in c) {
|
|
767
|
+
if (check[k]) check[k]();
|
|
768
|
+
else invalid(`invalid property: '${k}' (available properties: ${Object.keys(check).join(', ')})`);
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
};
|
|
772
|
+
describe('function: ' + (fn.displayName || fn.name), () => {
|
|
773
|
+
if (Array.isArray(cases)) {
|
|
774
|
+
for (let i = 0; i < cases.length; i++) {
|
|
775
|
+
let c = cases[i];
|
|
776
|
+
let title = `#${i}`;
|
|
777
|
+
if (Array.isArray(c.args)) title += ' ' + c.args.join(', ');
|
|
778
|
+
testCase(c, title);
|
|
779
|
+
}
|
|
780
|
+
} else {
|
|
781
|
+
let keys = Object.keys(cases);
|
|
782
|
+
for (let i = 0; i < keys.length; i++) {
|
|
783
|
+
testCase(cases[keys[i]], `#${i} ${keys[i]}`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* @param {function} construct - Constructor or function that returns an instance
|
|
791
|
+
* @param {string} method - Method name
|
|
792
|
+
* @param {object|object[]} cases - Cases
|
|
793
|
+
* @param {object} [opts] - Options
|
|
794
|
+
*/
|
|
795
|
+
function testMethod(construct, method, cases, opts = {}) {
|
|
796
|
+
let testCase = (c, title) => {
|
|
797
|
+
it(title, () => {
|
|
798
|
+
if (typeof c != 'object') invalid(`a test case must be an object`);
|
|
799
|
+
|
|
800
|
+
// ---- instantiate ----
|
|
801
|
+
let obj;
|
|
802
|
+
if (opts.static) {
|
|
803
|
+
if ('initArgs' in c) invalid(`'initArgs' is not for static method`);
|
|
804
|
+
obj = construct;
|
|
805
|
+
} else {
|
|
806
|
+
let initArgs = [];
|
|
807
|
+
if ('initArgs' in c) {
|
|
808
|
+
if (!Array.isArray(c.initArgs)) invalid(`'initArgs' must be an array`);
|
|
809
|
+
initArgs = c.initArgs;
|
|
810
|
+
delete c.initArgs;
|
|
811
|
+
}
|
|
812
|
+
try {
|
|
813
|
+
obj = new construct(...initArgs);
|
|
814
|
+
} catch (e) {
|
|
815
|
+
obj = construct(...initArgs);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// ---- call method ----
|
|
820
|
+
if (!(method in obj)) invalid(`no such method as '${method}'`);
|
|
821
|
+
let args = [];
|
|
822
|
+
if ('args' in c) { // args to pass
|
|
823
|
+
if (!Array.isArray(c.args)) invalid(`'args' must be an array`);
|
|
824
|
+
args = c.args;
|
|
825
|
+
delete c.args;
|
|
826
|
+
}
|
|
827
|
+
let r = obj[method](...args);
|
|
828
|
+
|
|
829
|
+
// ---- check the result ----
|
|
830
|
+
let check = {
|
|
831
|
+
returnsSelf() { // check if returns itself
|
|
832
|
+
assert.strictEqual(r, obj, `must return self`);
|
|
833
|
+
},
|
|
834
|
+
returnType() { // check return type
|
|
835
|
+
assertType(r, c.returnType, `return type failed`);
|
|
836
|
+
},
|
|
837
|
+
return() { // check return value
|
|
838
|
+
assertEqual(r, c.return, merge({msg: `return failed`}, opts));
|
|
839
|
+
},
|
|
840
|
+
props() { // check properties
|
|
841
|
+
assertProps(obj, c.props, opts);
|
|
842
|
+
},
|
|
843
|
+
test() { // custom test
|
|
844
|
+
if (typeof c.test != 'function') invalid(`'test' must be a function`);
|
|
845
|
+
c.test(r, obj, ...args);
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
for (let k in c) {
|
|
849
|
+
if (check[k]) check[k]();
|
|
850
|
+
else invalid(`invalid property: '${k}' (available properties: ${Object.keys(check).join(', ')})`);
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
};
|
|
854
|
+
describe('method: ' + method, () => {
|
|
855
|
+
if (Array.isArray(cases)) {
|
|
856
|
+
for (let i = 0; i < cases.length; i++) {
|
|
857
|
+
let c = cases[i];
|
|
858
|
+
let title = `#${i}`;
|
|
859
|
+
if (Array.isArray(c.args)) title += ' ' + c.args.join(', ');
|
|
860
|
+
testCase(c, title);
|
|
861
|
+
}
|
|
862
|
+
} else {
|
|
863
|
+
let keys = Object.keys(cases);
|
|
864
|
+
for (let i = 0; i < keys.length; i++) {
|
|
865
|
+
testCase(cases[keys[i]], `#${i} ${keys[i]}`);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* @param {function} construct - Constructor or function that returns an instance
|
|
873
|
+
* @param {object|object[]} cases - Cases
|
|
874
|
+
* @param {object} [opts] - Options
|
|
875
|
+
*/
|
|
876
|
+
function testInstance(construct, cases, opts = {}) {
|
|
877
|
+
let testCase = (c, title) => {
|
|
878
|
+
it(title, () => {
|
|
879
|
+
if (typeof c != 'object') invalid(`a test case must be an object`);
|
|
880
|
+
|
|
881
|
+
// ---- instantiate ----
|
|
882
|
+
let args = [];
|
|
883
|
+
if ('args' in c) {
|
|
884
|
+
if (!Array.isArray(c.args)) invalid(`'args' must be an array`);
|
|
885
|
+
args = c.args;
|
|
886
|
+
delete c.args;
|
|
887
|
+
}
|
|
888
|
+
let obj;
|
|
889
|
+
try {
|
|
890
|
+
obj = new construct(...args);
|
|
891
|
+
} catch (e) {
|
|
892
|
+
obj = construct(...args);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// ---- check the result ----
|
|
896
|
+
let check = {
|
|
897
|
+
props() { // check properties
|
|
898
|
+
assertProps(obj, c.props, opts);
|
|
899
|
+
},
|
|
900
|
+
test() { // custom check
|
|
901
|
+
if (typeof c.test != 'function') invalid(`'test' must be a function`);
|
|
902
|
+
c.test(obj, ...args);
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
for (let k in c) {
|
|
906
|
+
if (check[k]) check[k]();
|
|
907
|
+
else invalid(`invalid property: '${k}' (available properties: ${Object.keys(check).join(', ')})`);
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
};
|
|
911
|
+
describe(construct.name, () => {
|
|
912
|
+
if (Array.isArray(cases)) {
|
|
913
|
+
for (let i = 0; i < cases.length; i++) {
|
|
914
|
+
let c = cases[i];
|
|
915
|
+
let title = `#${i}`;
|
|
916
|
+
if (Array.isArray(c.args)) title += ' ' + c.args.join(', ');
|
|
917
|
+
testCase(c, title);
|
|
918
|
+
}
|
|
919
|
+
} else {
|
|
920
|
+
let keys = Object.keys(cases);
|
|
921
|
+
for (let i = 0; i < keys.length; i++) {
|
|
922
|
+
testCase(cases[keys[i]], `#${i} ${keys[i]}`);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
}var test=/*#__PURE__*/Object.freeze({__proto__:null,InvalidTest:InvalidTest,assertEqual:assertEqual,assertProps:assertProps,assertType:assertType,testFn:testFn,testInstance:testInstance,testMethod:testMethod});export{arr,clean$1 as clean,gen,io,is,isEmpty,isEmptyOrFalsey,isEmptyOrFalsy,merge$1 as merge,sh,test,time,web};
|