mm_tpl 2.7.2 → 2.7.3

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,200 @@
1
+ /**
2
+ * 基础语言类
3
+ * @description 提供语言包字典存储、翻译取词、复数形式选择与占位符插值能力
4
+ */
5
+
6
+ /**
7
+ * 基础语言类
8
+ * @class BaseLang
9
+ */
10
+ class BaseLang {
11
+ /**
12
+ * 创建语言实例
13
+ * @param {Object} config 语言包字典,形如 { zh_cn: { key: 值 }, default: 'zh_cn' }
14
+ */
15
+ constructor(config) {
16
+ /**
17
+ * 语言包字典
18
+ * @type {Object}
19
+ */
20
+ this.config = config || {};
21
+ /**
22
+ * 当前语言代码
23
+ * @type {String}
24
+ */
25
+ this.now = this.config.default || 'zh_cn';
26
+ /**
27
+ * 复数规则表,键为语言代码,值为根据数量返回复数索引的函数
28
+ * @type {Object}
29
+ */
30
+ this.rules = {
31
+ zh_cn: function (count) {
32
+ return 0;
33
+ },
34
+ en: function (count) {
35
+ return count === 1 ? 0 : 1;
36
+ }
37
+ };
38
+ }
39
+
40
+ /**
41
+ * 注册语言复数规则
42
+ * @param {String} code 语言代码
43
+ * @param {Function} fn 复数规则函数,入参为数量,返回复数索引
44
+ * @returns {BaseLang} 当前实例
45
+ */
46
+ registerPlural(code, fn) {
47
+ if (code && typeof fn === 'function') {
48
+ this.rules[code] = fn;
49
+ }
50
+ return this;
51
+ }
52
+
53
+ /**
54
+ * 设置当前语言
55
+ * 请求的语言不支持时回退到默认语言
56
+ * @param {String} lang 语言代码
57
+ * @returns {String} 设置后的语言代码
58
+ */
59
+ setLang(lang) {
60
+ if (this.isSupported(lang)) {
61
+ this.now = lang;
62
+ } else if (this.isSupported(this.config.default)) {
63
+ this.now = this.config.default;
64
+ }
65
+ return this.now;
66
+ }
67
+
68
+ /**
69
+ * 获取支持的语言列表
70
+ * 过滤掉配置中的默认语言标记
71
+ * @returns {Array<String>} 语言代码数组
72
+ */
73
+ getLangs() {
74
+ return Object.keys(this.config).filter(function (key) {
75
+ return key !== 'default';
76
+ });
77
+ }
78
+
79
+ /**
80
+ * 判断语言是否支持
81
+ * @param {String} lang 语言代码
82
+ * @returns {Boolean} 支持返回true,否则返回false
83
+ */
84
+ isSupported(lang) {
85
+ return !!lang && lang !== 'default' && typeof this.config[lang] === 'object' && this.config[lang] !== null;
86
+ }
87
+
88
+ /**
89
+ * 获取翻译词条
90
+ * 键支持 a.b 层级形式,词条缺失时回退到默认语言
91
+ * @param {String} key 词条键名
92
+ * @param {...any} args 位置占位符替换参数,依次替换 {0}、{1} 等
93
+ * @returns {*} 翻译文本;数组词条不带数量时原样返回
94
+ */
95
+ get(key, ...args) {
96
+ var val = this._getValue(key);
97
+ if (typeof val !== 'string' || !args.length) {
98
+ return val;
99
+ }
100
+ for (var i = 0; i < args.length; i++) {
101
+ val = val.split('{' + i + '}').join(args[i]);
102
+ }
103
+ return val;
104
+ }
105
+
106
+ /**
107
+ * 获取带复数形式的翻译文本
108
+ * @param {String} key 词条键名
109
+ * @param {Number} count 数量,用于选择复数形式并替换 {count} 占位符
110
+ * @param {...any} args 位置占位符替换参数
111
+ * @returns {String} 翻译文本
112
+ */
113
+ tc(key, count, ...args) {
114
+ var val = this._getValue(key);
115
+ var rule = this.rules[this.now] || this.rules.zh_cn;
116
+ var index = typeof rule === 'function' ? rule(count) : 0;
117
+ var text = Array.isArray(val) ? (val[index] || val[0] || '') : (typeof val === 'string' ? val : '');
118
+ text = String(text).split('{count}').join(count);
119
+ for (var i = 0; i < args.length; i++) {
120
+ text = text.split('{' + i + '}').join(args[i]);
121
+ }
122
+ return text;
123
+ }
124
+
125
+ /**
126
+ * 写入词条
127
+ * 写入当前语言字典,键支持 a.b 层级形式
128
+ * @param {String} key 词条键名
129
+ * @param {*} value 词条值
130
+ * @returns {BaseLang} 当前实例
131
+ */
132
+ set(key, value) {
133
+ var lang = this.config[this.now];
134
+ if (!lang || typeof lang !== 'object') {
135
+ lang = {};
136
+ this.config[this.now] = lang;
137
+ }
138
+ setByPath(lang, key, value);
139
+ return this;
140
+ }
141
+
142
+ /**
143
+ * 从当前语言字典取值,词条缺失时回退到默认语言
144
+ * @param {String} key 词条键名
145
+ * @returns {*} 词条值
146
+ * @private
147
+ */
148
+ _getValue(key) {
149
+ var now_dict = this.config[this.now];
150
+ var val = now_dict ? getByPath(now_dict, key) : undefined;
151
+ if (val === undefined && this.now !== this.config.default) {
152
+ var def_dict = this.config[this.config.default];
153
+ if (def_dict) {
154
+ val = getByPath(def_dict, key);
155
+ }
156
+ }
157
+ return val;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * 按路径从对象中取值
163
+ * @param {Object} obj 对象
164
+ * @param {String} path 点号路径
165
+ * @returns {*} 路径对应的值
166
+ */
167
+ function getByPath(obj, path) {
168
+ var keys = String(path).split('.');
169
+ var val = obj;
170
+ for (var i = 0; i < keys.length; i++) {
171
+ if (val === null || val === undefined || typeof val !== 'object') {
172
+ return undefined;
173
+ }
174
+ val = val[keys[i]];
175
+ }
176
+ return val;
177
+ }
178
+
179
+ /**
180
+ * 按路径向对象写入值
181
+ * @param {Object} obj 对象
182
+ * @param {String} path 点号路径
183
+ * @param {*} value 写入的值
184
+ * @returns {void}
185
+ */
186
+ function setByPath(obj, path, value) {
187
+ var keys = String(path).split('.');
188
+ var cur = obj;
189
+ for (var i = 0; i < keys.length - 1; i++) {
190
+ if (typeof cur[keys[i]] !== 'object' || cur[keys[i]] === null) {
191
+ cur[keys[i]] = {};
192
+ }
193
+ cur = cur[keys[i]];
194
+ }
195
+ cur[keys[keys.length - 1]] = value;
196
+ }
197
+
198
+ module.exports = {
199
+ BaseLang
200
+ };
package/lib/file.js ADDED
@@ -0,0 +1,258 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ /**
6
+ * 运行根目录,路径补全的默认基准,末尾带路径分隔符
7
+ */
8
+ var run_path = process.cwd() + path.join('/');
9
+ /**
10
+ * 系统根目录,@/ 前缀路径的解析基准
11
+ */
12
+ var root_path = path.resolve();
13
+
14
+ /**
15
+ * 文件工具类
16
+ * 提供路径补全、目录扫描、文件读取等纯函数能力,全部以静态方法提供
17
+ * 路径规则与 mm_expand 保持一致:/ 开头相对运行根目录、~/ 相对用户主目录、@/ 相对系统根目录,其余按基准目录解析
18
+ * @class
19
+ */
20
+ class FileUtil {
21
+ /**
22
+ * 获取运行根目录
23
+ * @returns {string} 运行根目录,末尾带路径分隔符
24
+ */
25
+ static getRunPath() {
26
+ return run_path;
27
+ }
28
+
29
+ /**
30
+ * 设置运行根目录
31
+ * @param {string} dir - 新的运行根目录
32
+ * @returns {string} 设置后的运行根目录
33
+ */
34
+ static setRunPath(dir) {
35
+ if (typeof (dir) !== 'string' || !dir) {
36
+ return run_path;
37
+ }
38
+ run_path = dir.endsWith(path.join('/')) ? dir : dir + path.join('/');
39
+ return run_path;
40
+ }
41
+
42
+ /**
43
+ * 补全文件路径
44
+ * @param {string} file - 文件路径
45
+ * @param {string} dir - 基准目录,相对路径以此为基准解析
46
+ * @returns {string} 完整路径
47
+ */
48
+ static fullname(file, dir) {
49
+ if (typeof (file) !== 'string') {
50
+ throw new Error('file参数必须是字符串');
51
+ }
52
+ if (!file) {
53
+ return run_path || process.cwd();
54
+ }
55
+ if (file.startsWith('/') && !file.startsWith('//') && file !== '/') {
56
+ return FileUtil._handleRootPath(file);
57
+ }
58
+ if (file.startsWith('~/')) {
59
+ return path.resolve(os.homedir(), file.substring(2));
60
+ }
61
+ if (file.startsWith('@/')) {
62
+ return path.resolve(root_path, file.substring(2));
63
+ }
64
+ if (file.startsWith('./') || file.startsWith('../')
65
+ || (!path.isAbsolute(file) && !file.startsWith('/'))) {
66
+ return FileUtil._handleRelativePath(file, dir);
67
+ }
68
+ return file;
69
+ }
70
+
71
+ /**
72
+ * 判断路径是否为文件
73
+ * @param {string} file_or_dir - 文件或目录路径
74
+ * @returns {boolean} 是文件返回true,否则返回false
75
+ */
76
+ static isFile(file_or_dir) {
77
+ try {
78
+ return fs.statSync(file_or_dir).isDirectory() !== true;
79
+ } catch {
80
+ return false;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * 判断路径是否为目录
86
+ * @param {string} file_or_dir - 文件或目录路径
87
+ * @returns {boolean} 是目录返回true,否则返回false
88
+ */
89
+ static isDir(file_or_dir) {
90
+ try {
91
+ return fs.statSync(file_or_dir).isDirectory();
92
+ } catch {
93
+ return false;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * 判断目录是否存在
99
+ * @param {string} dir - 目录路径
100
+ * @param {string} base - 基准目录
101
+ * @returns {boolean} 存在返回true,否则返回false
102
+ */
103
+ static hasDir(dir, base) {
104
+ return fs.existsSync(FileUtil.fullname(dir, base));
105
+ }
106
+
107
+ /**
108
+ * 判断文件是否存在
109
+ * @param {string} file - 文件路径
110
+ * @param {string} base - 基准目录
111
+ * @returns {boolean} 存在返回true,否则返回false
112
+ */
113
+ static hasFile(file, base) {
114
+ return fs.existsSync(FileUtil.fullname(file, base));
115
+ }
116
+
117
+ /**
118
+ * 扫描目录下的文件
119
+ * 只返回一级目录中的文件,keyword 支持 * 通配:*后缀 结尾匹配、前缀* 开头匹配、*包含* 包含匹配
120
+ * @param {string} dir - 目录路径
121
+ * @param {string} keyword - 文件名匹配关键词,为空时返回全部文件
122
+ * @returns {Array<string>} 文件全路径数组
123
+ */
124
+ static listFiles(dir, keyword) {
125
+ var root = FileUtil.fullname(dir);
126
+ var list = [];
127
+ var items = [];
128
+ try {
129
+ items = fs.readdirSync(root);
130
+ } catch {
131
+ return list;
132
+ }
133
+ for (var i = 0; i < items.length; i++) {
134
+ var name = items[i];
135
+ var f = path.join(root, name);
136
+ if (FileUtil.isFile(f) && (!keyword || FileUtil._matchName(name, keyword))) {
137
+ list.push(f);
138
+ }
139
+ }
140
+ return list;
141
+ }
142
+
143
+ /**
144
+ * 读取文本文件
145
+ * @param {string} file - 文件路径
146
+ * @param {string} encode - 编码方式,默认 utf-8
147
+ * @returns {string|undefined} 文件文本,文件不存在时返回 undefined
148
+ */
149
+ static loadText(file, encode) {
150
+ var f = FileUtil.fullname(file);
151
+ if (fs.existsSync(f)) {
152
+ return fs.readFileSync(f, encode || 'utf-8');
153
+ }
154
+ return undefined;
155
+ }
156
+
157
+ /**
158
+ * 读取 JSON 文件
159
+ * @param {string} file - 文件路径
160
+ * @returns {Object|null|undefined} 解析后的对象,文件不存在返回 undefined,解析失败返回 null
161
+ */
162
+ static loadJson(file) {
163
+ var f = FileUtil.fullname(file);
164
+ if (!fs.existsSync(f)) {
165
+ return undefined;
166
+ }
167
+ var text = fs.readFileSync(f, 'utf-8');
168
+ if (!text) {
169
+ return undefined;
170
+ }
171
+ try {
172
+ return JSON.parse(text);
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * 取路径的目录部分
180
+ * @param {string} file - 文件路径
181
+ * @returns {string} 目录路径
182
+ */
183
+ static dirname(file) {
184
+ return path.dirname(file + '');
185
+ }
186
+
187
+ /**
188
+ * 取路径的基础名
189
+ * @param {string} file - 文件路径
190
+ * @returns {string} 基础名
191
+ */
192
+ static basename(file) {
193
+ return path.basename(file + '');
194
+ }
195
+
196
+ /**
197
+ * 取路径的扩展名
198
+ * @param {string} file - 文件路径
199
+ * @returns {string} 扩展名,含点号
200
+ */
201
+ static extname(file) {
202
+ return path.extname(file + '');
203
+ }
204
+
205
+ /**
206
+ * 处理以 / 开头的路径,相对运行根目录解析
207
+ * @param {string} file - 文件路径
208
+ * @returns {string} 完整路径
209
+ * @private
210
+ */
211
+ static _handleRootPath(file) {
212
+ if (run_path.indexOf(file) === 0 || file.indexOf(run_path) === 0) {
213
+ return file;
214
+ }
215
+ return path.resolve(run_path || process.cwd(), file.substring(1));
216
+ }
217
+
218
+ /**
219
+ * 处理相对路径,以基准目录或运行根目录为基准解析
220
+ * @param {string} file - 文件路径
221
+ * @param {string} dir - 基准目录
222
+ * @returns {string} 完整路径
223
+ * @private
224
+ */
225
+ static _handleRelativePath(file, dir) {
226
+ var base_dir = dir && typeof (dir) === 'string' ? dir : run_path;
227
+ return path.resolve(FileUtil.fullname(base_dir), file);
228
+ }
229
+
230
+ /**
231
+ * 按关键词匹配文件名
232
+ * 与关键词全等、或按 * 通配匹配时返回true
233
+ * @param {string} name - 文件名
234
+ * @param {string} keyword - 匹配关键词
235
+ * @returns {boolean} 是否匹配
236
+ * @private
237
+ */
238
+ static _matchName(name, keyword) {
239
+ if (name === keyword) {
240
+ return true;
241
+ }
242
+ var clean = keyword.replace(/\*/g, '');
243
+ if (keyword.startsWith('*') && keyword.endsWith('*')) {
244
+ return name.indexOf(clean) !== -1;
245
+ }
246
+ if (keyword.startsWith('*')) {
247
+ return name.endsWith(clean);
248
+ }
249
+ if (keyword.endsWith('*')) {
250
+ return name.startsWith(clean);
251
+ }
252
+ return false;
253
+ }
254
+ }
255
+
256
+ module.exports = {
257
+ FileUtil
258
+ };
package/lib/hook.js CHANGED
@@ -1,7 +1,9 @@
1
-
2
1
  const {
3
2
  types
4
- } = require("util");
3
+ } = require('util');
4
+ const {
5
+ TplUtil
6
+ } = require('./util.js');
5
7
 
6
8
  /**
7
9
  * 钩子管理类
@@ -10,8 +12,13 @@ const {
10
12
  class Hook {
11
13
  /**
12
14
  * 创建钩子实例
15
+ * @param {Object} logger - 日志对象,需含 error 方法,缺省使用 console
13
16
  */
14
- constructor() {
17
+ constructor(logger) {
18
+ /**
19
+ * 日志对象
20
+ */
21
+ this._logger = logger || console;
15
22
  this._func = {};
16
23
  this._action = {};
17
24
  this._filter = {};
@@ -38,11 +45,11 @@ Hook.prototype.sort = function (list) {
38
45
  Hook.prototype._getName = function (func, alias) {
39
46
  if (!alias) {
40
47
  var str = func.toString();
41
- var name = str.between('function ', "(").trim();
48
+ var name = TplUtil.between(str, 'function ', '(').trim();
42
49
  if (name) {
43
50
  alias = name;
44
51
  } else {
45
- alias = (new Date()).toString().md5().substring(0, 8);
52
+ alias = TplUtil.md5((new Date()).toString()).substring(0, 8);
46
53
  }
47
54
  }
48
55
  return alias;
@@ -189,7 +196,7 @@ Hook.prototype.runFunc = async function (name, model, req, ...args) {
189
196
  }
190
197
  }
191
198
  } catch (err) {
192
- $.log.error(`[${this.constructor.name}] 执行函数钩子失败 ${item.alias}`, err);
199
+ this._logger.error(`[${this.constructor.name}] 执行函数钩子失败 ${item.alias}`, err);
193
200
  }
194
201
  }
195
202
  }
@@ -212,7 +219,7 @@ Hook.prototype.runAction = function (name, model, ...args) {
212
219
  try {
213
220
  ret += o.func(model, ...args);
214
221
  } catch (err) {
215
- $.log.error(`[${this.constructor.name}] 执行动作钩子失败 ${o.alias}`, err);
222
+ this._logger.error(`[${this.constructor.name}] 执行动作钩子失败 ${o.alias}`, err);
216
223
  }
217
224
  }
218
225
  }
@@ -235,7 +242,7 @@ Hook.prototype.runFilter = function (name, model, ret, ...args) {
235
242
  try {
236
243
  ret = item.func(model, ret, ...args) || ret;
237
244
  } catch (err) {
238
- $.log.error(`[${this.constructor.name}] 执行过滤钩子失败 ${item.alias}`, err);
245
+ this._logger.error(`[${this.constructor.name}] 执行过滤钩子失败 ${item.alias}`, err);
239
246
  }
240
247
  }
241
248
  }
package/lib/lang.js CHANGED
@@ -1,9 +1,12 @@
1
1
  const {
2
- Lang: BaseLang
3
- } = require('mm_expand');
2
+ BaseLang
3
+ } = require('./base_lang.js');
4
4
  const {
5
5
  TplUtil
6
6
  } = require('./util.js');
7
+ const {
8
+ FileUtil
9
+ } = require('./file.js');
7
10
 
8
11
  /**
9
12
  * 语言包加载器
@@ -34,7 +37,7 @@ Lang.prototype.getDirs = function () {
34
37
  if (!cg.lang_dir) {
35
38
  return [];
36
39
  }
37
- var dirs = [cg.lang_dir.fullname(tpl.dir)];
40
+ var dirs = [FileUtil.fullname(cg.lang_dir, tpl.dir)];
38
41
  if (!cg.theme_lang_dir) {
39
42
  return dirs;
40
43
  }
@@ -46,7 +49,7 @@ Lang.prototype.getDirs = function () {
46
49
  themes.push(tpl.current_theme);
47
50
  }
48
51
  for (var i = 0; i < themes.length; i++) {
49
- dirs.push(cg.theme_lang_dir.fullname(tpl.getThemeDir(themes[i])));
52
+ dirs.push(FileUtil.fullname(cg.theme_lang_dir, tpl.getThemeDir(themes[i])));
50
53
  }
51
54
  return dirs;
52
55
  };
@@ -57,13 +60,13 @@ Lang.prototype.getDirs = function () {
57
60
  * @returns {Array<string>} 语言包文件路径列表,已排序
58
61
  */
59
62
  Lang.prototype.getFiles = function (dir) {
60
- if (!dir || !dir.hasDir()) {
63
+ if (!dir || !FileUtil.hasDir(dir)) {
61
64
  return [];
62
65
  }
63
- var files = $.file.get(dir, '*.json');
66
+ var files = FileUtil.listFiles(dir, '*.json');
64
67
  var list = [];
65
68
  for (var i = 0; i < files.length; i++) {
66
- if (files[i].extname() === '.json') {
69
+ if (FileUtil.extname(files[i]) === '.json') {
67
70
  list.push(files[i]);
68
71
  }
69
72
  }
@@ -103,15 +106,15 @@ Lang.prototype.loadFiles = function (force) {
103
106
  var item = cache[key];
104
107
  // 文件未变化时复用缓存,返回副本避免调用方改动污染缓存
105
108
  if (!force && item && item.fingerprint === fingerprint) {
106
- return $.copy(item.dict);
109
+ return TplUtil.cloneDeep(item.dict);
107
110
  }
108
111
  var dict = {};
109
112
  for (var i = 0; i < dirs.length; i++) {
110
113
  var files = this.getFiles(dirs[i]);
111
114
  for (var j = 0; j < files.length; j++) {
112
115
  var f = files[j];
113
- var lang = f.basename().replace('.json', '');
114
- var data = f.loadJson();
116
+ var lang = FileUtil.basename(f).replace('.json', '');
117
+ var data = FileUtil.loadJson(f);
115
118
  if (!lang || !data || typeof (data) !== 'object') {
116
119
  continue;
117
120
  }
@@ -119,12 +122,12 @@ Lang.prototype.loadFiles = function (force) {
119
122
  dict[lang] = {};
120
123
  }
121
124
  // 高优先级目录的相同词条覆盖低优先级目录
122
- $.push(dict[lang], data, true);
125
+ TplUtil.mergeDeep(dict[lang], data, true);
123
126
  }
124
127
  }
125
128
  cache[key] = {
126
129
  fingerprint: fingerprint,
127
- dict: $.copy(dict)
130
+ dict: TplUtil.cloneDeep(dict)
128
131
  };
129
132
  return dict;
130
133
  };
@@ -139,12 +142,13 @@ Lang.prototype.buildDict = function (force) {
139
142
  var dict = this.loadFiles(force);
140
143
  var langs = this.tpl.config.langs;
141
144
  if (langs && Object.keys(langs).length) {
142
- $.push(dict, langs, true);
145
+ TplUtil.mergeDeep(dict, langs, true);
143
146
  }
144
- // 未配置任何语言包时,复用全局语言包字典
145
- // 深拷贝后再用,避免实例上 lang.set() 写入时改到全局 $.lang.config,进而污染其他实例
147
+ // 未配置任何语言包时,复用全局语言包字典 global_dict
148
+ // 深拷贝后再用,避免实例上 lang.set() 写入时改到全局字典,进而污染其他实例
146
149
  if (!Object.keys(dict).length) {
147
- dict = ($.lang && $.lang.config) ? $.copy($.lang.config) : {};
150
+ var global_dict = this.tpl.constructor.global_dict;
151
+ dict = (global_dict && Object.keys(global_dict).length) ? TplUtil.cloneDeep(global_dict) : {};
148
152
  }
149
153
  return Object.assign({}, dict);
150
154
  };
package/lib/memory.js CHANGED
@@ -98,6 +98,4 @@ Memory.prototype._key = function (user_id) {
98
98
  return String(user_id);
99
99
  };
100
100
 
101
- module.exports = {
102
- Memory
103
- };
101
+ module.exports = Memory;
package/lib/sqlite.js CHANGED
@@ -171,6 +171,4 @@ Sqlite.prototype._val = function (value) {
171
171
  return (typeof (value) === 'string') ? value : null;
172
172
  };
173
173
 
174
- module.exports = {
175
- Sqlite
176
- };
174
+ module.exports = Sqlite;
package/lib/template.js CHANGED
@@ -1,3 +1,7 @@
1
+ const {
2
+ FileUtil
3
+ } = require('./file.js');
4
+
1
5
  /**
2
6
  * 模板文件加载器
3
7
  * 模板按目录优先级覆盖:高优先级目录存在同名文件时覆盖低优先级目录的同名文件
@@ -27,7 +31,7 @@ Template.prototype.hasTheme = function (theme) {
27
31
  if (!theme) {
28
32
  return false;
29
33
  }
30
- return this.tpl.getThemeDir(theme).hasDir();
34
+ return FileUtil.hasDir(this.tpl.getThemeDir(theme));
31
35
  };
32
36
 
33
37
  /**
@@ -47,7 +51,7 @@ Template.prototype.hasStyle = function (style) {
47
51
  themes.push(cg.default_theme);
48
52
  }
49
53
  for (var i = 0; i < themes.length; i++) {
50
- if (themes[i] && tpl.getStyleDir(themes[i], style).hasDir()) {
54
+ if (themes[i] && FileUtil.hasDir(tpl.getStyleDir(themes[i], style))) {
51
55
  return true;
52
56
  }
53
57
  }
@@ -72,16 +76,16 @@ Template.prototype._fixExt = function (file) {
72
76
  * 按目录优先级搜索文件
73
77
  * @param {string} file - 文件名
74
78
  * @param {Array<string>} dirs - 目录列表,顺序即优先级,为空时视为未命中
75
- * @returns {string} 命中的文件全路径;未命中时返回最后一个候选路径,其 hasFile() 为 false
79
+ * @returns {string} 命中的文件全路径;未命中时返回最后一个候选路径,其 FileUtil.hasFile 为 false
76
80
  */
77
81
  Template.prototype._locate = function (file, dirs) {
78
82
  if (!dirs.length) {
79
- // 返回不存在的路径,交由调用方按 hasFile() 判断
80
- return file.fullname(this.tpl.dir) + '.not-exist';
83
+ // 返回不存在的路径,交由调用方按 FileUtil.hasFile 判断
84
+ return FileUtil.fullname(file, this.tpl.dir) + '.not-exist';
81
85
  }
82
- var f = file.fullname(dirs[0]);
83
- for (var i = 1; i < dirs.length && !f.hasFile(); i++) {
84
- f = file.fullname(dirs[i]);
86
+ var f = FileUtil.fullname(file, dirs[0]);
87
+ for (var i = 1; i < dirs.length && !FileUtil.hasFile(f); i++) {
88
+ f = FileUtil.fullname(file, dirs[i]);
85
89
  }
86
90
  return f;
87
91
  };
@@ -99,7 +103,7 @@ Template.prototype.getFile = function (file, options) {
99
103
  throw new Error('模板文件路径必须是字符串');
100
104
  }
101
105
  file = this._fixExt(file);
102
- var dirs = (options && options.dir) ? [options.dir.fullname(this.tpl.dir)] : this.tpl.getTemplateDirs();
106
+ var dirs = (options && options.dir) ? [FileUtil.fullname(options.dir, this.tpl.dir)] : this.tpl.getTemplateDirs();
103
107
  return this._locate(file, dirs);
104
108
  };
105
109
 
@@ -116,7 +120,7 @@ Template.prototype.getComponent = function (name, options) {
116
120
  throw new Error('组件名称必须是字符串');
117
121
  }
118
122
  var file = this._fixExt(name.replace(/^\.\//, ''));
119
- var dirs = (options && options.dir) ? [options.dir.fullname(this.tpl.dir)] : this.tpl.getComponentDirs();
123
+ var dirs = (options && options.dir) ? [FileUtil.fullname(options.dir, this.tpl.dir)] : this.tpl.getComponentDirs();
120
124
  return this._locate(file, dirs);
121
125
  };
122
126