UNPKG

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