mm_machine 2.9.3 → 2.9.5
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/dir_scanner.js +262 -0
- package/drive.js +668 -490
- package/id_gen.js +138 -0
- package/index.js +1141 -921
- package/mod.js +742 -711
- package/package.json +6 -2
- package/registry.js +634 -0
- package/word.js +209 -0
package/dir_scanner.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
var fs = require('fs');
|
|
2
|
+
var path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* DirScanner 目录检索器
|
|
6
|
+
* 独立工具类,负责扫描目录查找 JSON 配置文件
|
|
7
|
+
* 不依赖 Manager 或 Registry,可独立使用
|
|
8
|
+
*/
|
|
9
|
+
class DirScanner {
|
|
10
|
+
/**
|
|
11
|
+
* 构造函数
|
|
12
|
+
* @param {object} [cfg] 配置参数
|
|
13
|
+
* @param {number} [cfg.cache_ttl=30000] 缓存有效期(毫秒),默认 30 秒
|
|
14
|
+
*/
|
|
15
|
+
constructor(cfg) {
|
|
16
|
+
var opts = cfg || {};
|
|
17
|
+
this._cache = new Map();
|
|
18
|
+
this._cache_ttl = opts.cache_ttl || 30000;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 规范化扫描参数
|
|
24
|
+
* 处理参数重载:options 可选,force 可以是第三个参数
|
|
25
|
+
* @param {object|boolean} opts_in 配置对象或 force 标志
|
|
26
|
+
* @param {boolean} force_in force 标志
|
|
27
|
+
* @returns {{options: object, force: boolean}} 规范化后的参数对象
|
|
28
|
+
* @private
|
|
29
|
+
*/
|
|
30
|
+
DirScanner.prototype._normScanOpts = function (opts_in, force_in) {
|
|
31
|
+
var opts = opts_in;
|
|
32
|
+
var force = force_in;
|
|
33
|
+
if (typeof opts === 'boolean') {
|
|
34
|
+
force = opts;
|
|
35
|
+
opts = null;
|
|
36
|
+
}
|
|
37
|
+
return { options: opts || {}, force: force || false };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 获取缓存结果
|
|
42
|
+
* @param {string} key 缓存键
|
|
43
|
+
* @returns {Array|null} 缓存结果,不存在或已过期返回 null
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
46
|
+
DirScanner.prototype._getCache = function (key) {
|
|
47
|
+
var entry = this._cache.get(key);
|
|
48
|
+
if (!entry) return null;
|
|
49
|
+
if (Date.now() - entry.time > this._cache_ttl) {
|
|
50
|
+
this._cache.delete(key);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return entry.results;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 设置缓存
|
|
58
|
+
* @param {string} key 缓存键
|
|
59
|
+
* @param {Array} results 扫描结果
|
|
60
|
+
* @private
|
|
61
|
+
*/
|
|
62
|
+
DirScanner.prototype._setCache = function (key, results) {
|
|
63
|
+
this._cache.set(key, { results: results, time: Date.now() });
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 递归扫描指定目录,查找匹配的 JSON 配置文件
|
|
68
|
+
* @param {string} dir 要扫描的目录路径
|
|
69
|
+
* @param {string} pattern 文件匹配模式(如 '*.json')
|
|
70
|
+
* @param {object} [opts_in] 可选项
|
|
71
|
+
* @param {string[]} [opts_in.exclude=['node_modules', '.git']] 排除的目录名列表
|
|
72
|
+
* @param {Function} [opts_in.filter] 自定义过滤器,接收 {file, name} 对象,返回 true 表示保留
|
|
73
|
+
* @param {boolean} [force_in] 是否强制重新扫描(忽略缓存)
|
|
74
|
+
* @returns {Array<{file: string, name: string}>} 扫描结果数组
|
|
75
|
+
*/
|
|
76
|
+
DirScanner.prototype.scanDir = function (dir, pattern, opts_in, force_in) {
|
|
77
|
+
if (typeof dir !== 'string' || !dir) {
|
|
78
|
+
throw new TypeError('目录路径必须是字符串');
|
|
79
|
+
}
|
|
80
|
+
if (typeof pattern !== 'string' || !pattern) {
|
|
81
|
+
throw new TypeError('文件匹配模式必须是字符串');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
var opts = this._normScanOpts(opts_in, force_in);
|
|
85
|
+
var force = opts.force;
|
|
86
|
+
var options = opts.options;
|
|
87
|
+
|
|
88
|
+
// 缓存检查
|
|
89
|
+
var cache_key = dir + '::' + pattern;
|
|
90
|
+
if (!force) {
|
|
91
|
+
var cached = this._getCache(cache_key);
|
|
92
|
+
if (cached) return cached;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
var exclude = options.exclude || ['node_modules', '.git'];
|
|
96
|
+
var filter = options.filter || null;
|
|
97
|
+
var results = [];
|
|
98
|
+
var abs_dir = path.resolve(dir);
|
|
99
|
+
|
|
100
|
+
// 检查目录是否存在
|
|
101
|
+
if (!fs.existsSync(abs_dir)) {
|
|
102
|
+
return results;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this._scanDirRecu(abs_dir, pattern, exclude, filter, results);
|
|
106
|
+
|
|
107
|
+
// 设置缓存
|
|
108
|
+
this._setCache(cache_key, results);
|
|
109
|
+
return results;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 递归扫描目录内部方法
|
|
114
|
+
* @param {string} dir 当前目录
|
|
115
|
+
* @param {string} pattern 匹配模式
|
|
116
|
+
* @param {string[]} exclude 排除目录
|
|
117
|
+
* @param {Function|null} filter 自定义过滤器
|
|
118
|
+
* @param {Array} results 结果数组
|
|
119
|
+
* @private
|
|
120
|
+
*/
|
|
121
|
+
DirScanner.prototype._scanDirRecu = function (dir, pattern, exclude, filter, results) {
|
|
122
|
+
var items;
|
|
123
|
+
try {
|
|
124
|
+
// eslint-disable-next-line mm_eslint/variable-name
|
|
125
|
+
items = fs.readdirSync(dir, { withFileTypes: true });
|
|
126
|
+
} catch {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (var i = 0; i < items.length; i++) {
|
|
131
|
+
var item = items[i];
|
|
132
|
+
var cur_path = path.join(dir, item.name);
|
|
133
|
+
|
|
134
|
+
if (item.isDirectory()) {
|
|
135
|
+
// 跳过排除目录
|
|
136
|
+
if (exclude.indexOf(item.name) !== -1) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
this._scanDirRecu(cur_path, pattern, exclude, filter, results);
|
|
140
|
+
} else if (item.isFile()) {
|
|
141
|
+
// 检查文件是否匹配模式
|
|
142
|
+
if (!this._matchPattern(item.name, pattern)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
var name = this._extractName(cur_path);
|
|
147
|
+
var entry = { file: cur_path, name: name };
|
|
148
|
+
|
|
149
|
+
// 应用自定义过滤器
|
|
150
|
+
if (filter) {
|
|
151
|
+
if (!filter(entry)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
results.push(entry);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 检查文件名是否匹配模式
|
|
163
|
+
* @param {string} filename 文件名
|
|
164
|
+
* @param {string} pattern 匹配模式(如 *.json)
|
|
165
|
+
* @returns {boolean} 是否匹配
|
|
166
|
+
* @private
|
|
167
|
+
*/
|
|
168
|
+
DirScanner.prototype._matchPattern = function (filename, pattern) {
|
|
169
|
+
// 简单通配符匹配
|
|
170
|
+
if (pattern === '*') return true;
|
|
171
|
+
if (pattern === '*' + path.extname(filename)) return true;
|
|
172
|
+
if (pattern === filename) return true;
|
|
173
|
+
|
|
174
|
+
// 将 * 转换为正则
|
|
175
|
+
var regex_str = '^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$';
|
|
176
|
+
return new RegExp(regex_str).test(filename);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 从 JSON 文件提取 name
|
|
181
|
+
* 优先读取 JSON 文件中的 name 字段,失败则从文件名去扩展名
|
|
182
|
+
* @param {string} file_path 文件路径
|
|
183
|
+
* @returns {string} 模块名称
|
|
184
|
+
* @private
|
|
185
|
+
*/
|
|
186
|
+
DirScanner.prototype._extractName = function (file_path) {
|
|
187
|
+
try {
|
|
188
|
+
var content = fs.readFileSync(file_path, 'utf8');
|
|
189
|
+
var config = JSON.parse(content);
|
|
190
|
+
if (config && config.name) {
|
|
191
|
+
return config.name;
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
// JSON 解析失败,忽略
|
|
195
|
+
}
|
|
196
|
+
// 从文件名去扩展名
|
|
197
|
+
return path.basename(file_path, path.extname(file_path));
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 扫描并解析 JSON 配置文件
|
|
202
|
+
* @param {string} dir 要扫描的目录路径
|
|
203
|
+
* @param {string} pattern 文件匹配模式
|
|
204
|
+
* @param {object} [options] 可选项(同 scanDir)
|
|
205
|
+
* @param {boolean} [force] 是否强制重新扫描
|
|
206
|
+
* @returns {Array<{file: string, config: object}>} 扫描结果数组,JSON 解析失败时跳过该文件
|
|
207
|
+
*/
|
|
208
|
+
DirScanner.prototype.scanAndParse = function (dir, pattern, options, force) {
|
|
209
|
+
var files = this.scanDir(dir, pattern, options, force);
|
|
210
|
+
var results = [];
|
|
211
|
+
|
|
212
|
+
for (var i = 0; i < files.length; i++) {
|
|
213
|
+
var entry = files[i];
|
|
214
|
+
try {
|
|
215
|
+
var content = fs.readFileSync(entry.file, 'utf8');
|
|
216
|
+
var config = JSON.parse(content);
|
|
217
|
+
results.push({ file: entry.file, config: config });
|
|
218
|
+
} catch {
|
|
219
|
+
// JSON 解析失败时跳过该文件
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return results;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 扫描目录并一键注册到 Manager
|
|
229
|
+
* @param {string} dir 要扫描的目录路径
|
|
230
|
+
* @param {object} manager Manager 实例
|
|
231
|
+
* @param {boolean} [force] 是否强制重新扫描
|
|
232
|
+
* @returns {number} 成功注册的模块数量
|
|
233
|
+
*/
|
|
234
|
+
DirScanner.prototype.scanAndRegister = function (dir, manager, force) {
|
|
235
|
+
if (!manager || typeof manager.registerMod !== 'function') {
|
|
236
|
+
throw new TypeError('manager 必须包含 registerMod 方法');
|
|
237
|
+
}
|
|
238
|
+
var entries = this.scanAndParse(dir, '*.json', null, force);
|
|
239
|
+
var count = 0;
|
|
240
|
+
|
|
241
|
+
for (var i = 0; i < entries.length; i++) {
|
|
242
|
+
var entry = entries[i];
|
|
243
|
+
try {
|
|
244
|
+
manager.registerMod(entry.file, entry.config);
|
|
245
|
+
count++;
|
|
246
|
+
} catch {
|
|
247
|
+
// 单个模块注册失败不影响其他模块
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return count;
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 清除所有扫描缓存
|
|
257
|
+
*/
|
|
258
|
+
DirScanner.prototype.clearCache = function () {
|
|
259
|
+
this._cache.clear();
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
module.exports = { DirScanner };
|