/*
|
## Handler
|
|
处理数据模板。
|
|
* Handler.gen( template, name?, context? )
|
|
入口方法。
|
|
* Data Template Definition, DTD
|
|
处理数据模板定义。
|
|
* Handler.array( options )
|
* Handler.object( options )
|
* Handler.number( options )
|
* Handler.boolean( options )
|
* Handler.string( options )
|
* Handler.function( options )
|
* Handler.regexp( options )
|
|
处理路径(相对和绝对)。
|
|
* Handler.getValueByKeyPath( key, options )
|
|
* Data Placeholder Definition, DPD
|
|
处理数据占位符定义
|
|
* Handler.placeholder( placeholder, context, templateContext, options )
|
|
*/
|
|
var Constant = require('./constant')
|
var Util = require('./util')
|
var Parser = require('./parser')
|
var Random = require('./random/')
|
var RE = require('./regexp')
|
|
var Handler = {
|
extend: Util.extend
|
}
|
|
/*
|
template 属性值(即数据模板)
|
name 属性名
|
context 数据上下文,生成后的数据
|
templateContext 模板上下文,
|
|
Handle.gen(template, name, options)
|
context
|
currentContext, templateCurrentContext,
|
path, templatePath
|
root, templateRoot
|
*/
|
Handler.gen = function(template, name, context) {
|
/* jshint -W041 */
|
name = name == undefined ? '' : (name + '')
|
|
context = context || {}
|
context = {
|
// 当前访问路径,只有属性名,不包括生成规则
|
path: context.path || [Constant.GUID],
|
templatePath: context.templatePath || [Constant.GUID++],
|
// 最终属性值的上下文
|
currentContext: context.currentContext,
|
// 属性值模板的上下文
|
templateCurrentContext: context.templateCurrentContext || template,
|
// 最终值的根
|
root: context.root || context.currentContext,
|
// 模板的根
|
templateRoot: context.templateRoot || context.templateCurrentContext || template
|
}
|
// console.log('path:', context.path.join('.'), template)
|
|
var rule = Parser.parse(name)
|
var type = Util.type(template)
|
var data
|
|
if (Handler[type]) {
|
data = Handler[type]({
|
// 属性值类型
|
type: type,
|
// 属性值模板
|
template: template,
|
// 属性名 + 生成规则
|
name: name,
|
// 属性名
|
parsedName: name ? name.replace(Constant.RE_KEY, '$1') : name,
|
|
// 解析后的生成规则
|
rule: rule,
|
// 相关上下文
|
context: context
|
})
|
|
if (!context.root) context.root = data
|
return data
|
}
|
|
return template
|
}
|
|
Handler.extend({
|
array: function(options) {
|
var result = [],
|
i, ii;
|
|
// 'name|1': []
|
// 'name|count': []
|
// 'name|min-max': []
|
if (options.template.length === 0) return result
|
|
// 'arr': [{ 'email': '@EMAIL' }, { 'email': '@EMAIL' }]
|
if (!options.rule.parameters) {
|
for (i = 0; i < options.template.length; i++) {
|
options.context.path.push(i)
|
options.context.templatePath.push(i)
|
result.push(
|
Handler.gen(options.template[i], i, {
|
path: options.context.path,
|
templatePath: options.context.templatePath,
|
currentContext: result,
|
templateCurrentContext: options.template,
|
root: options.context.root || result,
|
templateRoot: options.context.templateRoot || options.template
|
})
|
)
|
options.context.path.pop()
|
options.context.templatePath.pop()
|
}
|
} else {
|
// 'method|1': ['GET', 'POST', 'HEAD', 'DELETE']
|
if (options.rule.min === 1 && options.rule.max === undefined) {
|
// fix #17
|
options.context.path.push(options.name)
|
options.context.templatePath.push(options.name)
|
result = Random.pick(
|
Handler.gen(options.template, undefined, {
|
path: options.context.path,
|
templatePath: options.context.templatePath,
|
currentContext: result,
|
templateCurrentContext: options.template,
|
root: options.context.root || result,
|
templateRoot: options.context.templateRoot || options.template
|
})
|
)
|
options.context.path.pop()
|
options.context.templatePath.pop()
|
} else {
|
// 'data|+1': [{}, {}]
|
if (options.rule.parameters[2]) {
|
options.template.__order_index = options.template.__order_index || 0
|
|
options.context.path.push(options.name)
|
options.context.templatePath.push(options.name)
|
result = Handler.gen(options.template, undefined, {
|
path: options.context.path,
|
templatePath: options.context.templatePath,
|
currentContext: result,
|
templateCurrentContext: options.template,
|
root: options.context.root || result,
|
templateRoot: options.context.templateRoot || options.template
|
})[
|
options.template.__order_index % options.template.length
|
]
|
|
options.template.__order_index += +options.rule.parameters[2]
|
|
options.context.path.pop()
|
options.context.templatePath.pop()
|
|
} else {
|
// 'data|1-10': [{}]
|
for (i = 0; i < options.rule.count; i++) {
|
// 'data|1-10': [{}, {}]
|
for (ii = 0; ii < options.template.length; ii++) {
|
options.context.path.push(result.length)
|
options.context.templatePath.push(ii)
|
result.push(
|
Handler.gen(options.template[ii], result.length, {
|
path: options.context.path,
|
templatePath: options.context.templatePath,
|
currentContext: result,
|
templateCurrentContext: options.template,
|
root: options.context.root || result,
|
templateRoot: options.context.templateRoot || options.template
|
})
|
)
|
options.context.path.pop()
|
options.context.templatePath.pop()
|
}
|
}
|
}
|
}
|
}
|
return result
|
},
|
object: function(options) {
|
var result = {},
|
keys, fnKeys, key, parsedKey, inc, i;
|
|
// 'obj|min-max': {}
|
/* jshint -W041 */
|
if (options.rule.min != undefined) {
|
keys = Util.keys(options.template)
|
keys = Random.shuffle(keys)
|
keys = keys.slice(0, options.rule.count)
|
for (i = 0; i < keys.length; i++) {
|
key = keys[i]
|
parsedKey = key.replace(Constant.RE_KEY, '$1')
|
options.context.path.push(parsedKey)
|
options.context.templatePath.push(key)
|
result[parsedKey] = Handler.gen(options.template[key], key, {
|
path: options.context.path,
|
templatePath: options.context.templatePath,
|
currentContext: result,
|
templateCurrentContext: options.template,
|
root: options.context.root || result,
|
templateRoot: options.context.templateRoot || options.template
|
})
|
options.context.path.pop()
|
options.context.templatePath.pop()
|
}
|
|
} else {
|
// 'obj': {}
|
keys = []
|
fnKeys = [] // #25 改变了非函数属性的顺序,查找起来不方便
|
for (key in options.template) {
|
(typeof options.template[key] === 'function' ? fnKeys : keys).push(key)
|
}
|
keys = keys.concat(fnKeys)
|
|
/*
|
会改变非函数属性的顺序
|
keys = Util.keys(options.template)
|
keys.sort(function(a, b) {
|
var afn = typeof options.template[a] === 'function'
|
var bfn = typeof options.template[b] === 'function'
|
if (afn === bfn) return 0
|
if (afn && !bfn) return 1
|
if (!afn && bfn) return -1
|
})
|
*/
|
|
for (i = 0; i < keys.length; i++) {
|
key = keys[i]
|
parsedKey = key.replace(Constant.RE_KEY, '$1')
|
options.context.path.push(parsedKey)
|
options.context.templatePath.push(key)
|
result[parsedKey] = Handler.gen(options.template[key], key, {
|
path: options.context.path,
|
templatePath: options.context.templatePath,
|
currentContext: result,
|
templateCurrentContext: options.template,
|
root: options.context.root || result,
|
templateRoot: options.context.templateRoot || options.template
|
})
|
options.context.path.pop()
|
options.context.templatePath.pop()
|
// 'id|+1': 1
|
inc = key.match(Constant.RE_KEY)
|
if (inc && inc[2] && Util.type(options.template[key]) === 'number') {
|
options.template[key] += parseInt(inc[2], 10)
|
}
|
}
|
}
|
return result
|
},
|
number: function(options) {
|
var result, parts;
|
if (options.rule.decimal) { // float
|
options.template += ''
|
parts = options.template.split('.')
|
// 'float1|.1-10': 10,
|
// 'float2|1-100.1-10': 1,
|
// 'float3|999.1-10': 1,
|
// 'float4|.3-10': 123.123,
|
parts[0] = options.rule.range ? options.rule.count : parts[0]
|
parts[1] = (parts[1] || '').slice(0, options.rule.dcount)
|
while (parts[1].length < options.rule.dcount) {
|
parts[1] += (
|
// 最后一位不能为 0:如果最后一位为 0,会被 JS 引擎忽略掉。
|
(parts[1].length < options.rule.dcount - 1) ? Random.character('number') : Random.character('123456789')
|
)
|
}
|
result = parseFloat(parts.join('.'), 10)
|
} else { // integer
|
// 'grade1|1-100': 1,
|
result = options.rule.range && !options.rule.parameters[2] ? options.rule.count : options.template
|
}
|
return result
|
},
|
boolean: function(options) {
|
var result;
|
// 'prop|multiple': false, 当前值是相反值的概率倍数
|
// 'prop|probability-probability': false, 当前值与相反值的概率
|
result = options.rule.parameters ? Random.bool(options.rule.min, options.rule.max, options.template) : options.template
|
return result
|
},
|
string: function(options) {
|
var result = '',
|
i, placeholders, ph, phed;
|
if (options.template.length) {
|
|
// 'foo': '★',
|
/* jshint -W041 */
|
if (options.rule.count == undefined) {
|
result += options.template
|
}
|
|
// 'star|1-5': '★',
|
for (i = 0; i < options.rule.count; i++) {
|
result += options.template
|
}
|
// 'email|1-10': '@EMAIL, ',
|
placeholders = result.match(Constant.RE_PLACEHOLDER) || [] // A-Z_0-9 > \w_
|
for (i = 0; i < placeholders.length; i++) {
|
ph = placeholders[i]
|
|
// 遇到转义斜杠,不需要解析占位符
|
if (/^\\/.test(ph)) {
|
placeholders.splice(i--, 1)
|
continue
|
}
|
|
phed = Handler.placeholder(ph, options.context.currentContext, options.context.templateCurrentContext, options)
|
|
// 只有一个占位符,并且没有其他字符
|
if (placeholders.length === 1 && ph === result && typeof phed !== typeof result) { //
|
result = phed
|
break
|
|
if (Util.isNumeric(phed)) {
|
result = parseFloat(phed, 10)
|
break
|
}
|
if (/^(true|false)$/.test(phed)) {
|
result = phed === 'true' ? true :
|
phed === 'false' ? false :
|
phed // 已经是布尔值
|
break
|
}
|
}
|
result = result.replace(ph, phed)
|
}
|
|
} else {
|
// 'ASCII|1-10': '',
|
// 'ASCII': '',
|
result = options.rule.range ? Random.string(options.rule.count) : options.template
|
}
|
return result
|
},
|
'function': function(options) {
|
// ( context, options )
|
return options.template.call(options.context.currentContext, options)
|
},
|
'regexp': function(options) {
|
var source = ''
|
|
// 'name': /regexp/,
|
/* jshint -W041 */
|
if (options.rule.count == undefined) {
|
source += options.template.source // regexp.source
|
}
|
|
// 'name|1-5': /regexp/,
|
for (var i = 0; i < options.rule.count; i++) {
|
source += options.template.source
|
}
|
|
return RE.Handler.gen(
|
RE.Parser.parse(
|
source
|
)
|
)
|
}
|
})
|
|
Handler.extend({
|
_all: function() {
|
var re = {};
|
for (var key in Random) re[key.toLowerCase()] = key
|
return re
|
},
|
// 处理占位符,转换为最终值
|
placeholder: function(placeholder, obj, templateContext, options) {
|
// console.log(options.context.path)
|
// 1 key, 2 params
|
Constant.RE_PLACEHOLDER.exec('')
|
var parts = Constant.RE_PLACEHOLDER.exec(placeholder),
|
key = parts && parts[1],
|
lkey = key && key.toLowerCase(),
|
okey = this._all()[lkey],
|
params = parts && parts[2] || ''
|
var pathParts = this.splitPathToArray(key)
|
|
// 解析占位符的参数
|
try {
|
// 1. 尝试保持参数的类型
|
/*
|
#24 [Window Firefox 30.0 引用 占位符 抛错](https://github.com/nuysoft/Mock/issues/24)
|
[BX9056: 各浏览器下 window.eval 方法的执行上下文存在差异](http://www.w3help.org/zh-cn/causes/BX9056)
|
应该属于 Window Firefox 30.0 的 BUG
|
*/
|
/* jshint -W061 */
|
params = eval('(function(){ return [].splice.call(arguments, 0 ) })(' + params + ')')
|
} catch (error) {
|
// 2. 如果失败,只能解析为字符串
|
// console.error(error)
|
// if (error instanceof ReferenceError) params = parts[2].split(/,\s*/);
|
// else throw error
|
params = parts[2].split(/,\s*/)
|
}
|
|
// 占位符优先引用数据模板中的属性
|
if (obj && (key in obj)) return obj[key]
|
|
// @index @key
|
// if (Constant.RE_INDEX.test(key)) return +options.name
|
// if (Constant.RE_KEY.test(key)) return options.name
|
|
// 绝对路径 or 相对路径
|
if (
|
key.charAt(0) === '/' ||
|
pathParts.length > 1
|
) return this.getValueByKeyPath(key, options)
|
|
// 递归引用数据模板中的属性
|
if (templateContext &&
|
(typeof templateContext === 'object') &&
|
(key in templateContext) &&
|
(placeholder !== templateContext[key]) // fix #15 避免自己依赖自己
|
) {
|
// 先计算被引用的属性值
|
templateContext[key] = Handler.gen(templateContext[key], key, {
|
currentContext: obj,
|
templateCurrentContext: templateContext
|
})
|
return templateContext[key]
|
}
|
|
// 如果未找到,则原样返回
|
if (!(key in Random) && !(lkey in Random) && !(okey in Random)) return placeholder
|
|
// 递归解析参数中的占位符
|
for (var i = 0; i < params.length; i++) {
|
Constant.RE_PLACEHOLDER.exec('')
|
if (Constant.RE_PLACEHOLDER.test(params[i])) {
|
params[i] = Handler.placeholder(params[i], obj, templateContext, options)
|
}
|
}
|
|
var handle = Random[key] || Random[lkey] || Random[okey]
|
switch (Util.type(handle)) {
|
case 'array':
|
// 自动从数组中取一个,例如 @areas
|
return Random.pick(handle)
|
case 'function':
|
// 执行占位符方法(大多数情况)
|
handle.options = options
|
var re = handle.apply(Random, params)
|
if (re === undefined) re = '' // 因为是在字符串中,所以默认为空字符串。
|
delete handle.options
|
return re
|
}
|
},
|
getValueByKeyPath: function(key, options) {
|
var originalKey = key
|
var keyPathParts = this.splitPathToArray(key)
|
var absolutePathParts = []
|
|
// 绝对路径
|
if (key.charAt(0) === '/') {
|
absolutePathParts = [options.context.path[0]].concat(
|
this.normalizePath(keyPathParts)
|
)
|
} else {
|
// 相对路径
|
if (keyPathParts.length > 1) {
|
absolutePathParts = options.context.path.slice(0)
|
absolutePathParts.pop()
|
absolutePathParts = this.normalizePath(
|
absolutePathParts.concat(keyPathParts)
|
)
|
|
}
|
}
|
|
key = keyPathParts[keyPathParts.length - 1]
|
var currentContext = options.context.root
|
var templateCurrentContext = options.context.templateRoot
|
for (var i = 1; i < absolutePathParts.length - 1; i++) {
|
currentContext = currentContext[absolutePathParts[i]]
|
templateCurrentContext = templateCurrentContext[absolutePathParts[i]]
|
}
|
// 引用的值已经计算好
|
if (currentContext && (key in currentContext)) return currentContext[key]
|
|
// 尚未计算,递归引用数据模板中的属性
|
if (templateCurrentContext &&
|
(typeof templateCurrentContext === 'object') &&
|
(key in templateCurrentContext) &&
|
(originalKey !== templateCurrentContext[key]) // fix #15 避免自己依赖自己
|
) {
|
// 先计算被引用的属性值
|
templateCurrentContext[key] = Handler.gen(templateCurrentContext[key], key, {
|
currentContext: currentContext,
|
templateCurrentContext: templateCurrentContext
|
})
|
return templateCurrentContext[key]
|
}
|
},
|
// https://github.com/kissyteam/kissy/blob/master/src/path/src/path.js
|
normalizePath: function(pathParts) {
|
var newPathParts = []
|
for (var i = 0; i < pathParts.length; i++) {
|
switch (pathParts[i]) {
|
case '..':
|
newPathParts.pop()
|
break
|
case '.':
|
break
|
default:
|
newPathParts.push(pathParts[i])
|
}
|
}
|
return newPathParts
|
},
|
splitPathToArray: function(path) {
|
var parts = path.split(/\/+/);
|
if (!parts[parts.length - 1]) parts = parts.slice(0, -1)
|
if (!parts[0]) parts = parts.slice(1)
|
return parts;
|
}
|
})
|
|
module.exports = Handler
|