mm_expand 2.4.2 → 2.4.4

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/lib/file.js CHANGED
@@ -1,797 +1,797 @@
1
- const {
2
- writeFileSync,
3
- existsSync,
4
- readFileSync,
5
- copyFileSync,
6
- statSync,
7
- readdirSync,
8
- mkdirSync,
9
- unlinkSync,
10
- promises: fsp
11
- } = require('fs');
12
- const {
13
- join,
14
- dirname,
15
- basename,
16
- extname,
17
- resolve,
18
- isAbsolute
19
- } = require('path');
20
- const os = require('os');
21
- const {
22
- rimrafSync
23
- } = require('rimraf');
24
- var ncp = require('ncp').ncp;
25
- const { Base } = require('./base.js');
26
-
27
- /**
28
- * 判断是否为文件
29
- * @param {string} file_or_dir 文件或目录
30
- * @returns {boolean} 是文件返回true,否则返回false
31
- */
32
- function isFile(file_or_dir) {
33
- return statSync(file_or_dir).isDirectory() !== true;
34
- }
35
-
36
- /**
37
- * 处理根路径
38
- * @param {string} file 文件路径
39
- * @returns {string} 完整路径
40
- */
41
- function handleRootPath(file) {
42
- if ($.run_path.indexOf(file) === 0 || file.indexOf($.run_path) === 0) {
43
- return file;
44
- }
45
- return resolve($.run_path || process.cwd(), file.substring(1));
46
- }
47
-
48
- /**
49
- * 处理用户主目录路径
50
- * @param {string} file 文件路径
51
- * @returns {string} 完整路径
52
- */
53
- function handleHomePath(file) {
54
- return resolve(os.homedir(), file.substring(2));
55
- }
56
-
57
- /**
58
- * 处理项目根路径
59
- * @param {string} file 文件路径
60
- * @returns {string} 完整路径
61
- */
62
- function handleAtPath(file) {
63
- return resolve($.root_path, file.substring(2));
64
- }
65
-
66
- /**
67
- * 处理相对路径
68
- * @param {string} file 文件路径
69
- * @param {string} dir 当前目录
70
- * @returns {string} 完整路径
71
- */
72
- function handleRelativePath(file, dir) {
73
- var base_dir = dir && typeof dir === 'string' ? dir : $.run_path;
74
- return resolve(fullname(base_dir), file);
75
- }
76
-
77
- /**
78
- * 验证文件路径
79
- * @param {string} file 文件路径
80
- * @returns {string | null} 验证结果或null
81
- */
82
- function validateFilePath(file) {
83
- if (typeof file !== 'string') {
84
- throw new Error('file参数必须是字符串');
85
- }
86
-
87
- // 如果是空字符串,返回当前运行路径
88
- if (!file) {
89
- return $.run_path || process.cwd();
90
- }
91
-
92
- return null;
93
- }
94
-
95
- /**
96
- * 补全文件路径
97
- * @param {string} file 文件路径
98
- * @param {string} dir 当前目录
99
- * @returns {string} 完整路径
100
- */
101
- function fullname(file, dir) {
102
- var result = validateFilePath(file);
103
- if (result) {
104
- return result;
105
- }
106
-
107
- // 处理以 / 开头的路径(从项目启动位置)
108
- if (file.startsWith('/') && !file.startsWith('//') && file !== '/') {
109
- return handleRootPath(file);
110
- }
111
-
112
- // 处理以 ~/ 开头的路径(从用户主目录开始)
113
- if (file.startsWith('~/')) {
114
- return handleHomePath(file);
115
- }
116
-
117
- // 处理以 @/ 开头的路径(从用户主目录开始)
118
- if (file.startsWith('@/')) {
119
- return handleAtPath(file);
120
- }
121
-
122
- // 处理相对路径
123
- if (file.startsWith('./') || file.startsWith('../')
124
-
125
- || (!isAbsolute(file) && !file.startsWith('/'))) {
126
- return handleRelativePath(file, dir);
127
- }
128
-
129
- // 如果已经是绝对路径,直接返回
130
- return file;
131
- }
132
-
133
- /**
134
- * 文件类函数
135
- */
136
- class File extends Base {
137
- /**
138
- * 构造函数,用于初始化文件类
139
- * @param {object} config 文件配置对象
140
- */
141
- constructor(config) {
142
- super(config);
143
- }
144
- }
145
-
146
- /**
147
- * 补全文件路径
148
- * @param {string} file 文件路径
149
- * @param {string} dir 当前目录
150
- * @returns {string} 完整路径
151
- */
152
- File.prototype.fullname = fullname;
153
-
154
- /**
155
- * 搜索目录下所有文件
156
- * @param {string} dir 目录地址
157
- * @param {string} keyword 搜索关键词
158
- * @param {string} keyword_dir 目录搜素关键词
159
- * @returns {Array} 文件路径数组
160
- */
161
- File.prototype.getAll = function (dir, keyword, keyword_dir) {
162
- // 文件数组
163
- var list = [];
164
-
165
- // 目录数组
166
- var dirs = [];
167
- // 获取相关目录
168
- eachDir(dirs, dir.fullname(), keyword_dir);
169
- var len = dirs.length;
170
- for (var i = 0; i < len; i++) {
171
- getFile(list, dirs[i], keyword);
172
- }
173
- return list;
174
- };
175
-
176
- /**
177
- * 搜索目录下所有文件(异步并行极速版)
178
- * @param {string} dir 目录地址
179
- * @param {string} keyword 搜索关键词
180
- * @param {string} keyword_dir 目录搜素关键词
181
- * @returns {Array} 文件路径数组
182
- */
183
- File.prototype.getAllAsync = async function (dir, keyword, keyword_dir) {
184
- var root_dir = dir.fullname();
185
- var matchedDirs = [];
186
-
187
- // 使用独立 eachDirAsync 并行收集匹配的目录
188
- await eachDirAsync(matchedDirs, root_dir, keyword_dir);
189
-
190
- /**
191
- * 异步获取目录下的匹配文件
192
- * @param {string} directory 目录路径
193
- * @returns {Array} 文件路径数组
194
- */
195
- async function getFileAsync(directory) {
196
- var items;
197
- try {
198
- items = await fsp.readdir(directory, { withFileTypes: true });
199
- } catch {
200
- return [];
201
- }
202
-
203
- var files = [];
204
- for (var i = 0; i < items.length; i++) {
205
- var item = items[i];
206
- if (item.isFile() && (!keyword || item.name.has(keyword))) {
207
- files.push(join(directory, item.name));
208
- }
209
- }
210
- return files;
211
- }
212
-
213
- // 没有匹配的子目录时,直接搜索根目录
214
- if (matchedDirs.length === 0) {
215
- return await getFileAsync(root_dir);
216
- }
217
-
218
- // 并行处理所有匹配目录的文件收集
219
- var file_lists = await Promise.all(matchedDirs.map(getFileAsync));
220
- return file_lists.flat();
221
- };
222
-
223
- /**
224
- * 获取当前目录下所有文件
225
- * @param {string} dir 目录地址
226
- * @param {string} keyword 搜索关键词
227
- * @returns {Array} 文件路径数组
228
- */
229
- File.prototype.get = function (dir, keyword) {
230
- var list = [];
231
- getFile(list, dir, keyword);
232
- return list;
233
- };
234
-
235
- /**
236
- * 加载文件
237
- * @param {string} file 文件路径
238
- * @param {string} encode 编码方式
239
- * @returns {string} 加载的文本
240
- */
241
- File.prototype.load = function (file, encode) {
242
- if (!file || typeof file !== 'string') {
243
- throw new Error('file参数必须是非空字符串');
244
- }
245
- try {
246
- if (existsSync(file) && isFile(file)) {
247
- return readFileSync(file, encode);
248
- }
249
- return null;
250
- } catch (error) {
251
- this.log('error', '加载文件失败:', error);
252
- return null;
253
- }
254
- };
255
-
256
- /**
257
- * 保存文件
258
- * @param {string} file 文件路径
259
- * @param {string} data 数据内容
260
- * @param {string} encode 编码方式
261
- * @returns {boolean} 保存成功返回true,否则返回false
262
- */
263
- File.prototype.save = function (file, data, encode) {
264
- if (!file || typeof file !== 'string') {
265
- throw new Error('file参数必须是非空字符串');
266
- }
267
- if (data === undefined || data === null) {
268
- throw new Error('data参数不能为空');
269
- }
270
- var enc = encode || 'utf-8';
271
- try {
272
- writeFileSync(file, data, enc);
273
- return true;
274
- } catch (e) {
275
- this.log('error', '保存文件失败:', e);
276
- return false;
277
- }
278
- };
279
-
280
- /**
281
- * 复制文件
282
- * @param {string} source 文件原路径
283
- * @param {string} target 目标路径
284
- * @returns {boolean} 复制成功返回true,否则返回false
285
- */
286
- File.prototype.copy = function (source, target) {
287
- if (!source || typeof source !== 'string') {
288
- throw new Error('source参数必须是非空字符串');
289
- }
290
- if (!target || typeof target !== 'string') {
291
- throw new Error('target参数必须是非空字符串');
292
- }
293
- try {
294
- copyFileSync(source.fullname(), target.fullname());
295
- return true;
296
- } catch (err) {
297
- this.log('error', err);
298
- return false;
299
- }
300
- };
301
-
302
- /**
303
- * 删除文件
304
- * @param {string} file 文件路径
305
- * @returns {boolean} 删除成功返回true,否则返回false
306
- */
307
- File.prototype.del = function (file) {
308
- if (!file || typeof file !== 'string') {
309
- throw new Error('file参数必须是非空字符串');
310
- }
311
- try {
312
- unlinkSync(file.fullname());
313
- return true;
314
- } catch (err) {
315
- this.log('error', err);
316
- return false;
317
- }
318
- };
319
-
320
- /**
321
- * 检查文件是否存在
322
- * @param {string} file 文件路径
323
- * @returns {boolean} 存在返回true,否则返回false
324
- */
325
- File.prototype.exists = function (file) {
326
- if (!file || typeof file !== 'string') {
327
- throw new Error('file参数必须是非空字符串');
328
- }
329
- return existsSync(file);
330
- };
331
-
332
- /**
333
- * 读取文件内容
334
- * @param {string} file 文件路径
335
- * @param {string} encode 编码方式
336
- * @returns {string} 文件内容
337
- */
338
- File.prototype.read = function (file, encode) {
339
- var enc = encode || 'utf-8';
340
- return this.load(file, enc);
341
- };
342
-
343
- /**
344
- * 写入文件内容
345
- * @param {string} file 文件路径
346
- * @param {string} data 数据内容
347
- * @param {string} encode 编码方式
348
- * @returns {boolean} 写入成功返回true,否则返回false
349
- */
350
- File.prototype.write = function (file, data, encode) {
351
- if (!data) {
352
- throw new Error('data参数不能为空');
353
- }
354
- return this.save(file, data, encode);
355
- };
356
-
357
- /**
358
- * 搜索目录下所有目录
359
- * @param {Array} list 结果路径数组
360
- * @param {string} dir 目录地址
361
- * @param {string} keyword 搜索关键词
362
- */
363
- function eachDir(list, dir, keyword) {
364
- var items = readdirSync(dir);
365
- var len = items.length;
366
-
367
- for (var i = 0; i < len; i++) {
368
- var name = items[i];
369
- var f = join(dir, name);
370
-
371
- // 识别是否为目录
372
- if (!isFile(f)) {
373
- // 判断是否包含关键词
374
- if (!keyword || name.has(keyword)) {
375
- list.push(f);
376
- }
377
- eachDir(list, f, keyword);
378
- }
379
- }
380
- }
381
- /**
382
- * 异步并行搜索目录下所有目录
383
- * @param {Array} list 结果路径数组
384
- * @param {string} dir 目录地址
385
- * @param {string} keyword 搜索关键词
386
- */
387
- async function eachDirAsync(list, dir, keyword) {
388
- var items;
389
- try {
390
- items = await fsp.readdir(dir, { withFileTypes: true });
391
- } catch {
392
- return;
393
- }
394
-
395
- var subDirs = [];
396
- for (var i = 0; i < items.length; i++) {
397
- var item = items[i];
398
- if (item.isDirectory()) {
399
- var full_path = join(dir, item.name);
400
- if (!keyword || item.name.has(keyword)) {
401
- list.push(full_path);
402
- }
403
- subDirs.push(full_path);
404
- }
405
- }
406
-
407
- // 并行遍历所有子目录
408
- if (subDirs.length > 0) {
409
- await Promise.all(subDirs.map((d) => {
410
- return eachDirAsync(list, d, keyword);
411
- }));
412
- }
413
- }
414
-
415
- /**
416
- * 获取当前目录下所有目录
417
- * @param {Array} list 结果路径数组
418
- * @param {string} dir 目录地址
419
- * @param {string} keyword 搜索关键词
420
- */
421
- function getDir(list, dir, keyword) {
422
- var items = readdirSync(dir);
423
- var len = items.length;
424
-
425
- for (var i = 0; i < len; i++) {
426
- var name = items[i];
427
- var f = join(dir, name);
428
-
429
- // 识别是否为目录
430
- if (!isFile(f)) {
431
- // 判断关键词是否相等
432
- if (!keyword || name.has(keyword)) {
433
- list.push(f);
434
- }
435
- }
436
- }
437
- }
438
-
439
- /**
440
- * 添加文件路径
441
- * @param {Array} list 结果路径数组
442
- * @param {string} dir 目录地址
443
- * @param {string} keyword 搜索关键词
444
- */
445
- function getFile(list, dir, keyword) {
446
- var items = readdirSync(dir);
447
- var len = items.length;
448
-
449
- for (var i = 0; i < len; i++) {
450
- var name = items[i];
451
- var f = join(dir, name);
452
-
453
- // 识别是否非目录
454
- if (isFile(f)) {
455
- // 判断关键词是否相等
456
- if (!keyword || name.has(keyword)) {
457
- list.push(f);
458
- }
459
- }
460
- }
461
- }
462
- /**
463
- * 目录类函数
464
- */
465
- class Dir extends Base {
466
- /**
467
- * 构造函数
468
- * @param {object} config 目录配置对象
469
- */
470
- constructor(config) {
471
- super(config);
472
- }
473
- }
474
-
475
- /**
476
- * 补全目录路径
477
- * @param {string} dir 目录路径
478
- * @param {string} base_dir 基础目录
479
- * @returns {string} 完整路径
480
- */
481
- Dir.prototype.fullname = fullname;
482
-
483
- /**
484
- * 搜索目录下所有目录
485
- * @param {string} dir 目录地址
486
- * @param {string} keyword 搜索关键词
487
- * @returns {Array} 目录路径数组
488
- */
489
- Dir.prototype.getAll = function (dir, keyword) {
490
- var list = [];
491
- eachDir(list, this.fullname(dir), keyword);
492
- return list;
493
- };
494
-
495
- /**
496
- * 搜索目录下所有目录(异步并行极速版)
497
- * @param {string} dir 目录地址
498
- * @param {string} keyword 搜索关键词
499
- * @returns {Array} 目录路径数组
500
- */
501
- Dir.prototype.getAllAsync = async function (dir, keyword) {
502
- var list = [];
503
- await eachDirAsync(list, this.fullname(dir), keyword);
504
- return list;
505
- };
506
-
507
- /**
508
- * 搜索当前目录下所有目录
509
- * @param {string} dir 目录地址
510
- * @param {string} keyword 搜索关键词
511
- * @returns {Array} 目录路径数组
512
- */
513
- Dir.prototype.get = function (dir, keyword) {
514
- var list = [];
515
- getDir(list, this.fullname(dir), keyword);
516
- return list;
517
- };
518
-
519
- /**
520
- * 复制目录
521
- * @param {string} source 源路径
522
- * @param {string} target 目标路径
523
- * @param {Function} func 回调函数
524
- */
525
- Dir.prototype.copy = function (source, target, func) {
526
- ncp(source.fullname(), target.fullname(), func);
527
- };
528
-
529
- /**
530
- * 删除目录
531
- * @param {string} dir 目录路径
532
- * @param {Function} func 回调函数
533
- */
534
- Dir.prototype.del = function (dir, func) {
535
- rimrafSync(dir.fullname(), func);
536
- };
537
-
538
- /**
539
- * 判断目录是否存在
540
- * @param {string} dir 目录路径
541
- * @returns {boolean} 存在返回true, 否则返回false
542
- */
543
- Dir.prototype.exists = function (dir) {
544
- return existsSync(dir.fullname());
545
- };
546
-
547
- /** === 文件类函数 === */
548
- /**
549
- * 补全路径
550
- * @param {string} dir 文件路径
551
- * @returns {string} 全路径
552
- */
553
- String.prototype.fullname = function (dir) {
554
- return fullname(this + '', dir);
555
- };
556
-
557
- /**
558
- * 获取当前目录下所有文件
559
- * @param {string} keyword 搜索关键词
560
- * @returns {Array} 文件路径数组
561
- */
562
- String.prototype.getFile = function (keyword) {
563
- return $.file.get(this + '', keyword);
564
- };
565
-
566
- /**
567
- * 搜索当前目录下所有目录
568
- * @param {string} keyword 搜索关键词
569
- * @returns {Array} 目录路径数组
570
- */
571
- String.prototype.getDir = function (keyword) {
572
- return $.dir.get(this + '', keyword);
573
- };
574
-
575
- /**
576
- * 复制文件
577
- * @param {string} file 保存路径
578
- * @returns {boolean} 复制成功返回true, 失败返回false
579
- */
580
- String.prototype.copyFile = function (file) {
581
- return copyFileSync(this.fullname(), file.fullname());
582
- };
583
-
584
- /**
585
- * 复制目录
586
- * @param {string} file 保存路径
587
- * @param {Function} func 回调函数
588
- * @returns {boolean} 复制成功返回true, 失败返回false
589
- */
590
- String.prototype.copyDir = function (file, func) {
591
- return ncp(this.fullname(), file.fullname(), func);
592
- };
593
-
594
- /**
595
- * 是否目录
596
- * @returns {boolean} 是目录返回true, 否则返回false
597
- */
598
- String.prototype.isDir = function () {
599
- var bl = false;
600
- try {
601
- bl = statSync(this + '').isDirectory();
602
- } catch {
603
- // $.log.error("非路径");
604
- }
605
- return bl;
606
- };
607
-
608
- /**
609
- * 是否文件
610
- * @returns {boolean} 是目录返回true, 否则返回false
611
- */
612
- String.prototype.isFile = function () {
613
- var bl = false;
614
- try {
615
- bl = statSync(this + '').isFile();
616
- } catch {
617
- // $.log.error("非路径");
618
- }
619
- return bl;
620
- };
621
-
622
- /**
623
- * 取路径
624
- * @returns {string} 字符串
625
- */
626
- String.prototype.dirname = function () {
627
- return dirname(this + '');
628
- };
629
- /**
630
- * 取基础名
631
- * @returns {string} 字符串
632
- */
633
- String.prototype.basename = function () {
634
- return basename(this + '');
635
- };
636
- /**
637
- * 取拓展名
638
- * @returns {string} 字符串
639
- */
640
- String.prototype.extname = function () {
641
- return extname(this + '');
642
- };
643
-
644
-
645
- /**
646
- * 保存文件
647
- * @param {string} text 被保存的文本
648
- * @returns {boolean} 保存成功返回true, 失败返回false
649
- */
650
- String.prototype.saveText = function (text) {
651
- try {
652
- writeFileSync(this.fullname(), text);
653
- return true;
654
- } catch (e) {
655
- $.log.error('保存文件失败:', e);
656
- return false;
657
- }
658
- };
659
-
660
- /**
661
- * 保存为JSON格式文件
662
- * @param {object} obj 被保存的内容
663
- * @param {boolean} format 是否格式化
664
- * @param {number} space 格式化缩进空格数
665
- * @returns {boolean} 保存成功返回true, 失败返回false
666
- */
667
- String.prototype.saveJson = function (obj, format = true, space = 2) {
668
- try {
669
- var text = '';
670
- if (format) {
671
- text = JSON.stringify(obj, null, space);
672
- } else {
673
- text = JSON.stringify(obj);
674
- }
675
- writeFileSync(this.fullname(), text);
676
- return true;
677
- } catch (e) {
678
- $.log.error('保存JSON文件失败:', e);
679
- return false;
680
- }
681
- };
682
-
683
- /**
684
- * 加载文件
685
- * @param {string} encode 编码方式
686
- * @returns {string} 加载的文本
687
- */
688
- String.prototype.loadText = function (encode) {
689
- var file = this.fullname();
690
- if (existsSync(file)) {
691
- var enc = encode || 'utf-8';
692
- return readFileSync(file, enc);
693
- } else {
694
- return undefined;
695
- }
696
- };
697
-
698
- /**
699
- * 加载xml
700
- * @param {string} file 当前路径
701
- * @returns {string} 加载的文本
702
- */
703
- String.prototype.loadXml = function (file) {
704
- var f = this.fullname(file);
705
- if (existsSync(f)) {
706
- var text = readFileSync(f, 'utf-8');
707
- if (text) {
708
- var ret = text.toXml();
709
- if (ret === null) {
710
- $.log.error('xml反序列化错误!', f);
711
- }
712
- return ret;
713
- }
714
- }
715
- return undefined;
716
- };
717
-
718
- /**
719
- * 加载json
720
- * @param {string} file 当前路径
721
- * @returns {object} 加载后转换的对象
722
- */
723
- String.prototype.loadJson = function (file) {
724
- var f = this.fullname(file);
725
- if (existsSync(f)) {
726
- var text = readFileSync(f, 'utf-8');
727
- if (text) {
728
- var ret = text.toJson();
729
- if (ret === null) {
730
- $.log.error('json反序列化错误!', f);
731
- }
732
- return ret;
733
- }
734
- }
735
- return undefined;
736
- };
737
-
738
- /**
739
- * 判断文件是否存在
740
- * @param {string} file 当前路径
741
- * @returns {boolean} 存在返回true, 不存在返回false
742
- */
743
- String.prototype.hasFile = function (file) {
744
- return existsSync(this.fullname(file));
745
- };
746
-
747
- /**
748
- * 删除文件
749
- * @param {string} file 当前路径
750
- */
751
- String.prototype.delFile = function (file) {
752
- unlinkSync(this.fullname(file), (e) => { });
753
- };
754
-
755
- /**
756
- * 判断目录是否存在
757
- * @param {string} dir 当前路径
758
- * @returns {boolean} 存在返回true, 不存在返回false
759
- */
760
- String.prototype.hasDir = function (dir) {
761
- return existsSync(this.fullname(dir));
762
- };
763
-
764
- /**
765
- * 删除目录
766
- * @param {string} dir 当前路径
767
- * @param {Function} func 回调函数
768
- */
769
- String.prototype.delDir = function (dir, func) {
770
- rimrafSync(this.fullname(dir), func);
771
- };
772
-
773
- /**
774
- * 创建路径
775
- * @param {string} dirbase 目录基础路径
776
- */
777
- String.prototype.addDir = function (dirbase) {
778
- var d = (this + '').fullname(dirbase);
779
-
780
- if (d.indexOf('.') !== -1) {
781
- d = d.dirname();
782
- }
783
- var l = $.slash;
784
- var arr = d.split(l);
785
- var dir = '';
786
- for (var i = 0; i < arr.length; i++) {
787
- dir += arr[i] + l;
788
- if (!existsSync(dir)) {
789
- mkdirSync(dir);
790
- }
791
- }
792
- };
793
-
794
- module.exports = {
795
- File,
796
- Dir
797
- };
1
+ const {
2
+ writeFileSync,
3
+ existsSync,
4
+ readFileSync,
5
+ copyFileSync,
6
+ statSync,
7
+ readdirSync,
8
+ mkdirSync,
9
+ unlinkSync,
10
+ promises: fsp
11
+ } = require('fs');
12
+ const {
13
+ join,
14
+ dirname,
15
+ basename,
16
+ extname,
17
+ resolve,
18
+ isAbsolute
19
+ } = require('path');
20
+ const os = require('os');
21
+ const {
22
+ rimrafSync
23
+ } = require('rimraf');
24
+ var ncp = require('ncp').ncp;
25
+ const { Base } = require('./base.js');
26
+
27
+ /**
28
+ * 判断是否为文件
29
+ * @param {string} file_or_dir 文件或目录
30
+ * @returns {boolean} 是文件返回true,否则返回false
31
+ */
32
+ function isFile(file_or_dir) {
33
+ return statSync(file_or_dir).isDirectory() !== true;
34
+ }
35
+
36
+ /**
37
+ * 处理根路径
38
+ * @param {string} file 文件路径
39
+ * @returns {string} 完整路径
40
+ */
41
+ function handleRootPath(file) {
42
+ if ($.run_path.indexOf(file) === 0 || file.indexOf($.run_path) === 0) {
43
+ return file;
44
+ }
45
+ return resolve($.run_path || process.cwd(), file.substring(1));
46
+ }
47
+
48
+ /**
49
+ * 处理用户主目录路径
50
+ * @param {string} file 文件路径
51
+ * @returns {string} 完整路径
52
+ */
53
+ function handleHomePath(file) {
54
+ return resolve(os.homedir(), file.substring(2));
55
+ }
56
+
57
+ /**
58
+ * 处理项目根路径
59
+ * @param {string} file 文件路径
60
+ * @returns {string} 完整路径
61
+ */
62
+ function handleAtPath(file) {
63
+ return resolve($.root_path, file.substring(2));
64
+ }
65
+
66
+ /**
67
+ * 处理相对路径
68
+ * @param {string} file 文件路径
69
+ * @param {string} dir 当前目录
70
+ * @returns {string} 完整路径
71
+ */
72
+ function handleRelativePath(file, dir) {
73
+ var base_dir = dir && typeof dir === 'string' ? dir : $.run_path;
74
+ return resolve(fullname(base_dir), file);
75
+ }
76
+
77
+ /**
78
+ * 验证文件路径
79
+ * @param {string} file 文件路径
80
+ * @returns {string | null} 验证结果或null
81
+ */
82
+ function validateFilePath(file) {
83
+ if (typeof file !== 'string') {
84
+ throw new Error('file参数必须是字符串');
85
+ }
86
+
87
+ // 如果是空字符串,返回当前运行路径
88
+ if (!file) {
89
+ return $.run_path || process.cwd();
90
+ }
91
+
92
+ return null;
93
+ }
94
+
95
+ /**
96
+ * 补全文件路径
97
+ * @param {string} file 文件路径
98
+ * @param {string} dir 当前目录
99
+ * @returns {string} 完整路径
100
+ */
101
+ function fullname(file, dir) {
102
+ var result = validateFilePath(file);
103
+ if (result) {
104
+ return result;
105
+ }
106
+
107
+ // 处理以 / 开头的路径(从项目启动位置)
108
+ if (file.startsWith('/') && !file.startsWith('//') && file !== '/') {
109
+ return handleRootPath(file);
110
+ }
111
+
112
+ // 处理以 ~/ 开头的路径(从用户主目录开始)
113
+ if (file.startsWith('~/')) {
114
+ return handleHomePath(file);
115
+ }
116
+
117
+ // 处理以 @/ 开头的路径(从用户主目录开始)
118
+ if (file.startsWith('@/')) {
119
+ return handleAtPath(file);
120
+ }
121
+
122
+ // 处理相对路径
123
+ if (file.startsWith('./') || file.startsWith('../')
124
+
125
+ || (!isAbsolute(file) && !file.startsWith('/'))) {
126
+ return handleRelativePath(file, dir);
127
+ }
128
+
129
+ // 如果已经是绝对路径,直接返回
130
+ return file;
131
+ }
132
+
133
+ /**
134
+ * 文件类函数
135
+ */
136
+ class File extends Base {
137
+ /**
138
+ * 构造函数,用于初始化文件类
139
+ * @param {object} config 文件配置对象
140
+ */
141
+ constructor(config) {
142
+ super(config);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * 补全文件路径
148
+ * @param {string} file 文件路径
149
+ * @param {string} dir 当前目录
150
+ * @returns {string} 完整路径
151
+ */
152
+ File.prototype.fullname = fullname;
153
+
154
+ /**
155
+ * 搜索目录下所有文件
156
+ * @param {string} dir 目录地址
157
+ * @param {string} keyword 搜索关键词
158
+ * @param {string} keyword_dir 目录搜素关键词
159
+ * @returns {Array} 文件路径数组
160
+ */
161
+ File.prototype.getAll = function (dir, keyword, keyword_dir) {
162
+ // 文件数组
163
+ var list = [];
164
+
165
+ // 目录数组
166
+ var dirs = [];
167
+ // 获取相关目录
168
+ eachDir(dirs, dir.fullname(), keyword_dir);
169
+ var len = dirs.length;
170
+ for (var i = 0; i < len; i++) {
171
+ getFile(list, dirs[i], keyword);
172
+ }
173
+ return list;
174
+ };
175
+
176
+ /**
177
+ * 搜索目录下所有文件(异步并行极速版)
178
+ * @param {string} dir 目录地址
179
+ * @param {string} keyword 搜索关键词
180
+ * @param {string} keyword_dir 目录搜素关键词
181
+ * @returns {Array} 文件路径数组
182
+ */
183
+ File.prototype.getAllAsync = async function (dir, keyword, keyword_dir) {
184
+ var root_dir = dir.fullname();
185
+ var matched_dirs = [];
186
+
187
+ // 使用独立 eachDirAsync 并行收集匹配的目录
188
+ await eachDirAsync(matched_dirs, root_dir, keyword_dir);
189
+
190
+ /**
191
+ * 异步获取目录下的匹配文件
192
+ * @param {string} directory 目录路径
193
+ * @returns {Array} 文件路径数组
194
+ */
195
+ async function getFileAsync(directory) {
196
+ var items;
197
+ try {
198
+ items = await fsp.readdir(directory, { withFileTypes: true });
199
+ } catch {
200
+ return [];
201
+ }
202
+
203
+ var files = [];
204
+ for (var i = 0; i < items.length; i++) {
205
+ var item = items[i];
206
+ if (item.isFile() && (!keyword || item.name.has(keyword))) {
207
+ files.push(join(directory, item.name));
208
+ }
209
+ }
210
+ return files;
211
+ }
212
+
213
+ // 没有匹配的子目录时,直接搜索根目录
214
+ if (matched_dirs.length === 0) {
215
+ return await getFileAsync(root_dir);
216
+ }
217
+
218
+ // 并行处理所有匹配目录的文件收集
219
+ var file_lists = await Promise.all(matched_dirs.map(getFileAsync));
220
+ return file_lists.flat();
221
+ };
222
+
223
+ /**
224
+ * 获取当前目录下所有文件
225
+ * @param {string} dir 目录地址
226
+ * @param {string} keyword 搜索关键词
227
+ * @returns {Array} 文件路径数组
228
+ */
229
+ File.prototype.get = function (dir, keyword) {
230
+ var list = [];
231
+ getFile(list, dir, keyword);
232
+ return list;
233
+ };
234
+
235
+ /**
236
+ * 加载文件
237
+ * @param {string} file 文件路径
238
+ * @param {string} encode 编码方式
239
+ * @returns {string} 加载的文本
240
+ */
241
+ File.prototype.load = function (file, encode) {
242
+ if (!file || typeof file !== 'string') {
243
+ throw new Error('file参数必须是非空字符串');
244
+ }
245
+ try {
246
+ if (existsSync(file) && isFile(file)) {
247
+ return readFileSync(file, encode);
248
+ }
249
+ return null;
250
+ } catch (error) {
251
+ this.log('error', '加载文件失败:', error);
252
+ return null;
253
+ }
254
+ };
255
+
256
+ /**
257
+ * 保存文件
258
+ * @param {string} file 文件路径
259
+ * @param {string} data 数据内容
260
+ * @param {string} encode 编码方式
261
+ * @returns {boolean} 保存成功返回true,否则返回false
262
+ */
263
+ File.prototype.save = function (file, data, encode) {
264
+ if (!file || typeof file !== 'string') {
265
+ throw new Error('file参数必须是非空字符串');
266
+ }
267
+ if (data === undefined || data === null) {
268
+ throw new Error('data参数不能为空');
269
+ }
270
+ var enc = encode || 'utf-8';
271
+ try {
272
+ writeFileSync(file, data, enc);
273
+ return true;
274
+ } catch (e) {
275
+ this.log('error', '保存文件失败:', e);
276
+ return false;
277
+ }
278
+ };
279
+
280
+ /**
281
+ * 复制文件
282
+ * @param {string} source 文件原路径
283
+ * @param {string} target 目标路径
284
+ * @returns {boolean} 复制成功返回true,否则返回false
285
+ */
286
+ File.prototype.copy = function (source, target) {
287
+ if (!source || typeof source !== 'string') {
288
+ throw new Error('source参数必须是非空字符串');
289
+ }
290
+ if (!target || typeof target !== 'string') {
291
+ throw new Error('target参数必须是非空字符串');
292
+ }
293
+ try {
294
+ copyFileSync(source.fullname(), target.fullname());
295
+ return true;
296
+ } catch (err) {
297
+ this.log('error', err);
298
+ return false;
299
+ }
300
+ };
301
+
302
+ /**
303
+ * 删除文件
304
+ * @param {string} file 文件路径
305
+ * @returns {boolean} 删除成功返回true,否则返回false
306
+ */
307
+ File.prototype.del = function (file) {
308
+ if (!file || typeof file !== 'string') {
309
+ throw new Error('file参数必须是非空字符串');
310
+ }
311
+ try {
312
+ unlinkSync(file.fullname());
313
+ return true;
314
+ } catch (err) {
315
+ this.log('error', err);
316
+ return false;
317
+ }
318
+ };
319
+
320
+ /**
321
+ * 检查文件是否存在
322
+ * @param {string} file 文件路径
323
+ * @returns {boolean} 存在返回true,否则返回false
324
+ */
325
+ File.prototype.exists = function (file) {
326
+ if (!file || typeof file !== 'string') {
327
+ throw new Error('file参数必须是非空字符串');
328
+ }
329
+ return existsSync(file);
330
+ };
331
+
332
+ /**
333
+ * 读取文件内容
334
+ * @param {string} file 文件路径
335
+ * @param {string} encode 编码方式
336
+ * @returns {string} 文件内容
337
+ */
338
+ File.prototype.read = function (file, encode) {
339
+ var enc = encode || 'utf-8';
340
+ return this.load(file, enc);
341
+ };
342
+
343
+ /**
344
+ * 写入文件内容
345
+ * @param {string} file 文件路径
346
+ * @param {string} data 数据内容
347
+ * @param {string} encode 编码方式
348
+ * @returns {boolean} 写入成功返回true,否则返回false
349
+ */
350
+ File.prototype.write = function (file, data, encode) {
351
+ if (!data) {
352
+ throw new Error('data参数不能为空');
353
+ }
354
+ return this.save(file, data, encode);
355
+ };
356
+
357
+ /**
358
+ * 搜索目录下所有目录
359
+ * @param {Array} list 结果路径数组
360
+ * @param {string} dir 目录地址
361
+ * @param {string} keyword 搜索关键词
362
+ */
363
+ function eachDir(list, dir, keyword) {
364
+ var items = readdirSync(dir);
365
+ var len = items.length;
366
+
367
+ for (var i = 0; i < len; i++) {
368
+ var name = items[i];
369
+ var f = join(dir, name);
370
+
371
+ // 识别是否为目录
372
+ if (!isFile(f)) {
373
+ // 判断是否包含关键词
374
+ if (!keyword || name.has(keyword)) {
375
+ list.push(f);
376
+ }
377
+ eachDir(list, f, keyword);
378
+ }
379
+ }
380
+ }
381
+ /**
382
+ * 异步并行搜索目录下所有目录
383
+ * @param {Array} list 结果路径数组
384
+ * @param {string} dir 目录地址
385
+ * @param {string} keyword 搜索关键词
386
+ */
387
+ async function eachDirAsync(list, dir, keyword) {
388
+ var items;
389
+ try {
390
+ items = await fsp.readdir(dir, { withFileTypes: true });
391
+ } catch {
392
+ return;
393
+ }
394
+
395
+ var subDirs = [];
396
+ for (var i = 0; i < items.length; i++) {
397
+ var item = items[i];
398
+ if (item.isDirectory()) {
399
+ var full_path = join(dir, item.name);
400
+ if (!keyword || item.name.has(keyword)) {
401
+ list.push(full_path);
402
+ }
403
+ subDirs.push(full_path);
404
+ }
405
+ }
406
+
407
+ // 并行遍历所有子目录
408
+ if (subDirs.length > 0) {
409
+ await Promise.all(subDirs.map((d) => {
410
+ return eachDirAsync(list, d, keyword);
411
+ }));
412
+ }
413
+ }
414
+
415
+ /**
416
+ * 获取当前目录下所有目录
417
+ * @param {Array} list 结果路径数组
418
+ * @param {string} dir 目录地址
419
+ * @param {string} keyword 搜索关键词
420
+ */
421
+ function getDir(list, dir, keyword) {
422
+ var items = readdirSync(dir);
423
+ var len = items.length;
424
+
425
+ for (var i = 0; i < len; i++) {
426
+ var name = items[i];
427
+ var f = join(dir, name);
428
+
429
+ // 识别是否为目录
430
+ if (!isFile(f)) {
431
+ // 判断关键词是否相等
432
+ if (!keyword || name.has(keyword)) {
433
+ list.push(f);
434
+ }
435
+ }
436
+ }
437
+ }
438
+
439
+ /**
440
+ * 添加文件路径
441
+ * @param {Array} list 结果路径数组
442
+ * @param {string} dir 目录地址
443
+ * @param {string} keyword 搜索关键词
444
+ */
445
+ function getFile(list, dir, keyword) {
446
+ var items = readdirSync(dir);
447
+ var len = items.length;
448
+
449
+ for (var i = 0; i < len; i++) {
450
+ var name = items[i];
451
+ var f = join(dir, name);
452
+
453
+ // 识别是否非目录
454
+ if (isFile(f)) {
455
+ // 判断关键词是否相等
456
+ if (!keyword || name.has(keyword)) {
457
+ list.push(f);
458
+ }
459
+ }
460
+ }
461
+ }
462
+ /**
463
+ * 目录类函数
464
+ */
465
+ class Dir extends Base {
466
+ /**
467
+ * 构造函数
468
+ * @param {object} config 目录配置对象
469
+ */
470
+ constructor(config) {
471
+ super(config);
472
+ }
473
+ }
474
+
475
+ /**
476
+ * 补全目录路径
477
+ * @param {string} dir 目录路径
478
+ * @param {string} base_dir 基础目录
479
+ * @returns {string} 完整路径
480
+ */
481
+ Dir.prototype.fullname = fullname;
482
+
483
+ /**
484
+ * 搜索目录下所有目录
485
+ * @param {string} dir 目录地址
486
+ * @param {string} keyword 搜索关键词
487
+ * @returns {Array} 目录路径数组
488
+ */
489
+ Dir.prototype.getAll = function (dir, keyword) {
490
+ var list = [];
491
+ eachDir(list, this.fullname(dir), keyword);
492
+ return list;
493
+ };
494
+
495
+ /**
496
+ * 搜索目录下所有目录(异步并行极速版)
497
+ * @param {string} dir 目录地址
498
+ * @param {string} keyword 搜索关键词
499
+ * @returns {Array} 目录路径数组
500
+ */
501
+ Dir.prototype.getAllAsync = async function (dir, keyword) {
502
+ var list = [];
503
+ await eachDirAsync(list, this.fullname(dir), keyword);
504
+ return list;
505
+ };
506
+
507
+ /**
508
+ * 搜索当前目录下所有目录
509
+ * @param {string} dir 目录地址
510
+ * @param {string} keyword 搜索关键词
511
+ * @returns {Array} 目录路径数组
512
+ */
513
+ Dir.prototype.get = function (dir, keyword) {
514
+ var list = [];
515
+ getDir(list, this.fullname(dir), keyword);
516
+ return list;
517
+ };
518
+
519
+ /**
520
+ * 复制目录
521
+ * @param {string} source 源路径
522
+ * @param {string} target 目标路径
523
+ * @param {Function} func 回调函数
524
+ */
525
+ Dir.prototype.copy = function (source, target, func) {
526
+ ncp(source.fullname(), target.fullname(), func);
527
+ };
528
+
529
+ /**
530
+ * 删除目录
531
+ * @param {string} dir 目录路径
532
+ * @param {Function} func 回调函数
533
+ */
534
+ Dir.prototype.del = function (dir, func) {
535
+ rimrafSync(dir.fullname(), func);
536
+ };
537
+
538
+ /**
539
+ * 判断目录是否存在
540
+ * @param {string} dir 目录路径
541
+ * @returns {boolean} 存在返回true, 否则返回false
542
+ */
543
+ Dir.prototype.exists = function (dir) {
544
+ return existsSync(dir.fullname());
545
+ };
546
+
547
+ /** === 文件类函数 === */
548
+ /**
549
+ * 补全路径
550
+ * @param {string} dir 文件路径
551
+ * @returns {string} 全路径
552
+ */
553
+ String.prototype.fullname = function (dir) {
554
+ return fullname(this + '', dir);
555
+ };
556
+
557
+ /**
558
+ * 获取当前目录下所有文件
559
+ * @param {string} keyword 搜索关键词
560
+ * @returns {Array} 文件路径数组
561
+ */
562
+ String.prototype.getFile = function (keyword) {
563
+ return $.file.get(this + '', keyword);
564
+ };
565
+
566
+ /**
567
+ * 搜索当前目录下所有目录
568
+ * @param {string} keyword 搜索关键词
569
+ * @returns {Array} 目录路径数组
570
+ */
571
+ String.prototype.getDir = function (keyword) {
572
+ return $.dir.get(this + '', keyword);
573
+ };
574
+
575
+ /**
576
+ * 复制文件
577
+ * @param {string} file 保存路径
578
+ * @returns {boolean} 复制成功返回true, 失败返回false
579
+ */
580
+ String.prototype.copyFile = function (file) {
581
+ return copyFileSync(this.fullname(), file.fullname());
582
+ };
583
+
584
+ /**
585
+ * 复制目录
586
+ * @param {string} file 保存路径
587
+ * @param {Function} func 回调函数
588
+ * @returns {boolean} 复制成功返回true, 失败返回false
589
+ */
590
+ String.prototype.copyDir = function (file, func) {
591
+ return ncp(this.fullname(), file.fullname(), func);
592
+ };
593
+
594
+ /**
595
+ * 是否目录
596
+ * @returns {boolean} 是目录返回true, 否则返回false
597
+ */
598
+ String.prototype.isDir = function () {
599
+ var bl = false;
600
+ try {
601
+ bl = statSync(this + '').isDirectory();
602
+ } catch {
603
+ // $.log.error("非路径");
604
+ }
605
+ return bl;
606
+ };
607
+
608
+ /**
609
+ * 是否文件
610
+ * @returns {boolean} 是目录返回true, 否则返回false
611
+ */
612
+ String.prototype.isFile = function () {
613
+ var bl = false;
614
+ try {
615
+ bl = statSync(this + '').isFile();
616
+ } catch {
617
+ // $.log.error("非路径");
618
+ }
619
+ return bl;
620
+ };
621
+
622
+ /**
623
+ * 取路径
624
+ * @returns {string} 字符串
625
+ */
626
+ String.prototype.dirname = function () {
627
+ return dirname(this + '');
628
+ };
629
+ /**
630
+ * 取基础名
631
+ * @returns {string} 字符串
632
+ */
633
+ String.prototype.basename = function () {
634
+ return basename(this + '');
635
+ };
636
+ /**
637
+ * 取拓展名
638
+ * @returns {string} 字符串
639
+ */
640
+ String.prototype.extname = function () {
641
+ return extname(this + '');
642
+ };
643
+
644
+
645
+ /**
646
+ * 保存文件
647
+ * @param {string} text 被保存的文本
648
+ * @returns {boolean} 保存成功返回true, 失败返回false
649
+ */
650
+ String.prototype.saveText = function (text) {
651
+ try {
652
+ writeFileSync(this.fullname(), text);
653
+ return true;
654
+ } catch (e) {
655
+ $.log.error('保存文件失败:', e);
656
+ return false;
657
+ }
658
+ };
659
+
660
+ /**
661
+ * 保存为JSON格式文件
662
+ * @param {object} obj 被保存的内容
663
+ * @param {boolean} format 是否格式化
664
+ * @param {number} space 格式化缩进空格数
665
+ * @returns {boolean} 保存成功返回true, 失败返回false
666
+ */
667
+ String.prototype.saveJson = function (obj, format = true, space = 2) {
668
+ try {
669
+ var text = '';
670
+ if (format) {
671
+ text = JSON.stringify(obj, null, space);
672
+ } else {
673
+ text = JSON.stringify(obj);
674
+ }
675
+ writeFileSync(this.fullname(), text);
676
+ return true;
677
+ } catch (e) {
678
+ $.log.error('保存JSON文件失败:', e);
679
+ return false;
680
+ }
681
+ };
682
+
683
+ /**
684
+ * 加载文件
685
+ * @param {string} encode 编码方式
686
+ * @returns {string} 加载的文本
687
+ */
688
+ String.prototype.loadText = function (encode) {
689
+ var file = this.fullname();
690
+ if (existsSync(file)) {
691
+ var enc = encode || 'utf-8';
692
+ return readFileSync(file, enc);
693
+ } else {
694
+ return undefined;
695
+ }
696
+ };
697
+
698
+ /**
699
+ * 加载xml
700
+ * @param {string} file 当前路径
701
+ * @returns {string} 加载的文本
702
+ */
703
+ String.prototype.loadXml = function (file) {
704
+ var f = this.fullname(file);
705
+ if (existsSync(f)) {
706
+ var text = readFileSync(f, 'utf-8');
707
+ if (text) {
708
+ var ret = text.toXml();
709
+ if (ret === null) {
710
+ $.log.error('xml反序列化错误!', f);
711
+ }
712
+ return ret;
713
+ }
714
+ }
715
+ return undefined;
716
+ };
717
+
718
+ /**
719
+ * 加载json
720
+ * @param {string} file 当前路径
721
+ * @returns {object} 加载后转换的对象
722
+ */
723
+ String.prototype.loadJson = function (file) {
724
+ var f = this.fullname(file);
725
+ if (existsSync(f)) {
726
+ var text = readFileSync(f, 'utf-8');
727
+ if (text) {
728
+ var ret = text.toJson();
729
+ if (ret === null) {
730
+ $.log.error('json反序列化错误!', f);
731
+ }
732
+ return ret;
733
+ }
734
+ }
735
+ return undefined;
736
+ };
737
+
738
+ /**
739
+ * 判断文件是否存在
740
+ * @param {string} file 当前路径
741
+ * @returns {boolean} 存在返回true, 不存在返回false
742
+ */
743
+ String.prototype.hasFile = function (file) {
744
+ return existsSync(this.fullname(file));
745
+ };
746
+
747
+ /**
748
+ * 删除文件
749
+ * @param {string} file 当前路径
750
+ */
751
+ String.prototype.delFile = function (file) {
752
+ unlinkSync(this.fullname(file), (e) => { });
753
+ };
754
+
755
+ /**
756
+ * 判断目录是否存在
757
+ * @param {string} dir 当前路径
758
+ * @returns {boolean} 存在返回true, 不存在返回false
759
+ */
760
+ String.prototype.hasDir = function (dir) {
761
+ return existsSync(this.fullname(dir));
762
+ };
763
+
764
+ /**
765
+ * 删除目录
766
+ * @param {string} dir 当前路径
767
+ * @param {Function} func 回调函数
768
+ */
769
+ String.prototype.delDir = function (dir, func) {
770
+ rimrafSync(this.fullname(dir), func);
771
+ };
772
+
773
+ /**
774
+ * 创建路径
775
+ * @param {string} dirbase 目录基础路径
776
+ */
777
+ String.prototype.addDir = function (dirbase) {
778
+ var d = (this + '').fullname(dirbase);
779
+
780
+ if (d.indexOf('.') !== -1) {
781
+ d = d.dirname();
782
+ }
783
+ var l = $.slash;
784
+ var arr = d.split(l);
785
+ var dir = '';
786
+ for (var i = 0; i < arr.length; i++) {
787
+ dir += arr[i] + l;
788
+ if (!existsSync(dir)) {
789
+ mkdirSync(dir);
790
+ }
791
+ }
792
+ };
793
+
794
+ module.exports = {
795
+ File,
796
+ Dir
797
+ };