UNPKG

16.4 kBJavaScriptView Raw
1var KISSY = require('kissy'),
2 Mock = require('./mock'),
3 Random = require('./random'),
4 Util = require('./util');
5
6// BEGIN(BROWSER)
7(function(undefined) {
8 if (typeof KISSY === 'undefined') return
9
10 var Mock4XTpl = {
11 debug: false
12 }
13
14 var XTemplate;
15
16 KISSY.use('xtemplate', function(S, T) {
17 XTemplate = T
18 })
19
20 if (!this.Mock) module.exports = Mock4XTpl
21
22 Mock.xtpl = function(input, options, helpers, partials) {
23 return Mock4XTpl.mock(input, options, helpers, partials)
24 }
25 Mock.xparse = function(input) {
26 return XTemplate.compiler.parse(input)
27 }
28
29 /*
30 参数 options 可以通过 {{mock ...}} 指定,有2种方式:
31 1. 为属性值设置单独的配置,例如:
32 {{title}}{{mock @EMAIL}}
33 2. 集中配置整个模板,例如:
34 {{#article}}{{title}}{{/article}}
35 {{mock title=@EMAIL }}
36 支持嵌套指定、上下文,支持所有 Mock 占位符。
37 */
38 Mock4XTpl.mock = function(input, options, helpers, partials) {
39 helpers = helpers ? Util.extend({}, helpers, XTemplate.RunTime.commands) :
40 XTemplate.RunTime.commands
41 partials = partials ? Util.extend({}, partials, XTemplate.RunTime.subTpls) :
42 XTemplate.RunTime.subTpls
43 // XTemplate.RunTime.subTpls // 全局子模板
44 // xtemplate.option.subTpls 局部子模板
45 return this.gen(input, null, options, helpers, partials, {})
46 }
47
48 Mock4XTpl.parse = function(input) {
49 return XTemplate.compiler.parse(input)
50 }
51
52 Mock4XTpl.gen = function(node, context, options, helpers, partials, other) {
53 if (typeof node === 'string') {
54 if (Mock4XTpl.debug) {
55 console.log('[tpl ]\n', node)
56 }
57 var ast = this.parse(node)
58 options = this.parseOptions(node, options)
59 var data = this.gen(ast, context, options, helpers, partials, other)
60 return data
61 }
62
63 context = context || [{}]
64 options = options || {}
65
66 node.type = node.type
67 // for (var n in node) node[n] = node[n]
68
69 if (this[node.type] === Util.noop) return
70
71 options.__path = options.__path || []
72
73 if (Mock4XTpl.debug) {
74 console.log()
75 console.group('[' + node.type + ']', JSON.stringify(node))
76 console.log('[context]', '[before]', context.length, JSON.stringify(context))
77 console.log('[options]', '[before]', options.__path.length, JSON.stringify(options))
78 console.log('[other ]', '[before]', JSON.stringify(other))
79 }
80
81 var preLength = options.__path.length
82 this[node.type](node, context, options, helpers, partials, other)
83
84 if (Mock4XTpl.debug) {
85 console.log('[__path ]', '[after ]', options.__path);
86 }
87
88 if (!other.hold ||
89 typeof other.hold === 'function' && !other.hold(node, options, context)) {
90 options.__path.splice(preLength)
91 }
92
93 if (Mock4XTpl.debug) {
94 console.log('[context]', '[after ]', context.length, JSON.stringify(context))
95 console.groupEnd()
96 }
97
98 return context[context.length - 1]
99 }
100
101
102 /*
103 {{email}}{{age}}
104 <!-- Mock { email: '@EMAIL' } -->
105 <!-- Mock { age: '@INT' } -->
106
107 > 关于模板引擎的注释节点 `{{! 注释内容 }}`(不是 HTML 注释 <!-- -->):**在 Handlebars 中,注释节点会出现在从 HTML 模板解析得到的语法树中**,因为 Mock.js 是基于 HTML 模板的语法树生成模拟数据,因此从理论上(尚未实现,原因下表),可以通过注释节点来配置数据模板,例如 `{{email}}{{! @EMAIL }}`。这种配置方式的好处有:1) 不需要敲打略显繁琐的 `{{email}}<!-- Mock { email: '@EMAIL' } -->`,2) 可以省去敲打 `email:`,3) 可以就近配置方便阅读。这种配置方式的坏处有:1) 过于零碎不易管理。另外,**KISSY XTempalte 不会把注释节点放入语法树中**。因此,暂时不提供这种配置方式。如果您有更好的想法,欢迎提交 [Issues]() 讨论。
108 */
109 Mock4XTpl.parseOptions = function(input, options) {
110 var rComment = /<!--\s*\n*Mock\s*\n*([\w\W]+?)\s*\n*-->/g;
111 var comments = input.match(rComment),
112 ret = {},
113 i, ma, option;
114 for (i = 0; comments && i < comments.length; i++) {
115 rComment.lastIndex = 0
116 ma = rComment.exec(comments[i])
117 if (ma) {
118 /*jslint evil: true */
119 option = new Function('return ' + ma[1])
120 option = option()
121 Util.extend(ret, option)
122 }
123 }
124 return Util.extend(ret, options)
125 }
126
127 Mock4XTpl.parseVal = function(expr, object) {
128
129 function queryArray(prop, context) {
130 if (typeof context === 'object' && prop in context) return [context[prop]]
131
132 var ret = []
133 for (var i = 0; i < context.length; i++) {
134 ret.push.apply(ret, query(prop, [context[i]]))
135 }
136 return ret
137 }
138
139 function queryObject(prop, context) {
140 if (typeof context === 'object' && prop in context) return [context[prop]]
141
142 var ret = [];
143 for (var key in context) {
144 ret.push.apply(ret, query(prop, [context[key]]))
145 }
146 return ret
147 }
148
149 function query(prop, set) {
150 var ret = [];
151 for (var i = 0; i < set.length; i++) {
152 if (typeof set [i] !== 'object') continue
153 if (prop in set [i]) ret.push(set [i][prop])
154 else {
155 ret.push.apply(ret, Util.isArray(set [i]) ?
156 queryArray(prop, set [i]) :
157 queryObject(prop, set [i]))
158 }
159 }
160 return ret
161 }
162
163 function parse(expr, context) {
164 var parts = typeof expr === 'string' ? expr.split('.') : expr.slice(0),
165 set = [context];
166 while (parts.length) {
167 set = query(parts.shift(), set)
168 }
169 return set
170 }
171
172 return parse(expr, object)
173 }
174
175 Mock4XTpl.val = function(name, options, context, def) {
176 if (name !== options.__path[options.__path.length - 1]) throw new Error(name + '!==' + options.__path)
177 if (def !== undefined) def = Mock.mock(def)
178 if (options) {
179 var mocked = Mock.mock(options)
180 if (Util.isString(mocked)) return mocked
181
182 // TODO 深沉嵌套配置
183 var ret = Mock4XTpl.parseVal(options.__path, mocked)
184 if (ret.length > 0) return ret[0]
185
186 if (name in mocked) {
187 return mocked[name]
188 }
189 }
190 if (Util.isArray(context[0])) return {}
191 return def !== undefined ? def : name
192 }
193
194 Mock4XTpl.program = function(node, context, options, helpers, partials, other) {
195 // node.statements
196 for (var i = 0; i < node.statements.length; i++) {
197 this.gen(node.statements[i], context, options, helpers, partials, other)
198 }
199 // node.inverse
200 for (var j = 0; node.inverse && j < node.inverse.length; j++) {
201 this.gen(node.inverse[j], context, options, helpers, partials, other)
202 }
203 }
204
205 Mock4XTpl.block = function(node, context, options, helpers, partials, other) { // mustache program inverse
206 var contextLength = context.length
207
208 // node.tpl
209 this.gen(node.tpl, context, options, helpers, partials, Util.extend({}, other, {
210 /*
211 TODO
212 {{#noop}}{{body}}{{/noop}} -> { "noop": { "body": "body"} }
213 {{#list nav}}{{url}}{{/list}} -> { "nav": { "url": "url", "title": "title"} }
214 */
215 def: {}, //
216 hold: true
217 }))
218
219 // node.program
220 var currentContext = context[0],
221 mocked, i, len;
222 if (Util.type(currentContext) === 'array') {
223 mocked = this.val(options.__path[options.__path.length - 1], options, context)
224 len = mocked && mocked.length || Random.integer(3, 7)
225 for (i = 0; i < len; i++) {
226 // test_relational_expression_each > mocked[i] != undefined
227 currentContext.push(mocked && mocked[i] !== undefined ? mocked[i] : {})
228
229 options.__path.push(i)
230 context.unshift(currentContext[currentContext.length - 1])
231
232 this.gen(node.program, context, options, helpers, partials, other)
233
234 options.__path.pop()
235 context.shift()
236 }
237 } else this.gen(node.program, context, options, helpers, partials, other)
238
239 if (!other.hold ||
240 typeof other.hold === 'function' && !other.hold(node, options, context)) {
241 context.splice(0, context.length - contextLength)
242 }
243 }
244
245 Mock4XTpl.tpl = function(node, context, options, helpers, partials, other) {
246 if (node.params && node.params.length) {
247 other = Util.extend({}, other, {
248 def: {
249 'each': [],
250 'if': '@BOOL(2,1,true)', // node.params[0].type === 'id' ? '@BOOL(2,1,true)' : undefined,
251 'unless': '@BOOL(2,1,false)',
252 'with': {}
253 }[node.path.string],
254 hold: {
255 'each': true,
256 'if': function(_, __, ___, name, value) { // 暂时不需要关注前三个参数:node, options, context。
257 return typeof value === 'object'
258 },
259 'unless': function(_, __, ___, name, value) {
260 return typeof value === 'object'
261 },
262 'with': true,
263 'include': false
264 }[node.path.string]
265 })
266 // node.params
267 for (var i = 0, input; i < node.params.length; i++) {
268 if (node.path.string === 'include') {
269 input = partials && partials[node.params[i].value]
270 } else input = node.params[i]
271 if (input) this.gen(input, context, options, helpers, partials, other)
272 }
273 // node.hash
274 if (node.hash) {
275 this.gen(node.hash, context, options, helpers, partials, other)
276 }
277 } else {
278 this.gen(node.path, context, options, helpers, partials, other)
279 }
280 }
281 Mock4XTpl.tplExpression = function(node, context, options, helpers, partials, other) {
282 this.gen(node.expression, context, options, helpers, partials, other)
283 }
284
285 Mock4XTpl.content = Util.noop
286 Mock4XTpl.unaryExpression = Util.noop
287
288 Mock4XTpl.multiplicativeExpression =
289 Mock4XTpl.additiveExpression = function(node, context, options, helpers, partials, other) {
290 // TODO 如果参与运算是数值型,默认为整数或浮点数
291 this.gen(node.op1, context, options, helpers, partials, Util.extend({}, other, {
292 def: function() {
293 return node.op2.type === 'number' ?
294 node.op2.value.indexOf('.') > -1 ?
295 Random.float(-Math.pow(10, 10), Math.pow(10, 10), 1, Math.pow(10, 6)) :
296 Random.integer() :
297 undefined
298 }()
299 }))
300 this.gen(node.op2, context, options, helpers, partials, Util.extend({}, other, {
301 def: function() {
302 return node.op1.type === 'number' ?
303 node.op1.value.indexOf('.') > -1 ?
304 Random.float(-Math.pow(10, 10), Math.pow(10, 10), 1, Math.pow(10, 6)) :
305 Random.integer() :
306 undefined
307 }()
308 }))
309 }
310
311 Mock4XTpl.relationalExpression = function(node, context, options, helpers, partials, other) {
312 this.gen(node.op1, context, options, helpers, partials, other)
313 this.gen(node.op2, context, options, helpers, partials, other)
314 }
315
316 Mock4XTpl.equalityExpression = Util.noop
317 Mock4XTpl.conditionalAndExpression = Util.noop
318 Mock4XTpl.conditionalOrExpression = Util.noop
319 Mock4XTpl.string = Util.noop
320 Mock4XTpl.number = Util.noop
321 Mock4XTpl.boolean = Util.noop
322
323 Mock4XTpl.hash = function(node, context, options, helpers, partials, other) {
324 var pairs = node.value,
325 key;
326 for (key in pairs) {
327 this.gen(pairs[key], context, options, helpers, partials, other)
328 }
329 }
330
331 Mock4XTpl.id = function(node, context, options, helpers, partials, other) {
332 var contextLength = context.length
333
334 var parts = node.parts,
335 currentContext = context[node.depth],
336 i, len, cur, def, val;
337
338 function fix(currentContext, index, length, name, val) {
339 var type = Util.type(currentContext[name]),
340 valType = Util.type(val);
341 val = val === 'true' ? true :
342 val === 'false' ? false : val
343 if (type === 'undefined') {
344 // 如果不是最后一个属性,并且当前值不是 [] 或 {},则修正为 [] 或 {}
345 if (index < length - 1 && !Util.isObjectOrArray(val)) {
346 currentContext[name] = {}
347 } else {
348 currentContext[name] = Util.isArray(val) && [] || val
349 }
350 } else {
351 // 已有值
352 // 如果不是最后一个属性,并且不是 [] 或 {},则修正为 [] 或 {}
353 if (index < length - 1 && type !== 'object' && type !== 'array') {
354 currentContext[name] = Util.isArray(val) && [] || {}
355 } else {
356 /*
357 其他情况下,
358 尽量不改变类型(对象、数组、基本类型)的情况下,覆盖已有值,
359 以支持在后面的模拟过程中修正模拟值。
360 */
361 if (type !== 'object' && type !== 'array' &&
362 valType !== 'object' && valType !== 'array') {
363 currentContext[name] = val
364 }
365 }
366
367
368 }
369 return currentContext[name]
370 }
371
372 if (Util.isArray(currentContext)) currentContext = context[node.depth + 1]
373
374 for (i = 0, len = parts.length; i < len; i++) {
375 /*
376 TODO 过滤掉 this、内置占位符(xindex、xcount、xkey)、helper,
377 然而万全之策是先检查 options 中是否存在对应的配置,如果没有则忽略,如果有则生成。
378 不过,在应用中不建议覆盖内置占位符。
379 TODO 遇到 xindex、xcount 要修正为数组
380 TODO 遇到 xkey 要修正为对象
381 */
382 if (i === 0 && parts[i] === 'this') continue
383 if (/^(xindex|xcount|xkey)$/.test(parts[i])) continue // TODO 需要判断内置占位符(xindex、xcount、xkey)的位置吗,例如是否是第一个?
384 if (i === 0 && len === 1 && parts[i] in helpers) continue
385
386 options.__path.push(parts[i])
387
388 cur = parts[i]
389
390 def = i === len - 1 ?
391 other.def !== undefined ? other.def :
392 context[0][cur] : {}
393 val = this.val(cur, options, context, def)
394
395 if (Mock4XTpl.debug) {
396 console.log('[def ]', JSON.stringify(def))
397 console.log('[val ]', JSON.stringify(val))
398 }
399
400 val = fix(currentContext, i, len, cur, val)
401
402 if (Util.isObjectOrArray(currentContext[cur])) {
403 context.unshift(currentContext = currentContext[cur])
404 }
405 }
406
407 if (!other.hold ||
408 typeof other.hold === 'function' && !other.hold(node, options, context, cur, val)) {
409 context.splice(0, context.length - contextLength)
410 }
411 }
412
413}).call(this);
414// END(BROWSER)
\No newline at end of file