UNPKG

115 kBJavaScriptView Raw
1/*
2 * liquidjs@9.40.0, https://github.com/harttle/liquidjs
3 * (c) 2016-2022 harttle
4 * Released under the MIT License.
5 */
6/*! *****************************************************************************
7Copyright (c) Microsoft Corporation.
8
9Permission to use, copy, modify, and/or distribute this software for any
10purpose with or without fee is hereby granted.
11
12THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
13REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
14AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
15INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
16LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
17OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
18PERFORMANCE OF THIS SOFTWARE.
19***************************************************************************** */
20
21var __assign = function() {
22 __assign = Object.assign || function __assign(t) {
23 for (var s, i = 1, n = arguments.length; i < n; i++) {
24 s = arguments[i];
25 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
26 }
27 return t;
28 };
29 return __assign.apply(this, arguments);
30};
31
32function __awaiter(thisArg, _arguments, P, generator) {
33 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
34 return new (P || (P = Promise))(function (resolve, reject) {
35 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
36 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
37 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
38 step((generator = generator.apply(thisArg, _arguments || [])).next());
39 });
40}
41
42class Drop {
43 liquidMethodMissing(key) {
44 return undefined;
45 }
46}
47
48const toString$1 = Object.prototype.toString;
49const toLowerCase = String.prototype.toLowerCase;
50const hasOwnProperty = Object.hasOwnProperty;
51function isString(value) {
52 return typeof value === 'string';
53}
54// eslint-disable-next-line @typescript-eslint/ban-types
55function isFunction(value) {
56 return typeof value === 'function';
57}
58function isPromise(val) {
59 return val && isFunction(val.then);
60}
61function isIterator(val) {
62 return val && isFunction(val.next) && isFunction(val.throw) && isFunction(val.return);
63}
64function escapeRegex(str) {
65 return str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
66}
67function stringify(value) {
68 value = toValue(value);
69 if (isString(value))
70 return value;
71 if (isNil(value))
72 return '';
73 if (isArray(value))
74 return value.map(x => stringify(x)).join('');
75 return String(value);
76}
77function toValue(value) {
78 return (value instanceof Drop && isFunction(value.valueOf)) ? value.valueOf() : value;
79}
80function isNumber(value) {
81 return typeof value === 'number';
82}
83function toLiquid(value) {
84 if (value && isFunction(value.toLiquid))
85 return toLiquid(value.toLiquid());
86 return value;
87}
88function isNil(value) {
89 return value == null;
90}
91function isArray(value) {
92 // be compatible with IE 8
93 return toString$1.call(value) === '[object Array]';
94}
95function isIterable(value) {
96 return isObject(value) && Symbol.iterator in value;
97}
98/*
99 * Iterates over own enumerable string keyed properties of an object and invokes iteratee for each property.
100 * The iteratee is invoked with three arguments: (value, key, object).
101 * Iteratee functions may exit iteration early by explicitly returning false.
102 * @param {Object} object The object to iterate over.
103 * @param {Function} iteratee The function invoked per iteration.
104 * @return {Object} Returns object.
105 */
106function forOwn(obj, iteratee) {
107 obj = obj || {};
108 for (const k in obj) {
109 if (hasOwnProperty.call(obj, k)) {
110 if (iteratee(obj[k], k, obj) === false)
111 break;
112 }
113 }
114 return obj;
115}
116function last$1(arr) {
117 return arr[arr.length - 1];
118}
119/*
120 * Checks if value is the language type of Object.
121 * (e.g. arrays, functions, objects, regexes, new Number(0), and new String(''))
122 * @param {any} value The value to check.
123 * @return {Boolean} Returns true if value is an object, else false.
124 */
125function isObject(value) {
126 const type = typeof value;
127 return value !== null && (type === 'object' || type === 'function');
128}
129function range(start, stop, step = 1) {
130 const arr = [];
131 for (let i = start; i < stop; i += step) {
132 arr.push(i);
133 }
134 return arr;
135}
136function padStart(str, length, ch = ' ') {
137 return pad(str, length, ch, (str, ch) => ch + str);
138}
139function padEnd(str, length, ch = ' ') {
140 return pad(str, length, ch, (str, ch) => str + ch);
141}
142function pad(str, length, ch, add) {
143 str = String(str);
144 let n = length - str.length;
145 while (n-- > 0)
146 str = add(str, ch);
147 return str;
148}
149function identify(val) {
150 return val;
151}
152function snakeCase(str) {
153 return str.replace(/(\w?)([A-Z])/g, (_, a, b) => (a ? a + '_' : '') + b.toLowerCase());
154}
155function changeCase(str) {
156 const hasLowerCase = [...str].some(ch => ch >= 'a' && ch <= 'z');
157 return hasLowerCase ? str.toUpperCase() : str.toLowerCase();
158}
159function ellipsis(str, N) {
160 return str.length > N ? str.substr(0, N - 3) + '...' : str;
161}
162// compare string in case-insensitive way, undefined values to the tail
163function caseInsensitiveCompare(a, b) {
164 if (a == null && b == null)
165 return 0;
166 if (a == null)
167 return 1;
168 if (b == null)
169 return -1;
170 a = toLowerCase.call(a);
171 b = toLowerCase.call(b);
172 if (a < b)
173 return -1;
174 if (a > b)
175 return 1;
176 return 0;
177}
178function argumentsToValue(fn) {
179 return (...args) => fn(...args.map(toValue));
180}
181function escapeRegExp(text) {
182 return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
183}
184
185class Node {
186 constructor(key, value, next, prev) {
187 this.key = key;
188 this.value = value;
189 this.next = next;
190 this.prev = prev;
191 }
192}
193class LRU {
194 constructor(limit, size = 0) {
195 this.limit = limit;
196 this.size = size;
197 this.cache = {};
198 this.head = new Node('HEAD', null, null, null);
199 this.tail = new Node('TAIL', null, null, null);
200 this.head.next = this.tail;
201 this.tail.prev = this.head;
202 }
203 write(key, value) {
204 if (this.cache[key]) {
205 this.cache[key].value = value;
206 }
207 else {
208 const node = new Node(key, value, this.head.next, this.head);
209 this.head.next.prev = node;
210 this.head.next = node;
211 this.cache[key] = node;
212 this.size++;
213 this.ensureLimit();
214 }
215 }
216 read(key) {
217 if (!this.cache[key])
218 return;
219 const { value } = this.cache[key];
220 this.remove(key);
221 this.write(key, value);
222 return value;
223 }
224 remove(key) {
225 const node = this.cache[key];
226 node.prev.next = node.next;
227 node.next.prev = node.prev;
228 delete this.cache[key];
229 this.size--;
230 }
231 clear() {
232 this.head.next = this.tail;
233 this.tail.prev = this.head;
234 this.size = 0;
235 this.cache = {};
236 }
237 ensureLimit() {
238 if (this.size > this.limit)
239 this.remove(this.tail.prev.key);
240 }
241}
242
243function domResolve(root, path) {
244 const base = document.createElement('base');
245 base.href = root;
246 const head = document.getElementsByTagName('head')[0];
247 head.insertBefore(base, head.firstChild);
248 const a = document.createElement('a');
249 a.href = path;
250 const resolved = a.href;
251 head.removeChild(base);
252 return resolved;
253}
254function resolve(root, filepath, ext) {
255 if (root.length && last$1(root) !== '/')
256 root += '/';
257 const url = domResolve(root, filepath);
258 return url.replace(/^(\w+:\/\/[^/]+)(\/[^?]+)/, (str, origin, path) => {
259 const last = path.split('/').pop();
260 if (/\.\w+$/.test(last))
261 return str;
262 return origin + path + ext;
263 });
264}
265function readFile(url) {
266 return __awaiter(this, void 0, void 0, function* () {
267 return new Promise((resolve, reject) => {
268 const xhr = new XMLHttpRequest();
269 xhr.onload = () => {
270 if (xhr.status >= 200 && xhr.status < 300) {
271 resolve(xhr.responseText);
272 }
273 else {
274 reject(new Error(xhr.statusText));
275 }
276 };
277 xhr.onerror = () => {
278 reject(new Error('An error occurred whilst receiving the response.'));
279 };
280 xhr.open('GET', url);
281 xhr.send();
282 });
283 });
284}
285function readFileSync(url) {
286 const xhr = new XMLHttpRequest();
287 xhr.open('GET', url, false);
288 xhr.send();
289 if (xhr.status < 200 || xhr.status >= 300) {
290 throw new Error(xhr.statusText);
291 }
292 return xhr.responseText;
293}
294function exists(filepath) {
295 return __awaiter(this, void 0, void 0, function* () {
296 return true;
297 });
298}
299function existsSync(filepath) {
300 return true;
301}
302function dirname(filepath) {
303 return domResolve(filepath, '.');
304}
305const sep = '/';
306
307var fs = /*#__PURE__*/Object.freeze({
308 __proto__: null,
309 resolve: resolve,
310 readFile: readFile,
311 readFileSync: readFileSync,
312 exists: exists,
313 existsSync: existsSync,
314 dirname: dirname,
315 sep: sep
316});
317
318function isComparable(arg) {
319 return arg && isFunction(arg.equals);
320}
321
322function isTruthy(val, ctx) {
323 return !isFalsy(val, ctx);
324}
325function isFalsy(val, ctx) {
326 if (ctx.opts.jsTruthy) {
327 return !val;
328 }
329 else {
330 return val === false || undefined === val || val === null;
331 }
332}
333
334const defaultOperators = {
335 '==': (l, r) => {
336 if (isComparable(l))
337 return l.equals(r);
338 if (isComparable(r))
339 return r.equals(l);
340 return toValue(l) === toValue(r);
341 },
342 '!=': (l, r) => {
343 if (isComparable(l))
344 return !l.equals(r);
345 if (isComparable(r))
346 return !r.equals(l);
347 return toValue(l) !== toValue(r);
348 },
349 '>': (l, r) => {
350 if (isComparable(l))
351 return l.gt(r);
352 if (isComparable(r))
353 return r.lt(l);
354 return toValue(l) > toValue(r);
355 },
356 '<': (l, r) => {
357 if (isComparable(l))
358 return l.lt(r);
359 if (isComparable(r))
360 return r.gt(l);
361 return toValue(l) < toValue(r);
362 },
363 '>=': (l, r) => {
364 if (isComparable(l))
365 return l.geq(r);
366 if (isComparable(r))
367 return r.leq(l);
368 return toValue(l) >= toValue(r);
369 },
370 '<=': (l, r) => {
371 if (isComparable(l))
372 return l.leq(r);
373 if (isComparable(r))
374 return r.geq(l);
375 return toValue(l) <= toValue(r);
376 },
377 'contains': (l, r) => {
378 l = toValue(l);
379 r = toValue(r);
380 return l && isFunction(l.indexOf) ? l.indexOf(r) > -1 : false;
381 },
382 'and': (l, r, ctx) => isTruthy(toValue(l), ctx) && isTruthy(toValue(r), ctx),
383 'or': (l, r, ctx) => isTruthy(toValue(l), ctx) || isTruthy(toValue(r), ctx)
384};
385
386// **DO NOT CHANGE THIS FILE**
387//
388// This file is generated by bin/character-gen.js
389// bitmask character types to boost performance
390const TYPES = [0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 4, 4, 4, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 2, 8, 0, 0, 0, 0, 8, 0, 0, 0, 64, 0, 65, 0, 0, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 0, 0, 2, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0];
391const IDENTIFIER = 1;
392const BLANK = 4;
393const QUOTE = 8;
394const INLINE_BLANK = 16;
395const NUMBER = 32;
396const SIGN = 64;
397TYPES[160] = TYPES[5760] = TYPES[6158] = TYPES[8192] = TYPES[8193] = TYPES[8194] = TYPES[8195] = TYPES[8196] = TYPES[8197] = TYPES[8198] = TYPES[8199] = TYPES[8200] = TYPES[8201] = TYPES[8202] = TYPES[8232] = TYPES[8233] = TYPES[8239] = TYPES[8287] = TYPES[12288] = BLANK;
398
399function createTrie(operators) {
400 const trie = {};
401 for (const [name, handler] of Object.entries(operators)) {
402 let node = trie;
403 for (let i = 0; i < name.length; i++) {
404 const c = name[i];
405 node[c] = node[c] || {};
406 if (i === name.length - 1 && (TYPES[name.charCodeAt(i)] & IDENTIFIER)) {
407 node[c].needBoundary = true;
408 }
409 node = node[c];
410 }
411 node.handler = handler;
412 node.end = true;
413 }
414 return trie;
415}
416
417const escapeMap = {
418 '&': '&amp;',
419 '<': '&lt;',
420 '>': '&gt;',
421 '"': '&#34;',
422 "'": '&#39;'
423};
424const unescapeMap = {
425 '&amp;': '&',
426 '&lt;': '<',
427 '&gt;': '>',
428 '&#34;': '"',
429 '&#39;': "'"
430};
431function escape(str) {
432 return stringify(str).replace(/&|<|>|"|'/g, m => escapeMap[m]);
433}
434function unescape(str) {
435 return stringify(str).replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m]);
436}
437function escapeOnce(str) {
438 return escape(unescape(stringify(str)));
439}
440function newlineToBr(v) {
441 return stringify(v).replace(/\n/g, '<br />\n');
442}
443function stripHtml(v) {
444 return stringify(v).replace(/<script.*?<\/script>|<!--.*?-->|<style.*?<\/style>|<.*?>/g, '');
445}
446
447const abs = argumentsToValue(Math.abs);
448const atLeast = argumentsToValue(Math.max);
449const atMost = argumentsToValue(Math.min);
450const ceil = argumentsToValue(Math.ceil);
451const dividedBy = argumentsToValue((dividend, divisor, integerArithmetic = false) => integerArithmetic ? Math.floor(dividend / divisor) : dividend / divisor);
452const floor = argumentsToValue(Math.floor);
453const minus = argumentsToValue((v, arg) => v - arg);
454const modulo = argumentsToValue((v, arg) => v % arg);
455const times = argumentsToValue((v, arg) => v * arg);
456function round(v, arg = 0) {
457 v = toValue(v);
458 arg = toValue(arg);
459 const amp = Math.pow(10, arg);
460 return Math.round(v * amp) / amp;
461}
462function plus(v, arg) {
463 v = toValue(v);
464 arg = toValue(arg);
465 return Number(v) + Number(arg);
466}
467
468const urlDecode = (x) => stringify(x).split('+').map(decodeURIComponent).join(' ');
469const urlEncode = (x) => stringify(x).split(' ').map(encodeURIComponent).join('+');
470
471function toEnumerable(val) {
472 val = toValue(val);
473 if (isArray(val))
474 return val;
475 if (isString(val) && val.length > 0)
476 return [val];
477 if (isIterable(val))
478 return Array.from(val);
479 if (isObject(val))
480 return Object.keys(val).map((key) => [key, val[key]]);
481 return [];
482}
483function toArray(val) {
484 if (isNil(val))
485 return [];
486 if (isArray(val))
487 return val;
488 return [val];
489}
490
491const join = argumentsToValue((v, arg) => toArray(v).join(arg === undefined ? ' ' : arg));
492const last = argumentsToValue((v) => isArray(v) ? last$1(v) : '');
493const first = argumentsToValue((v) => isArray(v) ? v[0] : '');
494const reverse = argumentsToValue((v) => [...toArray(v)].reverse());
495function sort(arr, property) {
496 arr = toValue(arr);
497 const getValue = (obj) => property ? this.context.getFromScope(obj, stringify(property).split('.')) : obj;
498 return [...toArray(arr)].sort((lhs, rhs) => {
499 lhs = getValue(lhs);
500 rhs = getValue(rhs);
501 return lhs < rhs ? -1 : (lhs > rhs ? 1 : 0);
502 });
503}
504function sortNatural(input, property) {
505 input = toValue(input);
506 const propertyString = stringify(property);
507 const compare = property === undefined
508 ? caseInsensitiveCompare
509 : (lhs, rhs) => caseInsensitiveCompare(lhs[propertyString], rhs[propertyString]);
510 return [...toArray(input)].sort(compare);
511}
512const size = (v) => (v && v.length) || 0;
513function map(arr, property) {
514 arr = toValue(arr);
515 return toArray(arr).map(obj => this.context.getFromScope(obj, stringify(property).split('.')));
516}
517function compact(arr) {
518 arr = toValue(arr);
519 return toArray(arr).filter(x => !isNil(toValue(x)));
520}
521function concat(v, arg = []) {
522 v = toValue(v);
523 arg = toArray(arg).map(v => toValue(v));
524 return toArray(v).concat(arg);
525}
526function slice(v, begin, length = 1) {
527 v = toValue(v);
528 if (isNil(v))
529 return [];
530 if (!isArray(v))
531 v = stringify(v);
532 begin = begin < 0 ? v.length + begin : begin;
533 return v.slice(begin, begin + length);
534}
535function where(arr, property, expected) {
536 arr = toValue(arr);
537 return toArray(arr).filter(obj => {
538 const value = this.context.getFromScope(obj, stringify(property).split('.'));
539 if (expected === undefined)
540 return isTruthy(value, this.context);
541 if (isComparable(expected))
542 return expected.equals(value);
543 return value === expected;
544 });
545}
546function uniq(arr) {
547 arr = toValue(arr);
548 const u = {};
549 return (arr || []).filter(val => {
550 if (hasOwnProperty.call(u, String(val)))
551 return false;
552 u[String(val)] = true;
553 return true;
554 });
555}
556
557const rFormat = /%([-_0^#:]+)?(\d+)?([EO])?(.)/;
558const monthNames = [
559 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
560 'September', 'October', 'November', 'December'
561];
562const dayNames = [
563 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
564];
565const monthNamesShort = monthNames.map(abbr);
566const dayNamesShort = dayNames.map(abbr);
567const suffixes = {
568 1: 'st',
569 2: 'nd',
570 3: 'rd',
571 'default': 'th'
572};
573function abbr(str) {
574 return str.slice(0, 3);
575}
576// prototype extensions
577function daysInMonth(d) {
578 const feb = isLeapYear(d) ? 29 : 28;
579 return [31, feb, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
580}
581function getDayOfYear(d) {
582 let num = 0;
583 for (let i = 0; i < d.getMonth(); ++i) {
584 num += daysInMonth(d)[i];
585 }
586 return num + d.getDate();
587}
588function getWeekOfYear(d, startDay) {
589 // Skip to startDay of this week
590 const now = getDayOfYear(d) + (startDay - d.getDay());
591 // Find the first startDay of the year
592 const jan1 = new Date(d.getFullYear(), 0, 1);
593 const then = (7 - jan1.getDay() + startDay);
594 return String(Math.floor((now - then) / 7) + 1);
595}
596function isLeapYear(d) {
597 const year = d.getFullYear();
598 return !!((year & 3) === 0 && (year % 100 || (year % 400 === 0 && year)));
599}
600function getSuffix(d) {
601 const str = d.getDate().toString();
602 const index = parseInt(str.slice(-1));
603 return suffixes[index] || suffixes['default'];
604}
605function century(d) {
606 return parseInt(d.getFullYear().toString().substring(0, 2), 10);
607}
608// default to 0
609const padWidths = {
610 d: 2,
611 e: 2,
612 H: 2,
613 I: 2,
614 j: 3,
615 k: 2,
616 l: 2,
617 L: 3,
618 m: 2,
619 M: 2,
620 S: 2,
621 U: 2,
622 W: 2
623};
624// default to '0'
625const padChars = {
626 a: ' ',
627 A: ' ',
628 b: ' ',
629 B: ' ',
630 c: ' ',
631 e: ' ',
632 k: ' ',
633 l: ' ',
634 p: ' ',
635 P: ' '
636};
637const formatCodes = {
638 a: (d) => dayNamesShort[d.getDay()],
639 A: (d) => dayNames[d.getDay()],
640 b: (d) => monthNamesShort[d.getMonth()],
641 B: (d) => monthNames[d.getMonth()],
642 c: (d) => d.toLocaleString(),
643 C: (d) => century(d),
644 d: (d) => d.getDate(),
645 e: (d) => d.getDate(),
646 H: (d) => d.getHours(),
647 I: (d) => String(d.getHours() % 12 || 12),
648 j: (d) => getDayOfYear(d),
649 k: (d) => d.getHours(),
650 l: (d) => String(d.getHours() % 12 || 12),
651 L: (d) => d.getMilliseconds(),
652 m: (d) => d.getMonth() + 1,
653 M: (d) => d.getMinutes(),
654 N: (d, opts) => {
655 const width = Number(opts.width) || 9;
656 const str = String(d.getMilliseconds()).substr(0, width);
657 return padEnd(str, width, '0');
658 },
659 p: (d) => (d.getHours() < 12 ? 'AM' : 'PM'),
660 P: (d) => (d.getHours() < 12 ? 'am' : 'pm'),
661 q: (d) => getSuffix(d),
662 s: (d) => Math.round(d.getTime() / 1000),
663 S: (d) => d.getSeconds(),
664 u: (d) => d.getDay() || 7,
665 U: (d) => getWeekOfYear(d, 0),
666 w: (d) => d.getDay(),
667 W: (d) => getWeekOfYear(d, 1),
668 x: (d) => d.toLocaleDateString(),
669 X: (d) => d.toLocaleTimeString(),
670 y: (d) => d.getFullYear().toString().substring(2, 4),
671 Y: (d) => d.getFullYear(),
672 z: (d, opts) => {
673 const nOffset = Math.abs(d.getTimezoneOffset());
674 const h = Math.floor(nOffset / 60);
675 const m = nOffset % 60;
676 return (d.getTimezoneOffset() > 0 ? '-' : '+') +
677 padStart(h, 2, '0') +
678 (opts.flags[':'] ? ':' : '') +
679 padStart(m, 2, '0');
680 },
681 't': () => '\t',
682 'n': () => '\n',
683 '%': () => '%'
684};
685formatCodes.h = formatCodes.b;
686function strftime (d, formatStr) {
687 let output = '';
688 let remaining = formatStr;
689 let match;
690 while ((match = rFormat.exec(remaining))) {
691 output += remaining.slice(0, match.index);
692 remaining = remaining.slice(match.index + match[0].length);
693 output += format(d, match);
694 }
695 return output + remaining;
696}
697function format(d, match) {
698 const [input, flagStr = '', width, modifier, conversion] = match;
699 const convert = formatCodes[conversion];
700 if (!convert)
701 return input;
702 const flags = {};
703 for (const flag of flagStr)
704 flags[flag] = true;
705 let ret = String(convert(d, { flags, width, modifier }));
706 let padChar = padChars[conversion] || '0';
707 let padWidth = width || padWidths[conversion] || 0;
708 if (flags['^'])
709 ret = ret.toUpperCase();
710 else if (flags['#'])
711 ret = changeCase(ret);
712 if (flags['_'])
713 padChar = ' ';
714 else if (flags['0'])
715 padChar = '0';
716 if (flags['-'])
717 padWidth = 0;
718 return padStart(ret, padWidth, padChar);
719}
720
721// one minute in milliseconds
722const OneMinute = 60000;
723const hostTimezoneOffset = new Date().getTimezoneOffset();
724const ISO8601_TIMEZONE_PATTERN = /([zZ]|([+-])(\d{2}):(\d{2}))$/;
725/**
726 * A date implementation with timezone info, just like Ruby date
727 *
728 * Implementation:
729 * - create a Date offset by it's timezone difference, avoiding overriding a bunch of methods
730 * - rewrite getTimezoneOffset() to trick strftime
731 */
732class TimezoneDate {
733 constructor(init, timezoneOffset) {
734 if (init instanceof TimezoneDate) {
735 this.date = init.date;
736 timezoneOffset = init.timezoneOffset;
737 }
738 else {
739 const diff = (hostTimezoneOffset - timezoneOffset) * OneMinute;
740 const time = new Date(init).getTime() + diff;
741 this.date = new Date(time);
742 }
743 this.timezoneOffset = timezoneOffset;
744 }
745 getTime() {
746 return this.date.getTime();
747 }
748 getMilliseconds() {
749 return this.date.getMilliseconds();
750 }
751 getSeconds() {
752 return this.date.getSeconds();
753 }
754 getMinutes() {
755 return this.date.getMinutes();
756 }
757 getHours() {
758 return this.date.getHours();
759 }
760 getDay() {
761 return this.date.getDay();
762 }
763 getDate() {
764 return this.date.getDate();
765 }
766 getMonth() {
767 return this.date.getMonth();
768 }
769 getFullYear() {
770 return this.date.getFullYear();
771 }
772 toLocaleTimeString(locale) {
773 return this.date.toLocaleTimeString(locale);
774 }
775 toLocaleDateString(locale) {
776 return this.date.toLocaleDateString(locale);
777 }
778 getTimezoneOffset() {
779 return this.timezoneOffset;
780 }
781 /**
782 * Create a Date object fixed to it's declared Timezone. Both
783 * - 2021-08-06T02:29:00.000Z and
784 * - 2021-08-06T02:29:00.000+08:00
785 * will always be displayed as
786 * - 2021-08-06 02:29:00
787 * regardless timezoneOffset in JavaScript realm
788 *
789 * The implementation hack:
790 * Instead of calling `.getMonth()`/`.getUTCMonth()` respect to `preserveTimezones`,
791 * we create a different Date to trick strftime, it's both simpler and more performant.
792 * Given that a template is expected to be parsed fewer times than rendered.
793 */
794 static createDateFixedToTimezone(dateString) {
795 const m = dateString.match(ISO8601_TIMEZONE_PATTERN);
796 // representing a UTC timestamp
797 if (m && m[1] === 'Z') {
798 return new TimezoneDate(+new Date(dateString), 0);
799 }
800 // has a timezone specified
801 if (m && m[2] && m[3] && m[4]) {
802 const [, , sign, hours, minutes] = m;
803 const delta = (sign === '+' ? -1 : 1) * (parseInt(hours, 10) * 60 + parseInt(minutes, 10));
804 return new TimezoneDate(+new Date(dateString), delta);
805 }
806 return new Date(dateString);
807 }
808}
809
810function date(v, arg) {
811 const opts = this.context.opts;
812 let date;
813 v = toValue(v);
814 arg = stringify(arg);
815 if (v === 'now' || v === 'today') {
816 date = new Date();
817 }
818 else if (isNumber(v)) {
819 date = new Date(v * 1000);
820 }
821 else if (isString(v)) {
822 if (/^\d+$/.test(v)) {
823 date = new Date(+v * 1000);
824 }
825 else if (opts.preserveTimezones) {
826 date = TimezoneDate.createDateFixedToTimezone(v);
827 }
828 else {
829 date = new Date(v);
830 }
831 }
832 else {
833 date = v;
834 }
835 if (!isValidDate(date))
836 return v;
837 if (opts.hasOwnProperty('timezoneOffset')) {
838 date = new TimezoneDate(date, opts.timezoneOffset);
839 }
840 return strftime(date, arg);
841}
842function isValidDate(date) {
843 return (date instanceof Date || date instanceof TimezoneDate) && !isNaN(date.getTime());
844}
845
846function Default(value, defaultValue, ...args) {
847 value = toValue(value);
848 if (isArray(value) || isString(value))
849 return value.length ? value : defaultValue;
850 if (value === false && (new Map(args)).get('allow_false'))
851 return false;
852 return isFalsy(value, this.context) ? defaultValue : value;
853}
854function json(value) {
855 return JSON.stringify(value);
856}
857const raw$1 = identify;
858
859class LiquidError extends Error {
860 constructor(err, token) {
861 super(err.message);
862 this.originalError = err;
863 this.token = token;
864 this.context = '';
865 }
866 update() {
867 const err = this.originalError;
868 this.context = mkContext(this.token);
869 this.message = mkMessage(err.message, this.token);
870 this.stack = this.message + '\n' + this.context +
871 '\n' + this.stack + '\nFrom ' + err.stack;
872 }
873}
874class TokenizationError extends LiquidError {
875 constructor(message, token) {
876 super(new Error(message), token);
877 this.name = 'TokenizationError';
878 super.update();
879 }
880}
881class ParseError extends LiquidError {
882 constructor(err, token) {
883 super(err, token);
884 this.name = 'ParseError';
885 this.message = err.message;
886 super.update();
887 }
888}
889class RenderError extends LiquidError {
890 constructor(err, tpl) {
891 super(err, tpl.token);
892 this.name = 'RenderError';
893 this.message = err.message;
894 super.update();
895 }
896 static is(obj) {
897 return obj.name === 'RenderError';
898 }
899}
900class UndefinedVariableError extends LiquidError {
901 constructor(err, token) {
902 super(err, token);
903 this.name = 'UndefinedVariableError';
904 this.message = err.message;
905 super.update();
906 }
907}
908// only used internally; raised where we don't have token information,
909// so it can't be an UndefinedVariableError.
910class InternalUndefinedVariableError extends Error {
911 constructor(variableName) {
912 super(`undefined variable: ${variableName}`);
913 this.name = 'InternalUndefinedVariableError';
914 this.variableName = variableName;
915 }
916}
917class AssertionError extends Error {
918 constructor(message) {
919 super(message);
920 this.name = 'AssertionError';
921 this.message = message + '';
922 }
923}
924function mkContext(token) {
925 const [line] = token.getPosition();
926 const lines = token.input.split('\n');
927 const begin = Math.max(line - 2, 1);
928 const end = Math.min(line + 3, lines.length);
929 const context = range(begin, end + 1)
930 .map(lineNumber => {
931 const indicator = (lineNumber === line) ? '>> ' : ' ';
932 const num = padStart(String(lineNumber), String(end).length);
933 const text = lines[lineNumber - 1];
934 return `${indicator}${num}| ${text}`;
935 })
936 .join('\n');
937 return context;
938}
939function mkMessage(msg, token) {
940 if (token.file)
941 msg += `, file:${token.file}`;
942 const [line, col] = token.getPosition();
943 msg += `, line:${line}, col:${col}`;
944 return msg;
945}
946
947function assert(predicate, message) {
948 if (!predicate) {
949 const msg = typeof message === 'function'
950 ? message()
951 : (message || `expect ${predicate} to be true`);
952 throw new AssertionError(msg);
953 }
954}
955
956/**
957 * String related filters
958 *
959 * * prefer stringify() to String() since `undefined`, `null` should eval ''
960 */
961function append(v, arg) {
962 assert(arguments.length === 2, 'append expect 2 arguments');
963 return stringify(v) + stringify(arg);
964}
965function prepend(v, arg) {
966 assert(arguments.length === 2, 'prepend expect 2 arguments');
967 return stringify(arg) + stringify(v);
968}
969function lstrip(v, chars) {
970 if (chars) {
971 chars = escapeRegExp(stringify(chars));
972 return stringify(v).replace(new RegExp(`^[${chars}]+`, 'g'), '');
973 }
974 return stringify(v).replace(/^\s+/, '');
975}
976function downcase(v) {
977 return stringify(v).toLowerCase();
978}
979function upcase(str) {
980 return stringify(str).toUpperCase();
981}
982function remove(v, arg) {
983 return stringify(v).split(String(arg)).join('');
984}
985function removeFirst(v, l) {
986 return stringify(v).replace(String(l), '');
987}
988function rstrip(str, chars) {
989 if (chars) {
990 chars = escapeRegExp(stringify(chars));
991 return stringify(str).replace(new RegExp(`[${chars}]+$`, 'g'), '');
992 }
993 return stringify(str).replace(/\s+$/, '');
994}
995function split(v, arg) {
996 const arr = stringify(v).split(String(arg));
997 // align to ruby split, which is the behavior of shopify/liquid
998 // see: https://ruby-doc.org/core-2.4.0/String.html#method-i-split
999 while (arr.length && arr[arr.length - 1] === '')
1000 arr.pop();
1001 return arr;
1002}
1003function strip(v, chars) {
1004 if (chars) {
1005 chars = escapeRegExp(stringify(chars));
1006 return stringify(v)
1007 .replace(new RegExp(`^[${chars}]+`, 'g'), '')
1008 .replace(new RegExp(`[${chars}]+$`, 'g'), '');
1009 }
1010 return stringify(v).trim();
1011}
1012function stripNewlines(v) {
1013 return stringify(v).replace(/\n/g, '');
1014}
1015function capitalize(str) {
1016 str = stringify(str);
1017 return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
1018}
1019function replace(v, pattern, replacement) {
1020 return stringify(v).split(String(pattern)).join(replacement);
1021}
1022function replaceFirst(v, arg1, arg2) {
1023 return stringify(v).replace(String(arg1), arg2);
1024}
1025function truncate(v, l = 50, o = '...') {
1026 v = stringify(v);
1027 if (v.length <= l)
1028 return v;
1029 return v.substring(0, l - o.length) + o;
1030}
1031function truncatewords(v, l = 15, o = '...') {
1032 const arr = stringify(v).split(/\s+/);
1033 let ret = arr.slice(0, l).join(' ');
1034 if (arr.length >= l)
1035 ret += o;
1036 return ret;
1037}
1038
1039var builtinFilters = /*#__PURE__*/Object.freeze({
1040 __proto__: null,
1041 escape: escape,
1042 escapeOnce: escapeOnce,
1043 newlineToBr: newlineToBr,
1044 stripHtml: stripHtml,
1045 abs: abs,
1046 atLeast: atLeast,
1047 atMost: atMost,
1048 ceil: ceil,
1049 dividedBy: dividedBy,
1050 floor: floor,
1051 minus: minus,
1052 modulo: modulo,
1053 times: times,
1054 round: round,
1055 plus: plus,
1056 urlDecode: urlDecode,
1057 urlEncode: urlEncode,
1058 join: join,
1059 last: last,
1060 first: first,
1061 reverse: reverse,
1062 sort: sort,
1063 sortNatural: sortNatural,
1064 size: size,
1065 map: map,
1066 compact: compact,
1067 concat: concat,
1068 slice: slice,
1069 where: where,
1070 uniq: uniq,
1071 date: date,
1072 Default: Default,
1073 json: json,
1074 raw: raw$1,
1075 append: append,
1076 prepend: prepend,
1077 lstrip: lstrip,
1078 downcase: downcase,
1079 upcase: upcase,
1080 remove: remove,
1081 removeFirst: removeFirst,
1082 rstrip: rstrip,
1083 split: split,
1084 strip: strip,
1085 stripNewlines: stripNewlines,
1086 capitalize: capitalize,
1087 replace: replace,
1088 replaceFirst: replaceFirst,
1089 truncate: truncate,
1090 truncatewords: truncatewords
1091});
1092
1093var TokenKind;
1094(function (TokenKind) {
1095 TokenKind[TokenKind["Number"] = 1] = "Number";
1096 TokenKind[TokenKind["Literal"] = 2] = "Literal";
1097 TokenKind[TokenKind["Tag"] = 4] = "Tag";
1098 TokenKind[TokenKind["Output"] = 8] = "Output";
1099 TokenKind[TokenKind["HTML"] = 16] = "HTML";
1100 TokenKind[TokenKind["Filter"] = 32] = "Filter";
1101 TokenKind[TokenKind["Hash"] = 64] = "Hash";
1102 TokenKind[TokenKind["PropertyAccess"] = 128] = "PropertyAccess";
1103 TokenKind[TokenKind["Word"] = 256] = "Word";
1104 TokenKind[TokenKind["Range"] = 512] = "Range";
1105 TokenKind[TokenKind["Quoted"] = 1024] = "Quoted";
1106 TokenKind[TokenKind["Operator"] = 2048] = "Operator";
1107 TokenKind[TokenKind["Delimited"] = 12] = "Delimited";
1108})(TokenKind || (TokenKind = {}));
1109
1110function isDelimitedToken(val) {
1111 return !!(getKind(val) & TokenKind.Delimited);
1112}
1113function isOperatorToken(val) {
1114 return getKind(val) === TokenKind.Operator;
1115}
1116function isHTMLToken(val) {
1117 return getKind(val) === TokenKind.HTML;
1118}
1119function isOutputToken(val) {
1120 return getKind(val) === TokenKind.Output;
1121}
1122function isTagToken(val) {
1123 return getKind(val) === TokenKind.Tag;
1124}
1125function isQuotedToken(val) {
1126 return getKind(val) === TokenKind.Quoted;
1127}
1128function isLiteralToken(val) {
1129 return getKind(val) === TokenKind.Literal;
1130}
1131function isNumberToken(val) {
1132 return getKind(val) === TokenKind.Number;
1133}
1134function isPropertyAccessToken(val) {
1135 return getKind(val) === TokenKind.PropertyAccess;
1136}
1137function isWordToken(val) {
1138 return getKind(val) === TokenKind.Word;
1139}
1140function isRangeToken(val) {
1141 return getKind(val) === TokenKind.Range;
1142}
1143function getKind(val) {
1144 return val ? val.kind : -1;
1145}
1146
1147var typeGuards = /*#__PURE__*/Object.freeze({
1148 __proto__: null,
1149 isDelimitedToken: isDelimitedToken,
1150 isOperatorToken: isOperatorToken,
1151 isHTMLToken: isHTMLToken,
1152 isOutputToken: isOutputToken,
1153 isTagToken: isTagToken,
1154 isQuotedToken: isQuotedToken,
1155 isLiteralToken: isLiteralToken,
1156 isNumberToken: isNumberToken,
1157 isPropertyAccessToken: isPropertyAccessToken,
1158 isWordToken: isWordToken,
1159 isRangeToken: isRangeToken
1160});
1161
1162class NullDrop extends Drop {
1163 equals(value) {
1164 return isNil(toValue(value));
1165 }
1166 gt() {
1167 return false;
1168 }
1169 geq() {
1170 return false;
1171 }
1172 lt() {
1173 return false;
1174 }
1175 leq() {
1176 return false;
1177 }
1178 valueOf() {
1179 return null;
1180 }
1181}
1182
1183class EmptyDrop extends Drop {
1184 equals(value) {
1185 if (value instanceof EmptyDrop)
1186 return false;
1187 value = toValue(value);
1188 if (isString(value) || isArray(value))
1189 return value.length === 0;
1190 if (isObject(value))
1191 return Object.keys(value).length === 0;
1192 return false;
1193 }
1194 gt() {
1195 return false;
1196 }
1197 geq() {
1198 return false;
1199 }
1200 lt() {
1201 return false;
1202 }
1203 leq() {
1204 return false;
1205 }
1206 valueOf() {
1207 return '';
1208 }
1209}
1210
1211class BlankDrop extends EmptyDrop {
1212 equals(value) {
1213 if (value === false)
1214 return true;
1215 if (isNil(toValue(value)))
1216 return true;
1217 if (isString(value))
1218 return /^\s*$/.test(value);
1219 return super.equals(value);
1220 }
1221}
1222
1223const nil = new NullDrop();
1224const literalValues = {
1225 'true': true,
1226 'false': false,
1227 'nil': nil,
1228 'null': nil,
1229 'empty': new EmptyDrop(),
1230 'blank': new BlankDrop()
1231};
1232
1233const rHex = /[\da-fA-F]/;
1234const rOct = /[0-7]/;
1235const escapeChar = {
1236 b: '\b',
1237 f: '\f',
1238 n: '\n',
1239 r: '\r',
1240 t: '\t',
1241 v: '\x0B'
1242};
1243function hexVal(c) {
1244 const code = c.charCodeAt(0);
1245 if (code >= 97)
1246 return code - 87;
1247 if (code >= 65)
1248 return code - 55;
1249 return code - 48;
1250}
1251function parseStringLiteral(str) {
1252 let ret = '';
1253 for (let i = 1; i < str.length - 1; i++) {
1254 if (str[i] !== '\\') {
1255 ret += str[i];
1256 continue;
1257 }
1258 if (escapeChar[str[i + 1]] !== undefined) {
1259 ret += escapeChar[str[++i]];
1260 }
1261 else if (str[i + 1] === 'u') {
1262 let val = 0;
1263 let j = i + 2;
1264 while (j <= i + 5 && rHex.test(str[j])) {
1265 val = val * 16 + hexVal(str[j++]);
1266 }
1267 i = j - 1;
1268 ret += String.fromCharCode(val);
1269 }
1270 else if (!rOct.test(str[i + 1])) {
1271 ret += str[++i];
1272 }
1273 else {
1274 let j = i + 1;
1275 let val = 0;
1276 while (j <= i + 3 && rOct.test(str[j])) {
1277 val = val * 8 + hexVal(str[j++]);
1278 }
1279 i = j - 1;
1280 ret += String.fromCharCode(val);
1281 }
1282 }
1283 return ret;
1284}
1285
1286class Expression {
1287 constructor(tokens) {
1288 this.postfix = [...toPostfix(tokens)];
1289 }
1290 *evaluate(ctx, lenient) {
1291 assert(ctx, 'unable to evaluate: context not defined');
1292 const operands = [];
1293 for (const token of this.postfix) {
1294 if (isOperatorToken(token)) {
1295 const r = operands.pop();
1296 const l = operands.pop();
1297 const result = yield evalOperatorToken(ctx.opts.operators, token, l, r, ctx);
1298 operands.push(result);
1299 }
1300 else {
1301 operands.push(yield evalToken(token, ctx, lenient && this.postfix.length === 1));
1302 }
1303 }
1304 return operands[0];
1305 }
1306}
1307function evalToken(token, ctx, lenient = false) {
1308 if (isPropertyAccessToken(token))
1309 return evalPropertyAccessToken(token, ctx, lenient);
1310 if (isRangeToken(token))
1311 return evalRangeToken(token, ctx);
1312 if (isLiteralToken(token))
1313 return evalLiteralToken(token);
1314 if (isNumberToken(token))
1315 return evalNumberToken(token);
1316 if (isWordToken(token))
1317 return token.getText();
1318 if (isQuotedToken(token))
1319 return evalQuotedToken(token);
1320}
1321function evalPropertyAccessToken(token, ctx, lenient) {
1322 const props = token.props.map(prop => evalToken(prop, ctx, false));
1323 try {
1324 return ctx.get([token.propertyName, ...props]);
1325 }
1326 catch (e) {
1327 if (lenient && e.name === 'InternalUndefinedVariableError')
1328 return null;
1329 throw (new UndefinedVariableError(e, token));
1330 }
1331}
1332function evalNumberToken(token) {
1333 const str = token.whole.content + '.' + (token.decimal ? token.decimal.content : '');
1334 return Number(str);
1335}
1336function evalQuotedToken(token) {
1337 return parseStringLiteral(token.getText());
1338}
1339function evalOperatorToken(operators, token, lhs, rhs, ctx) {
1340 const impl = operators[token.operator];
1341 return impl(lhs, rhs, ctx);
1342}
1343function evalLiteralToken(token) {
1344 return literalValues[token.literal];
1345}
1346function evalRangeToken(token, ctx) {
1347 const low = evalToken(token.lhs, ctx);
1348 const high = evalToken(token.rhs, ctx);
1349 return range(+low, +high + 1);
1350}
1351function* toPostfix(tokens) {
1352 const ops = [];
1353 for (const token of tokens) {
1354 if (isOperatorToken(token)) {
1355 while (ops.length && ops[ops.length - 1].getPrecedence() > token.getPrecedence()) {
1356 yield ops.pop();
1357 }
1358 ops.push(token);
1359 }
1360 else
1361 yield token;
1362 }
1363 while (ops.length) {
1364 yield ops.pop();
1365 }
1366}
1367
1368class Token {
1369 constructor(kind, input, begin, end, file) {
1370 this.kind = kind;
1371 this.input = input;
1372 this.begin = begin;
1373 this.end = end;
1374 this.file = file;
1375 }
1376 getText() {
1377 return this.input.slice(this.begin, this.end);
1378 }
1379 getPosition() {
1380 let [row, col] = [1, 1];
1381 for (let i = 0; i < this.begin; i++) {
1382 if (this.input[i] === '\n') {
1383 row++;
1384 col = 1;
1385 }
1386 else
1387 col++;
1388 }
1389 return [row, col];
1390 }
1391 size() {
1392 return this.end - this.begin;
1393 }
1394}
1395
1396class DelimitedToken extends Token {
1397 constructor(kind, content, input, begin, end, trimLeft, trimRight, file) {
1398 super(kind, input, begin, end, file);
1399 this.trimLeft = false;
1400 this.trimRight = false;
1401 this.content = this.getText();
1402 const tl = content[0] === '-';
1403 const tr = last$1(content) === '-';
1404 this.content = content
1405 .slice(tl ? 1 : 0, tr ? -1 : content.length)
1406 .trim();
1407 this.trimLeft = tl || trimLeft;
1408 this.trimRight = tr || trimRight;
1409 }
1410}
1411
1412function whiteSpaceCtrl(tokens, options) {
1413 let inRaw = false;
1414 for (let i = 0; i < tokens.length; i++) {
1415 const token = tokens[i];
1416 if (!isDelimitedToken(token))
1417 continue;
1418 if (!inRaw && token.trimLeft) {
1419 trimLeft(tokens[i - 1], options.greedy);
1420 }
1421 if (isTagToken(token)) {
1422 if (token.name === 'raw')
1423 inRaw = true;
1424 else if (token.name === 'endraw')
1425 inRaw = false;
1426 }
1427 if (!inRaw && token.trimRight) {
1428 trimRight(tokens[i + 1], options.greedy);
1429 }
1430 }
1431}
1432function trimLeft(token, greedy) {
1433 if (!token || !isHTMLToken(token))
1434 return;
1435 const mask = greedy ? BLANK : INLINE_BLANK;
1436 while (TYPES[token.input.charCodeAt(token.end - 1 - token.trimRight)] & mask)
1437 token.trimRight++;
1438}
1439function trimRight(token, greedy) {
1440 if (!token || !isHTMLToken(token))
1441 return;
1442 const mask = greedy ? BLANK : INLINE_BLANK;
1443 while (TYPES[token.input.charCodeAt(token.begin + token.trimLeft)] & mask)
1444 token.trimLeft++;
1445 if (token.input.charAt(token.begin + token.trimLeft) === '\n')
1446 token.trimLeft++;
1447}
1448
1449class NumberToken extends Token {
1450 constructor(whole, decimal) {
1451 super(TokenKind.Number, whole.input, whole.begin, decimal ? decimal.end : whole.end, whole.file);
1452 this.whole = whole;
1453 this.decimal = decimal;
1454 }
1455}
1456
1457class IdentifierToken extends Token {
1458 constructor(input, begin, end, file) {
1459 super(TokenKind.Word, input, begin, end, file);
1460 this.input = input;
1461 this.begin = begin;
1462 this.end = end;
1463 this.file = file;
1464 this.content = this.getText();
1465 }
1466 isNumber(allowSign = false) {
1467 const begin = allowSign && TYPES[this.input.charCodeAt(this.begin)] & SIGN
1468 ? this.begin + 1
1469 : this.begin;
1470 for (let i = begin; i < this.end; i++) {
1471 if (!(TYPES[this.input.charCodeAt(i)] & NUMBER))
1472 return false;
1473 }
1474 return true;
1475 }
1476}
1477
1478class LiteralToken extends Token {
1479 constructor(input, begin, end, file) {
1480 super(TokenKind.Literal, input, begin, end, file);
1481 this.input = input;
1482 this.begin = begin;
1483 this.end = end;
1484 this.file = file;
1485 this.literal = this.getText();
1486 }
1487}
1488
1489const precedence = {
1490 '==': 1,
1491 '!=': 1,
1492 '>': 1,
1493 '<': 1,
1494 '>=': 1,
1495 '<=': 1,
1496 'contains': 1,
1497 'and': 0,
1498 'or': 0
1499};
1500class OperatorToken extends Token {
1501 constructor(input, begin, end, file) {
1502 super(TokenKind.Operator, input, begin, end, file);
1503 this.input = input;
1504 this.begin = begin;
1505 this.end = end;
1506 this.file = file;
1507 this.operator = this.getText();
1508 }
1509 getPrecedence() {
1510 const key = this.getText();
1511 return key in precedence ? precedence[key] : 1;
1512 }
1513}
1514
1515class PropertyAccessToken extends Token {
1516 constructor(variable, props, end) {
1517 super(TokenKind.PropertyAccess, variable.input, variable.begin, end, variable.file);
1518 this.variable = variable;
1519 this.props = props;
1520 this.propertyName = this.variable instanceof IdentifierToken
1521 ? this.variable.getText()
1522 : parseStringLiteral(this.variable.getText());
1523 }
1524}
1525
1526class FilterToken extends Token {
1527 constructor(name, args, input, begin, end, file) {
1528 super(TokenKind.Filter, input, begin, end, file);
1529 this.name = name;
1530 this.args = args;
1531 }
1532}
1533
1534class HashToken extends Token {
1535 constructor(input, begin, end, name, value, file) {
1536 super(TokenKind.Hash, input, begin, end, file);
1537 this.input = input;
1538 this.begin = begin;
1539 this.end = end;
1540 this.name = name;
1541 this.value = value;
1542 this.file = file;
1543 }
1544}
1545
1546class QuotedToken extends Token {
1547 constructor(input, begin, end, file) {
1548 super(TokenKind.Quoted, input, begin, end, file);
1549 this.input = input;
1550 this.begin = begin;
1551 this.end = end;
1552 this.file = file;
1553 }
1554}
1555
1556class HTMLToken extends Token {
1557 constructor(input, begin, end, file) {
1558 super(TokenKind.HTML, input, begin, end, file);
1559 this.input = input;
1560 this.begin = begin;
1561 this.end = end;
1562 this.file = file;
1563 this.trimLeft = 0;
1564 this.trimRight = 0;
1565 }
1566 getContent() {
1567 return this.input.slice(this.begin + this.trimLeft, this.end - this.trimRight);
1568 }
1569}
1570
1571class RangeToken extends Token {
1572 constructor(input, begin, end, lhs, rhs, file) {
1573 super(TokenKind.Range, input, begin, end, file);
1574 this.input = input;
1575 this.begin = begin;
1576 this.end = end;
1577 this.lhs = lhs;
1578 this.rhs = rhs;
1579 this.file = file;
1580 }
1581}
1582
1583class OutputToken extends DelimitedToken {
1584 constructor(input, begin, end, options, file) {
1585 const { trimOutputLeft, trimOutputRight, outputDelimiterLeft, outputDelimiterRight } = options;
1586 const value = input.slice(begin + outputDelimiterLeft.length, end - outputDelimiterRight.length);
1587 super(TokenKind.Output, value, input, begin, end, trimOutputLeft, trimOutputRight, file);
1588 }
1589}
1590
1591function matchOperator(str, begin, trie, end = str.length) {
1592 let node = trie;
1593 let i = begin;
1594 let info;
1595 while (node[str[i]] && i < end) {
1596 node = node[str[i++]];
1597 if (node['end'])
1598 info = node;
1599 }
1600 if (!info)
1601 return -1;
1602 if (info['needBoundary'] && (TYPES[str.charCodeAt(i)] & IDENTIFIER))
1603 return -1;
1604 return i;
1605}
1606
1607class LiquidTagToken extends DelimitedToken {
1608 constructor(input, begin, end, options, file) {
1609 const value = input.slice(begin, end);
1610 super(TokenKind.Tag, value, input, begin, end, false, false, file);
1611 if (!/\S/.test(value)) {
1612 // A line that contains only whitespace.
1613 this.name = '';
1614 this.args = '';
1615 }
1616 else {
1617 const tokenizer = new Tokenizer(this.content, options.operatorsTrie);
1618 this.name = tokenizer.readTagName();
1619 if (!this.name)
1620 throw new TokenizationError(`illegal liquid tag syntax`, this);
1621 tokenizer.skipBlank();
1622 this.args = tokenizer.remaining();
1623 }
1624 }
1625}
1626
1627class Tokenizer {
1628 constructor(input, trie = defaultOptions.operatorsTrie, file = '') {
1629 this.input = input;
1630 this.trie = trie;
1631 this.file = file;
1632 this.p = 0;
1633 this.rawBeginAt = -1;
1634 this.N = input.length;
1635 }
1636 readExpression() {
1637 return new Expression(this.readExpressionTokens());
1638 }
1639 *readExpressionTokens() {
1640 const operand = this.readValue();
1641 if (!operand)
1642 return;
1643 yield operand;
1644 while (this.p < this.N) {
1645 const operator = this.readOperator();
1646 if (!operator)
1647 return;
1648 const operand = this.readValue();
1649 if (!operand)
1650 return;
1651 yield operator;
1652 yield operand;
1653 }
1654 }
1655 readOperator() {
1656 this.skipBlank();
1657 const end = matchOperator(this.input, this.p, this.trie);
1658 if (end === -1)
1659 return;
1660 return new OperatorToken(this.input, this.p, (this.p = end), this.file);
1661 }
1662 readFilters() {
1663 const filters = [];
1664 while (true) {
1665 const filter = this.readFilter();
1666 if (!filter)
1667 return filters;
1668 filters.push(filter);
1669 }
1670 }
1671 readFilter() {
1672 this.skipBlank();
1673 if (this.end())
1674 return null;
1675 assert(this.peek() === '|', () => `unexpected token at ${this.snapshot()}`);
1676 this.p++;
1677 const begin = this.p;
1678 const name = this.readIdentifier();
1679 if (!name.size())
1680 return null;
1681 const args = [];
1682 this.skipBlank();
1683 if (this.peek() === ':') {
1684 do {
1685 ++this.p;
1686 const arg = this.readFilterArg();
1687 arg && args.push(arg);
1688 this.skipBlank();
1689 assert(this.end() || this.peek() === ',' || this.peek() === '|', () => `unexpected character ${this.snapshot()}`);
1690 } while (this.peek() === ',');
1691 }
1692 return new FilterToken(name.getText(), args, this.input, begin, this.p, this.file);
1693 }
1694 readFilterArg() {
1695 const key = this.readValue();
1696 if (!key)
1697 return;
1698 this.skipBlank();
1699 if (this.peek() !== ':')
1700 return key;
1701 ++this.p;
1702 const value = this.readValue();
1703 return [key.getText(), value];
1704 }
1705 readTopLevelTokens(options = defaultOptions) {
1706 const tokens = [];
1707 while (this.p < this.N) {
1708 const token = this.readTopLevelToken(options);
1709 tokens.push(token);
1710 }
1711 whiteSpaceCtrl(tokens, options);
1712 return tokens;
1713 }
1714 readTopLevelToken(options) {
1715 const { tagDelimiterLeft, outputDelimiterLeft } = options;
1716 if (this.rawBeginAt > -1)
1717 return this.readEndrawOrRawContent(options);
1718 if (this.match(tagDelimiterLeft))
1719 return this.readTagToken(options);
1720 if (this.match(outputDelimiterLeft))
1721 return this.readOutputToken(options);
1722 return this.readHTMLToken([tagDelimiterLeft, outputDelimiterLeft]);
1723 }
1724 readHTMLToken(stopStrings) {
1725 const begin = this.p;
1726 while (this.p < this.N) {
1727 if (stopStrings.some(str => this.match(str)))
1728 break;
1729 ++this.p;
1730 }
1731 return new HTMLToken(this.input, begin, this.p, this.file);
1732 }
1733 readTagToken(options = defaultOptions) {
1734 const { file, input } = this;
1735 const begin = this.p;
1736 if (this.readToDelimiter(options.tagDelimiterRight) === -1) {
1737 throw this.mkError(`tag ${this.snapshot(begin)} not closed`, begin);
1738 }
1739 const token = new TagToken(input, begin, this.p, options, file);
1740 if (token.name === 'raw')
1741 this.rawBeginAt = begin;
1742 return token;
1743 }
1744 readToDelimiter(delimiter) {
1745 while (this.p < this.N) {
1746 if ((this.peekType() & QUOTE)) {
1747 this.readQuoted();
1748 continue;
1749 }
1750 ++this.p;
1751 if (this.rmatch(delimiter))
1752 return this.p;
1753 }
1754 return -1;
1755 }
1756 readOutputToken(options = defaultOptions) {
1757 const { file, input } = this;
1758 const { outputDelimiterRight } = options;
1759 const begin = this.p;
1760 if (this.readToDelimiter(outputDelimiterRight) === -1) {
1761 throw this.mkError(`output ${this.snapshot(begin)} not closed`, begin);
1762 }
1763 return new OutputToken(input, begin, this.p, options, file);
1764 }
1765 readEndrawOrRawContent(options) {
1766 const { tagDelimiterLeft, tagDelimiterRight } = options;
1767 const begin = this.p;
1768 let leftPos = this.readTo(tagDelimiterLeft) - tagDelimiterLeft.length;
1769 while (this.p < this.N) {
1770 if (this.readIdentifier().getText() !== 'endraw') {
1771 leftPos = this.readTo(tagDelimiterLeft) - tagDelimiterLeft.length;
1772 continue;
1773 }
1774 while (this.p <= this.N) {
1775 if (this.rmatch(tagDelimiterRight)) {
1776 const end = this.p;
1777 if (begin === leftPos) {
1778 this.rawBeginAt = -1;
1779 return new TagToken(this.input, begin, end, options, this.file);
1780 }
1781 else {
1782 this.p = leftPos;
1783 return new HTMLToken(this.input, begin, leftPos, this.file);
1784 }
1785 }
1786 if (this.rmatch(tagDelimiterLeft))
1787 break;
1788 this.p++;
1789 }
1790 }
1791 throw this.mkError(`raw ${this.snapshot(this.rawBeginAt)} not closed`, begin);
1792 }
1793 readLiquidTagTokens(options = defaultOptions) {
1794 const tokens = [];
1795 while (this.p < this.N) {
1796 const token = this.readLiquidTagToken(options);
1797 if (token.name)
1798 tokens.push(token);
1799 }
1800 return tokens;
1801 }
1802 readLiquidTagToken(options) {
1803 const { file, input } = this;
1804 const begin = this.p;
1805 let end = this.N;
1806 if (this.readToDelimiter('\n') !== -1)
1807 end = this.p;
1808 return new LiquidTagToken(input, begin, end, options, file);
1809 }
1810 mkError(msg, begin) {
1811 return new TokenizationError(msg, new IdentifierToken(this.input, begin, this.N, this.file));
1812 }
1813 snapshot(begin = this.p) {
1814 return JSON.stringify(ellipsis(this.input.slice(begin), 16));
1815 }
1816 /**
1817 * @deprecated
1818 */
1819 readWord() {
1820 console.warn('Tokenizer#readWord() will be removed, use #readIdentifier instead');
1821 return this.readIdentifier();
1822 }
1823 readIdentifier() {
1824 this.skipBlank();
1825 const begin = this.p;
1826 while (this.peekType() & IDENTIFIER)
1827 ++this.p;
1828 return new IdentifierToken(this.input, begin, this.p, this.file);
1829 }
1830 readTagName() {
1831 this.skipBlank();
1832 // Handle inline comment tags
1833 if (this.input[this.p] === '#')
1834 return this.input.slice(this.p, ++this.p);
1835 return this.readIdentifier().getText();
1836 }
1837 readHashes(jekyllStyle) {
1838 const hashes = [];
1839 while (true) {
1840 const hash = this.readHash(jekyllStyle);
1841 if (!hash)
1842 return hashes;
1843 hashes.push(hash);
1844 }
1845 }
1846 readHash(jekyllStyle) {
1847 this.skipBlank();
1848 if (this.peek() === ',')
1849 ++this.p;
1850 const begin = this.p;
1851 const name = this.readIdentifier();
1852 if (!name.size())
1853 return;
1854 let value;
1855 this.skipBlank();
1856 const sep = jekyllStyle ? '=' : ':';
1857 if (this.peek() === sep) {
1858 ++this.p;
1859 value = this.readValue();
1860 }
1861 return new HashToken(this.input, begin, this.p, name, value, this.file);
1862 }
1863 remaining() {
1864 return this.input.slice(this.p);
1865 }
1866 advance(i = 1) {
1867 this.p += i;
1868 }
1869 end() {
1870 return this.p >= this.N;
1871 }
1872 readTo(end) {
1873 while (this.p < this.N) {
1874 ++this.p;
1875 if (this.rmatch(end))
1876 return this.p;
1877 }
1878 return -1;
1879 }
1880 readValue() {
1881 const value = this.readQuoted() || this.readRange();
1882 if (value)
1883 return value;
1884 if (this.peek() === '[') {
1885 this.p++;
1886 const prop = this.readQuoted();
1887 if (!prop)
1888 return;
1889 if (this.peek() !== ']')
1890 return;
1891 this.p++;
1892 return new PropertyAccessToken(prop, [], this.p);
1893 }
1894 const variable = this.readIdentifier();
1895 if (!variable.size())
1896 return;
1897 let isNumber = variable.isNumber(true);
1898 const props = [];
1899 while (true) {
1900 if (this.peek() === '[') {
1901 isNumber = false;
1902 this.p++;
1903 const prop = this.readValue() || new IdentifierToken(this.input, this.p, this.p, this.file);
1904 this.readTo(']');
1905 props.push(prop);
1906 }
1907 else if (this.peek() === '.' && this.peek(1) !== '.') { // skip range syntax
1908 this.p++;
1909 const prop = this.readIdentifier();
1910 if (!prop.size())
1911 break;
1912 if (!prop.isNumber())
1913 isNumber = false;
1914 props.push(prop);
1915 }
1916 else
1917 break;
1918 }
1919 if (!props.length && literalValues.hasOwnProperty(variable.content)) {
1920 return new LiteralToken(this.input, variable.begin, variable.end, this.file);
1921 }
1922 if (isNumber)
1923 return new NumberToken(variable, props[0]);
1924 return new PropertyAccessToken(variable, props, this.p);
1925 }
1926 readRange() {
1927 this.skipBlank();
1928 const begin = this.p;
1929 if (this.peek() !== '(')
1930 return;
1931 ++this.p;
1932 const lhs = this.readValueOrThrow();
1933 this.p += 2;
1934 const rhs = this.readValueOrThrow();
1935 ++this.p;
1936 return new RangeToken(this.input, begin, this.p, lhs, rhs, this.file);
1937 }
1938 readValueOrThrow() {
1939 const value = this.readValue();
1940 assert(value, () => `unexpected token ${this.snapshot()}, value expected`);
1941 return value;
1942 }
1943 readQuoted() {
1944 this.skipBlank();
1945 const begin = this.p;
1946 if (!(this.peekType() & QUOTE))
1947 return;
1948 ++this.p;
1949 let escaped = false;
1950 while (this.p < this.N) {
1951 ++this.p;
1952 if (this.input[this.p - 1] === this.input[begin] && !escaped)
1953 break;
1954 if (escaped)
1955 escaped = false;
1956 else if (this.input[this.p - 1] === '\\')
1957 escaped = true;
1958 }
1959 return new QuotedToken(this.input, begin, this.p, this.file);
1960 }
1961 *readFileNameTemplate(options) {
1962 const { outputDelimiterLeft } = options;
1963 const htmlStopStrings = [',', ' ', outputDelimiterLeft];
1964 const htmlStopStringSet = new Set(htmlStopStrings);
1965 // break on ',' and ' ', outputDelimiterLeft only stops HTML token
1966 while (this.p < this.N && !htmlStopStringSet.has(this.peek())) {
1967 yield this.match(outputDelimiterLeft)
1968 ? this.readOutputToken(options)
1969 : this.readHTMLToken(htmlStopStrings);
1970 }
1971 }
1972 match(word) {
1973 for (let i = 0; i < word.length; i++) {
1974 if (word[i] !== this.input[this.p + i])
1975 return false;
1976 }
1977 return true;
1978 }
1979 rmatch(pattern) {
1980 for (let i = 0; i < pattern.length; i++) {
1981 if (pattern[pattern.length - 1 - i] !== this.input[this.p - 1 - i])
1982 return false;
1983 }
1984 return true;
1985 }
1986 peekType(n = 0) {
1987 return TYPES[this.input.charCodeAt(this.p + n)];
1988 }
1989 peek(n = 0) {
1990 return this.input[this.p + n];
1991 }
1992 skipBlank() {
1993 while (this.peekType() & BLANK)
1994 ++this.p;
1995 }
1996}
1997
1998class TagToken extends DelimitedToken {
1999 constructor(input, begin, end, options, file) {
2000 const { trimTagLeft, trimTagRight, tagDelimiterLeft, tagDelimiterRight } = options;
2001 const value = input.slice(begin + tagDelimiterLeft.length, end - tagDelimiterRight.length);
2002 super(TokenKind.Tag, value, input, begin, end, trimTagLeft, trimTagRight, file);
2003 const tokenizer = new Tokenizer(this.content, options.operatorsTrie);
2004 this.name = tokenizer.readTagName();
2005 if (!this.name)
2006 throw new TokenizationError(`illegal tag syntax`, this);
2007 tokenizer.skipBlank();
2008 this.args = tokenizer.remaining();
2009 }
2010}
2011
2012class ParseStream {
2013 constructor(tokens, parseToken) {
2014 this.handlers = {};
2015 this.stopRequested = false;
2016 this.tokens = tokens;
2017 this.parseToken = parseToken;
2018 }
2019 on(name, cb) {
2020 this.handlers[name] = cb;
2021 return this;
2022 }
2023 trigger(event, arg) {
2024 const h = this.handlers[event];
2025 return h ? (h.call(this, arg), true) : false;
2026 }
2027 start() {
2028 this.trigger('start');
2029 let token;
2030 while (!this.stopRequested && (token = this.tokens.shift())) {
2031 if (this.trigger('token', token))
2032 continue;
2033 if (isTagToken(token) && this.trigger(`tag:${token.name}`, token)) {
2034 continue;
2035 }
2036 const template = this.parseToken(token, this.tokens);
2037 this.trigger('template', template);
2038 }
2039 if (!this.stopRequested)
2040 this.trigger('end');
2041 return this;
2042 }
2043 stop() {
2044 this.stopRequested = true;
2045 return this;
2046 }
2047}
2048
2049/**
2050 * Key-Value Pairs Representing Tag Arguments
2051 * Example:
2052 * For the markup `, foo:'bar', coo:2 reversed %}`,
2053 * hash['foo'] === 'bar'
2054 * hash['coo'] === 2
2055 * hash['reversed'] === undefined
2056 */
2057class Hash {
2058 constructor(markup, jekyllStyle) {
2059 this.hash = {};
2060 const tokenizer = new Tokenizer(markup, {});
2061 for (const hash of tokenizer.readHashes(jekyllStyle)) {
2062 this.hash[hash.name.content] = hash.value;
2063 }
2064 }
2065 *render(ctx) {
2066 const hash = {};
2067 for (const key of Object.keys(this.hash)) {
2068 hash[key] = this.hash[key] === undefined ? true : yield evalToken(this.hash[key], ctx);
2069 }
2070 return hash;
2071 }
2072}
2073
2074function isKeyValuePair(arr) {
2075 return isArray(arr);
2076}
2077
2078class Filter {
2079 constructor(name, impl, args, liquid) {
2080 this.name = name;
2081 this.impl = impl || identify;
2082 this.args = args;
2083 this.liquid = liquid;
2084 }
2085 render(value, context) {
2086 const argv = [];
2087 for (const arg of this.args) {
2088 if (isKeyValuePair(arg))
2089 argv.push([arg[0], evalToken(arg[1], context)]);
2090 else
2091 argv.push(evalToken(arg, context));
2092 }
2093 return this.impl.apply({ context, liquid: this.liquid }, [value, ...argv]);
2094 }
2095}
2096
2097class Value {
2098 /**
2099 * @param str the value to be valuated, eg.: "foobar" | truncate: 3
2100 */
2101 constructor(str, liquid) {
2102 this.filters = [];
2103 const tokenizer = new Tokenizer(str, liquid.options.operatorsTrie);
2104 this.initial = tokenizer.readExpression();
2105 this.filters = tokenizer.readFilters().map(({ name, args }) => new Filter(name, liquid.filters.get(name), args, liquid));
2106 }
2107 *value(ctx, lenient) {
2108 lenient = lenient || (ctx.opts.lenientIf && this.filters.length > 0 && this.filters[0].name === 'default');
2109 let val = yield this.initial.evaluate(ctx, lenient);
2110 for (const filter of this.filters) {
2111 val = yield filter.render(val, ctx);
2112 }
2113 return val;
2114 }
2115}
2116
2117// convert an async iterator to a Promise
2118function toPromise(val) {
2119 return __awaiter(this, void 0, void 0, function* () {
2120 if (!isIterator(val))
2121 return val;
2122 let value;
2123 let done = false;
2124 let next = 'next';
2125 do {
2126 const state = val[next](value);
2127 done = state.done;
2128 value = state.value;
2129 next = 'next';
2130 try {
2131 if (isIterator(value))
2132 value = toPromise(value);
2133 if (isPromise(value))
2134 value = yield value;
2135 }
2136 catch (err) {
2137 next = 'throw';
2138 value = err;
2139 }
2140 } while (!done);
2141 return value;
2142 });
2143}
2144// convert an async iterator to a value in a synchronous manner
2145function toValueSync(val) {
2146 if (!isIterator(val))
2147 return val;
2148 let value;
2149 let done = false;
2150 let next = 'next';
2151 do {
2152 const state = val[next](value);
2153 done = state.done;
2154 value = state.value;
2155 next = 'next';
2156 if (isIterator(value)) {
2157 try {
2158 value = toValueSync(value);
2159 }
2160 catch (err) {
2161 next = 'throw';
2162 value = err;
2163 }
2164 }
2165 } while (!done);
2166 return value;
2167}
2168const toThenable = toPromise;
2169
2170var assign = {
2171 parse: function (token) {
2172 const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie);
2173 this.key = tokenizer.readIdentifier().content;
2174 tokenizer.skipBlank();
2175 assert(tokenizer.peek() === '=', () => `illegal token ${token.getText()}`);
2176 tokenizer.advance();
2177 this.value = new Value(tokenizer.remaining(), this.liquid);
2178 },
2179 render: function* (ctx) {
2180 ctx.bottom()[this.key] = yield this.value.value(ctx, this.liquid.options.lenientIf);
2181 }
2182};
2183
2184class ForloopDrop extends Drop {
2185 constructor(length, collection, variable) {
2186 super();
2187 this.i = 0;
2188 this.length = length;
2189 this.name = `${variable}-${collection}`;
2190 }
2191 next() {
2192 this.i++;
2193 }
2194 index0() {
2195 return this.i;
2196 }
2197 index() {
2198 return this.i + 1;
2199 }
2200 first() {
2201 return this.i === 0;
2202 }
2203 last() {
2204 return this.i === this.length - 1;
2205 }
2206 rindex() {
2207 return this.length - this.i;
2208 }
2209 rindex0() {
2210 return this.length - this.i - 1;
2211 }
2212 valueOf() {
2213 return JSON.stringify(this);
2214 }
2215}
2216
2217const MODIFIERS = ['offset', 'limit', 'reversed'];
2218var For = {
2219 type: 'block',
2220 parse: function (token, remainTokens) {
2221 const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie);
2222 const variable = tokenizer.readIdentifier();
2223 const inStr = tokenizer.readIdentifier();
2224 const collection = tokenizer.readValue();
2225 assert(variable.size() && inStr.content === 'in' && collection, () => `illegal tag: ${token.getText()}`);
2226 this.variable = variable.content;
2227 this.collection = collection;
2228 this.hash = new Hash(tokenizer.remaining());
2229 this.templates = [];
2230 this.elseTemplates = [];
2231 let p;
2232 const stream = this.liquid.parser.parseStream(remainTokens)
2233 .on('start', () => (p = this.templates))
2234 .on('tag:else', () => (p = this.elseTemplates))
2235 .on('tag:endfor', () => stream.stop())
2236 .on('template', (tpl) => p.push(tpl))
2237 .on('end', () => {
2238 throw new Error(`tag ${token.getText()} not closed`);
2239 });
2240 stream.start();
2241 },
2242 render: function* (ctx, emitter) {
2243 const r = this.liquid.renderer;
2244 let collection = toEnumerable(yield evalToken(this.collection, ctx));
2245 if (!collection.length) {
2246 yield r.renderTemplates(this.elseTemplates, ctx, emitter);
2247 return;
2248 }
2249 const continueKey = 'continue-' + this.variable + '-' + this.collection.getText();
2250 ctx.push({ continue: ctx.getRegister(continueKey) });
2251 const hash = yield this.hash.render(ctx);
2252 ctx.pop();
2253 const modifiers = this.liquid.options.orderedFilterParameters
2254 ? Object.keys(hash).filter(x => MODIFIERS.includes(x))
2255 : MODIFIERS.filter(x => hash[x] !== undefined);
2256 collection = modifiers.reduce((collection, modifier) => {
2257 if (modifier === 'offset')
2258 return offset(collection, hash['offset']);
2259 if (modifier === 'limit')
2260 return limit(collection, hash['limit']);
2261 return reversed(collection);
2262 }, collection);
2263 ctx.setRegister(continueKey, (hash['offset'] || 0) + collection.length);
2264 const scope = { forloop: new ForloopDrop(collection.length, this.collection.getText(), this.variable) };
2265 ctx.push(scope);
2266 for (const item of collection) {
2267 scope[this.variable] = item;
2268 yield r.renderTemplates(this.templates, ctx, emitter);
2269 if (emitter['break']) {
2270 emitter['break'] = false;
2271 break;
2272 }
2273 emitter['continue'] = false;
2274 scope.forloop.next();
2275 }
2276 ctx.pop();
2277 }
2278};
2279function reversed(arr) {
2280 return [...arr].reverse();
2281}
2282function offset(arr, count) {
2283 return arr.slice(count);
2284}
2285function limit(arr, count) {
2286 return arr.slice(0, count);
2287}
2288
2289var capture = {
2290 parse: function (tagToken, remainTokens) {
2291 const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operatorsTrie);
2292 this.variable = readVariableName(tokenizer);
2293 assert(this.variable, () => `${tagToken.args} not valid identifier`);
2294 this.templates = [];
2295 const stream = this.liquid.parser.parseStream(remainTokens);
2296 stream.on('tag:endcapture', () => stream.stop())
2297 .on('template', (tpl) => this.templates.push(tpl))
2298 .on('end', () => {
2299 throw new Error(`tag ${tagToken.getText()} not closed`);
2300 });
2301 stream.start();
2302 },
2303 render: function* (ctx) {
2304 const r = this.liquid.renderer;
2305 const html = yield r.renderTemplates(this.templates, ctx);
2306 ctx.bottom()[this.variable] = html;
2307 }
2308};
2309function readVariableName(tokenizer) {
2310 const word = tokenizer.readIdentifier().content;
2311 if (word)
2312 return word;
2313 const quoted = tokenizer.readQuoted();
2314 if (quoted)
2315 return evalQuotedToken(quoted);
2316}
2317
2318var Case = {
2319 parse: function (tagToken, remainTokens) {
2320 this.cond = new Value(tagToken.args, this.liquid);
2321 this.cases = [];
2322 this.elseTemplates = [];
2323 let p = [];
2324 const stream = this.liquid.parser.parseStream(remainTokens)
2325 .on('tag:when', (token) => {
2326 p = [];
2327 const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie);
2328 while (!tokenizer.end()) {
2329 const value = tokenizer.readValue();
2330 this.cases.push({
2331 val: value,
2332 templates: p
2333 });
2334 tokenizer.readTo(',');
2335 }
2336 })
2337 .on('tag:else', () => (p = this.elseTemplates))
2338 .on('tag:endcase', () => stream.stop())
2339 .on('template', (tpl) => p.push(tpl))
2340 .on('end', () => {
2341 throw new Error(`tag ${tagToken.getText()} not closed`);
2342 });
2343 stream.start();
2344 },
2345 render: function* (ctx, emitter) {
2346 const r = this.liquid.renderer;
2347 const cond = toValue(yield this.cond.value(ctx, ctx.opts.lenientIf));
2348 for (const branch of this.cases) {
2349 const val = evalToken(branch.val, ctx, ctx.opts.lenientIf);
2350 if (val === cond) {
2351 yield r.renderTemplates(branch.templates, ctx, emitter);
2352 return;
2353 }
2354 }
2355 yield r.renderTemplates(this.elseTemplates, ctx, emitter);
2356 }
2357};
2358
2359var comment = {
2360 parse: function (tagToken, remainTokens) {
2361 const stream = this.liquid.parser.parseStream(remainTokens);
2362 stream
2363 .on('token', (token) => {
2364 if (token.name === 'endcomment')
2365 stream.stop();
2366 })
2367 .on('end', () => {
2368 throw new Error(`tag ${tagToken.getText()} not closed`);
2369 });
2370 stream.start();
2371 }
2372};
2373
2374var BlockMode;
2375(function (BlockMode) {
2376 /* store rendered html into blocks */
2377 BlockMode[BlockMode["OUTPUT"] = 0] = "OUTPUT";
2378 /* output rendered html directly */
2379 BlockMode[BlockMode["STORE"] = 1] = "STORE";
2380})(BlockMode || (BlockMode = {}));
2381var BlockMode$1 = BlockMode;
2382
2383var render = {
2384 parseFilePath,
2385 renderFilePath,
2386 parse: function (token) {
2387 const args = token.args;
2388 const tokenizer = new Tokenizer(args, this.liquid.options.operatorsTrie);
2389 this['file'] = this.parseFilePath(tokenizer, this.liquid);
2390 this['currentFile'] = token.file;
2391 while (!tokenizer.end()) {
2392 tokenizer.skipBlank();
2393 const begin = tokenizer.p;
2394 const keyword = tokenizer.readIdentifier();
2395 if (keyword.content === 'with' || keyword.content === 'for') {
2396 tokenizer.skipBlank();
2397 // can be normal key/value pair, like "with: true"
2398 if (tokenizer.peek() !== ':') {
2399 const value = tokenizer.readValue();
2400 // can be normal key, like "with,"
2401 if (value) {
2402 const beforeAs = tokenizer.p;
2403 const asStr = tokenizer.readIdentifier();
2404 let alias;
2405 if (asStr.content === 'as')
2406 alias = tokenizer.readIdentifier();
2407 else
2408 tokenizer.p = beforeAs;
2409 this[keyword.content] = { value, alias: alias && alias.content };
2410 tokenizer.skipBlank();
2411 if (tokenizer.peek() === ',')
2412 tokenizer.advance();
2413 // matched!
2414 continue;
2415 }
2416 }
2417 }
2418 /**
2419 * restore cursor if with/for not matched
2420 */
2421 tokenizer.p = begin;
2422 break;
2423 }
2424 this.hash = new Hash(tokenizer.remaining());
2425 },
2426 render: function* (ctx, emitter) {
2427 const { liquid, hash } = this;
2428 const filepath = yield this.renderFilePath(this['file'], ctx, liquid);
2429 assert(filepath, () => `illegal filename "${filepath}"`);
2430 const childCtx = new Context({}, ctx.opts, { sync: ctx.sync, globals: ctx.globals, strictVariables: ctx.strictVariables });
2431 const scope = childCtx.bottom();
2432 __assign(scope, yield hash.render(ctx));
2433 if (this['with']) {
2434 const { value, alias } = this['with'];
2435 scope[alias || filepath] = evalToken(value, ctx);
2436 }
2437 if (this['for']) {
2438 const { value, alias } = this['for'];
2439 let collection = evalToken(value, ctx);
2440 collection = toEnumerable(collection);
2441 scope['forloop'] = new ForloopDrop(collection.length, value.getText(), alias);
2442 for (const item of collection) {
2443 scope[alias] = item;
2444 const templates = yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile']);
2445 yield liquid.renderer.renderTemplates(templates, childCtx, emitter);
2446 scope['forloop'].next();
2447 }
2448 }
2449 else {
2450 const templates = yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile']);
2451 yield liquid.renderer.renderTemplates(templates, childCtx, emitter);
2452 }
2453 }
2454};
2455/**
2456 * @return null for "none",
2457 * @return Template[] for quoted with tags and/or filters
2458 * @return Token for expression (not quoted)
2459 * @throws TypeError if cannot read next token
2460 */
2461function parseFilePath(tokenizer, liquid) {
2462 if (liquid.options.dynamicPartials) {
2463 const file = tokenizer.readValue();
2464 if (file === undefined)
2465 throw new TypeError(`illegal argument "${tokenizer.input}"`);
2466 if (file.getText() === 'none')
2467 return null;
2468 if (isQuotedToken(file)) {
2469 // for filenames like "files/{{file}}", eval as liquid template
2470 const templates = liquid.parse(evalQuotedToken(file));
2471 return optimize(templates);
2472 }
2473 return file;
2474 }
2475 const tokens = [...tokenizer.readFileNameTemplate(liquid.options)];
2476 const templates = optimize(liquid.parser.parseTokens(tokens));
2477 return templates === 'none' ? null : templates;
2478}
2479function optimize(templates) {
2480 // for filenames like "files/file.liquid", extract the string directly
2481 if (templates.length === 1 && isHTMLToken(templates[0].token))
2482 return templates[0].token.getContent();
2483 return templates;
2484}
2485function renderFilePath(file, ctx, liquid) {
2486 if (typeof file === 'string')
2487 return file;
2488 if (Array.isArray(file))
2489 return liquid.renderer.renderTemplates(file, ctx);
2490 return evalToken(file, ctx);
2491}
2492
2493var include = {
2494 parseFilePath,
2495 renderFilePath,
2496 parse: function (token) {
2497 const args = token.args;
2498 const tokenizer = new Tokenizer(args, this.liquid.options.operatorsTrie);
2499 this['file'] = this.parseFilePath(tokenizer, this.liquid);
2500 this['currentFile'] = token.file;
2501 const begin = tokenizer.p;
2502 const withStr = tokenizer.readIdentifier();
2503 if (withStr.content === 'with') {
2504 tokenizer.skipBlank();
2505 if (tokenizer.peek() !== ':') {
2506 this.withVar = tokenizer.readValue();
2507 }
2508 else
2509 tokenizer.p = begin;
2510 }
2511 else
2512 tokenizer.p = begin;
2513 this.hash = new Hash(tokenizer.remaining(), this.liquid.options.jekyllInclude);
2514 },
2515 render: function* (ctx, emitter) {
2516 const { liquid, hash, withVar } = this;
2517 const { renderer } = liquid;
2518 const filepath = yield this.renderFilePath(this['file'], ctx, liquid);
2519 assert(filepath, () => `illegal filename "${filepath}"`);
2520 const saved = ctx.saveRegister('blocks', 'blockMode');
2521 ctx.setRegister('blocks', {});
2522 ctx.setRegister('blockMode', BlockMode$1.OUTPUT);
2523 const scope = yield hash.render(ctx);
2524 if (withVar)
2525 scope[filepath] = evalToken(withVar, ctx);
2526 const templates = yield liquid._parsePartialFile(filepath, ctx.sync, this['currentFile']);
2527 ctx.push(ctx.opts.jekyllInclude ? { include: scope } : scope);
2528 yield renderer.renderTemplates(templates, ctx, emitter);
2529 ctx.pop();
2530 ctx.restoreRegister(saved);
2531 }
2532};
2533
2534var decrement = {
2535 parse: function (token) {
2536 const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie);
2537 this.variable = tokenizer.readIdentifier().content;
2538 },
2539 render: function (context, emitter) {
2540 const scope = context.environments;
2541 if (!isNumber(scope[this.variable])) {
2542 scope[this.variable] = 0;
2543 }
2544 emitter.write(stringify(--scope[this.variable]));
2545 }
2546};
2547
2548var cycle = {
2549 parse: function (tagToken) {
2550 const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operatorsTrie);
2551 const group = tokenizer.readValue();
2552 tokenizer.skipBlank();
2553 this.candidates = [];
2554 if (group) {
2555 if (tokenizer.peek() === ':') {
2556 this.group = group;
2557 tokenizer.advance();
2558 }
2559 else
2560 this.candidates.push(group);
2561 }
2562 while (!tokenizer.end()) {
2563 const value = tokenizer.readValue();
2564 if (value)
2565 this.candidates.push(value);
2566 tokenizer.readTo(',');
2567 }
2568 assert(this.candidates.length, () => `empty candidates: ${tagToken.getText()}`);
2569 },
2570 render: function (ctx, emitter) {
2571 const group = evalToken(this.group, ctx);
2572 const fingerprint = `cycle:${group}:` + this.candidates.join(',');
2573 const groups = ctx.getRegister('cycle');
2574 let idx = groups[fingerprint];
2575 if (idx === undefined) {
2576 idx = groups[fingerprint] = 0;
2577 }
2578 const candidate = this.candidates[idx];
2579 idx = (idx + 1) % this.candidates.length;
2580 groups[fingerprint] = idx;
2581 const html = evalToken(candidate, ctx);
2582 emitter.write(html);
2583 }
2584};
2585
2586var If = {
2587 parse: function (tagToken, remainTokens) {
2588 this.branches = [];
2589 this.elseTemplates = [];
2590 let p;
2591 this.liquid.parser.parseStream(remainTokens)
2592 .on('start', () => this.branches.push({
2593 predicate: new Value(tagToken.args, this.liquid),
2594 templates: (p = [])
2595 }))
2596 .on('tag:elsif', (token) => this.branches.push({
2597 predicate: new Value(token.args, this.liquid),
2598 templates: (p = [])
2599 }))
2600 .on('tag:else', () => (p = this.elseTemplates))
2601 .on('tag:endif', function () { this.stop(); })
2602 .on('template', (tpl) => p.push(tpl))
2603 .on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`); })
2604 .start();
2605 },
2606 render: function* (ctx, emitter) {
2607 const r = this.liquid.renderer;
2608 for (const { predicate, templates } of this.branches) {
2609 const value = yield predicate.value(ctx, ctx.opts.lenientIf);
2610 if (isTruthy(value, ctx)) {
2611 yield r.renderTemplates(templates, ctx, emitter);
2612 return;
2613 }
2614 }
2615 yield r.renderTemplates(this.elseTemplates, ctx, emitter);
2616 }
2617};
2618
2619var increment = {
2620 parse: function (token) {
2621 const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie);
2622 this.variable = tokenizer.readIdentifier().content;
2623 },
2624 render: function (context, emitter) {
2625 const scope = context.environments;
2626 if (!isNumber(scope[this.variable])) {
2627 scope[this.variable] = 0;
2628 }
2629 const val = scope[this.variable];
2630 scope[this.variable]++;
2631 emitter.write(stringify(val));
2632 }
2633};
2634
2635var layout = {
2636 parseFilePath,
2637 renderFilePath,
2638 parse: function (token, remainTokens) {
2639 const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie);
2640 this['file'] = this.parseFilePath(tokenizer, this.liquid);
2641 this['currentFile'] = token.file;
2642 this.hash = new Hash(tokenizer.remaining());
2643 this.tpls = this.liquid.parser.parseTokens(remainTokens);
2644 },
2645 render: function* (ctx, emitter) {
2646 const { liquid, hash, file } = this;
2647 const { renderer } = liquid;
2648 if (file === null) {
2649 ctx.setRegister('blockMode', BlockMode$1.OUTPUT);
2650 yield renderer.renderTemplates(this.tpls, ctx, emitter);
2651 return;
2652 }
2653 const filepath = yield this.renderFilePath(this['file'], ctx, liquid);
2654 assert(filepath, () => `illegal filename "${filepath}"`);
2655 const templates = yield liquid._parseLayoutFile(filepath, ctx.sync, this['currentFile']);
2656 // render remaining contents and store rendered results
2657 ctx.setRegister('blockMode', BlockMode$1.STORE);
2658 const html = yield renderer.renderTemplates(this.tpls, ctx);
2659 const blocks = ctx.getRegister('blocks');
2660 // set whole content to anonymous block if anonymous doesn't specified
2661 if (blocks[''] === undefined)
2662 blocks[''] = (parent, emitter) => emitter.write(html);
2663 ctx.setRegister('blockMode', BlockMode$1.OUTPUT);
2664 // render the layout file use stored blocks
2665 ctx.push(yield hash.render(ctx));
2666 yield renderer.renderTemplates(templates, ctx, emitter);
2667 ctx.pop();
2668 }
2669};
2670
2671class BlockDrop extends Drop {
2672 constructor(
2673 // the block render from layout template
2674 superBlockRender = () => '') {
2675 super();
2676 this.superBlockRender = superBlockRender;
2677 }
2678 /**
2679 * Provide parent access in child block by
2680 * {{ block.super }}
2681 */
2682 super() {
2683 return this.superBlockRender();
2684 }
2685}
2686
2687var block = {
2688 parse(token, remainTokens) {
2689 const match = /\w+/.exec(token.args);
2690 this.block = match ? match[0] : '';
2691 this.tpls = [];
2692 this.liquid.parser.parseStream(remainTokens)
2693 .on('tag:endblock', function () { this.stop(); })
2694 .on('template', (tpl) => this.tpls.push(tpl))
2695 .on('end', () => { throw new Error(`tag ${token.getText()} not closed`); })
2696 .start();
2697 },
2698 *render(ctx, emitter) {
2699 const blockRender = this.getBlockRender(ctx);
2700 if (ctx.getRegister('blockMode') === BlockMode$1.STORE) {
2701 ctx.getRegister('blocks')[this.block] = blockRender;
2702 }
2703 else {
2704 yield blockRender(new BlockDrop(), emitter);
2705 }
2706 },
2707 getBlockRender(ctx) {
2708 const { liquid, tpls } = this;
2709 const renderChild = ctx.getRegister('blocks')[this.block];
2710 const renderCurrent = function* (superBlock, emitter) {
2711 // add {{ block.super }} support when rendering
2712 ctx.push({ block: superBlock });
2713 yield liquid.renderer.renderTemplates(tpls, ctx, emitter);
2714 ctx.pop();
2715 };
2716 return renderChild
2717 ? (superBlock, emitter) => renderChild(new BlockDrop(() => renderCurrent(superBlock, emitter)), emitter)
2718 : renderCurrent;
2719 }
2720};
2721
2722var raw = {
2723 parse: function (tagToken, remainTokens) {
2724 this.tokens = [];
2725 const stream = this.liquid.parser.parseStream(remainTokens);
2726 stream
2727 .on('token', (token) => {
2728 if (token.name === 'endraw')
2729 stream.stop();
2730 else
2731 this.tokens.push(token);
2732 })
2733 .on('end', () => {
2734 throw new Error(`tag ${tagToken.getText()} not closed`);
2735 });
2736 stream.start();
2737 },
2738 render: function () {
2739 return this.tokens.map((token) => token.getText()).join('');
2740 }
2741};
2742
2743class TablerowloopDrop extends ForloopDrop {
2744 constructor(length, cols, collection, variable) {
2745 super(length, collection, variable);
2746 this.length = length;
2747 this.cols = cols;
2748 }
2749 row() {
2750 return Math.floor(this.i / this.cols) + 1;
2751 }
2752 col0() {
2753 return (this.i % this.cols);
2754 }
2755 col() {
2756 return this.col0() + 1;
2757 }
2758 col_first() {
2759 return this.col0() === 0;
2760 }
2761 col_last() {
2762 return this.col() === this.cols;
2763 }
2764}
2765
2766var tablerow = {
2767 parse: function (tagToken, remainTokens) {
2768 const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operatorsTrie);
2769 const variable = tokenizer.readIdentifier();
2770 tokenizer.skipBlank();
2771 const tmp = tokenizer.readIdentifier();
2772 assert(tmp && tmp.content === 'in', () => `illegal tag: ${tagToken.getText()}`);
2773 this.variable = variable.content;
2774 this.collection = tokenizer.readValue();
2775 this.hash = new Hash(tokenizer.remaining());
2776 this.templates = [];
2777 let p;
2778 const stream = this.liquid.parser.parseStream(remainTokens)
2779 .on('start', () => (p = this.templates))
2780 .on('tag:endtablerow', () => stream.stop())
2781 .on('template', (tpl) => p.push(tpl))
2782 .on('end', () => {
2783 throw new Error(`tag ${tagToken.getText()} not closed`);
2784 });
2785 stream.start();
2786 },
2787 render: function* (ctx, emitter) {
2788 let collection = toEnumerable(yield evalToken(this.collection, ctx));
2789 const hash = yield this.hash.render(ctx);
2790 const offset = hash.offset || 0;
2791 const limit = (hash.limit === undefined) ? collection.length : hash.limit;
2792 collection = collection.slice(offset, offset + limit);
2793 const cols = hash.cols || collection.length;
2794 const r = this.liquid.renderer;
2795 const tablerowloop = new TablerowloopDrop(collection.length, cols, this.collection.getText(), this.variable);
2796 const scope = { tablerowloop };
2797 ctx.push(scope);
2798 for (let idx = 0; idx < collection.length; idx++, tablerowloop.next()) {
2799 scope[this.variable] = collection[idx];
2800 if (tablerowloop.col0() === 0) {
2801 if (tablerowloop.row() !== 1)
2802 emitter.write('</tr>');
2803 emitter.write(`<tr class="row${tablerowloop.row()}">`);
2804 }
2805 emitter.write(`<td class="col${tablerowloop.col()}">`);
2806 yield r.renderTemplates(this.templates, ctx, emitter);
2807 emitter.write('</td>');
2808 }
2809 if (collection.length)
2810 emitter.write('</tr>');
2811 ctx.pop();
2812 }
2813};
2814
2815var unless = {
2816 parse: function (tagToken, remainTokens) {
2817 this.branches = [];
2818 this.elseTemplates = [];
2819 let p;
2820 this.liquid.parser.parseStream(remainTokens)
2821 .on('start', () => this.branches.push({
2822 predicate: new Value(tagToken.args, this.liquid),
2823 test: isFalsy,
2824 templates: (p = [])
2825 }))
2826 .on('tag:elsif', (token) => this.branches.push({
2827 predicate: new Value(token.args, this.liquid),
2828 test: isTruthy,
2829 templates: (p = [])
2830 }))
2831 .on('tag:else', () => (p = this.elseTemplates))
2832 .on('tag:endunless', function () { this.stop(); })
2833 .on('template', (tpl) => p.push(tpl))
2834 .on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`); })
2835 .start();
2836 },
2837 render: function* (ctx, emitter) {
2838 const r = this.liquid.renderer;
2839 for (const { predicate, test, templates } of this.branches) {
2840 const value = yield predicate.value(ctx, ctx.opts.lenientIf);
2841 if (test(value, ctx)) {
2842 yield r.renderTemplates(templates, ctx, emitter);
2843 return;
2844 }
2845 }
2846 yield r.renderTemplates(this.elseTemplates, ctx, emitter);
2847 }
2848};
2849
2850var Break = {
2851 render: function (ctx, emitter) {
2852 emitter['break'] = true;
2853 }
2854};
2855
2856var Continue = {
2857 render: function (ctx, emitter) {
2858 emitter['continue'] = true;
2859 }
2860};
2861
2862var echo = {
2863 parse: function (token) {
2864 this.value = new Value(token.args, this.liquid);
2865 },
2866 render: function* (ctx, emitter) {
2867 const val = yield this.value.value(ctx, false);
2868 emitter.write(val);
2869 }
2870};
2871
2872var liquid = {
2873 parse: function (token) {
2874 const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie);
2875 const tokens = tokenizer.readLiquidTagTokens(this.liquid.options);
2876 this.tpls = this.liquid.parser.parseTokens(tokens);
2877 },
2878 render: function* (ctx, emitter) {
2879 yield this.liquid.renderer.renderTemplates(this.tpls, ctx, emitter);
2880 }
2881};
2882
2883var inlineComment = {
2884 parse: function (tagToken, remainTokens) {
2885 if (tagToken.args.search(/\n\s*[^#\s]/g) !== -1) {
2886 throw new Error('every line of an inline comment must start with a \'#\' character');
2887 }
2888 }
2889};
2890
2891const tags = {
2892 assign, 'for': For, capture, 'case': Case, comment, include, render, decrement, increment, cycle, 'if': If, layout, block, raw, tablerow, unless, 'break': Break, 'continue': Continue, echo, liquid, '#': inlineComment
2893};
2894
2895var index = /*#__PURE__*/Object.freeze({
2896 __proto__: null,
2897 'default': tags
2898});
2899
2900const filters = new Map();
2901forOwn(builtinFilters, (conf, name) => {
2902 filters.set(snakeCase(name), conf);
2903});
2904const defaultOptions = {
2905 root: ['.'],
2906 layouts: ['.'],
2907 partials: ['.'],
2908 relativeReference: true,
2909 jekyllInclude: false,
2910 cache: undefined,
2911 extname: '',
2912 fs: fs,
2913 dynamicPartials: true,
2914 jsTruthy: false,
2915 trimTagRight: false,
2916 trimTagLeft: false,
2917 trimOutputRight: false,
2918 trimOutputLeft: false,
2919 greedy: true,
2920 tagDelimiterLeft: '{%',
2921 tagDelimiterRight: '%}',
2922 outputDelimiterLeft: '{{',
2923 outputDelimiterRight: '}}',
2924 preserveTimezones: false,
2925 strictFilters: false,
2926 strictVariables: false,
2927 ownPropertyOnly: false,
2928 lenientIf: false,
2929 globals: {},
2930 keepOutputType: false,
2931 operators: defaultOperators,
2932 operatorsTrie: createTrie(defaultOperators)
2933};
2934function normalize(options) {
2935 if (options.hasOwnProperty('operators')) {
2936 options.operatorsTrie = createTrie(options.operators);
2937 }
2938 if (options.hasOwnProperty('root')) {
2939 if (!options.hasOwnProperty('partials'))
2940 options.partials = options.root;
2941 if (!options.hasOwnProperty('layouts'))
2942 options.layouts = options.root;
2943 }
2944 if (options.hasOwnProperty('cache')) {
2945 let cache;
2946 if (typeof options.cache === 'number')
2947 cache = options.cache > 0 ? new LRU(options.cache) : undefined;
2948 else if (typeof options.cache === 'object')
2949 cache = options.cache;
2950 else
2951 cache = options.cache ? new LRU(1024) : undefined;
2952 options.cache = cache;
2953 }
2954 options = Object.assign(Object.assign(Object.assign({}, defaultOptions), (options.jekyllInclude ? { dynamicPartials: false } : {})), options);
2955 if (!options.fs.dirname && options.relativeReference) {
2956 console.warn('[LiquidJS] `fs.dirname` is required for relativeReference, set relativeReference to `false` to suppress this warning, or provide implementation for `fs.dirname`');
2957 options.relativeReference = false;
2958 }
2959 options.root = normalizeDirectoryList(options.root);
2960 options.partials = normalizeDirectoryList(options.partials);
2961 options.layouts = normalizeDirectoryList(options.layouts);
2962 options.outputEscape = options.outputEscape && getOutputEscapeFunction(options.outputEscape);
2963 return options;
2964}
2965function getOutputEscapeFunction(nameOrFunction) {
2966 if (isString(nameOrFunction)) {
2967 const filterImpl = filters.get(nameOrFunction);
2968 assert(isFunction(filterImpl), `filter "${nameOrFunction}" not found`);
2969 return filterImpl;
2970 }
2971 else {
2972 assert(isFunction(nameOrFunction), '`outputEscape` need to be of type string or function');
2973 return nameOrFunction;
2974 }
2975}
2976function normalizeDirectoryList(value) {
2977 let list = [];
2978 if (isArray(value))
2979 list = value;
2980 if (isString(value))
2981 list = [value];
2982 return list;
2983}
2984
2985class Context {
2986 constructor(env = {}, opts = defaultOptions, renderOptions = {}) {
2987 var _a, _b;
2988 /**
2989 * insert a Context-level empty scope,
2990 * for tags like `{% capture %}` `{% assign %}` to operate
2991 */
2992 this.scopes = [{}];
2993 this.registers = {};
2994 this.sync = !!renderOptions.sync;
2995 this.opts = opts;
2996 this.globals = (_a = renderOptions.globals) !== null && _a !== void 0 ? _a : opts.globals;
2997 this.environments = env;
2998 this.strictVariables = (_b = renderOptions.strictVariables) !== null && _b !== void 0 ? _b : this.opts.strictVariables;
2999 }
3000 getRegister(key) {
3001 return (this.registers[key] = this.registers[key] || {});
3002 }
3003 setRegister(key, value) {
3004 return (this.registers[key] = value);
3005 }
3006 saveRegister(...keys) {
3007 return keys.map(key => [key, this.getRegister(key)]);
3008 }
3009 restoreRegister(keyValues) {
3010 return keyValues.forEach(([key, value]) => this.setRegister(key, value));
3011 }
3012 getAll() {
3013 return [this.globals, this.environments, ...this.scopes]
3014 .reduce((ctx, val) => __assign(ctx, val), {});
3015 }
3016 get(paths) {
3017 const scope = this.findScope(paths[0]);
3018 return this.getFromScope(scope, paths);
3019 }
3020 getFromScope(scope, paths) {
3021 if (isString(paths))
3022 paths = paths.split('.');
3023 return paths.reduce((scope, path, i) => {
3024 scope = readProperty(scope, path, this.opts.ownPropertyOnly);
3025 if (isNil(scope) && this.strictVariables) {
3026 throw new InternalUndefinedVariableError(paths.slice(0, i + 1).join('.'));
3027 }
3028 return scope;
3029 }, scope);
3030 }
3031 push(ctx) {
3032 return this.scopes.push(ctx);
3033 }
3034 pop() {
3035 return this.scopes.pop();
3036 }
3037 bottom() {
3038 return this.scopes[0];
3039 }
3040 findScope(key) {
3041 for (let i = this.scopes.length - 1; i >= 0; i--) {
3042 const candidate = this.scopes[i];
3043 if (key in candidate)
3044 return candidate;
3045 }
3046 if (key in this.environments)
3047 return this.environments;
3048 return this.globals;
3049 }
3050}
3051function readProperty(obj, key, ownPropertyOnly) {
3052 if (isNil(obj))
3053 return obj;
3054 obj = toLiquid(obj);
3055 if (isArray(obj) && key < 0)
3056 return obj[obj.length + +key];
3057 const jsProperty = readJSProperty(obj, key, ownPropertyOnly);
3058 if (jsProperty === undefined && obj instanceof Drop)
3059 return obj.liquidMethodMissing(key);
3060 if (isFunction(jsProperty))
3061 return jsProperty.call(obj);
3062 if (key === 'size')
3063 return readSize(obj);
3064 else if (key === 'first')
3065 return readFirst(obj);
3066 else if (key === 'last')
3067 return readLast(obj);
3068 return jsProperty;
3069}
3070function readJSProperty(obj, key, ownPropertyOnly) {
3071 if (ownPropertyOnly && !Object.hasOwnProperty.call(obj, key))
3072 return undefined;
3073 return obj[key];
3074}
3075function readFirst(obj) {
3076 if (isArray(obj))
3077 return obj[0];
3078 return obj['first'];
3079}
3080function readLast(obj) {
3081 if (isArray(obj))
3082 return obj[obj.length - 1];
3083 return obj['last'];
3084}
3085function readSize(obj) {
3086 if (obj.hasOwnProperty('size') || obj['size'] !== undefined)
3087 return obj['size'];
3088 if (isArray(obj) || isString(obj))
3089 return obj.length;
3090 if (typeof obj === 'object')
3091 return Object.keys(obj).length;
3092}
3093
3094var LookupType;
3095(function (LookupType) {
3096 LookupType["Partials"] = "partials";
3097 LookupType["Layouts"] = "layouts";
3098 LookupType["Root"] = "root";
3099})(LookupType || (LookupType = {}));
3100class Loader {
3101 constructor(options) {
3102 this.options = options;
3103 if (options.relativeReference) {
3104 const sep = options.fs.sep;
3105 assert(sep, '`fs.sep` is required for relative reference');
3106 const rRelativePath = new RegExp(['.' + sep, '..' + sep, './', '../'].map(prefix => escapeRegex(prefix)).join('|'));
3107 this.shouldLoadRelative = (referencedFile) => rRelativePath.test(referencedFile);
3108 }
3109 else {
3110 this.shouldLoadRelative = (referencedFile) => false;
3111 }
3112 this.contains = this.options.fs.contains || (() => true);
3113 }
3114 *lookup(file, type, sync, currentFile) {
3115 const { fs } = this.options;
3116 const dirs = this.options[type];
3117 for (const filepath of this.candidates(file, dirs, currentFile, type !== LookupType.Root)) {
3118 if (sync ? fs.existsSync(filepath) : yield fs.exists(filepath))
3119 return filepath;
3120 }
3121 throw this.lookupError(file, dirs);
3122 }
3123 *candidates(file, dirs, currentFile, enforceRoot) {
3124 const { fs, extname } = this.options;
3125 if (this.shouldLoadRelative(file) && currentFile) {
3126 const referenced = fs.resolve(this.dirname(currentFile), file, extname);
3127 for (const dir of dirs) {
3128 if (!enforceRoot || this.contains(dir, referenced)) {
3129 // the relatively referenced file is within one of root dirs
3130 yield referenced;
3131 break;
3132 }
3133 }
3134 }
3135 for (const dir of dirs) {
3136 const referenced = fs.resolve(dir, file, extname);
3137 if (!enforceRoot || this.contains(dir, referenced)) {
3138 yield referenced;
3139 }
3140 }
3141 if (fs.fallback !== undefined) {
3142 const filepath = fs.fallback(file);
3143 if (filepath !== undefined)
3144 yield filepath;
3145 }
3146 }
3147 dirname(path) {
3148 const fs = this.options.fs;
3149 assert(fs.dirname, '`fs.dirname` is required for relative reference');
3150 return fs.dirname(path);
3151 }
3152 lookupError(file, roots) {
3153 const err = new Error('ENOENT');
3154 err.message = `ENOENT: Failed to lookup "${file}" in "${roots}"`;
3155 err.code = 'ENOENT';
3156 return err;
3157 }
3158}
3159
3160class SimpleEmitter {
3161 constructor() {
3162 this.buffer = '';
3163 }
3164 write(html) {
3165 this.buffer += stringify(html);
3166 }
3167}
3168
3169class StreamedEmitter {
3170 constructor() {
3171 this.buffer = '';
3172 this.stream = null;
3173 throw new Error('streaming not supported in browser');
3174 }
3175}
3176
3177class KeepingTypeEmitter {
3178 constructor() {
3179 this.buffer = '';
3180 }
3181 write(html) {
3182 html = toValue(html);
3183 // This will only preserve the type if the value is isolated.
3184 // I.E:
3185 // {{ my-port }} -> 42
3186 // {{ my-host }}:{{ my-port }} -> 'host:42'
3187 if (typeof html !== 'string' && this.buffer === '') {
3188 this.buffer = html;
3189 }
3190 else {
3191 this.buffer = stringify(this.buffer) + stringify(html);
3192 }
3193 }
3194}
3195
3196class Render {
3197 renderTemplatesToNodeStream(templates, ctx) {
3198 const emitter = new StreamedEmitter();
3199 Promise.resolve().then(() => toPromise(this.renderTemplates(templates, ctx, emitter)))
3200 .then(() => emitter.end(), err => emitter.error(err));
3201 return emitter.stream;
3202 }
3203 *renderTemplates(templates, ctx, emitter) {
3204 if (!emitter) {
3205 emitter = ctx.opts.keepOutputType ? new KeepingTypeEmitter() : new SimpleEmitter();
3206 }
3207 for (const tpl of templates) {
3208 try {
3209 // if tpl.render supports emitter, it'll return empty `html`
3210 const html = yield tpl.render(ctx, emitter);
3211 // if not, it'll return an `html`, write to the emitter for it
3212 html && emitter.write(html);
3213 if (emitter['break'] || emitter['continue'])
3214 break;
3215 }
3216 catch (e) {
3217 const err = RenderError.is(e) ? e : new RenderError(e, tpl);
3218 throw err;
3219 }
3220 }
3221 return emitter.buffer;
3222 }
3223}
3224
3225class TemplateImpl {
3226 constructor(token) {
3227 this.token = token;
3228 }
3229}
3230
3231class Tag extends TemplateImpl {
3232 constructor(token, tokens, liquid) {
3233 super(token);
3234 this.name = token.name;
3235 const impl = liquid.tags.get(token.name);
3236 this.impl = Object.create(impl);
3237 this.impl.liquid = liquid;
3238 if (this.impl.parse) {
3239 this.impl.parse(token, tokens);
3240 }
3241 }
3242 *render(ctx, emitter) {
3243 const hash = (yield new Hash(this.token.args).render(ctx));
3244 const impl = this.impl;
3245 if (isFunction(impl.render))
3246 return yield impl.render(ctx, emitter, hash);
3247 }
3248}
3249
3250class Output extends TemplateImpl {
3251 constructor(token, liquid) {
3252 super(token);
3253 this.value = new Value(token.content, liquid);
3254 const filters = this.value.filters;
3255 const outputEscape = liquid.options.outputEscape;
3256 if (filters.length && filters[filters.length - 1].name === 'raw') {
3257 filters.pop();
3258 }
3259 else if (outputEscape) {
3260 filters.push(new Filter(toString.call(outputEscape), outputEscape, [], liquid));
3261 }
3262 }
3263 *render(ctx, emitter) {
3264 const val = yield this.value.value(ctx, false);
3265 emitter.write(val);
3266 }
3267}
3268
3269class HTML extends TemplateImpl {
3270 constructor(token) {
3271 super(token);
3272 this.str = token.getContent();
3273 }
3274 *render(ctx, emitter) {
3275 emitter.write(this.str);
3276 }
3277}
3278
3279class Parser {
3280 constructor(liquid) {
3281 this.liquid = liquid;
3282 this.cache = this.liquid.options.cache;
3283 this.fs = this.liquid.options.fs;
3284 this.parseFile = this.cache ? this._parseFileCached : this._parseFile;
3285 this.loader = new Loader(this.liquid.options);
3286 }
3287 parse(html, filepath) {
3288 const tokenizer = new Tokenizer(html, this.liquid.options.operatorsTrie, filepath);
3289 const tokens = tokenizer.readTopLevelTokens(this.liquid.options);
3290 return this.parseTokens(tokens);
3291 }
3292 parseTokens(tokens) {
3293 let token;
3294 const templates = [];
3295 while ((token = tokens.shift())) {
3296 templates.push(this.parseToken(token, tokens));
3297 }
3298 return templates;
3299 }
3300 parseToken(token, remainTokens) {
3301 try {
3302 if (isTagToken(token)) {
3303 return new Tag(token, remainTokens, this.liquid);
3304 }
3305 if (isOutputToken(token)) {
3306 return new Output(token, this.liquid);
3307 }
3308 return new HTML(token);
3309 }
3310 catch (e) {
3311 throw new ParseError(e, token);
3312 }
3313 }
3314 parseStream(tokens) {
3315 return new ParseStream(tokens, (token, tokens) => this.parseToken(token, tokens));
3316 }
3317 *_parseFileCached(file, sync, type = LookupType.Root, currentFile) {
3318 const cache = this.cache;
3319 const key = this.loader.shouldLoadRelative(file) ? currentFile + ',' + file : type + ':' + file;
3320 const tpls = yield cache.read(key);
3321 if (tpls)
3322 return tpls;
3323 const task = this._parseFile(file, sync, type, currentFile);
3324 // sync mode: exec the task and cache the result
3325 // async mode: cache the task before exec
3326 const taskOrTpl = sync ? yield task : toPromise(task);
3327 cache.write(key, taskOrTpl);
3328 // note: concurrent tasks will be reused, cache for failed task is removed until its end
3329 try {
3330 return yield taskOrTpl;
3331 }
3332 catch (err) {
3333 cache.remove(key);
3334 throw err;
3335 }
3336 }
3337 *_parseFile(file, sync, type = LookupType.Root, currentFile) {
3338 const filepath = yield this.loader.lookup(file, type, sync, currentFile);
3339 return this.liquid.parse(sync ? this.fs.readFileSync(filepath) : yield this.fs.readFile(filepath), filepath);
3340 }
3341}
3342
3343class TagMap {
3344 constructor() {
3345 this.impls = {};
3346 }
3347 get(name) {
3348 const impl = this.impls[name];
3349 assert(impl, () => `tag "${name}" not found`);
3350 return impl;
3351 }
3352 set(name, impl) {
3353 this.impls[name] = impl;
3354 }
3355}
3356
3357class FilterMap {
3358 constructor(strictFilters, liquid) {
3359 this.strictFilters = strictFilters;
3360 this.liquid = liquid;
3361 this.impls = {};
3362 }
3363 get(name) {
3364 const impl = this.impls[name];
3365 assert(impl || !this.strictFilters, () => `undefined filter: ${name}`);
3366 return impl;
3367 }
3368 set(name, impl) {
3369 this.impls[name] = impl;
3370 }
3371 create(name, args) {
3372 return new Filter(name, this.get(name), args, this.liquid);
3373 }
3374}
3375
3376const version = '9.40.0';
3377class Liquid {
3378 constructor(opts = {}) {
3379 this.options = normalize(opts);
3380 this.parser = new Parser(this);
3381 this.renderer = new Render();
3382 this.filters = new FilterMap(this.options.strictFilters, this);
3383 this.tags = new TagMap();
3384 forOwn(tags, (conf, name) => this.registerTag(snakeCase(name), conf));
3385 forOwn(builtinFilters, (handler, name) => this.registerFilter(snakeCase(name), handler));
3386 }
3387 parse(html, filepath) {
3388 return this.parser.parse(html, filepath);
3389 }
3390 _render(tpl, scope, renderOptions) {
3391 const ctx = new Context(scope, this.options, renderOptions);
3392 return this.renderer.renderTemplates(tpl, ctx);
3393 }
3394 render(tpl, scope, renderOptions) {
3395 return __awaiter(this, void 0, void 0, function* () {
3396 return toPromise(this._render(tpl, scope, Object.assign(Object.assign({}, renderOptions), { sync: false })));
3397 });
3398 }
3399 renderSync(tpl, scope, renderOptions) {
3400 return toValueSync(this._render(tpl, scope, Object.assign(Object.assign({}, renderOptions), { sync: true })));
3401 }
3402 renderToNodeStream(tpl, scope, renderOptions = {}) {
3403 const ctx = new Context(scope, this.options, renderOptions);
3404 return this.renderer.renderTemplatesToNodeStream(tpl, ctx);
3405 }
3406 _parseAndRender(html, scope, renderOptions) {
3407 const tpl = this.parse(html);
3408 return this._render(tpl, scope, renderOptions);
3409 }
3410 parseAndRender(html, scope, renderOptions) {
3411 return __awaiter(this, void 0, void 0, function* () {
3412 return toPromise(this._parseAndRender(html, scope, Object.assign(Object.assign({}, renderOptions), { sync: false })));
3413 });
3414 }
3415 parseAndRenderSync(html, scope, renderOptions) {
3416 return toValueSync(this._parseAndRender(html, scope, Object.assign(Object.assign({}, renderOptions), { sync: true })));
3417 }
3418 _parsePartialFile(file, sync, currentFile) {
3419 return this.parser.parseFile(file, sync, LookupType.Partials, currentFile);
3420 }
3421 _parseLayoutFile(file, sync, currentFile) {
3422 return this.parser.parseFile(file, sync, LookupType.Layouts, currentFile);
3423 }
3424 parseFile(file) {
3425 return __awaiter(this, void 0, void 0, function* () {
3426 return toPromise(this.parser.parseFile(file, false));
3427 });
3428 }
3429 parseFileSync(file) {
3430 return toValueSync(this.parser.parseFile(file, true));
3431 }
3432 renderFile(file, ctx, renderOptions) {
3433 return __awaiter(this, void 0, void 0, function* () {
3434 const templates = yield this.parseFile(file);
3435 return this.render(templates, ctx, renderOptions);
3436 });
3437 }
3438 renderFileSync(file, ctx, renderOptions) {
3439 const templates = this.parseFileSync(file);
3440 return this.renderSync(templates, ctx, renderOptions);
3441 }
3442 renderFileToNodeStream(file, scope, renderOptions) {
3443 return __awaiter(this, void 0, void 0, function* () {
3444 const templates = yield this.parseFile(file);
3445 return this.renderToNodeStream(templates, scope, renderOptions);
3446 });
3447 }
3448 _evalValue(str, ctx) {
3449 const value = new Value(str, this);
3450 return value.value(ctx, false);
3451 }
3452 evalValue(str, ctx) {
3453 return __awaiter(this, void 0, void 0, function* () {
3454 return toPromise(this._evalValue(str, ctx));
3455 });
3456 }
3457 evalValueSync(str, ctx) {
3458 return toValueSync(this._evalValue(str, ctx));
3459 }
3460 registerFilter(name, filter) {
3461 this.filters.set(name, filter);
3462 }
3463 registerTag(name, tag) {
3464 this.tags.set(name, tag);
3465 }
3466 plugin(plugin) {
3467 return plugin.call(this, Liquid);
3468 }
3469 express() {
3470 const self = this; // eslint-disable-line
3471 let firstCall = true;
3472 return function (filePath, ctx, callback) {
3473 if (firstCall) {
3474 firstCall = false;
3475 const dirs = normalizeDirectoryList(this.root);
3476 self.options.root.unshift(...dirs);
3477 self.options.layouts.unshift(...dirs);
3478 self.options.partials.unshift(...dirs);
3479 }
3480 self.renderFile(filePath, ctx).then(html => callback(null, html), callback);
3481 };
3482 }
3483}
3484
3485export { AssertionError, Context, Drop, Expression, Hash, InternalUndefinedVariableError, Liquid, LiquidError, ParseError, ParseStream, RenderError, TagToken, TimezoneDate, Token, TokenKind, TokenizationError, Tokenizer, typeGuards as TypeGuards, UndefinedVariableError, Value, assert, createTrie, defaultOperators, defaultOptions, evalQuotedToken, evalToken, builtinFilters as filters, isFalsy, isTruthy, index as tags, toPromise, toThenable, toValue, toValueSync, version };