brazejs
Version:
Liquid template engine for the Braze variant by pure JavaScript: compatible to Braze, easy to extend.
1,366 lines (1,327 loc) • 114 kB
JavaScript
/*
* brazejs@1.7.1, https://github.com/yq314/brazejs
* (c) 2019-2020 QingYE
* Released under the MIT License.
*/
'use strict';
var path = require('path');
var fs = require('fs');
var rp_ = require('request-promise-cache');
var crypto = require('crypto');
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __awaiter(thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
var toStr = Object.prototype.toString;
var specials = [
'-',
'[',
']',
'/',
'{',
'}',
'(',
')',
'*',
'+',
'?',
'.',
'\\',
'^',
'$',
'|'
];
var regex = RegExp('[' + specials.join('\\') + ']', 'g');
function escapeRegExp(str) {
return str.replace(regex, '\\$&');
}
/*
* Checks if value is classified as a String primitive or object.
* @param {any} value The value to check.
* @return {Boolean} Returns true if value is a string, else false.
*/
function isString(value) {
return toStr.call(value) === '[object String]';
}
function isFunction(value) {
return typeof value === 'function';
}
function promisify(fn) {
return function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return new Promise(function (resolve, reject) {
fn.apply(void 0, args.concat([function (err, result) {
err ? reject(err) : resolve(result);
}]));
});
};
}
function stringify(value) {
if (isNil(value))
return '';
value = toLiquid(value);
if (isArray(value)) {
var str = value.map(function (v) { return toValueStr(v); }).join(', ');
return "[" + str + "]";
}
if (isObject(value)) {
var str = Object.keys(value).map(function (k) { return "\"" + k + "\"=>" + toValueStr(value[k]); }).join(', ');
return "{" + str + "}";
}
return String(value);
}
function toValueStr(value) {
if (isString(value))
return "\"" + value + "\"";
return stringify(value);
}
function toLiquid(value) {
if (isFunction(value.toLiquid))
return toLiquid(value.toLiquid());
return value;
}
function isNil(value) {
return value === null || value === undefined;
}
function isArray(value) {
// be compatible with IE 8
return toStr.call(value) === '[object Array]';
}
/*
* Iterates over own enumerable string keyed properties of an object and invokes iteratee for each property.
* The iteratee is invoked with three arguments: (value, key, object).
* Iteratee functions may exit iteration early by explicitly returning false.
* @param {Object} object The object to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @return {Object} Returns object.
*/
function forOwn(object, iteratee) {
object = object || {};
for (var k in object) {
if (object.hasOwnProperty(k)) {
if (iteratee(object[k], k, object) === false)
break;
}
}
return object;
}
function last(arr) {
return arr[arr.length - 1];
}
/*
* Checks if value is the language type of Object.
* (e.g. arrays, functions, objects, regexes, new Number(0), and new String(''))
* @param {any} value The value to check.
* @return {Boolean} Returns true if value is an object, else false.
*/
function isObject(value) {
var type = typeof value;
return value !== null && (type === 'object' || type === 'function');
}
function range(start, stop, step) {
if (step === void 0) { step = 1; }
var arr = [];
for (var i = start; i < stop; i += step) {
arr.push(i);
}
return arr;
}
function padStart(str, length, ch) {
if (ch === void 0) { ch = ' '; }
str = String(str);
var n = length - str.length;
while (n-- > 0)
str = ch + str;
return str;
}
var Drop = /** @class */ (function () {
function Drop() {
}
Drop.prototype.valueOf = function () {
return undefined;
};
Drop.prototype.liquidMethodMissing = function (key) {
return undefined;
};
return Drop;
}());
var LiquidError = /** @class */ (function (_super) {
__extends(LiquidError, _super);
function LiquidError(err, token) {
var _this = _super.call(this, err.message) || this;
_this.originalError = err;
_this.token = token;
return _this;
}
LiquidError.prototype.update = function () {
var err = this.originalError;
var context = mkContext(this.token);
this.message = mkMessage(err.message, this.token);
this.stack = this.message + '\n' + context +
'\n' + this.stack + '\nFrom ' + err.stack;
};
return LiquidError;
}(Error));
var TokenizationError = /** @class */ (function (_super) {
__extends(TokenizationError, _super);
function TokenizationError(message, token) {
var _this = _super.call(this, new Error(message), token) || this;
_this.name = 'TokenizationError';
_super.prototype.update.call(_this);
return _this;
}
return TokenizationError;
}(LiquidError));
var ParseError = /** @class */ (function (_super) {
__extends(ParseError, _super);
function ParseError(err, token) {
var _this = _super.call(this, err, token) || this;
_this.name = 'ParseError';
_this.message = err.message;
_super.prototype.update.call(_this);
return _this;
}
return ParseError;
}(LiquidError));
var RenderError = /** @class */ (function (_super) {
__extends(RenderError, _super);
function RenderError(err, tpl) {
var _this = _super.call(this, err, tpl.token) || this;
_this.name = 'RenderError';
_this.message = err.message;
_super.prototype.update.call(_this);
return _this;
}
return RenderError;
}(LiquidError));
var RenderBreakError = /** @class */ (function (_super) {
__extends(RenderBreakError, _super);
function RenderBreakError(message) {
var _this = _super.call(this, message) || this;
_this.resolvedHTML = '';
_this.name = 'RenderBreakError';
_this.message = message + '';
return _this;
}
return RenderBreakError;
}(Error));
var AssertionError = /** @class */ (function (_super) {
__extends(AssertionError, _super);
function AssertionError(message) {
var _this = _super.call(this, message) || this;
_this.name = 'AssertionError';
_this.message = message + '';
return _this;
}
return AssertionError;
}(Error));
function mkContext(token) {
var lines = token.input.split('\n');
var begin = Math.max(token.line - 2, 1);
var end = Math.min(token.line + 3, lines.length);
var context = range(begin, end + 1)
.map(function (lineNumber) {
var indicator = (lineNumber === token.line) ? '>> ' : ' ';
var num = padStart(String(lineNumber), String(end).length);
var text = lines[lineNumber - 1];
return "" + indicator + num + "| " + text;
})
.join('\n');
return context;
}
function mkMessage(msg, token) {
if (token.file)
msg += ", file:" + token.file;
msg += ", line:" + token.line + ", col:" + token.col;
return msg;
}
function assert (predicate, message) {
if (!predicate) {
message = message || "expect " + predicate + " to be true";
throw new AssertionError(message);
}
}
var defaultOptions = {
root: ['.'],
cache: false,
extname: '',
dynamicPartials: true,
trimTagRight: false,
trimTagLeft: false,
trimOutputRight: false,
trimOutputLeft: false,
greedy: true,
tagDelimiterLeft: '{%',
tagDelimiterRight: '%}',
outputDelimiterLeft: '{{',
outputDelimiterRight: '}}',
strictFilters: false,
strictVariables: false
};
function normalize(options) {
options = options || {};
if (options.hasOwnProperty('root')) {
options.root = normalizeStringArray(options.root);
}
return options;
}
function applyDefault(options) {
return __assign({}, defaultOptions, options);
}
function normalizeStringArray(value) {
if (isArray(value))
return value;
if (isString(value))
return [value];
return [];
}
var Context = /** @class */ (function () {
function Context(ctx, opts) {
if (ctx === void 0) { ctx = {}; }
this.scopes = [{}];
this.registers = {};
this.opts = applyDefault(opts);
this.environments = ctx;
}
Context.prototype.getRegister = function (key, defaultValue) {
if (defaultValue === void 0) { defaultValue = {}; }
return (this.registers[key] = this.registers[key] || defaultValue);
};
Context.prototype.setRegister = function (key, value) {
return (this.registers[key] = value);
};
Context.prototype.getAll = function () {
return [this.environments].concat(this.scopes).reduce(function (ctx, val) { return __assign(ctx, val); }, {});
};
Context.prototype.get = function (path$$1) {
return __awaiter(this, void 0, void 0, function () {
var paths, ctx, _i, paths_1, path_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.parseProp(path$$1)];
case 1:
paths = _a.sent();
ctx = this.findScope(paths[0]) || this.environments;
for (_i = 0, paths_1 = paths; _i < paths_1.length; _i++) {
path_1 = paths_1[_i];
ctx = readProperty(ctx, path_1);
if (isNil(ctx) && this.opts.strictVariables) {
throw new TypeError("undefined variable: " + path_1);
}
}
return [2 /*return*/, ctx];
}
});
});
};
Context.prototype.push = function (ctx) {
return this.scopes.push(ctx);
};
Context.prototype.pop = function () {
return this.scopes.pop();
};
Context.prototype.front = function () {
return this.scopes[0];
};
Context.prototype.findScope = function (key) {
for (var i = this.scopes.length - 1; i >= 0; i--) {
var candidate = this.scopes[i];
if (key in candidate) {
return candidate;
}
}
return null;
};
/*
* Parse property access sequence from access string
* @example
* accessSeq("foo.bar") // ['foo', 'bar']
* accessSeq("foo['bar']") // ['foo', 'bar']
* accessSeq("foo['b]r']") // ['foo', 'b]r']
* accessSeq("foo[bar.coo]") // ['foo', 'bar'], for bar.coo == 'bar'
*/
Context.prototype.parseProp = function (str) {
return __awaiter(this, void 0, void 0, function () {
function push() {
if (name.length)
seq.push(name);
name = '';
}
var seq, name, j, i, _a, delemiter, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
str = String(str);
seq = [];
name = '';
i = 0;
_c.label = 1;
case 1:
if (!(i < str.length)) return [3 /*break*/, 10];
_a = str[i];
switch (_a) {
case '[': return [3 /*break*/, 2];
case '.': return [3 /*break*/, 7];
}
return [3 /*break*/, 8];
case 2:
push();
delemiter = str[i + 1];
if (!/['"]/.test(delemiter)) return [3 /*break*/, 3];
j = str.indexOf(delemiter, i + 2);
assert(j !== -1, "unbalanced " + delemiter + ": " + str);
name = str.slice(i + 2, j);
push();
i = j + 2;
return [3 /*break*/, 6];
case 3:
j = matchRightBracket(str, i + 1);
assert(j !== -1, "unbalanced []: " + str);
name = str.slice(i + 1, j);
if (!!/^[+-]?\d+$/.test(name)) return [3 /*break*/, 5];
_b = String;
return [4 /*yield*/, this.get(name)];
case 4:
name = _b.apply(void 0, [_c.sent()]);
_c.label = 5;
case 5:
push();
i = j + 1;
_c.label = 6;
case 6: return [3 /*break*/, 9];
case 7:
push();
i++;
return [3 /*break*/, 9];
case 8:
name += str[i++];
_c.label = 9;
case 9: return [3 /*break*/, 1];
case 10:
push();
if (!seq.length) {
throw new TypeError("invalid path:\"" + str + "\"");
}
return [2 /*return*/, seq];
}
});
});
};
return Context;
}());
function readProperty(obj, key) {
if (isNil(obj))
return obj;
obj = toLiquid(obj);
if (obj instanceof Drop) {
if (isFunction(obj[key]))
return obj[key]();
if (obj.hasOwnProperty(key))
return obj[key];
return obj.liquidMethodMissing(key);
}
// Support Braze's operation
if (key === 'first' && isArray(obj)) {
return obj[0];
}
else if (key === 'last' && isArray(obj)) {
return obj[obj.length - 1];
}
return key === 'size' ? readSize(obj) : obj[key];
}
function readSize(obj) {
if (!isNil(obj['size']))
return obj['size'];
if (isArray(obj) || isString(obj))
return obj.length;
return obj['size'];
}
function matchRightBracket(str, begin) {
var stack = 1; // count of '[' - count of ']'
for (var i = begin; i < str.length; i++) {
if (str[i] === '[') {
stack++;
}
if (str[i] === ']') {
stack--;
if (stack === 0) {
return i;
}
}
}
return -1;
}
var Types = /*#__PURE__*/Object.freeze({
ParseError: ParseError,
TokenizationError: TokenizationError,
RenderBreakError: RenderBreakError,
AssertionError: AssertionError,
Drop: Drop
});
var statAsync = promisify(fs.stat);
var readFileAsync = promisify(fs.readFile);
var fs$1 = {
exists: function (filepath) {
return statAsync(filepath).then(function () { return true; }).catch(function () { return false; });
},
readFile: function (filepath) {
return readFileAsync(filepath, 'utf8');
},
resolve: function (root, file, ext) {
if (!path.extname(file))
file += ext;
return path.resolve(root, file);
}
};
var Token = /** @class */ (function () {
function Token(raw, input, line, col, file) {
this.trimLeft = false;
this.trimRight = false;
this.type = 'notset';
this.col = col;
this.line = line;
this.raw = raw;
this.value = raw;
this.input = input;
this.file = file;
}
return Token;
}());
var DelimitedToken = /** @class */ (function (_super) {
__extends(DelimitedToken, _super);
function DelimitedToken(raw, value, input, line, pos, trimLeft, trimRight, file) {
var _this = _super.call(this, raw, input, line, pos, file) || this;
var tl = value[0] === '-';
var tr = last(value) === '-';
_this.value = value
.slice(tl ? 1 : 0, tr ? -1 : value.length)
.trim();
_this.trimLeft = tl || trimLeft;
_this.trimRight = tr || trimRight;
return _this;
}
return DelimitedToken;
}(Token));
// quote related
var singleQuoted = /'[^']*'/;
var doubleQuoted = /"[^"]*"/;
var quoted = new RegExp(singleQuoted.source + "|" + doubleQuoted.source);
var quoteBalanced = new RegExp("(?:" + quoted.source + "|[^'\"])*");
// basic types
var number = /[+-]?(?:\d+\.?\d*|\.?\d+)/;
var bool = /true|false/;
// property access
var identifier = /[\w-]+[?]?/;
var subscript = new RegExp("\\[(?:" + quoted.source + "|[\\w-\\.]+)\\]");
var literal = new RegExp("(?:" + quoted.source + "|" + bool.source + "|" + number.source + ")");
var variable = new RegExp(identifier.source + "(?:\\." + identifier.source + "|" + subscript.source + ")*");
// range related
var rangeLimit = new RegExp("(?:" + variable.source + "|" + number.source + ")");
var range$1 = new RegExp("\\(" + rangeLimit.source + "\\.\\." + rangeLimit.source + "\\)");
var rangeCapture = new RegExp("\\((" + rangeLimit.source + ")\\.\\.(" + rangeLimit.source + ")\\)");
var value = new RegExp("(?:" + variable.source + "|" + literal.source + "|" + range$1.source + ")");
// hash related
var hash = new RegExp("(?:" + identifier.source + ")\\s*:\\s*(?:" + value.source + ")");
var hashCapture = new RegExp("(" + identifier.source + ")\\s*:\\s*(" + value.source + ")", 'g');
// full match
var tagLine = new RegExp("^\\s*(" + identifier.source + ")\\s*([\\s\\S]*?)\\s*$");
var quotedLine = new RegExp("^" + quoted.source + "$");
var rangeLine = new RegExp("^" + rangeCapture.source + "$");
var attribute = new RegExp("^\\s*(?:(" + identifier.source + ")\\.)?\\$\\{\\s*([\\s\\S]+?)\\s*\\}\\s*$");
var operators = [
/\s+or\s+/,
/\s+and\s+/,
/==|!=|<=|>=|<|>|\s+contains\s+/
];
var TagToken = /** @class */ (function (_super) {
__extends(TagToken, _super);
function TagToken(raw, value$$1, input, line, pos, options, file) {
var _this = _super.call(this, raw, value$$1, input, line, pos, options.trimTagLeft, options.trimTagRight, file) || this;
_this.type = 'tag';
var match = _this.value.match(tagLine);
if (!match) {
throw new TokenizationError("illegal tag syntax", _this);
}
_this.name = match[1];
_this.args = match[2];
return _this;
}
TagToken.is = function (token) {
return token.type === 'tag';
};
return TagToken;
}(DelimitedToken));
var HTMLToken = /** @class */ (function (_super) {
__extends(HTMLToken, _super);
function HTMLToken(str, input, line, col, file) {
var _this = _super.call(this, str, input, line, col, file) || this;
_this.type = 'html';
_this.value = str;
return _this;
}
HTMLToken.is = function (token) {
return token.type === 'html';
};
return HTMLToken;
}(Token));
function whiteSpaceCtrl(tokens, options) {
options = __assign({ greedy: true }, options);
var inRaw = false;
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (!inRaw && token.trimLeft) {
trimLeft(tokens[i - 1], options.greedy);
}
if (TagToken.is(token)) {
if (token.name === 'raw')
inRaw = true;
else if (token.name === 'endraw')
inRaw = false;
}
if (!inRaw && token.trimRight) {
trimRight(tokens[i + 1], options.greedy);
}
}
}
function trimLeft(token, greedy) {
if (!token || !HTMLToken.is(token))
return;
var rLeft = greedy ? /\s+$/g : /[\t\r ]*$/g;
token.value = token.value.replace(rLeft, '');
}
function trimRight(token, greedy) {
if (!token || !HTMLToken.is(token))
return;
var rRight = greedy ? /^\s+/g : /^[\t\r ]*\n?/g;
token.value = token.value.replace(rRight, '');
}
var OutputToken = /** @class */ (function (_super) {
__extends(OutputToken, _super);
function OutputToken(raw, value, input, line, pos, options, file) {
var _this = _super.call(this, raw, value, input, line, pos, options.trimOutputLeft, options.trimOutputRight, file) || this;
_this.type = 'output';
return _this;
}
OutputToken.is = function (token) {
return token.type === 'output';
};
return OutputToken;
}(DelimitedToken));
var ParseState;
(function (ParseState) {
ParseState[ParseState["HTML"] = 0] = "HTML";
ParseState[ParseState["OUTPUT"] = 1] = "OUTPUT";
ParseState[ParseState["TAG"] = 2] = "TAG";
ParseState[ParseState["ATTRIBUTE"] = 3] = "ATTRIBUTE";
ParseState[ParseState["CONTENT_BLOCKS"] = 4] = "CONTENT_BLOCKS";
})(ParseState || (ParseState = {}));
var Tokenizer = /** @class */ (function () {
function Tokenizer(options) {
this.options = applyDefault(options);
}
Tokenizer.prototype.tokenize = function (input, file) {
var tokens = [];
var _a = this.options, tagDelimiterLeft = _a.tagDelimiterLeft, tagDelimiterRight = _a.tagDelimiterRight, outputDelimiterLeft = _a.outputDelimiterLeft, outputDelimiterRight = _a.outputDelimiterRight;
var attributeLeft = '${';
var attributeRight = '}';
var contentBlocksTag = 'content_blocks';
var p = 0;
var curLine = 1;
var state = ParseState.HTML;
var buffer = '';
var lineBegin = 0;
var line = 1;
var col = 1;
var originalState = ParseState.HTML;
while (p < input.length) {
if (input[p] === '\n') {
curLine++;
lineBegin = p + 1;
}
if (state === ParseState.HTML) {
if (input.substr(p, outputDelimiterLeft.length) === outputDelimiterLeft) {
if (buffer)
tokens.push(new HTMLToken(buffer, input, line, col, file));
line = curLine;
col = p - lineBegin + 1;
buffer = outputDelimiterLeft;
p += outputDelimiterLeft.length;
// handle content blocks
while (input.substr(p, 1) === ' ') {
buffer += ' ';
p += 1;
}
if (input.substr(p, contentBlocksTag.length) === contentBlocksTag) {
buffer += contentBlocksTag;
p += contentBlocksTag.length;
state = ParseState.CONTENT_BLOCKS;
}
else {
state = ParseState.OUTPUT;
}
continue;
}
else if (input.substr(p, tagDelimiterLeft.length) === tagDelimiterLeft) {
if (buffer)
tokens.push(new HTMLToken(buffer, input, line, col, file));
buffer = tagDelimiterLeft;
line = curLine;
col = p - lineBegin + 1;
p += tagDelimiterLeft.length;
state = ParseState.TAG;
continue;
}
}
else if ((state === ParseState.OUTPUT || state === ParseState.CONTENT_BLOCKS) &&
input.substr(p, attributeLeft.length) === attributeLeft) {
originalState = state;
buffer += attributeLeft;
line = curLine;
col = p - lineBegin + 1;
p += attributeLeft.length;
state = ParseState.ATTRIBUTE;
continue;
}
else if (state === ParseState.ATTRIBUTE && input[p] === attributeRight) {
buffer += attributeRight;
line = curLine;
col = p - lineBegin + 1;
p += attributeRight.length;
state = originalState;
originalState = ParseState.HTML;
continue;
}
else if ((state === ParseState.OUTPUT || state === ParseState.CONTENT_BLOCKS) &&
input.substr(p, outputDelimiterRight.length) === outputDelimiterRight) {
buffer += outputDelimiterRight;
var TokenType = state === ParseState.OUTPUT ? OutputToken : TagToken;
tokens.push(new TokenType(buffer, buffer.slice(outputDelimiterLeft.length, -outputDelimiterRight.length), input, line, col, this.options, file));
p += outputDelimiterRight.length;
buffer = '';
line = curLine;
col = p - lineBegin + 1;
state = ParseState.HTML;
continue;
}
else if (input.substr(p, tagDelimiterRight.length) === tagDelimiterRight) {
buffer += tagDelimiterRight;
tokens.push(new TagToken(buffer, buffer.slice(tagDelimiterLeft.length, -tagDelimiterRight.length), input, line, col, this.options, file));
p += tagDelimiterRight.length;
buffer = '';
line = curLine;
col = p - lineBegin + 1;
state = ParseState.HTML;
continue;
}
buffer += input[p++];
}
if (state !== ParseState.HTML) {
var t = state === ParseState.OUTPUT ? 'output' : 'tag';
var str = buffer.length > 16 ? buffer.slice(0, 13) + '...' : buffer;
throw new TokenizationError(t + " \"" + str + "\" not closed", new Token(buffer, input, line, col, file));
}
if (buffer)
tokens.push(new HTMLToken(buffer, input, line, col, file));
whiteSpaceCtrl(tokens, this.options);
return tokens;
};
return Tokenizer;
}());
var Render = /** @class */ (function () {
function Render() {
}
Render.prototype.renderTemplates = function (templates, ctx) {
return __awaiter(this, void 0, void 0, function () {
var html, _i, templates_1, tpl, _a, e_1;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
assert(ctx, 'unable to evalTemplates: context undefined');
html = '';
_i = 0, templates_1 = templates;
_b.label = 1;
case 1:
if (!(_i < templates_1.length)) return [3 /*break*/, 6];
tpl = templates_1[_i];
_b.label = 2;
case 2:
_b.trys.push([2, 4, , 5]);
_a = html;
return [4 /*yield*/, tpl.render(ctx)];
case 3:
html = _a + _b.sent();
return [3 /*break*/, 5];
case 4:
e_1 = _b.sent();
if (e_1.name === 'AbortError') {
e_1.resolvedHTML = '';
throw e_1;
}
if (e_1.name === 'RenderBreakError') {
e_1.resolvedHTML = html;
throw e_1;
}
throw e_1.name === 'RenderError' ? e_1 : new RenderError(e_1, tpl);
case 5:
_i++;
return [3 /*break*/, 1];
case 6: return [2 /*return*/, html];
}
});
});
};
return Render;
}());
function isComparable(arg) {
return arg && isFunction(arg.equals);
}
var EmptyDrop = /** @class */ (function (_super) {
__extends(EmptyDrop, _super);
function EmptyDrop() {
return _super !== null && _super.apply(this, arguments) || this;
}
EmptyDrop.prototype.equals = function (value) {
if (isString(value) || isArray(value))
return value.length === 0;
if (isObject(value))
return Object.keys(value).length === 0;
return false;
};
EmptyDrop.prototype.gt = function () {
return false;
};
EmptyDrop.prototype.geq = function () {
return false;
};
EmptyDrop.prototype.lt = function () {
return false;
};
EmptyDrop.prototype.leq = function () {
return false;
};
EmptyDrop.prototype.valueOf = function () {
return '';
};
return EmptyDrop;
}(Drop));
var BlankDrop = /** @class */ (function (_super) {
__extends(BlankDrop, _super);
function BlankDrop() {
return _super !== null && _super.apply(this, arguments) || this;
}
BlankDrop.prototype.equals = function (value) {
if (value === false)
return true;
if (isNil(value instanceof Drop ? value.valueOf() : value))
return true;
if (isString(value))
return /^\s*$/.test(value);
return _super.prototype.equals.call(this, value);
};
return BlankDrop;
}(EmptyDrop));
var NullDrop = /** @class */ (function (_super) {
__extends(NullDrop, _super);
function NullDrop() {
return _super !== null && _super.apply(this, arguments) || this;
}
NullDrop.prototype.equals = function (value) {
return isNil(value instanceof Drop ? value.valueOf() : value) || value instanceof BlankDrop;
};
NullDrop.prototype.gt = function () {
return false;
};
NullDrop.prototype.geq = function () {
return false;
};
NullDrop.prototype.lt = function () {
return false;
};
NullDrop.prototype.leq = function () {
return false;
};
NullDrop.prototype.valueOf = function () {
return null;
};
return NullDrop;
}(Drop));
var binaryOperators = {
'==': function (l, r) {
if (isComparable(l))
return l.equals(r);
if (isComparable(r))
return r.equals(l);
return l === r;
},
'!=': function (l, r) {
if (isComparable(l))
return !l.equals(r);
if (isComparable(r))
return !r.equals(l);
return l !== r;
},
'>': function (l, r) {
if (isComparable(l))
return l.gt(r);
if (isComparable(r))
return r.lt(l);
return l > r;
},
'<': function (l, r) {
if (isComparable(l))
return l.lt(r);
if (isComparable(r))
return r.gt(l);
return l < r;
},
'>=': function (l, r) {
if (isComparable(l))
return l.geq(r);
if (isComparable(r))
return r.leq(l);
return l >= r;
},
'<=': function (l, r) {
if (isComparable(l))
return l.leq(r);
if (isComparable(r))
return r.geq(l);
return l <= r;
},
'contains': function (l, r) {
return l && isFunction(l.indexOf) ? l.indexOf(r) > -1 : false;
},
'and': function (l, r) { return isTruthy(l) && isTruthy(r); },
'or': function (l, r) { return isTruthy(l) || isTruthy(r); }
};
function parseExp(exp, ctx) {
return __awaiter(this, void 0, void 0, function () {
var operatorREs, match, i, operatorRE, expRE, l, op, r, low, high;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
assert(ctx, 'unable to parseExp: scope undefined');
operatorREs = operators;
i = 0;
_a.label = 1;
case 1:
if (!(i < operatorREs.length)) return [3 /*break*/, 5];
operatorRE = operatorREs[i];
expRE = new RegExp("^(" + quoteBalanced.source + ")(" + operatorRE.source + ")(" + quoteBalanced.source + ")$");
if (!(match = exp.match(expRE))) return [3 /*break*/, 4];
return [4 /*yield*/, parseExp(match[1], ctx)];
case 2:
l = _a.sent();
op = binaryOperators[match[2].trim()];
return [4 /*yield*/, parseExp(match[3], ctx)];
case 3:
r = _a.sent();
return [2 /*return*/, op(l, r)];
case 4:
i++;
return [3 /*break*/, 1];
case 5:
if (!(match = exp.match(rangeLine))) return [3 /*break*/, 8];
return [4 /*yield*/, evalValue(match[1], ctx)];
case 6:
low = _a.sent();
return [4 /*yield*/, evalValue(match[2], ctx)];
case 7:
high = _a.sent();
return [2 /*return*/, range(+low, +high + 1)];
case 8: return [2 /*return*/, parseValue(exp, ctx)];
}
});
});
}
function evalExp(str, ctx) {
return __awaiter(this, void 0, void 0, function () {
var value$$1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, parseExp(str, ctx)];
case 1:
value$$1 = _a.sent();
return [2 /*return*/, value$$1 instanceof Drop ? value$$1.valueOf() : value$$1];
}
});
});
}
function parseValue(str, ctx) {
return __awaiter(this, void 0, void 0, function () {
var re, match;
return __generator(this, function (_a) {
if (!str)
return [2 /*return*/, null];
str = str.trim();
if (str === 'true')
return [2 /*return*/, true];
if (str === 'false')
return [2 /*return*/, false];
if (str === 'nil' || str === 'null')
return [2 /*return*/, new NullDrop()];
if (str === 'empty')
return [2 /*return*/, new EmptyDrop()];
if (str === 'blank')
return [2 /*return*/, new BlankDrop()];
if (!isNaN(Number(str)))
return [2 /*return*/, Number(str)];
if ((str[0] === '"' || str[0] === "'") && str[0] === last(str))
return [2 /*return*/, str.slice(1, -1)
// for Braze, strip {{ and }}
];
re = new RegExp(escapeRegExp(ctx.opts.outputDelimiterLeft) + "\\s*(.*?)\\s*" + escapeRegExp(ctx.opts.outputDelimiterRight));
str = str.replace(re, '$1');
if ((match = str.match(attribute))) {
if (match[1])
return [2 /*return*/, ctx.get(match[1] + "['" + match[2] + "']")];
return [2 /*return*/, ctx.get(match[2])];
}
return [2 /*return*/, ctx.get(str)];
});
});
}
function evalValue(str, ctx) {
return __awaiter(this, void 0, void 0, function () {
var value$$1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, parseValue(str, ctx)];
case 1:
value$$1 = _a.sent();
return [2 /*return*/, value$$1 instanceof Drop ? value$$1.valueOf() : value$$1];
}
});
});
}
function isTruthy(val) {
return !isFalsy(val);
}
function isFalsy(val) {
return val === false || undefined === val || val === null;
}
/**
* Key-Value Pairs Representing Tag Arguments
* Example:
* For the markup `{% include 'head.html' foo='bar' %}`,
* hash['foo'] === 'bar'
*/
var Hash = /** @class */ (function () {
function Hash() {
}
Hash.create = function (markup, ctx) {
return __awaiter(this, void 0, void 0, function () {
var instance, match, k, v, _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
instance = new Hash();
hashCapture.lastIndex = 0;
_c.label = 1;
case 1:
if (!(match = hashCapture.exec(markup))) return [3 /*break*/, 3];
k = match[1];
v = match[2];
_a = instance;
_b = k;
return [4 /*yield*/, evalValue(v, ctx)];
case 2:
_a[_b] = _c.sent();
return [3 /*break*/, 1];
case 3: return [2 /*return*/, instance];
}
});
});
};
return Hash;
}());
var Template = /** @class */ (function () {
function Template(token) {
this.token = token;
}
return Template;
}());
var Tag = /** @class */ (function (_super) {
__extends(Tag, _super);
function Tag(token, tokens, liquid) {
var _this = _super.call(this, token) || this;
_this.name = token.name;
var impl = Tag.impls[token.name];
assert(impl, "tag " + token.name + " not found");
_this.impl = Object.create(impl);
_this.impl.liquid = liquid;
if (_this.impl.parse) {
_this.impl.parse(token, tokens);
}
return _this;
}
Tag.prototype.render = function (ctx) {
return __awaiter(this, void 0, void 0, function () {
var hash, impl, _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0: return [4 /*yield*/, Hash.create(this.token.args, ctx)];
case 1:
hash = _c.sent();
impl = this.impl;
if (!isFunction(impl.render)) return [3 /*break*/, 3];
_b = stringify;
return [4 /*yield*/, impl.render(ctx, hash)];
case 2:
_a = _b.apply(void 0, [_c.sent()]);
return [3 /*break*/, 4];
case 3:
_a = '';
_c.label = 4;
case 4: return [2 /*return*/, _a];
}
});
});
};
Tag.register = function (name, tag) {
Tag.impls[name] = tag;
};
Tag.clear = function () {
Tag.impls = {};
};
Tag.impls = {};
return Tag;
}(Template));
var Filter = /** @class */ (function () {
function Filter(name, args, strictFilters) {
var impl = Filter.impls[name];
if (!impl && strictFilters)
throw new TypeError("undefined filter: " + name);
this.name = name;
this.impl = impl || (function (x) { return x; });
this.args = args;
}
Filter.prototype.render = function (value, context) {
return __awaiter(this, void 0, void 0, function () {
var argv, _i, _a, arg, _b, _c, _d, _e, _f;
return __generator(this, function (_g) {
switch (_g.label) {
case 0:
argv = [];
_i = 0, _a = this.args;
_g.label = 1;
case 1:
if (!(_i < _a.length)) return [3 /*break*/, 6];
arg = _a[_i];
if (!isArray(arg)) return [3 /*break*/, 3];
_c = (_b = argv).push;
_d = [arg[0]];
return [4 /*yield*/, evalValue(arg[1], context)];
case 2:
_c.apply(_b, [_d.concat([_g.sent()])]);
return [3 /*break*/, 5];
case 3:
_f = (_e = argv).push;
return [4 /*yield*/, evalValue(arg, context)];
case 4:
_f.apply(_e, [_g.sent()]);
_g.label = 5;
case 5:
_i++;
return [3 /*break*/, 1];
case 6: return [2 /*return*/, this.impl.apply({ context: context }, [value].concat(argv))];
}
});
});
};
Filter.register = function (name, filter) {
Filter.impls[name] = filter;
};
Filter.clear = function () {
Filter.impls = {};
};
Filter.impls = {};
return Filter;
}());
var ParseStream = /** @class */ (function () {
function ParseStream(tokens, parseToken) {
this.handlers = {};
this.stopRequested = false;
this.tokens = tokens;
this.parseToken = parseToken;
}
ParseStream.prototype.on = function (name, cb) {
this.handlers[name] = cb;
return this;
};
ParseStream.prototype.trigger = function (event, arg) {
var h = this.handlers[event];
return h ? (h(arg), true) : false;
};
ParseStream.prototype.start = function () {
this.trigger('start');
var token;
while (!this.stopRequested && (token = this.tokens.shift())) {
if (this.trigger('token', token))
continue;
if (TagToken.is(token) && this.trigger("tag:" + token.name, token)) {
continue;
}
var template = this.parseToken(token, this.tokens);
this.trigger('template', template);
}
if (!this.stopRequested)
this.trigger('end');
return this;
};
ParseStream.prototype.stop = function () {
this.stopRequested = true;
return this;
};
return ParseStream;
}());
var Value = /** @class */ (function () {
/**
* @param str value string, like: "i have a dream | truncate: 3
*/
function Value(str, strictFilters) {
this.filters = [];
var tokens = Value.tokenize(str);
this.strictFilters = strictFilters;
this.initial = tokens[0];
this.parseFilters(tokens, 1);
}
Value.prototype.parseFilters = function (tokens, begin) {
var i = begin;
while (i < tokens.length) {
if (tokens[i] !== '|') {
i++;
continue;
}
var j = ++i;
while (i < tokens.length && tokens[i] !== '|')
i++;
this.parseFilter(tokens, j, i);
}
};
Value.prototype.parseFilter = function (tokens, begin, end) {
var name = tokens[begin];
var args = [];
var argName, argValue;
for (var i = begin + 1; i < end + 1; i++) {
if (i === end || tokens[i] === ',') {
if (argName || argValue) {
args.push(argName ? [argName, argValue] : argValue);
}
argValue = argName = undefined;
}
else if (tokens[i] === ':') {
argName = argValue;
argValue = undefined;
}
else if (argValue === undefined) {
argValue = tokens[i];
}
}
this.filters.push(new Filter(name, args, this.strictFilters));
};
Value.prototype.value = function (ctx) {
return __awaiter(this, void 0, void 0, function () {
var val, _i, _a, filter;
return __generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, evalExp(this.initial, ctx)];
case 1:
val = _b.sent();
_i = 0, _a = this.filters;
_b.label = 2;
case 2:
if (!(_i < _a.length)) return [3 /*break*/, 5];
filter = _a[_i];
return [4 /*yield*/, filter.render(val, ctx)];
case 3:
val = _b.sent();
_b.label = 4;
case 4:
_i++;
return [3 /*break*/, 2];
case 5: return [2 /*return*/, val];
}
});
});
};
Value.tokenize = function (str) {
var tokens = [];
var i = 0;
while (i < str.length) {
var ch = str[i];
if (ch === '"' || ch === "'") {
var j = i;
for (i += 2; i < str.length && str[i - 1] !== ch; ++i)
;
tokens.push(str.slice(j, i));
}
else if