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