UNPKG

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