apim-tools
Version:
APIM Tools
249 lines (226 loc) • 6.07 kB
JavaScript
/**
* @file smarty 文件渲染处理器
* @author sparklewhy@gmail.com
*/
;
const pathUtil = require('path');
const fs = require('fs');
const isPlainObject = require('lodash.isplainobject');
const phpProcessor = require('./php-processor');
const helper = require('./helper');
/**
* 默认选项定义
*
* @type {Object}
*/
const defaultOptions = {
/**
* php 处理器的配置选项
*
* @type Object
*/
php: null,
/**
* 渲染 smarty 的 php 文件基础路径
*
* @type {string}
*/
baseDir: '',
/**
* 渲染的模板的 php 文件,路径相对于 `baseDir` 选项
*
* @type {string}
*/
renderFile: 'smarty.php'
};
/**
* 初始化 smarty 模板渲染文件
*
* @param {Object} options 处理器选项
* @param {Object} logger log 工具
*/
function initSmartyRendererFile(options, logger) {
let renderFile = pathUtil.join(options.baseDir, options.renderFile);
// 创建 smarty 模板渲染的 php 文件,如果不存在的话
try {
if (!fs.existsSync(renderFile)) {
let renderFileContent = fs.readFileSync(
pathUtil.join(__dirname, './smarty-renderer.php')
).toString();
logger.info('create smarty render file: ', renderFile);
fs.writeFileSync(renderFile, renderFileContent);
}
}
catch (e) {
logger.error('init smarty render file fail:', e.toString());
}
options.renderSmartyPhpFile = renderFile;
}
/**
* 渲染 smarty 模板
*
* @param {Object} data 要处理的 php 文件的数据
* @param {Object} options 预处理器的选项
* @param {Object} context 当前请求的上下文信息
* @param {Function} callback 执行的回调
*/
function processSmarty(data, options, context, callback) {
phpProcessor(
{
tplPath: data._tpl,
tplData: JSON.stringify(helper.normalizeMockData(data)),
path: options.renderSmartyPhpFile
},
options.php,
context,
callback
);
}
/**
* 将包含特定 smarty 渲染信息的数据进行渲染。
* e.g.,
* <code>
* {
* _tpl: 'personList.tpl',
* _data: {
* listData: [{name: 'Jack', from: 'USA'}]
* }
* }
* </code>
* 上述数据,最后将转成 `personList.tpl` 渲染后的 html 字符串
*
* 局部渲染的例子:
* <code>
* {
* status: 0,
* count: 100,
* page: 1,
* tpl: {
* _tpl: 'personList.tpl',
* _data: {
* listData: [{name: 'Jack', from: 'USA'}]
* }
* }
* }
* </code>
*
* 上述数据,tpl值将最后替换成 `personList.tpl` 渲染后的 html 字符串
* <code>
* {
* status: 0,
* count: 100,
* page: 1,
* tpl: '<personList.tpl render result>'
* }
* </code>
*
* @param {Object} data 要渲染的数据
* @param {Object} options 预处理器的选项
* @param {Object} context 当前请求的上下文信息
* @param {Function} callback 执行的回调
* @return {void}
*/
function renderSmarty(data, options, context, callback) {
let tplPath = data._tpl;
if (tplPath) {
return processSmarty(data, options, context, callback);
}
let processObjCounter = 0;
let errorInfos = [];
let resultData = {};
let renderDone = function (name, err, result) {
processObjCounter--;
if (err) {
errorInfos.push(err.toString());
}
else {
name && (resultData[name] = result && result._data);
}
if (processObjCounter <= 0) {
if (errorInfos.length) {
callback(errorInfos.join('; '));
}
else {
callback(null, resultData);
}
}
};
let keys = Object.keys(data);
let toProcessObjs = [];
for (let i = 0, len = keys.length; i < len; i++) {
let name = keys[i];
let value = data[name];
if (isPlainObject(value)) {
processObjCounter++;
toProcessObjs.push({
name,
value
});
}
else {
resultData[name] = value;
}
}
if (processObjCounter) {
toProcessObjs.forEach(function (item) {
renderSmarty(
item.value, options,
context, renderDone.bind(this, item.name)
);
});
}
else {
renderDone();
}
}
/**
* 滤掉空的选项
*
* @param {Object} options 选项
* @return {Object}
*/
function filterNullOptions(options) {
let result = {};
options && Object.keys(options).forEach(k => {
if (options[k] != null) {
result[k] = options[k];
}
});
return result;
}
/**
* smarty 处理器
*
* @param {Object} mockData 要渲染的 smarty 数据
* @param {Object=} options 处理器选项
* @param {string=} options.baseDir 模板渲染的根目录
* @param {string=} options.php php 处理器的选项,具体参见 {@link php-processor} ,可选
* @param {string=} options.renderFile 负责渲染 smarty 的 php 文件路径,默认会自动生成,
* 如果不存在,路径相对于 `baseDir`,可选
* @param {Object} context 请求上下文
* @return {Promise}
*/
function runSmarty(mockData, options, context) {
let processorOptions = {};
Object.assign(
processorOptions,
defaultOptions,
filterNullOptions(options)
);
initSmartyRendererFile(processorOptions, context.logger);
return new Promise((resolve, reject) => {
if (!isPlainObject(mockData)) {
/* eslint-disable fecs-camelcase */
return resolve({_data: mockData});
}
renderSmarty(mockData, processorOptions, context, (err, result) => {
if (err) {
reject(err);
}
else {
resolve(result);
}
});
});
}
module.exports = exports = runSmarty;