@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.
@@ -0,0 +1,926 @@
1
+ 'use strict';var os=require('node:os'),fs=require('node:fs'),fsp=require('node:fs/promises'),path=require('node:path'),node_stream=require('node:stream'),node_process=require('node:process'),node_child_process=require('node:child_process'),assert=require('node:assert');function _interopNamespaceDefault(e){var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var fsp__namespace=/*#__PURE__*/_interopNamespaceDefault(fsp);/*!
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
+ node_child_process.exec(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) node_process.env.NODE_ENV = set ? value : '';
468
+ return node_process.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) node_process.env.NODE_ENV = set ? value : '';
479
+ return node_process.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__namespace.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__namespace.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__namespace.mkdir(path.dirname(_dst), {recursive: true}).then(fsp__namespace.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 node_stream.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});exports.arr=arr;exports.clean=clean$1;exports.gen=gen;exports.io=io;exports.is=is;exports.isEmpty=isEmpty;exports.isEmptyOrFalsey=isEmptyOrFalsey;exports.isEmptyOrFalsy=isEmptyOrFalsy;exports.merge=merge$1;exports.sh=sh;exports.test=test;exports.time=time;exports.web=web;