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