arc-templates
Version:
Fully powered JavaScript template engine with halfway-decent syntax.
139 lines (113 loc) • 4.91 kB
JavaScript
Object.defineProperty(exports, '__esModule', {
value: true
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _Lexer = require('./Lexer');
var _Lexer2 = _interopRequireDefault(_Lexer);
var _Context = require('./Context');
var _Context2 = _interopRequireDefault(_Context);
var _tokens = require('./tokens');
var _tokens2 = _interopRequireDefault(_tokens);
var _bluebird = require('bluebird');
var _bluebird2 = _interopRequireDefault(_bluebird);
var _babelCore = require('babel-core');
const MISSING_FILENAME = '<string>';
const globalEval = eval;
function nameOrExpression(token, defaultIfEmpty) {
let value = token.value.trim();
if (value === '') {
if (defaultIfEmpty !== undefined) {
value = defaultIfEmpty;
} else {
throw new Error(token.begin + ': ' + token.token + ' tag must contain a name or expression.');
}
}
return value.startsWith('(') ? value : JSON.stringify(value);
}
function compile(text, filename) {
const lexer = new _Lexer2.default(text, filename);
const buffer = [];
for (let token of lexer.lex()) {
switch (token.token) {
case _tokens2.default.DOCUMENT:
buffer.push('this._appendRaw(' + JSON.stringify(token.value) + ');');
break;
case _tokens2.default.EXPRESSION:
buffer.push('this._append(' + token.value + ');');
break;
case _tokens2.default.JAVASCRIPT:
buffer.push(token.value + ';');
break;
case _tokens2.default.LAYOUT:
buffer.push('this._layout = ' + nameOrExpression(token) + ';');
break;
case _tokens2.default.BLOCK_REFERENCE:
buffer.push('this._appendRaw(this.child[' + nameOrExpression(token, 'content') + '] || "");');
break;
case _tokens2.default.BLOCK_NAME:
buffer.push('this._currentBlock = ' + nameOrExpression(token) + ';');
break;
case _tokens2.default.PARTIAL:
buffer.push('this.partial = this._locals.partial = yield this._partial(' + nameOrExpression(token) + ');');
buffer.push('this._appendRaw(this._locals.partial.content);');
break;
default:
throw new Error("Internal error.");
}
}
return buffer.join('\n');
}
class Template {
constructor(arc) {
this.arc = arc;
}
get filename() {
return this._filename || MISSING_FILENAME;
}
set filename(value) {
this._filename = value;
}
load() {
if (this.text !== undefined) {
return _bluebird2.default.resolve(this.text);
} else {
return this.arc.filesystem.readFile(this.filename).then(text => this.text = text);
}
}
compile() {
return this.load().then(text => {
// As much as I dislike eval, GeneratorFunction.constructor isn't working yet for Node *or* Babel.
// The awkward extra function wrapper for ES5 is required because Babel does not yet support 'with' statements within generator functions.
const funcText = this.arc.supportES5 ? '(function () { with (this._locals) with (this.data) { return (function *() {\n' + compile(text, this.filename) + '\n}).bind(this); } })' : '(function *() { with (this._locals) with (this.data) {\n' + compile(text, this.filename) + '\n} })';
// We also Babel-transform for modern Node versions because it doesn't yet support block scoping (const, let, etc) outside of strict mode (as of 2015-10-07).
// Ideally, for modern Node, this should just be: globalEval(funcText)
const func = this.arc.supportES5 ? globalEval((0, _babelCore.transform)(funcText, { blacklist: ['strict'] }).code) : globalEval((0, _babelCore.transform)(funcText, { whitelist: ['es6.blockScoping'] }).code);
const context = new _Context2.default(this, func, this.filename);
return context._execute.bind(context);
});
}
evaluate(data, child) {
return this.compile().then(execute => execute(data, child));
}
joinedPath(path) {
if (this.filename === MISSING_FILENAME) {
return path;
}
return this.arc.path.join(this.arc.path.dirname(this.filename), path);
}
static fromFile(arc, filename) {
const result = new Template(arc);
result.filename = filename;
return result;
}
static fromString(arc, text, filename) {
const result = new Template(arc);
result.text = text;
result.filename = filename;
return result;
}
}
exports.default = Template;
module.exports = exports.default;
//# sourceMappingURL=Template.js.map
;