mm_tpl 2.7.1 → 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,288 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 原生模板语法规则
5
+ * @type {Object}
6
+ */
7
+ var nativeRule = {
8
+ test: /[ \t\r\n]*<!--\[(#?)((?:==|=#|[=-])?)[ \t]*([\w\W]*?)[ \t]*(-?)\]-->/,
9
+
10
+ /**
11
+ * 处理原生模板语法
12
+ * @param {string} match - 匹配的字符串
13
+ * @param {string} comment - 注释标记
14
+ * @param {string} output - 输出类型
15
+ * @param {string} code - 代码内容
16
+ * @returns {Object} 处理结果
17
+ */
18
+ use: function use(match, comment, output, code /*, trimMode*/) {
19
+ output = {
20
+ '-': 'raw',
21
+ '=': 'escape',
22
+ '': false,
23
+ // v3 compat: raw output
24
+ '==': 'raw',
25
+ '=#': 'raw'
26
+ }[output];
27
+
28
+ // ejs compat: comment tag
29
+ if (comment) {
30
+ code = '/*' + code + '*/';
31
+ output = false;
32
+ }
33
+
34
+ // ejs compat: trims following newline
35
+ // if (trimMode) {}
36
+
37
+ return {
38
+ code: code,
39
+ output: output
40
+ };
41
+ }
42
+ };
43
+
44
+ /**
45
+ * 简洁模板语法规则
46
+ * @type {Object}
47
+ */
48
+ var artRule = {
49
+ // 表达式内允许出现非嵌套的对象字面量,如 ${@$component('bar', { id: 1 })}
50
+ // 若只用 [\w\W]*? 会停在第一个 } 上,把对象字面量截断成语法错误
51
+ test: /[ \t\r\n]*\$\{([@#]?)[ \t]*(\/?)((?:[^{}]|\{[^{}]*\})*?)[ \t]*\}/,
52
+
53
+ /**
54
+ * 处理简洁模板语法
55
+ * @param {string} match - 匹配的字符串
56
+ * @param {string} raw - 原始输出标记
57
+ * @param {string} close - 闭合标记
58
+ * @param {string} code - 代码内容
59
+ * @returns {Object} 处理结果
60
+ */
61
+ use(match, raw, close, code) {
62
+ var compiler = this;
63
+ var options = compiler.options;
64
+ var esTokens = compiler.getEsTokens(code);
65
+ var values = esTokens.map(function (token) {
66
+ return token.value;
67
+ });
68
+ var result = {};
69
+
70
+ var group = void 0;
71
+ var output = raw ? 'raw' : false;
72
+ var key = close + values.shift();
73
+
74
+ // 旧版语法升级提示
75
+ var warn = function warn(oldSyntax, newSyntax) {
76
+ console.warn((options.filename || 'anonymous') + ':' + (match.line + 1) + ':' + (match.start + 1) + '\n' + (
77
+ 'Template upgrade: ${' + oldSyntax + '} -> ${' + newSyntax + '}'));
78
+ };
79
+
80
+ // v3 compat: #value
81
+ if (raw === '#') {
82
+ warn('#value', '@value');
83
+ }
84
+
85
+ switch (key) {
86
+ case 'set':
87
+ code = 'var ' + values.join('').trim();
88
+ break;
89
+
90
+ case 'if':
91
+ code = 'if(' + values.join('').trim() + '){';
92
+ break;
93
+
94
+ case 'else':
95
+ var indexIf = values.indexOf('if');
96
+
97
+ if (~indexIf) {
98
+ values.splice(0, indexIf + 1);
99
+ code = '}else if(' + values.join('').trim() + '){';
100
+ } else {
101
+ code = '}else{';
102
+ }
103
+
104
+ break;
105
+
106
+ case '/if':
107
+ code = '}';
108
+ break;
109
+
110
+ case 'loop':
111
+ case 'each':
112
+ group = artRule._split(esTokens);
113
+ group.shift();
114
+
115
+ if (group[1] === 'as') {
116
+ // ... v3 compat ...
117
+ warn('each object as value index', 'each object value index');
118
+ group.splice(1, 1);
119
+ }
120
+
121
+ var object = group[0] || '$data';
122
+ var value = group[1] || '$value';
123
+ var index = group[2] || '$index';
124
+
125
+ code = '$each(' + object + ',function(' + value + ',' + index + '){';
126
+
127
+ break;
128
+
129
+ case '/loop':
130
+ case '/each':
131
+ code = '})';
132
+ break;
133
+
134
+ case 'block':
135
+ group = artRule._split(esTokens);
136
+ group.shift();
137
+ code = 'block(' + group.join(',').trim() + ',function(){';
138
+ break;
139
+
140
+ case '/block':
141
+ code = '})';
142
+ break;
143
+
144
+ case 'echo':
145
+ key = 'print';
146
+ warn('echo value', 'value');
147
+ case 'print':
148
+ case 'include':
149
+ case 'extend':
150
+ if (values.join('').trim().indexOf('(') !== 0) {
151
+ // 执行函数省略 `()` 与 `,`
152
+ group = artRule._split(esTokens);
153
+ group.shift();
154
+ code = key + '(' + group.join(',') + ')';
155
+ break;
156
+ }
157
+
158
+ default:
159
+ if (~values.indexOf('|')) {
160
+ var v3split = ':'; // ... v3 compat ...
161
+
162
+ // 将过滤器解析成二维数组
163
+ var _group = esTokens.reduce(function (group, token) {
164
+ var value = token.value,
165
+ type = token.type;
166
+
167
+ if (value === '|') {
168
+ group.push([]);
169
+ } else if (type !== 'whitespace' && type !== 'comment') {
170
+ if (!group.length) {
171
+ group.push([]);
172
+ }
173
+ if (value === v3split && group[group.length - 1].length === 1) {
174
+ warn('value | filter: argv', 'value | filter argv');
175
+ } else {
176
+ group[group.length - 1].push(token);
177
+ }
178
+ }
179
+ return group;
180
+ }, []).map(function (g) {
181
+ return artRule._split(g);
182
+ });
183
+
184
+ // 将过滤器管道化
185
+ code = _group.reduce(function (accumulator, filter) {
186
+ var name = filter.shift();
187
+ filter.unshift(accumulator);
188
+
189
+ return '$imports.' + name + '(' + filter.join(',') + ')';
190
+ }, _group.shift().join(' ').trim());
191
+ }
192
+
193
+ output = output || 'escape';
194
+
195
+ break;
196
+ }
197
+
198
+ result.code = code;
199
+ result.output = output;
200
+ return result;
201
+ },
202
+
203
+ /**
204
+ * 将多个javascript表达式拆分成组
205
+ * @param {Array} esTokens - ES令牌数组
206
+ * @returns {Array} 分组后的表达式数组
207
+ */
208
+ _split: function _split(esTokens) {
209
+ esTokens = esTokens.filter(function (_ref) {
210
+ var type = _ref.type;
211
+
212
+ return type !== 'whitespace' && type !== 'comment';
213
+ });
214
+
215
+ var current = 0;
216
+ var lastToken = esTokens.shift();
217
+ var punctuator = 'punctuator';
218
+ var close = /\]|\)/;
219
+ var group = [
220
+ [lastToken]
221
+ ];
222
+
223
+ while (current < esTokens.length) {
224
+ var esToken = esTokens[current];
225
+
226
+ if (esToken.type === punctuator || lastToken.type === punctuator && !close.test(lastToken.value)) {
227
+ group[group.length - 1].push(esToken);
228
+ } else {
229
+ group.push([esToken]);
230
+ }
231
+
232
+ lastToken = esToken;
233
+
234
+ current++;
235
+ }
236
+
237
+ return group.map(function (g) {
238
+ return g.map(function (g) {
239
+ return g.value;
240
+ }).join('');
241
+ });
242
+ }
243
+ };
244
+
245
+ /**
246
+ * HTML注释模板语法规则
247
+ * @type {Object}
248
+ */
249
+ var htmlRule = Object.assign({}, artRule, {
250
+ test: /[ \t\r\n]*<!--\{([@#]?)[ \t]*(\/?)([\w\W]*?)[ \t]*\}-->/
251
+ });
252
+
253
+ /**
254
+ * JS注释模板语法规则
255
+ * @type {Object}
256
+ */
257
+ var jsRule = Object.assign({}, artRule, {
258
+ test: /[ \t\r\n]*\/\*\[([@#]?)[ \t]*(\/?)([\w\W]*?)[ \t]*\]\*\//
259
+ });
260
+
261
+ /**
262
+ * Python注释模板语法规则
263
+ * @type {Object}
264
+ */
265
+ var pyRule = Object.assign({}, artRule, {
266
+ test: /[ \t\r\n]*##([@#]?)[ \t]*(\/?)([\w\W]*?)[ \t]*##/
267
+ });
268
+
269
+ /**
270
+ * Python三引号注释模板语法规则
271
+ * @type {Object}
272
+ */
273
+ var pyRule2 = Object.assign({}, artRule, {
274
+ test: /"""#([@#]?)[ \t]*(\/?)([\w\W]*?)[ \t]*#"""/
275
+ });
276
+
277
+ module.exports = {
278
+ htmlRule,
279
+ jsRule,
280
+ artRule,
281
+ nativeRule,
282
+ pyRule,
283
+ pyRule2
284
+ };
285
+
286
+
287
+
288
+
package/lib/util.js ADDED
@@ -0,0 +1,196 @@
1
+ const fs = require('fs');
2
+ const crypto = require('crypto');
3
+
4
+ /**
5
+ * 模板工具类
6
+ * 收纳与实例状态无关的纯辅助函数,全部以静态方法提供
7
+ * @class
8
+ */
9
+ class TplUtil {
10
+ /**
11
+ * 获取文件修改时间
12
+ * @param {string} file - 文件路径
13
+ * @returns {number} 修改时间戳,取不到时返回0
14
+ */
15
+ static getFileTime(file) {
16
+ try {
17
+ return fs.statSync(file).mtimeMs;
18
+ } catch {
19
+ return 0;
20
+ }
21
+ }
22
+
23
+ /**
24
+ * 转义正则表达式特殊字符
25
+ * @param {string} str - 原始字符串
26
+ * @returns {string} 转义后的字符串
27
+ */
28
+ static escapeRegExp(str) {
29
+ return str.replace('\\', '\\\\').replace('$', '\\$').replace('[', '\\[').replace(']', '\\]')
30
+ .replace('{', '\\{').replace('}', '\\}').replace('/', '\\/').replace('*', '\\*');
31
+ }
32
+
33
+ /**
34
+ * 构建字符转义规则
35
+ * 转义时把语法字符替换为中间字符,渲染后再还原,避免与模板语法冲突
36
+ * @param {Array<string>} change_str - 需要转义与还原的字符串列表
37
+ * @returns {Object} 转义规则,含 regex_in、str_in、regex_out、str_out
38
+ */
39
+ static buildEscapeRule(change_str) {
40
+ var rule = {
41
+ regex_in: [],
42
+ str_in: [],
43
+ regex_out: [],
44
+ str_out: []
45
+ };
46
+ var lt = change_str || [];
47
+ for (var i = 0; i < lt.length; i++) {
48
+ var str = lt[i];
49
+ rule.regex_in.push(new RegExp(TplUtil.escapeRegExp(str), 'gi'));
50
+ var str_in = str.split('').join('·') + '·';
51
+ rule.str_in.push(str_in);
52
+ rule.regex_out.push(new RegExp(TplUtil.escapeRegExp(str_in), 'gi'));
53
+ rule.str_out.push(str.replace('\\', ''));
54
+ }
55
+ return rule;
56
+ }
57
+
58
+ /**
59
+ * 转义或还原字符串
60
+ * @param {string} body - 字符串
61
+ * @param {Object} rule - 转义规则
62
+ * @param {boolean} restore - 是否还原
63
+ * @returns {string} 处理后的字符串
64
+ */
65
+ static escape(body, rule, restore) {
66
+ if (!body) {
67
+ return body;
68
+ }
69
+ var rx = restore ? rule.regex_out : rule.regex_in;
70
+ var st = restore ? rule.str_out : rule.str_in;
71
+ for (var i = 0; i < rx.length; i++) {
72
+ body = body.replace(rx[i], st[i]);
73
+ }
74
+ return body;
75
+ }
76
+
77
+ /**
78
+ * 深度合并对象
79
+ * 目标已有的键:同为对象则递归合并,类型相同则转换后覆盖,类型不同保持原值
80
+ * 目标没有的键:supplement 为 true 时补充,否则跳过
81
+ * @param {Object} target - 被合并的对象
82
+ * @param {Object} source - 用作合并的对象
83
+ * @param {boolean} supplement - 是否补充目标没有的键
84
+ * @returns {Object} 修改后的目标对象
85
+ */
86
+ static mergeDeep(target, source, supplement) {
87
+ if (!target || !source || typeof (target) !== 'object' || typeof (source) !== 'object') {
88
+ return target;
89
+ }
90
+ for (var key in source) {
91
+ if (Object.prototype.hasOwnProperty.call(target, key)) {
92
+ var target_val = target[key];
93
+ var source_val = source[key];
94
+ if (typeof (target_val) === 'object' && typeof (source_val) === 'object') {
95
+ TplUtil.mergeDeep(target_val, source_val, supplement);
96
+ } else if (typeof (target_val) === typeof (source_val)) {
97
+ target[key] = TplUtil._convertValue(target_val, source_val);
98
+ }
99
+ } else if (supplement) {
100
+ target[key] = source[key];
101
+ }
102
+ }
103
+ return target;
104
+ }
105
+
106
+ /**
107
+ * 按目标值的类型转换源值
108
+ * @param {any} target_val - 目标值
109
+ * @param {any} source_val - 源值
110
+ * @returns {any} 转换后的值
111
+ * @private
112
+ */
113
+ static _convertValue(target_val, source_val) {
114
+ var target_type = typeof (target_val);
115
+ if (target_type === 'number') {
116
+ var num = Number(source_val);
117
+ return isNaN(num) ? 0 : num;
118
+ }
119
+ if (target_type === 'boolean') {
120
+ if (source_val === '0' || source_val === 'false' || source_val === 'False') {
121
+ return false;
122
+ }
123
+ if (source_val === '1' || source_val === 'true' || source_val === 'True') {
124
+ return true;
125
+ }
126
+ return Boolean(source_val);
127
+ }
128
+ if (target_type === 'string') {
129
+ return String(source_val);
130
+ }
131
+ return source_val;
132
+ }
133
+
134
+ /**
135
+ * 深拷贝对象或值
136
+ * 仅处理数组与普通对象,最大递归深度 10,超出时直接返回原值
137
+ * @param {any} source - 源对象或值
138
+ * @param {number} depth - 当前递归深度,内部使用
139
+ * @returns {any} 拷贝后的对象或值
140
+ * @private
141
+ */
142
+ static cloneDeep(source, depth) {
143
+ var level = depth || 0;
144
+ if (level > 10 || !source || typeof (source) !== 'object') {
145
+ return source;
146
+ }
147
+ if (Array.isArray(source)) {
148
+ var new_arr = [];
149
+ for (var i = 0; i < source.length; i++) {
150
+ new_arr.push(TplUtil.cloneDeep(source[i], level + 1));
151
+ }
152
+ return new_arr;
153
+ }
154
+ var new_obj = {};
155
+ for (var key in source) {
156
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
157
+ new_obj[key] = TplUtil.cloneDeep(source[key], level + 1);
158
+ }
159
+ }
160
+ return new_obj;
161
+ }
162
+
163
+ /**
164
+ * 计算字符串的 MD5 值
165
+ * @param {string} str - 原始字符串
166
+ * @returns {string} 十六进制摘要
167
+ */
168
+ static md5(str) {
169
+ return crypto.createHash('md5').update(str + '').digest('hex');
170
+ }
171
+
172
+ /**
173
+ * 取两个标记符之间的子串
174
+ * 任一标记符不存在时返回空字符串
175
+ * @param {string} str - 原始字符串
176
+ * @param {string} left_str - 左侧标记符
177
+ * @param {string} right_str - 右侧标记符
178
+ * @returns {string} 截取后的字符串
179
+ */
180
+ static between(str, left_str, right_str) {
181
+ var start = (str + '').indexOf(left_str);
182
+ if (start === -1) {
183
+ return '';
184
+ }
185
+ var from = start + left_str.length;
186
+ var end = (str + '').indexOf(right_str, from);
187
+ if (end === -1) {
188
+ return '';
189
+ }
190
+ return (str + '').substring(from, end);
191
+ }
192
+ }
193
+
194
+ module.exports = {
195
+ TplUtil
196
+ };
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "mm_tpl",
3
- "version": "2.7.1",
3
+ "version": "2.7.3",
4
4
  "description": "Super Meimei Template Engine - High-performance template engine based on art-template with MVC pattern, hook system, theme switching and caching mechanism",
5
5
  "main": "index.js",
6
+ "io": true,
7
+ "priority": 100,
6
8
  "scripts": {
7
9
  "dev": "nodemon test.js",
10
+ "demo": "node example/demo.js",
8
11
  "test": "node test.js",
9
12
  "coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage"
10
13
  },
@@ -13,6 +16,10 @@
13
16
  "url": "git+https://gitee.com/qiuwenwu91/mm_tpl.git"
14
17
  },
15
18
  "keywords": [
19
+ "mm_mod",
20
+ "mm_infra",
21
+ "mm_common",
22
+ "mm_web",
16
23
  "template",
17
24
  "engine",
18
25
  "mvc",
@@ -26,6 +33,8 @@
26
33
  "nodejs",
27
34
  "javascript"
28
35
  ],
36
+ "layer": "infra",
37
+ "group": "common",
29
38
  "author": "邱文武",
30
39
  "license": "MIT",
31
40
  "bugs": {
@@ -33,8 +42,7 @@
33
42
  },
34
43
  "homepage": "https://gitee.com/qiuwenwu91/mm_tpl#readme",
35
44
  "dependencies": {
36
- "art-template": "^4.13.4",
37
- "mm_expand": "^2.5.4"
45
+ "art-template": "^4.13.4"
38
46
  },
39
47
  "engines": {
40
48
  "node": ">=12.0.0"
@@ -43,8 +51,14 @@
43
51
  "index.js",
44
52
  "hook.js",
45
53
  "tpl_rule.js",
54
+ "lib/",
55
+ "doc/",
46
56
  "README.md",
47
57
  "README_EN.md",
48
58
  "package.json"
49
- ]
59
+ ],
60
+ "devDependencies": {
61
+ "eslint": "^9.39.5",
62
+ "eslint-plugin-jsdoc": "^65.0.0"
63
+ }
50
64
  }