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.
Files changed (8) hide show
  1. package/dir_scanner.js +262 -0
  2. package/drive.js +668 -490
  3. package/id_gen.js +138 -0
  4. package/index.js +1141 -921
  5. package/mod.js +742 -711
  6. package/package.json +6 -2
  7. package/registry.js +634 -0
  8. 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 };