UNPKG

18.2 kBJavaScriptView Raw
1var Mock = require('./mock'),
2 Random = require('./random'),
3 Util = require('./util'),
4 Handlebars = require('handlebars');
5
6// BEGIN(BROWSER)
7
8/*
9 Mock4Tpl - 基于客户端模板生成模拟数据
10
11 https://github.com/nuysoft/Mock
12 墨智 mozhi.gyy@taobao.com nuysoft@gmail.com
13*/
14(function(undefined) {
15 var Mock4Tpl = {
16 version: '0.0.1'
17 }
18
19 if (!this.Mock) module.exports = Mock4Tpl
20
21 Mock.tpl = function(input, options, helpers, partials) {
22 return Mock4Tpl.mock(input, options, helpers, partials)
23 }
24 Mock.parse = function(input) {
25 return Handlebars.parse(input)
26 }
27
28 /*
29 Mock4Tpl.mock(input)
30 Mock4Tpl.mock(input, options)
31 Mock4Tpl.mock(input, options, helpers)
32 Mock4Tpl.mock(input, options, helpers, partials)
33 */
34 Mock4Tpl.mock = function(input, options, helpers, partials) {
35 helpers = helpers ? Util.extend({}, helpers, Handlebars.helpers) :
36 Handlebars.helpers
37 partials = partials ? Util.extend({}, partials, Handlebars.partials) :
38 Handlebars.partials
39 return Handle.gen(input, null, options, helpers, partials)
40 }
41
42 var Handle = {
43 debug: Mock4Tpl.debug || false,
44 extend: Util.extend
45 }
46
47 /*
48 Handle.gen(input, context, options, helpers, partials)
49 Handle.gen(ast, context, options, helpers, partials)
50 Handle.gen(node, context, options, helpers, partials)
51
52 input HTML 模板
53 ast HTML 模板
54 node
55 context
56 options
57 helpers
58 partials
59
60 ## 构造路径
61 * 对于对象
62 {
63 a: {
64 b: {
65 c: d
66 }
67 }
68 }
69
70 [ a, b, c, d]
71 * 对于数组
72 {
73 a: [{
74 b: [{
75 c: d
76 }
77 ]
78 }
79 ]
80 }
81 ->
82 [ a, [], b, [], c ]
83
84 */
85 Handle.gen = function(node, context, options, helpers, partials) {
86 if (Util.isString(node)) {
87 var ast = Handlebars.parse(node)
88 options = Handle.parseOptions(node, options)
89 var data = Handle.gen(ast, context, options, helpers, partials)
90 return data
91 }
92
93 context = context || [{}]
94 options = options || {}
95
96 if (this[node.type] === Util.noop) return
97
98 options.__path = options.__path || []
99
100 if (Mock4Tpl.debug || Handle.debug) {
101 console.log()
102 console.group('[' + node.type + ']', JSON.stringify(node))
103 // console.log('[context]', context.length, JSON.stringify(context))
104 console.log('[options]', options.__path.length, JSON.stringify(options))
105 }
106
107 var preLength = options.__path.length
108 this[node.type](node, context, options, helpers, partials)
109 options.__path.splice(preLength)
110
111 if (Mock4Tpl.debug || Handle.debug) {
112 // console.log('[context]', context.length, JSON.stringify(context))
113 console.groupEnd()
114 }
115
116 return context[context.length - 1]
117 }
118
119 Handle.parseOptions = function(input, options) {
120 var rComment = /<!--\s*\n*Mock\s*\n*([\w\W]+?)\s*\n*-->/g;
121 var comments = input.match(rComment),
122 ret = {}, i, ma, option;
123 for (i = 0; comments && i < comments.length; i++) {
124 rComment.lastIndex = 0
125 ma = rComment.exec(comments[i])
126 if (ma) {
127 /*jslint evil: true */
128 option = new Function('return ' + ma[1])
129 option = option()
130 Util.extend(ret, option)
131 }
132 }
133 return Util.extend(ret, options)
134 }
135
136 /*
137 name 字符串,属性名
138 options 字符串或对象,数据模板
139 context 父节点,任意值
140 def 默认值
141 */
142 Handle.val = function(name, options, context, def) {
143 if (name !== options.__path[options.__path.length - 1]) throw new Error(name + '!==' + options.__path)
144 if (Mock4Tpl.debug || Handle.debug) console.log('[options]', name, options.__path);
145 if (def !== undefined) def = Mock.mock(def)
146 if (options) {
147 var mocked = Mock.mock(options)
148 if (Util.isString(mocked)) return mocked
149 if (name in mocked) {
150 return mocked[name]
151 }
152 }
153 if (Util.isArray(context[0])) return {}
154 return def !== undefined ? def : (name) || Random.word()
155 }
156
157
158 /*
159 AST
160 */
161
162 Handle.program = function(node, context, options, helpers, partials) {
163 for (var i = 0; i < node.statements.length; i++) {
164 this.gen(node.statements[i], context, options, helpers, partials)
165 }
166 // TODO node.inverse
167 }
168
169 Handle.mustache = function(node, context, options, helpers, partials) { // string id params
170 var i,
171 currentContext = context[0],
172 contextLength = context.length;
173
174 if (Util.type(currentContext) === 'array') {
175 currentContext.push({})
176 currentContext = currentContext[currentContext.length - 1]
177 context.unshift(currentContext)
178 }
179
180 // "isHelper": 1
181 // 为何要明确的 isHelper?因为 eligibleHelper 实在不可靠!
182 if (node.isHelper || helpers && helpers[node.id.string]) {
183 // node.params
184 if (node.params.length === 0) {
185 // TODO test_helper_this_with_register_and_holder
186 } else {
187 for (i = 0; i < node.params.length; i++) {
188 this.gen(node.params[i], context, options, helpers, partials)
189 }
190 }
191 // node.hash
192 if (node.hash) this.gen(node.hash, context, options, helpers, partials)
193 } else {
194 // node.id
195 this.gen(node.id, context, options, helpers, partials)
196 /*
197 node.id.type === 'DATA'
198 eg @index,放到 DATA 中处理 TODO
199 */
200 }
201 if (context.length > contextLength) context.splice(0, context.length - contextLength)
202 }
203
204 Handle.block = function(node, context, options, helpers, partials) { // mustache program inverse
205 var parts = node.mustache.id.parts,
206 i, len, cur, val, type,
207 currentContext = context[0],
208 contextLength = context.length;
209
210 if (node.inverse) {} // TODO
211
212 /*
213 ## 关于自定义 block
214 {{#block}}{{...}}{{/block}}
215 | helper | type | 模板引擎的行为 | 模拟数据 |
216 | ------ | -------- | ----------------------------------------- | ------------ |
217 | Y | function | 由 helper 负责返回最后的结果 | 不处理 |
218 | N | array | 遍历该数组,渲染包含的 statements | 默认为对象 |
219 | N | object | 把该对象作为新 context,渲染包含的 statements | 默认为对象 |
220 | N | boolean | 用当前 context 渲染包含的 statements | 默认为对象 |
221
222 ### 为什么默认为对象
223 无论默认为对象或数组,当真实数据的类型与默认数据不匹配时,模拟数据的渲染结果都会与预期不符合。
224 而把模拟数据的默认值设置为对象,则可以在渲染 object、boolean 时大致符合预期。
225 更直观(易读)的做法是
226 1. 明确指定数据的类型:数组 []、对象 {}、布尔 true/false。
227 2. 遍历数组时,用 each helper。
228 3. 为数据属性设置 Mock 参数,例如 `arr|5-10`: []。
229 然而,目前开发过程中遇到的更多的是因此数组,这不是一个好习惯,因为在理解上会造成模棱两可的印象。
230 我希望 Mock 是一个可用,并且好用的工具,除了这两个原则之外,任何固有的原则都可以放弃,
231 因此如果有任何感受和建议,请反馈给我,如果可以贡献代码就更好了。
232
233 ### 另外,为什么称为上下文 context,而不是作用域 scope 呢?
234 在 Mock4Tpl 的模拟过程中,以及模板引擎的渲染过程中,只是在某个对象或数组上设置或设置属性,是上下文的概念,根本没有“作用域”的概念。
235 虽然从理解的角度,这两个过程与作用域极为“相似”,但是如果描述成“相似”,其实是对本质和概念的误导。
236 但是。。。好吧,确实“作用域”要更形象,更易于被不了解内部原理的人所接受。
237 */
238
239 // block.mustache
240 if (node.mustache.isHelper ||
241 helpers && helpers[node.mustache.id.string]) {
242 type = parts[0] // helper: each if unless with log
243 // 指定 Handle 为上下文是为了使用 Handle 的方法
244 val = (Helpers[type] || Helpers.custom).apply(this, arguments)
245 currentContext = context[0]
246 } else {
247 for (i = 0; i < parts.length; i++) {
248 options.__path.push(parts[i])
249
250 cur = parts[i]
251
252 val = this.val(cur, options, context, {})
253 currentContext[cur] = Util.isArray(val) && [] || val
254
255 type = Util.type(currentContext[cur])
256 if (type === 'object' || type === 'array') {
257 currentContext = currentContext[cur]
258 context.unshift(currentContext)
259 }
260 }
261 }
262
263 // block.program
264 if (node.program) {
265 if (Util.type(currentContext) === 'array') {
266 len = val.length || Random.integer(3, 7)
267 // Handle.program() 可以自己解析和生成数据,但是不知道该重复几次,所以这里需要循环调用
268 for (i = 0; i < len; i++) {
269 currentContext.push(typeof val[i] !== 'undefined' ? val[i] : {})
270
271 options.__path.push('[]')
272 context.unshift(currentContext[currentContext.length - 1])
273 this.gen(node.program, context, options, helpers, partials)
274 options.__path.pop()
275 context.shift()
276 }
277 } else this.gen(node.program, context, options, helpers, partials)
278 }
279
280 if (context.length > contextLength) context.splice(0, context.length - contextLength)
281 }
282
283 Handle.hash = function(node, context, options, helpers, partials) {
284 var pairs = node.pairs,
285 pair, i, j;
286 for (i = 0; i < pairs.length; i++) {
287 pair = pairs[i]
288 for (j = 1; j < pair.length; j++) {
289 this.gen(pair[j], context, options, helpers, partials)
290 }
291 }
292 }
293
294 Handle.ID = function(node, context, options) { // , helpers, partials
295 var parts = node.parts,
296 i, len, cur, prev, def, val, type, valType, preOptions,
297 currentContext = context[node.depth], // back path, eg {{../permalink}}
298 contextLength = context.length;
299
300 if (Util.isArray(currentContext)) currentContext = context[node.depth + 1]
301
302 if (!parts.length) {
303 // TODO 修正父节点的类型
304 } else {
305 for (i = 0, len = parts.length; i < len; i++) {
306 options.__path.push(parts[i])
307
308 cur = parts[i]
309 prev = parts[i - 1]
310 preOptions = options[prev]
311
312 def = i === len - 1 ? currentContext[cur] : {}
313 val = this.val(cur, /*preOptions && preOptions[cur] ? preOptions :*/ options, context, def)
314
315 type = Util.type(currentContext[cur])
316 valType = Util.type(val)
317 if (type === 'undefined') {
318 // 如果不是最后一个属性,并且当前值不是 [] 或 {},则修正为 [] 或 {}
319 if (i < len - 1 && valType !== 'object' && valType !== 'array') {
320 currentContext[cur] = {}
321 } else {
322 currentContext[cur] = Util.isArray(val) && [] || val
323 }
324 } else {
325 // 已有值
326 // 如果不是最后一个属性,并且不是 [] 或 {},则修正为 [] 或 {}
327 if (i < len - 1 && type !== 'object' && type !== 'array') {
328 currentContext[cur] = Util.isArray(val) && [] || {}
329 }
330 }
331
332 type = Util.type(currentContext[cur])
333 if (type === 'object' || type === 'array') {
334 currentContext = currentContext[cur]
335 context.unshift(currentContext)
336 }
337 }
338 }
339 if (context.length > contextLength) context.splice(0, context.length - contextLength)
340 }
341
342 Handle.partial = function(node, context, options, helpers, partials) {
343 var name = node.partialName.name,
344 partial = partials && partials[name],
345 contextLength = context.length;
346
347 if (partial) Handle.gen(partial, context, options, helpers, partials)
348
349 if (context.length > contextLength) context.splice(0, context.length - contextLength)
350 }
351 Handle.content = Util.noop
352 Handle.PARTIAL_NAME = Util.noop
353 Handle.DATA = Util.noop
354 Handle.STRING = Util.noop
355 Handle.INTEGER = Util.noop
356 Handle.BOOLEAN = Util.noop
357 Handle.comment = Util.noop
358
359 var Helpers = {}
360
361 Helpers.each = function(node, context, options) {
362 var i, len, cur, val, parts, def, type,
363 currentContext = context[0];
364
365 parts = node.mustache.params[0].parts // each 只需要处理第一个参数,更多的参数由 each 自己处理
366 for (i = 0, len = parts.length; i < len; i++) {
367 options.__path.push(parts[i])
368
369 cur = parts[i]
370 def = i === len - 1 ? [] : {}
371
372 val = this.val(cur, options, context, def)
373
374 currentContext[cur] = Util.isArray(val) && [] || val
375
376 type = Util.type(currentContext[cur])
377 if (type === 'object' || type === 'array') {
378 currentContext = currentContext[cur]
379 context.unshift(currentContext)
380 }
381 }
382
383 return val
384 }
385
386 Helpers['if'] = Helpers.unless = function(node, context, options) {
387 var params = node.mustache.params,
388 i, j, cur, val, parts, def, type,
389 currentContext = context[0];
390
391 for (i = 0; i < params.length; i++) {
392 parts = params[i].parts
393 for (j = 0; j < parts.length; j++) {
394 if (i === 0) options.__path.push(parts[j])
395
396 cur = parts[j]
397 def = j === parts.length - 1 ? '@BOOL(2,1,true)' : {}
398
399 val = this.val(cur, options, context, def)
400 if (j === parts.length - 1) {
401 val = val === 'true' ? true :
402 val === 'false' ? false : val
403 }
404
405 currentContext[cur] = Util.isArray(val) ? [] : val
406
407 type = Util.type(currentContext[cur])
408 if (type === 'object' || type === 'array') {
409 currentContext = currentContext[cur]
410 context.unshift(currentContext)
411 }
412 }
413 }
414 return val
415 }
416
417 Helpers['with'] = function(node, context, options) {
418 var i, cur, val, parts, def,
419 currentContext = context[0];
420
421 parts = node.mustache.params[0].parts
422 for (i = 0; i < parts.length; i++) {
423 options.__path.push(parts[i])
424
425 cur = parts[i]
426 def = {}
427
428 val = this.val(cur, options, context, def)
429
430 currentContext = currentContext[cur] = val
431 context.unshift(currentContext)
432 }
433 return val
434 }
435
436 Helpers.log = function() {
437 // {{log "Look at me!"}}
438 }
439
440 Helpers.custom = function(node, context, options) {
441 var i, len, cur, val, parts, def, type,
442 currentContext = context[0];
443
444 // custom helper
445 // 如果 helper 没有参数,则认为是在当前上下文中判断 helper 是否为 true
446 if (node.mustache.params.length === 0) {
447 return
448
449 // 按理说,Mock4Tpl 不需要也不应该模拟 helper 的行为(返回值),只需要处理 helper 的 params 和 statements。
450 // 之所以保留下面的代码,是为了以防需要时扩展,也就是说,如果连 helper 也需要模拟的话!
451 options.__path.push(node.mustache.id.string)
452
453 cur = node.mustache.id.string
454 def = '@BOOL(2,1,true)'
455
456 val = this.val(cur, options, context, def)
457
458 currentContext[cur] = Util.isArray(val) && [] || val
459
460 type = Util.type(currentContext[cur])
461 if (type === 'object' || type === 'array') {
462 currentContext = currentContext[cur]
463 context.unshift(currentContext)
464 }
465
466 } else {
467 parts = node.mustache.params[0].parts
468 for (i = 0, len = parts.length; i < len; i++) {
469 options.__path.push(parts[i])
470
471 cur = parts[i]
472 def = i === len - 1 ? [] : {} // 默认值也可以是 [],如果有必要的话
473
474 val = this.val(cur, options, context, def)
475
476 currentContext[cur] = Util.isArray(val) && [] || val
477
478 type = Util.type(currentContext[cur])
479 if (type === 'object' || type === 'array') {
480 currentContext = currentContext[cur]
481 context.unshift(currentContext)
482 }
483 }
484 }
485 return val
486 }
487
488}).call(this);
489// END(BROWSER)
\No newline at end of file