arc-templates
Version:
Fully powered JavaScript template engine with halfway-decent syntax.
188 lines (158 loc) • 6.74 kB
JavaScript
var _createClass = require('babel-runtime/helpers/create-class')['default'];
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
var _getIterator = require('babel-runtime/core-js/get-iterator')['default'];
var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
Object.defineProperty(exports, '__esModule', {
value: true
});
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');
var MISSING_FILENAME = '<string>';
var globalEval = eval;
function nameOrExpression(token, defaultIfEmpty) {
var 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) {
var lexer = new _Lexer2['default'](text, filename);
var buffer = [];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = _getIterator(lexer.lex()), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var token = _step.value;
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.");
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator['return']) {
_iterator['return']();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return buffer.join('\n');
}
var Template = (function () {
function Template(arc) {
_classCallCheck(this, Template);
this.arc = arc;
}
_createClass(Template, [{
key: 'load',
value: function load() {
var _this = this;
if (this.text !== undefined) {
return _bluebird2['default'].resolve(this.text);
} else {
return this.arc.filesystem.readFile(this.filename).then(function (text) {
return _this.text = text;
});
}
}
}, {
key: 'compile',
value: function compile() {
var _this2 = this;
return this.load().then(function (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.
var funcText = _this2.arc.supportES5 ? '(function () { with (this._locals) with (this.data) { return (function *() {\n' + _compile(text, _this2.filename) + '\n}).bind(this); } })' : '(function *() { with (this._locals) with (this.data) {\n' + _compile(text, _this2.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)
var func = _this2.arc.supportES5 ? globalEval((0, _babelCore.transform)(funcText, { blacklist: ['strict'] }).code) : globalEval((0, _babelCore.transform)(funcText, { whitelist: ['es6.blockScoping'] }).code);
var context = new _Context2['default'](_this2, func, _this2.filename);
return context._execute.bind(context);
});
}
}, {
key: 'evaluate',
value: function evaluate(data, child) {
return this.compile().then(function (execute) {
return execute(data, child);
});
}
}, {
key: 'joinedPath',
value: function joinedPath(path) {
if (this.filename === MISSING_FILENAME) {
return path;
}
return this.arc.path.join(this.arc.path.dirname(this.filename), path);
}
}, {
key: 'filename',
get: function get() {
return this._filename || MISSING_FILENAME;
},
set: function set(value) {
this._filename = value;
}
}], [{
key: 'fromFile',
value: function fromFile(arc, filename) {
var result = new Template(arc);
result.filename = filename;
return result;
}
}, {
key: 'fromString',
value: function fromString(arc, text, filename) {
var result = new Template(arc);
result.text = text;
result.filename = filename;
return result;
}
}]);
return Template;
})();
exports['default'] = Template;
module.exports = exports['default'];
//# sourceMappingURL=Template.js.map
;