wzq
Version:
微证券开发工具,集成ssi-server、构建等功能
229 lines (200 loc) • 7.16 kB
JavaScript
;
var extend = require('extend');
var fs = require('fs');
var path = require('path');
var async = require('async');
var format = require('date-format');
var syntaxReg = /<!--#([^\r\n]+?)-->/mg;
var includeFileReg = /<!--#\s*include\s+(file|virtual)=(['"])([^\r\n\s]+?)\2\s*(.*)-->/;
var setVarReg = /<!--#\s*set\s+var=(['"])([^\r\n]+?)\1\s+value=(['"])([^\r\n]*?)\3\s*-->/;
var echoReg = /<!--#\s*echo\s+var=(['"])([^\r\n]+?)\1(\s+default=(['"])([^\r\n]+?)\4)?\s*-->/;
var ifReg = /<!--#\s*if\s+expr=(['"])([^\r\n]+?)\1\s*-->/;
var elifReg = /<!--#\s*elif\s+expr=(['"])([^\r\n]+?)\1\s*-->/;
var elseReg = /<!--#\s*else\s*-->/;
var endifReg = /<!--#\s*endif\s*-->/;
var payload = {
DATE_LOCAL: format('yyyy-MM-dd-hh-mm-ss', new Date()).replace(/-/g, '')
}
/**
* Resolve source
*
* @param {String} tpl Source
* @return {Function}
* @since 0.1.0
* @version 0.1.2
*/
function resolve(tpl) {
//resolve set/echo/if
var fnStr = 'var _r="";\nwith(__data){\n';
var matches, key, val, pat, eq;
var start = 0,
lastMatches;
var resolveLine = function(str) {
//This is stupid but works for "\r\b\f\u\v\n".
//Here we assume line break is "\n"
return str.replace(/\r/mg, '').replace(/\\/mg, '\\\\').replace(/"/mg, '\\"').replace(/\n/mg, '\\n\\\n');
};
while (!!(matches = syntaxReg.exec(tpl))) {
fnStr += '_r += "' + resolveLine(tpl.slice(start, matches.index)) + '";\n';
start = matches[0].length + matches.index;
lastMatches = matches;
if(echoReg.test(matches[0])) {
key = RegExp.$2;
val = RegExp.$5 || "";
fnStr += '_r += ' + key + '||"' + val + '";\n';
}
}
if (lastMatches) {
fnStr += '_r+="' + resolveLine(tpl.slice(lastMatches.index + lastMatches[0].length)) + '";';
} else {
fnStr += '_r+="' + resolveLine(tpl) + '";';
}
fnStr += '};\nreturn _r;';
return new Function('__data', fnStr);
}
/**
* SSI is a tool to resolve ssi syntax.
*
* @param {Object} initOptions
* @class
* @since 0.1.0
* @version 0.1.0
*/
var SSI = function(initOptions) {
this.options = extend({
baseDir: '.',
encoding: 'utf-8',
payload: {}
}, initOptions || {});
};
SSI.prototype = {
regExps: {
includeFileReg: includeFileReg,
setVarReg: setVarReg,
echoReg: echoReg,
ifReg: ifReg,
elifReg: elifReg,
elseReg: elseReg,
endifReg: endifReg
},
/**
*
* @param {Object} config
*/
setDefaultOptions: function(options) {
return extend(this.options, options || {});
},
/**
*
* <!--# include file="../path/relative/to/current/file" -->
* <!--# include virtual="/path/relative/to/options.baseDir" -->
*
* <!--# set var="k" value="v" -->
*
* <!--# echo var="n" default="default" -->
*
* <!--# if expr="test" -->
* <!--# elif expr="" -->
* <!--# else -->
* <!--# endif -->
*
* @param {String} content
* @param {Object} options
* @param {Function} callback
*/
compile: function(content, options, callback) {
var func;
if (arguments.length < 3) {
callback = options;
options = {};
}
options = extend({}, this.options, options || {});
this.resolveIncludes(content, options, function(err, content) {
if(err) {
return callback(err);
}
func = resolve(content);
try {
return callback(null, func(payload || {}));
} catch (ex) {
return callback(ex);
}
// return callback(null, content);
});
},
/**
* Rsolves all file includes.
*
* @param content
* @param options
* @param callback
*/
resolveIncludes: function(content, options, callback) {
var matches, seg, isVirtual, basePath, tpath, subOptions, ssi = this;
async.whilst( // https://www.npmjs.org/package/async#whilst-test-fn-callback-
function test() {return !!(matches = includeFileReg.exec(content)); },
function insertInclude(next) {
seg = matches[0];
isVirtual = RegExp.$1 == 'virtual';
basePath = (isVirtual && options.dirname && RegExp.$3.charAt(0) !== '/')? options.dirname : options.baseDir;
tpath = path.join(basePath, RegExp.$3.replace(options.htmlPrefix, ''));
fs.lstat(tpath,
function(err, stats) {
if (err) {
return next(err);
}
if (stats.isDirectory()) {
tpath = tpath.replace(/(\/)?$/, '/index.html');
}
fs.readFile(tpath, {
encoding: options.encoding
}, function(err, innerContentRaw) {
if (err) {
return next(err);
}
// ensure that included files can include other files with relative paths
subOptions = extend({}, options, {dirname: path.dirname(tpath)});
ssi.resolveIncludes(innerContentRaw, subOptions, function(err, innerContent) {
if (err) {
return next(err);
}
content = content.slice(0, matches.index) + innerContent + content.slice(matches.index + seg.length);
next(null, content);
});
}
);
}
);
},
function includesComplete(err) {
if (err) {
return callback(err);
}
return callback(null, content);
}
);
},
/**
*
* @param {String} filepath
* @param {Object} options
* @param {Function} callback
*/
compileFile: function(filepath, options, callback) {
if (arguments.length < 3) {
callback = options;
options = {};
}
options = extend({}, this.options, options || {});
options.dirname = path.dirname(filepath);
var ssi = this;
return fs.readFile(filepath, {
encoding: options.encoding
}, function(err, content) {
if (err) {
return callback(err);
} else return ssi.compile(content, options, callback);
});
}
};
module.exports = SSI;