@skeptools/skep-organization-project 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.jsii +22 -2139
  2. package/API.md +533 -418
  3. package/lib/skep-organization-project.js +36 -5
  4. package/node_modules/liquidjs/README.md +110 -67
  5. package/node_modules/liquidjs/dist/build/base64-impl-browser.d.ts +2 -0
  6. package/node_modules/liquidjs/dist/build/base64-impl-browser.spec.d.ts +1 -0
  7. package/node_modules/liquidjs/dist/build/crypto-impl-browser.d.ts +2 -0
  8. package/node_modules/liquidjs/dist/build/crypto-impl-browser.spec.d.ts +1 -0
  9. package/node_modules/liquidjs/dist/context/context.d.ts +10 -4
  10. package/node_modules/liquidjs/dist/context/scope.d.ts +2 -2
  11. package/node_modules/liquidjs/dist/drop/blank-drop.d.ts +1 -1
  12. package/node_modules/liquidjs/dist/drop/block-drop.d.ts +3 -2
  13. package/node_modules/liquidjs/dist/drop/comparable.d.ts +11 -5
  14. package/node_modules/liquidjs/dist/drop/drop.d.ts +3 -1
  15. package/node_modules/liquidjs/dist/drop/empty-drop.d.ts +1 -1
  16. package/node_modules/liquidjs/dist/drop/null-drop.d.ts +1 -1
  17. package/node_modules/liquidjs/dist/filters/array.d.ts +9 -3
  18. package/node_modules/liquidjs/dist/filters/base64-impl.d.ts +2 -0
  19. package/node_modules/liquidjs/dist/filters/base64.d.ts +9 -0
  20. package/node_modules/liquidjs/dist/filters/crypto-impl.d.ts +2 -0
  21. package/node_modules/liquidjs/dist/filters/crypto.d.ts +8 -0
  22. package/node_modules/liquidjs/dist/filters/math.d.ts +1 -1
  23. package/node_modules/liquidjs/dist/filters/misc.d.ts +2 -2
  24. package/node_modules/liquidjs/dist/filters/string.d.ts +2 -1
  25. package/node_modules/liquidjs/dist/fs/fs-impl.d.ts +2 -1
  26. package/node_modules/liquidjs/dist/fs/fs.d.ts +4 -2
  27. package/node_modules/liquidjs/dist/fs/loader.d.ts +2 -1
  28. package/node_modules/liquidjs/dist/index.d.ts +8 -3
  29. package/node_modules/liquidjs/dist/liquid-options.d.ts +8 -2
  30. package/node_modules/liquidjs/dist/liquid.browser.min.js +1 -1
  31. package/node_modules/liquidjs/dist/liquid.browser.min.js.map +1 -1
  32. package/node_modules/liquidjs/dist/liquid.browser.mjs +836 -406
  33. package/node_modules/liquidjs/dist/liquid.browser.umd.js +1683 -788
  34. package/node_modules/liquidjs/dist/liquid.browser.umd.js.map +1 -1
  35. package/node_modules/liquidjs/dist/liquid.d.ts +1 -0
  36. package/node_modules/liquidjs/dist/liquid.node.js +848 -426
  37. package/node_modules/liquidjs/dist/liquid.node.mjs +849 -422
  38. package/node_modules/liquidjs/dist/parser/parser.d.ts +1 -0
  39. package/node_modules/liquidjs/dist/parser/token-kind.d.ts +1 -0
  40. package/node_modules/liquidjs/dist/parser/tokenizer.d.ts +4 -3
  41. package/node_modules/liquidjs/dist/render/expression.d.ts +11 -1
  42. package/node_modules/liquidjs/dist/render/operator.d.ts +11 -4
  43. package/node_modules/liquidjs/dist/tags/case.d.ts +2 -2
  44. package/node_modules/liquidjs/dist/tags/for.d.ts +2 -2
  45. package/node_modules/liquidjs/dist/tags/include.d.ts +2 -0
  46. package/node_modules/liquidjs/dist/tags/layout.d.ts +1 -0
  47. package/node_modules/liquidjs/dist/tags/render.d.ts +2 -0
  48. package/node_modules/liquidjs/dist/tags/tablerow.d.ts +2 -2
  49. package/node_modules/liquidjs/dist/tokens/literal-token.d.ts +2 -2
  50. package/node_modules/liquidjs/dist/tokens/operator-token.d.ts +3 -2
  51. package/node_modules/liquidjs/dist/tokens/property-access-token.d.ts +3 -2
  52. package/node_modules/liquidjs/dist/tokens/tag-token.d.ts +1 -1
  53. package/node_modules/liquidjs/dist/tokens/value-token.d.ts +2 -1
  54. package/node_modules/liquidjs/dist/util/async.d.ts +2 -0
  55. package/node_modules/liquidjs/dist/util/liquid-date.d.ts +7 -0
  56. package/node_modules/liquidjs/dist/util/operator-trie.d.ts +4 -8
  57. package/node_modules/liquidjs/dist/util/strftime.d.ts +2 -1
  58. package/node_modules/liquidjs/dist/util/type-guards.d.ts +2 -1
  59. package/node_modules/liquidjs/dist/util/underscore.d.ts +5 -1
  60. package/node_modules/liquidjs/package.json +20 -14
  61. package/package.json +47 -41
@@ -1,6 +1,6 @@
1
1
  /*
2
- * liquidjs@10.20.1, https://github.com/harttle/liquidjs
3
- * (c) 2016-2025 harttle
2
+ * liquidjs@10.30.1, https://github.com/harttle/liquidjs
3
+ * (c) 2016-2026 harttle
4
4
  * Released under the MIT License.
5
5
  */
6
6
  class Token {
@@ -32,7 +32,7 @@ class Token {
32
32
  }
33
33
 
34
34
  class Drop {
35
- liquidMethodMissing(key) {
35
+ liquidMethodMissing(key, context) {
36
36
  return undefined;
37
37
  }
38
38
  }
@@ -53,9 +53,6 @@ function isPromise(val) {
53
53
  function isIterator(val) {
54
54
  return val && isFunction(val.next) && isFunction(val.throw) && isFunction(val.return);
55
55
  }
56
- function escapeRegex(str) {
57
- return str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
58
- }
59
56
  function stringify(value) {
60
57
  value = toValue(value);
61
58
  if (isString(value))
@@ -66,6 +63,25 @@ function stringify(value) {
66
63
  return value.map(x => stringify(x)).join('');
67
64
  return String(value);
68
65
  }
66
+ function advanceCodePoints(str, from, count) {
67
+ let index = from;
68
+ for (let i = 0; i < count && index < str.length; i++)
69
+ index += str.codePointAt(index) > 0xFFFF ? 2 : 1;
70
+ return index;
71
+ }
72
+ function codePointLength(str) {
73
+ let length = 0;
74
+ for (let index = 0; index < str.length; index = advanceCodePoints(str, index, 1))
75
+ length++;
76
+ return length;
77
+ }
78
+ function readArrayElement(arr, index, ownPropertyOnly) {
79
+ if (index < 0)
80
+ index = arr.length + index;
81
+ if (ownPropertyOnly && !hasOwnProperty.call(arr, index))
82
+ return undefined;
83
+ return arr[index];
84
+ }
69
85
  function toEnumerable(val) {
70
86
  val = toValue(val);
71
87
  if (isArray(val))
@@ -90,8 +106,7 @@ function toValue(value) {
90
106
  return (value instanceof Drop && isFunction(value.valueOf)) ? value.valueOf() : value;
91
107
  }
92
108
  function toNumber(value) {
93
- value = Number(value);
94
- return isNaN(value) ? 0 : value;
109
+ return +toValue(value) || 0;
95
110
  }
96
111
  function isNumber(value) {
97
112
  return typeof value === 'number';
@@ -163,10 +178,10 @@ function padEnd(str, length, ch = ' ') {
163
178
  }
164
179
  function pad(str, length, ch, add) {
165
180
  str = String(str);
166
- let n = length - str.length;
167
- while (n-- > 0)
168
- str = add(str, ch);
169
- return str;
181
+ const n = length - str.length;
182
+ if (n <= 0)
183
+ return str;
184
+ return add(str, ch.repeat(n));
170
185
  }
171
186
  function identify(val) {
172
187
  return val;
@@ -178,13 +193,26 @@ function changeCase(str) {
178
193
  function ellipsis(str, N) {
179
194
  return str.length > N ? str.slice(0, N - 3) + '...' : str;
180
195
  }
196
+ function orderedCompare(a, b) {
197
+ if (isNil(a) && isNil(b))
198
+ return 0;
199
+ if (isNil(a))
200
+ return 1;
201
+ if (isNil(b))
202
+ return -1;
203
+ if (a < b)
204
+ return -1;
205
+ if (a > b)
206
+ return 1;
207
+ return 0;
208
+ }
181
209
  // compare string in case-insensitive way, undefined values to the tail
182
210
  function caseInsensitiveCompare(a, b) {
183
- if (a == null && b == null)
211
+ if (isNil(a) && isNil(b))
184
212
  return 0;
185
- if (a == null)
213
+ if (isNil(a))
186
214
  return 1;
187
- if (b == null)
215
+ if (isNil(b))
188
216
  return -1;
189
217
  a = toLowerCase.call(a);
190
218
  b = toLowerCase.call(b);
@@ -197,8 +225,8 @@ function caseInsensitiveCompare(a, b) {
197
225
  function argumentsToValue(fn) {
198
226
  return function (...args) { return fn.call(this, ...args.map(toValue)); };
199
227
  }
200
- function escapeRegExp(text) {
201
- return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
228
+ function argumentsToNumber(fn) {
229
+ return function (...args) { return fn.call(this, ...args.map(toNumber)); };
202
230
  }
203
231
  /** Return an array containing unique elements from _array_. Works with nested arrays and objects. */
204
232
  function* strictUniq(array) {
@@ -364,8 +392,8 @@ function assertEmpty(predicate, message = `unexpected ${JSON.stringify(predicate
364
392
  }
365
393
 
366
394
  class NullDrop extends Drop {
367
- equals(value) {
368
- return isNil(toValue(value));
395
+ *equals(value) {
396
+ return isNil(yield toValue(value));
369
397
  }
370
398
  gt() {
371
399
  return false;
@@ -385,10 +413,10 @@ class NullDrop extends Drop {
385
413
  }
386
414
 
387
415
  class EmptyDrop extends Drop {
388
- equals(value) {
416
+ *equals(value) {
389
417
  if (value instanceof EmptyDrop)
390
418
  return false;
391
- value = toValue(value);
419
+ value = yield toValue(value);
392
420
  if (isString(value) || isArray(value))
393
421
  return value.length === 0;
394
422
  if (isObject(value))
@@ -416,14 +444,14 @@ class EmptyDrop extends Drop {
416
444
  }
417
445
 
418
446
  class BlankDrop extends EmptyDrop {
419
- equals(value) {
447
+ *equals(value) {
420
448
  if (value === false)
421
449
  return true;
422
- if (isNil(toValue(value)))
450
+ if (isNil(yield toValue(value)))
423
451
  return true;
424
452
  if (isString(value))
425
453
  return /^\s*$/.test(value);
426
- return super.equals(value);
454
+ return yield super.equals(value);
427
455
  }
428
456
  static is(value) {
429
457
  return value instanceof BlankDrop;
@@ -463,6 +491,42 @@ class ForloopDrop extends Drop {
463
491
  }
464
492
  }
465
493
 
494
+ class SimpleEmitter {
495
+ constructor() {
496
+ this.buffer = '';
497
+ }
498
+ write(html) {
499
+ this.buffer += stringify(html);
500
+ }
501
+ }
502
+
503
+ class StreamedEmitter {
504
+ constructor() {
505
+ this.buffer = '';
506
+ this.stream = null;
507
+ throw new Error('streaming not supported in browser');
508
+ }
509
+ }
510
+
511
+ class KeepingTypeEmitter {
512
+ constructor() {
513
+ this.buffer = '';
514
+ }
515
+ write(html) {
516
+ html = toValue(html);
517
+ // This will only preserve the type if the value is isolated.
518
+ // I.E:
519
+ // {{ my-port }} -> 42
520
+ // {{ my-host }}:{{ my-port }} -> 'host:42'
521
+ if (typeof html !== 'string' && this.buffer === '') {
522
+ this.buffer = html;
523
+ }
524
+ else {
525
+ this.buffer = stringify(this.buffer) + stringify(html);
526
+ }
527
+ }
528
+ }
529
+
466
530
  class BlockDrop extends Drop {
467
531
  constructor(
468
532
  // the block render from layout template
@@ -474,8 +538,10 @@ class BlockDrop extends Drop {
474
538
  * Provide parent access in child block by
475
539
  * {{ block.super }}
476
540
  */
477
- super() {
478
- return this.superBlockRender();
541
+ *super() {
542
+ const emitter = new SimpleEmitter();
543
+ yield this.superBlockRender(emitter);
544
+ return emitter.buffer;
479
545
  }
480
546
  }
481
547
 
@@ -498,7 +564,16 @@ const literalValues = {
498
564
  'blank': new BlankDrop()
499
565
  };
500
566
 
567
+ // Tries are built once per input object and reused: the Tokenizer rebuilds them
568
+ // on every instantiation, but `input` (operators/literalValues) is a stable
569
+ // reference. WeakMap-keying by `input` lets short-lived operator objects (and
570
+ // their tries) be garbage collected. The returned trie is treated as read-only
571
+ // by callers (matchTrie only reads it); do not mutate it.
572
+ const trieCache = new WeakMap();
501
573
  function createTrie(input) {
574
+ const cached = trieCache.get(input);
575
+ if (cached)
576
+ return cached;
502
577
  const trie = {};
503
578
  for (const [name, data] of Object.entries(input)) {
504
579
  let node = trie;
@@ -513,6 +588,7 @@ function createTrie(input) {
513
588
  node.data = data;
514
589
  node.end = true;
515
590
  }
591
+ trieCache.set(input, trie);
516
592
  return trie;
517
593
  }
518
594
 
@@ -552,6 +628,12 @@ function __awaiter(thisArg, _arguments, P, generator) {
552
628
  });
553
629
  }
554
630
 
631
+ function toLiquidAsync(asyncFn, syncFn) {
632
+ const syncImpl = syncFn || asyncFn;
633
+ return (sync, ...args) => {
634
+ return sync ? syncImpl(...args) : asyncFn(...args);
635
+ };
636
+ }
555
637
  // convert an async iterator to a Promise
556
638
  function toPromise(val) {
557
639
  return __awaiter(this, void 0, void 0, function* () {
@@ -562,7 +644,7 @@ function toPromise(val) {
562
644
  let next = 'next';
563
645
  do {
564
646
  const state = val[next](value);
565
- done = state.done;
647
+ done = !!state.done;
566
648
  value = state.value;
567
649
  next = 'next';
568
650
  try {
@@ -588,7 +670,7 @@ function toValueSync(val) {
588
670
  let next = 'next';
589
671
  do {
590
672
  const state = val[next](value);
591
- done = state.done;
673
+ done = !!state.done;
592
674
  value = state.value;
593
675
  next = 'next';
594
676
  if (isIterator(value)) {
@@ -618,12 +700,9 @@ function getDayOfYear(d) {
618
700
  return num + d.getDate();
619
701
  }
620
702
  function getWeekOfYear(d, startDay) {
621
- // Skip to startDay of this week
622
- const now = getDayOfYear(d) + (startDay - d.getDay());
623
- // Find the first startDay of the year
624
- const jan1 = new Date(d.getFullYear(), 0, 1);
625
- const then = (7 - jan1.getDay() + startDay);
626
- return String(Math.floor((now - then) / 7) + 1);
703
+ // Days before the first startDay of the year belong to week 0
704
+ const daysSinceStartDay = (d.getDay() - startDay + 7) % 7;
705
+ return String(Math.floor((getDayOfYear(d) - 1 - daysSinceStartDay + 7) / 7));
627
706
  }
628
707
  function isLeapYear(d) {
629
708
  const year = d.getFullYear();
@@ -641,10 +720,11 @@ function ordinal(d) {
641
720
  }
642
721
  }
643
722
  function century(d) {
644
- return parseInt(d.getFullYear().toString().substring(0, 2), 10);
723
+ return Math.floor(d.getFullYear() / 100);
645
724
  }
646
725
  // default to 0
647
726
  const padWidths = {
727
+ C: 2,
648
728
  d: 2,
649
729
  e: 2,
650
730
  H: 2,
@@ -657,7 +737,9 @@ const padWidths = {
657
737
  M: 2,
658
738
  S: 2,
659
739
  U: 2,
660
- W: 2
740
+ W: 2,
741
+ y: 2,
742
+ Y: 4
661
743
  };
662
744
  const padSpaceChars = new Set('aAbBceklpP');
663
745
  function getTimezoneOffset(d, opts) {
@@ -687,14 +769,16 @@ const formatCodes = {
687
769
  m: (d) => d.getMonth() + 1,
688
770
  M: (d) => d.getMinutes(),
689
771
  N: (d, opts) => {
772
+ var _a;
690
773
  const width = Number(opts.width) || 9;
691
- const str = String(d.getMilliseconds()).slice(0, width);
774
+ const str = padStart(String(d.getMilliseconds()), 3, '0').slice(0, width);
775
+ (_a = opts.memoryLimit) === null || _a === void 0 ? void 0 : _a.use(width - str.length);
692
776
  return padEnd(str, width, '0');
693
777
  },
694
778
  p: (d) => (d.getHours() < 12 ? 'AM' : 'PM'),
695
779
  P: (d) => (d.getHours() < 12 ? 'am' : 'pm'),
696
780
  q: (d) => ordinal(d),
697
- s: (d) => Math.round(d.getTime() / 1000),
781
+ s: (d) => Math.floor(d.dateValue() / 1000),
698
782
  S: (d) => d.getSeconds(),
699
783
  u: (d) => d.getDay() || 7,
700
784
  U: (d) => getWeekOfYear(d, 0),
@@ -702,7 +786,7 @@ const formatCodes = {
702
786
  W: (d) => getWeekOfYear(d, 1),
703
787
  x: (d) => d.toLocaleDateString(),
704
788
  X: (d) => d.toLocaleTimeString(),
705
- y: (d) => d.getFullYear().toString().slice(2, 4),
789
+ y: (d) => (d.getFullYear() % 100 + 100) % 100,
706
790
  Y: (d) => d.getFullYear(),
707
791
  z: getTimezoneOffset,
708
792
  Z: (d, opts) => d.getTimeZoneName() || getTimezoneOffset(d, opts),
@@ -711,18 +795,18 @@ const formatCodes = {
711
795
  '%': () => '%'
712
796
  };
713
797
  formatCodes.h = formatCodes.b;
714
- function strftime(d, formatStr) {
798
+ function strftime(d, formatStr, memoryLimit) {
715
799
  let output = '';
716
800
  let remaining = formatStr;
717
801
  let match;
718
802
  while ((match = rFormat.exec(remaining))) {
719
803
  output += remaining.slice(0, match.index);
720
804
  remaining = remaining.slice(match.index + match[0].length);
721
- output += format(d, match);
805
+ output += format(d, match, memoryLimit);
722
806
  }
723
807
  return output + remaining;
724
808
  }
725
- function format(d, match) {
809
+ function format(d, match, memoryLimit) {
726
810
  const [input, flagStr = '', width, modifier, conversion] = match;
727
811
  const convert = formatCodes[conversion];
728
812
  if (!convert)
@@ -730,9 +814,9 @@ function format(d, match) {
730
814
  const flags = {};
731
815
  for (const flag of flagStr)
732
816
  flags[flag] = true;
733
- let ret = String(convert(d, { flags, width, modifier }));
817
+ let ret = String(convert(d, { flags, width, modifier, memoryLimit }));
734
818
  let padChar = padSpaceChars.has(conversion) ? ' ' : '0';
735
- let padWidth = width || padWidths[conversion] || 0;
819
+ let padWidth = Number(width) || padWidths[conversion] || 0;
736
820
  if (flags['^'])
737
821
  ret = ret.toUpperCase();
738
822
  else if (flags['#'])
@@ -743,6 +827,9 @@ function format(d, match) {
743
827
  padChar = '0';
744
828
  if (flags['-'])
745
829
  padWidth = 0;
830
+ memoryLimit === null || memoryLimit === void 0 ? void 0 : memoryLimit.use(Number(padWidth) - ret.length);
831
+ if (padChar === '0' && ret[0] === '-')
832
+ return '-' + padStart(ret.slice(1), padWidth - 1, padChar);
746
833
  return padStart(ret, padWidth, padChar);
747
834
  }
748
835
 
@@ -791,6 +878,15 @@ class LiquidDate {
791
878
  getTime() {
792
879
  return this.displayDate.getTime();
793
880
  }
881
+ /**
882
+ * The underlying UTC timestamp in milliseconds, unaffected by the display
883
+ * timezone. Use this (not `getTime()`) for timezone-invariant values like
884
+ * `%s`: `getTime()` reads `displayDate`, which is deliberately shifted by
885
+ * the display timezone offset so wall-clock getters can delegate to Date.
886
+ */
887
+ dateValue() {
888
+ return this.date.getTime();
889
+ }
794
890
  getMilliseconds() {
795
891
  return this.displayDate.getMilliseconds();
796
892
  }
@@ -902,13 +998,15 @@ class Limiter {
902
998
  this.limit = limit;
903
999
  }
904
1000
  use(count) {
905
- count = toNumber(count);
906
- assert(this.base + count <= this.limit, this.message);
907
- this.base += count;
1001
+ if (+count > 0) {
1002
+ assert(this.base + +count <= this.limit, this.message);
1003
+ this.base += +count;
1004
+ }
908
1005
  }
909
1006
  check(count) {
910
- count = toNumber(count);
911
- assert(count <= this.limit, this.message);
1007
+ if (+count > 0) {
1008
+ assert(+count <= this.limit, this.message);
1009
+ }
912
1010
  }
913
1011
  }
914
1012
 
@@ -939,13 +1037,11 @@ class TagToken extends DelimitedToken {
939
1037
  const { trimTagLeft, trimTagRight, tagDelimiterLeft, tagDelimiterRight } = options;
940
1038
  const [valueBegin, valueEnd] = [begin + tagDelimiterLeft.length, end - tagDelimiterRight.length];
941
1039
  super(TokenKind.Tag, [valueBegin, valueEnd], input, begin, end, trimTagLeft, trimTagRight, file);
942
- this.tokenizer = new Tokenizer(input, options.operators, file, this.contentRange);
1040
+ this.tokenizer = new Tokenizer(input, options.operators, file, this.contentRange, options.groupedExpressions);
943
1041
  this.name = this.tokenizer.readTagName();
944
1042
  this.tokenizer.assert(this.name, `illegal tag syntax, tag name expected`);
945
1043
  this.tokenizer.skipBlank();
946
- }
947
- get args() {
948
- return this.tokenizer.input.slice(this.tokenizer.p, this.contentRange[1]);
1044
+ this.args = this.tokenizer.input.slice(this.tokenizer.p, this.contentRange[1]);
949
1045
  }
950
1046
  }
951
1047
 
@@ -1040,8 +1136,7 @@ class OperatorToken extends Token {
1040
1136
  this.operator = this.getText();
1041
1137
  }
1042
1138
  getPrecedence() {
1043
- const key = this.getText();
1044
- return key in operatorPrecedences ? operatorPrecedences[key] : 1;
1139
+ return this.operator in operatorPrecedences ? operatorPrecedences[this.operator] : 1;
1045
1140
  }
1046
1141
  }
1047
1142
 
@@ -1155,7 +1250,7 @@ class RangeToken extends Token {
1155
1250
  class LiquidTagToken extends DelimitedToken {
1156
1251
  constructor(input, begin, end, options, file) {
1157
1252
  super(TokenKind.Tag, [begin, end], input, begin, end, false, false, file);
1158
- this.tokenizer = new Tokenizer(input, options.operators, file, this.contentRange);
1253
+ this.tokenizer = new Tokenizer(input, options.operators, file, this.contentRange, options.groupedExpressions);
1159
1254
  this.name = this.tokenizer.readTagName();
1160
1255
  this.tokenizer.assert(this.name, 'illegal liquid tag syntax');
1161
1256
  this.tokenizer.skipBlank();
@@ -1191,42 +1286,6 @@ function getPerformance() {
1191
1286
  polyfill;
1192
1287
  }
1193
1288
 
1194
- class SimpleEmitter {
1195
- constructor() {
1196
- this.buffer = '';
1197
- }
1198
- write(html) {
1199
- this.buffer += stringify(html);
1200
- }
1201
- }
1202
-
1203
- class StreamedEmitter {
1204
- constructor() {
1205
- this.buffer = '';
1206
- this.stream = null;
1207
- throw new Error('streaming not supported in browser');
1208
- }
1209
- }
1210
-
1211
- class KeepingTypeEmitter {
1212
- constructor() {
1213
- this.buffer = '';
1214
- }
1215
- write(html) {
1216
- html = toValue(html);
1217
- // This will only preserve the type if the value is isolated.
1218
- // I.E:
1219
- // {{ my-port }} -> 42
1220
- // {{ my-host }}:{{ my-port }} -> 'host:42'
1221
- if (typeof html !== 'string' && this.buffer === '') {
1222
- this.buffer = html;
1223
- }
1224
- else {
1225
- this.buffer = stringify(this.buffer) + stringify(html);
1226
- }
1227
- }
1228
- }
1229
-
1230
1289
  class Render {
1231
1290
  renderTemplatesToNodeStream(templates, ctx) {
1232
1291
  const emitter = new StreamedEmitter();
@@ -1238,6 +1297,7 @@ class Render {
1238
1297
  if (!emitter) {
1239
1298
  emitter = ctx.opts.keepOutputType ? new KeepingTypeEmitter() : new SimpleEmitter();
1240
1299
  }
1300
+ ctx.renderLimit.check(getPerformance().now());
1241
1301
  const errors = [];
1242
1302
  for (const tpl of templates) {
1243
1303
  ctx.renderLimit.check(getPerformance().now());
@@ -1264,6 +1324,33 @@ class Render {
1264
1324
  }
1265
1325
  }
1266
1326
 
1327
+ function isKeyValuePair(arr) {
1328
+ return isArray(arr);
1329
+ }
1330
+
1331
+ class Filter {
1332
+ constructor(token, options, liquid) {
1333
+ this.token = token;
1334
+ this.name = token.name;
1335
+ this.handler = isFunction(options)
1336
+ ? options
1337
+ : (isFunction(options === null || options === void 0 ? void 0 : options.handler) ? options.handler : identify);
1338
+ this.raw = !isFunction(options) && !!(options === null || options === void 0 ? void 0 : options.raw);
1339
+ this.args = token.args;
1340
+ this.liquid = liquid;
1341
+ }
1342
+ *render(value, context) {
1343
+ const argv = [];
1344
+ for (const arg of this.args) {
1345
+ if (isKeyValuePair(arg))
1346
+ argv.push([arg[0], yield evalToken(arg[1], context)]);
1347
+ else
1348
+ argv.push(yield evalToken(arg, context));
1349
+ }
1350
+ return yield this.handler.apply({ context, token: this.token, liquid: this.liquid }, [value, ...argv]);
1351
+ }
1352
+ }
1353
+
1267
1354
  class Expression {
1268
1355
  constructor(tokens) {
1269
1356
  this.postfix = [...toPostfix(tokens)];
@@ -1294,6 +1381,11 @@ class Expression {
1294
1381
  return !!this.postfix.length;
1295
1382
  }
1296
1383
  }
1384
+ /**
1385
+ * Evaluate `token`, preserving Drops.
1386
+ * Operators use this so `Comparable` drops (`empty`, `nil`, `blank`) and custom drops
1387
+ * are compared as drops rather than as their `valueOf()` result.
1388
+ */
1297
1389
  function* evalToken(token, ctx, lenient = false) {
1298
1390
  if (!token)
1299
1391
  return;
@@ -1303,11 +1395,32 @@ function* evalToken(token, ctx, lenient = false) {
1303
1395
  return yield evalPropertyAccessToken(token, ctx, lenient);
1304
1396
  if (isRangeToken(token))
1305
1397
  return yield evalRangeToken(token, ctx);
1398
+ if (isFilteredValueToken(token))
1399
+ return yield evalFilteredValueToken(token, ctx, lenient);
1400
+ }
1401
+ function* evalFilteredValueToken(token, ctx, lenient) {
1402
+ assert(ctx.liquid, 'FilteredValueToken evaluation requires liquid instance in context');
1403
+ lenient = lenient || (ctx.opts.lenientIf && token.filters.length > 0 && token.filters[0].name === 'default');
1404
+ let val = yield token.initial.evaluate(ctx, lenient);
1405
+ for (const filterToken of token.filters) {
1406
+ const filterImpl = ctx.liquid.filters[filterToken.name];
1407
+ assert(filterImpl || !ctx.liquid.options.strictFilters, () => `undefined filter: ${filterToken.name}`);
1408
+ const filter = new Filter(filterToken, filterImpl, ctx.liquid);
1409
+ val = yield filter.render(val, ctx);
1410
+ }
1411
+ return val;
1412
+ }
1413
+ /**
1414
+ * Evaluate `token` to the value filters and tags consume.
1415
+ * Awaits a promise returned by `Drop.valueOf()`.
1416
+ */
1417
+ function* evalTokenValue(token, ctx, lenient = false) {
1418
+ return yield toValue(yield evalToken(token, ctx, lenient));
1306
1419
  }
1307
1420
  function* evalPropertyAccessToken(token, ctx, lenient) {
1308
1421
  const props = [];
1309
1422
  for (const prop of token.props) {
1310
- props.push((yield evalToken(prop, ctx, false)));
1423
+ props.push((yield evalTokenValue(prop, ctx, false)));
1311
1424
  }
1312
1425
  try {
1313
1426
  if (token.variable) {
@@ -1328,8 +1441,8 @@ function evalQuotedToken(token) {
1328
1441
  return token.content;
1329
1442
  }
1330
1443
  function* evalRangeToken(token, ctx) {
1331
- const low = yield evalToken(token.lhs, ctx);
1332
- const high = yield evalToken(token.rhs, ctx);
1444
+ const low = (yield evalTokenValue(token.lhs, ctx));
1445
+ const high = (yield evalTokenValue(token.rhs, ctx));
1333
1446
  ctx.memoryLimit.use(high - low + 1);
1334
1447
  return range(+low, +high + 1);
1335
1448
  }
@@ -1364,67 +1477,75 @@ function isFalsy(val, ctx) {
1364
1477
  }
1365
1478
 
1366
1479
  const defaultOperators = {
1367
- '==': equals,
1368
- '!=': (l, r) => !equals(l, r),
1369
- '>': (l, r) => {
1480
+ '==': function* (l, r) { return yield equals(l, r); },
1481
+ '!=': function* (l, r) { return !(yield equals(l, r)); },
1482
+ '>': function* (l, r) {
1370
1483
  if (isComparable(l))
1371
- return l.gt(r);
1484
+ return yield l.gt(r);
1372
1485
  if (isComparable(r))
1373
- return r.lt(l);
1374
- return toValue(l) > toValue(r);
1486
+ return yield r.lt(l);
1487
+ return (yield toValue(l)) > (yield toValue(r));
1375
1488
  },
1376
- '<': (l, r) => {
1489
+ '<': function* (l, r) {
1377
1490
  if (isComparable(l))
1378
- return l.lt(r);
1491
+ return yield l.lt(r);
1379
1492
  if (isComparable(r))
1380
- return r.gt(l);
1381
- return toValue(l) < toValue(r);
1493
+ return yield r.gt(l);
1494
+ return (yield toValue(l)) < (yield toValue(r));
1382
1495
  },
1383
- '>=': (l, r) => {
1496
+ '>=': function* (l, r) {
1384
1497
  if (isComparable(l))
1385
- return l.geq(r);
1498
+ return yield l.geq(r);
1386
1499
  if (isComparable(r))
1387
- return r.leq(l);
1388
- return toValue(l) >= toValue(r);
1500
+ return yield r.leq(l);
1501
+ return (yield toValue(l)) >= (yield toValue(r));
1389
1502
  },
1390
- '<=': (l, r) => {
1503
+ '<=': function* (l, r) {
1391
1504
  if (isComparable(l))
1392
- return l.leq(r);
1505
+ return yield l.leq(r);
1393
1506
  if (isComparable(r))
1394
- return r.geq(l);
1395
- return toValue(l) <= toValue(r);
1507
+ return yield r.geq(l);
1508
+ return (yield toValue(l)) <= (yield toValue(r));
1396
1509
  },
1397
- 'contains': (l, r) => {
1398
- l = toValue(l);
1510
+ 'contains': function* (l, r) {
1511
+ l = yield toValue(l);
1399
1512
  if (isArray(l))
1400
- return l.some((i) => equals(i, r));
1513
+ return yield arrayIncludes(l, r);
1401
1514
  if (isFunction(l === null || l === void 0 ? void 0 : l.indexOf))
1402
- return l.indexOf(toValue(r)) > -1;
1515
+ return l.indexOf(yield toValue(r)) > -1;
1403
1516
  return false;
1404
1517
  },
1405
- 'not': (v, ctx) => isFalsy(toValue(v), ctx),
1406
- 'and': (l, r, ctx) => isTruthy(toValue(l), ctx) && isTruthy(toValue(r), ctx),
1407
- 'or': (l, r, ctx) => isTruthy(toValue(l), ctx) || isTruthy(toValue(r), ctx)
1518
+ 'not': function* (v, ctx) { return isFalsy(yield toValue(v), ctx); },
1519
+ 'and': function* (l, r, ctx) { return isTruthy(yield toValue(l), ctx) && isTruthy(yield toValue(r), ctx); },
1520
+ 'or': function* (l, r, ctx) { return isTruthy(yield toValue(l), ctx) || isTruthy(yield toValue(r), ctx); }
1408
1521
  };
1409
- function equals(lhs, rhs) {
1522
+ function* equals(lhs, rhs) {
1410
1523
  if (isComparable(lhs))
1411
- return lhs.equals(rhs);
1524
+ return yield lhs.equals(rhs);
1412
1525
  if (isComparable(rhs))
1413
- return rhs.equals(lhs);
1414
- lhs = toValue(lhs);
1415
- rhs = toValue(rhs);
1526
+ return yield rhs.equals(lhs);
1527
+ lhs = yield toValue(lhs);
1528
+ rhs = yield toValue(rhs);
1416
1529
  if (isArray(lhs)) {
1417
- return isArray(rhs) && arrayEquals(lhs, rhs);
1530
+ return isArray(rhs) && (yield arrayEquals(lhs, rhs));
1418
1531
  }
1419
1532
  return lhs === rhs;
1420
1533
  }
1421
- function arrayEquals(lhs, rhs) {
1534
+ function* arrayEquals(lhs, rhs) {
1422
1535
  if (lhs.length !== rhs.length)
1423
1536
  return false;
1424
- return !lhs.some((value, i) => !equals(value, rhs[i]));
1537
+ for (let i = 0; i < lhs.length; i++) {
1538
+ if (!(yield equals(lhs[i], rhs[i])))
1539
+ return false;
1540
+ }
1541
+ return true;
1425
1542
  }
1426
- function arrayIncludes(arr, item) {
1427
- return arr.some(value => equals(value, item));
1543
+ function* arrayIncludes(arr, item) {
1544
+ for (const value of arr) {
1545
+ if (yield equals(value, item))
1546
+ return true;
1547
+ }
1548
+ return false;
1428
1549
  }
1429
1550
 
1430
1551
  class Node {
@@ -1560,6 +1681,20 @@ var fs = /*#__PURE__*/Object.freeze({
1560
1681
  sep: sep
1561
1682
  });
1562
1683
 
1684
+ function chargeJsonReplacerValue(memoryLimit, val) {
1685
+ if (typeof val === 'string') {
1686
+ memoryLimit.use(val.length);
1687
+ }
1688
+ else if (val === null || typeof val === 'number' || typeof val === 'boolean') {
1689
+ memoryLimit.use(JSON.stringify(val).length);
1690
+ }
1691
+ else if (Array.isArray(val)) {
1692
+ memoryLimit.use(val.length + 1);
1693
+ }
1694
+ else if (typeof val === 'object') {
1695
+ memoryLimit.use(2);
1696
+ }
1697
+ }
1563
1698
  function defaultFilter(value, defaultValue, ...args) {
1564
1699
  value = toValue(value);
1565
1700
  if (isArray(value) || isString(value))
@@ -1569,19 +1704,29 @@ function defaultFilter(value, defaultValue, ...args) {
1569
1704
  return isFalsy(value, this.context) ? defaultValue : value;
1570
1705
  }
1571
1706
  function json(value, space = 0) {
1572
- return JSON.stringify(value, null, space);
1707
+ const memoryLimit = this.context.memoryLimit;
1708
+ return JSON.stringify(value, (_key, val) => {
1709
+ chargeJsonReplacerValue(memoryLimit, val);
1710
+ return val;
1711
+ }, space);
1573
1712
  }
1574
1713
  function inspect(value, space = 0) {
1714
+ const memoryLimit = this.context.memoryLimit;
1575
1715
  const ancestors = [];
1576
1716
  return JSON.stringify(value, function (_key, value) {
1577
- if (typeof value !== 'object' || value === null)
1717
+ if (typeof value !== 'object' || value === null) {
1718
+ chargeJsonReplacerValue(memoryLimit, value);
1578
1719
  return value;
1720
+ }
1579
1721
  // `this` is the object that value is contained in, i.e., its direct parent.
1580
1722
  while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this)
1581
1723
  ancestors.pop();
1582
- if (ancestors.includes(value))
1724
+ if (ancestors.includes(value)) {
1725
+ memoryLimit.use('[Circular]'.length);
1583
1726
  return '[Circular]';
1727
+ }
1584
1728
  ancestors.push(value);
1729
+ chargeJsonReplacerValue(memoryLimit, value);
1585
1730
  return value;
1586
1731
  }, space);
1587
1732
  }
@@ -1636,10 +1781,33 @@ function newline_to_br(v) {
1636
1781
  this.context.memoryLimit.use(str.length);
1637
1782
  return str.replace(/\r?\n/gm, '<br />\n');
1638
1783
  }
1784
+ // Raw-text blocks (HTML5) plus '<...>' as the catch-all kind; a regex
1785
+ // equivalent is O(n^2) in V8 on unclosed openers.
1639
1786
  function strip_html(v) {
1640
1787
  const str = stringify(v);
1641
1788
  this.context.memoryLimit.use(str.length);
1642
- return str.replace(/<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>|<.*?>|<!--[\s\S]*?-->/g, '');
1789
+ const blocks = new Map([['<script', '</script>'], ['<style', '</style>'], ['<!--', '-->'], ['<', '>']]);
1790
+ let out = '';
1791
+ let i = 0;
1792
+ while (i < str.length) {
1793
+ const lt = str.indexOf('<', i);
1794
+ if (lt < 0)
1795
+ return out + str.slice(i);
1796
+ out += str.slice(i, lt);
1797
+ for (const [opener, closer] of blocks) {
1798
+ if (!str.startsWith(opener, lt))
1799
+ continue;
1800
+ const e = str.indexOf(closer, lt + opener.length);
1801
+ if (e >= 0) {
1802
+ i = e + closer.length;
1803
+ break;
1804
+ }
1805
+ blocks.delete(opener);
1806
+ }
1807
+ if (i <= lt)
1808
+ return out + str.slice(lt);
1809
+ }
1810
+ return out;
1643
1811
  }
1644
1812
 
1645
1813
  var htmlFilters = /*#__PURE__*/Object.freeze({
@@ -1730,6 +1898,7 @@ const defaultOptions = {
1730
1898
  globals: {},
1731
1899
  keepOutputType: false,
1732
1900
  operators: defaultOperators,
1901
+ groupedExpressions: false,
1733
1902
  memoryLimit: Infinity,
1734
1903
  parseLimit: Infinity,
1735
1904
  renderLimit: Infinity
@@ -1826,7 +1995,7 @@ function trimRight(token, greedy) {
1826
1995
  }
1827
1996
 
1828
1997
  class Tokenizer {
1829
- constructor(input, operators = defaultOptions.operators, file, range) {
1998
+ constructor(input, operators = defaultOptions.operators, file, range, groupedExpressions = false) {
1830
1999
  this.input = input;
1831
2000
  this.file = file;
1832
2001
  this.rawBeginAt = -1;
@@ -1834,6 +2003,7 @@ class Tokenizer {
1834
2003
  this.N = range ? range[1] : input.length;
1835
2004
  this.opTrie = createTrie(operators);
1836
2005
  this.literalTrie = createTrie(literalValues);
2006
+ this.groupedExpressions = groupedExpressions;
1837
2007
  }
1838
2008
  readExpression() {
1839
2009
  return new Expression(this.readExpressionTokens());
@@ -1895,6 +2065,8 @@ class Tokenizer {
1895
2065
  this.skipBlank();
1896
2066
  if (this.end())
1897
2067
  return null;
2068
+ if (this.peek() === ')' || this.peek() === ',')
2069
+ return null;
1898
2070
  this.assert(this.read() === '|', `expected "|" before filter`);
1899
2071
  const name = this.readIdentifier();
1900
2072
  if (!name.size()) {
@@ -1909,10 +2081,10 @@ class Tokenizer {
1909
2081
  const arg = this.readFilterArg();
1910
2082
  arg && args.push(arg);
1911
2083
  this.skipBlank();
1912
- this.assert(this.end() || this.peek() === ',' || this.peek() === '|', () => `unexpected character ${this.snapshot()}`);
2084
+ this.assert(this.end() || this.peek() === ',' || this.peek() === '|' || this.peek() === ')', () => `unexpected character ${this.snapshot()}`);
1913
2085
  } while (this.peek() === ',');
1914
2086
  }
1915
- else if (this.peek() === '|' || this.end()) ;
2087
+ else if (this.peek() === '|' || this.peek() === ')' || this.peek() === ',' || this.end()) ;
1916
2088
  else {
1917
2089
  throw this.error('expected ":" after filter name');
1918
2090
  }
@@ -2118,7 +2290,10 @@ class Tokenizer {
2118
2290
  readValue() {
2119
2291
  this.skipBlank();
2120
2292
  const begin = this.p;
2121
- const variable = this.readLiteral() || this.readQuoted() || this.readRange() || this.readNumber();
2293
+ let variable = this.readLiteral() || this.readQuoted() || this.readNumber();
2294
+ if (!variable && this.peek() === '(') {
2295
+ variable = this.readGroupOrRange();
2296
+ }
2122
2297
  const props = this.readProperties(!variable);
2123
2298
  if (!props.length)
2124
2299
  return variable;
@@ -2197,17 +2372,30 @@ class Tokenizer {
2197
2372
  this.p = end;
2198
2373
  return literal;
2199
2374
  }
2200
- readRange() {
2375
+ readGroupOrRange() {
2201
2376
  this.skipBlank();
2202
2377
  const begin = this.p;
2203
2378
  if (this.peek() !== '(')
2204
2379
  return;
2205
2380
  ++this.p;
2206
2381
  const lhs = this.readValueOrThrow();
2207
- this.p += 2;
2208
- const rhs = this.readValueOrThrow();
2209
- ++this.p;
2210
- return new RangeToken(this.input, begin, this.p, lhs, rhs, this.file);
2382
+ this.skipBlank();
2383
+ if (this.peek() === '.' && this.peek(1) === '.') {
2384
+ this.p += 2;
2385
+ const rhs = this.readValueOrThrow();
2386
+ this.skipBlank();
2387
+ this.assert(this.read() === ')', 'invalid range syntax');
2388
+ return new RangeToken(this.input, begin, this.p, lhs, rhs, this.file);
2389
+ }
2390
+ if (this.groupedExpressions) {
2391
+ const initial = new Expression([lhs, ...this.readExpressionTokens()]);
2392
+ this.assert(initial.valid(), () => `invalid value expression: ${this.snapshot()}`);
2393
+ const filters = this.readFilters();
2394
+ this.skipBlank();
2395
+ this.assert(this.read() === ')', 'unbalanced parentheses');
2396
+ return new FilteredValueToken(initial, filters, this.input, begin, this.p, this.file);
2397
+ }
2398
+ throw this.error('invalid range syntax');
2211
2399
  }
2212
2400
  readValueOrThrow() {
2213
2401
  const value = this.readValue();
@@ -2234,7 +2422,7 @@ class Tokenizer {
2234
2422
  }
2235
2423
  *readFileNameTemplate(options) {
2236
2424
  const { outputDelimiterLeft } = options;
2237
- const htmlStopStrings = [',', ' ', outputDelimiterLeft];
2425
+ const htmlStopStrings = [',', ' ', '\r', '\n', '\t', outputDelimiterLeft];
2238
2426
  const htmlStopStringSet = new Set(htmlStopStrings);
2239
2427
  // break on ',' and ' ', outputDelimiterLeft only stops HTML token
2240
2428
  while (this.p < this.N && !htmlStopStringSet.has(this.peek())) {
@@ -2361,33 +2549,6 @@ function createTagClass(options) {
2361
2549
  };
2362
2550
  }
2363
2551
 
2364
- function isKeyValuePair(arr) {
2365
- return isArray(arr);
2366
- }
2367
-
2368
- class Filter {
2369
- constructor(token, options, liquid) {
2370
- this.token = token;
2371
- this.name = token.name;
2372
- this.handler = isFunction(options)
2373
- ? options
2374
- : (isFunction(options === null || options === void 0 ? void 0 : options.handler) ? options.handler : identify);
2375
- this.raw = !isFunction(options) && !!(options === null || options === void 0 ? void 0 : options.raw);
2376
- this.args = token.args;
2377
- this.liquid = liquid;
2378
- }
2379
- *render(value, context) {
2380
- const argv = [];
2381
- for (const arg of this.args) {
2382
- if (isKeyValuePair(arg))
2383
- argv.push([arg[0], yield evalToken(arg[1], context)]);
2384
- else
2385
- argv.push(yield evalToken(arg, context));
2386
- }
2387
- return yield this.handler.apply({ context, token: this.token, liquid: this.liquid }, [value, ...argv]);
2388
- }
2389
- }
2390
-
2391
2552
  class Value {
2392
2553
  /**
2393
2554
  * @param str the value to be valuated, eg.: "foobar" | truncate: 3
@@ -2395,7 +2556,7 @@ class Value {
2395
2556
  constructor(input, liquid) {
2396
2557
  this.filters = [];
2397
2558
  const token = typeof input === 'string'
2398
- ? new Tokenizer(input, liquid.options.operators).readFilteredValue()
2559
+ ? new Tokenizer(input, liquid.options.operators, undefined, undefined, liquid.options.groupedExpressions).readFilteredValue()
2399
2560
  : input;
2400
2561
  this.initial = token.initial;
2401
2562
  this.filters = token.filters.map(token => new Filter(token, this.getFilter(liquid, token.name), liquid));
@@ -2419,7 +2580,7 @@ class Output extends TemplateImpl {
2419
2580
  constructor(token, liquid) {
2420
2581
  var _a;
2421
2582
  super(token);
2422
- const tokenizer = new Tokenizer(token.input, liquid.options.operators, token.file, token.contentRange);
2583
+ const tokenizer = new Tokenizer(token.input, liquid.options.operators, token.file, token.contentRange, liquid.options.groupedExpressions);
2423
2584
  this.value = new Value(tokenizer.readFilteredValue(), liquid);
2424
2585
  const filters = this.value.filters;
2425
2586
  const outputEscape = liquid.options.outputEscape;
@@ -2429,7 +2590,7 @@ class Output extends TemplateImpl {
2429
2590
  }
2430
2591
  }
2431
2592
  *render(ctx, emitter) {
2432
- const val = yield this.value.value(ctx, false);
2593
+ const val = yield toValue(yield this.value.value(ctx, false));
2433
2594
  emitter.write(val);
2434
2595
  }
2435
2596
  *arguments() {
@@ -2705,10 +2866,31 @@ function* extractValueTokenVariables(token) {
2705
2866
  yield* extractValueTokenVariables(token.lhs);
2706
2867
  yield* extractValueTokenVariables(token.rhs);
2707
2868
  }
2869
+ else if (isFilteredValueToken(token)) {
2870
+ yield* extractGroupedExpressionTokenVariables(token);
2871
+ }
2708
2872
  else if (isPropertyAccessToken(token)) {
2709
2873
  yield extractPropertyAccessVariable(token);
2710
2874
  }
2711
2875
  }
2876
+ function* extractGroupedExpressionTokenVariables(token) {
2877
+ if (!isFilteredValueToken(token))
2878
+ return;
2879
+ for (const t of token.initial.postfix) {
2880
+ if (isValueToken(t))
2881
+ yield* extractValueTokenVariables(t);
2882
+ }
2883
+ for (const filter of token.filters) {
2884
+ for (const arg of filter.args) {
2885
+ if (isKeyValuePair(arg) && arg[1]) {
2886
+ yield* extractValueTokenVariables(arg[1]);
2887
+ }
2888
+ else if (isValueToken(arg)) {
2889
+ yield* extractValueTokenVariables(arg);
2890
+ }
2891
+ }
2892
+ }
2893
+ }
2712
2894
  function extractPropertyAccessVariable(token) {
2713
2895
  const segments = [];
2714
2896
  // token is not guaranteed to have `file` set. We'll try to get it from a prop if not.
@@ -2785,44 +2967,47 @@ var LookupType;
2785
2967
  })(LookupType || (LookupType = {}));
2786
2968
  class Loader {
2787
2969
  constructor(options) {
2970
+ var _a, _b, _c, _d;
2788
2971
  this.options = options;
2789
2972
  if (options.relativeReference) {
2790
2973
  const sep = options.fs.sep;
2791
2974
  assert(sep, '`fs.sep` is required for relative reference');
2792
- const rRelativePath = new RegExp(['.' + sep, '..' + sep, './', '../'].map(prefix => escapeRegex(prefix)).join('|'));
2793
- this.shouldLoadRelative = (referencedFile) => rRelativePath.test(referencedFile);
2975
+ const prefixes = ['.' + sep, '..' + sep, './', '../'];
2976
+ this.shouldLoadRelative = (referencedFile) => prefixes.some(prefix => referencedFile.startsWith(prefix));
2794
2977
  }
2795
2978
  else {
2796
2979
  this.shouldLoadRelative = (_referencedFile) => false;
2797
2980
  }
2798
- this.contains = this.options.fs.contains || (() => true);
2981
+ const fs = options.fs;
2982
+ this.contains = toLiquidAsync(((_a = fs.contains) === null || _a === void 0 ? void 0 : _a.bind(fs)) || (() => __awaiter(this, void 0, void 0, function* () { return true; })), ((_b = fs.containsSync) === null || _b === void 0 ? void 0 : _b.bind(fs)) || (() => true));
2983
+ this.exists = toLiquidAsync(((_c = fs.exists) === null || _c === void 0 ? void 0 : _c.bind(fs)) || (() => __awaiter(this, void 0, void 0, function* () { return false; })), (_d = fs.existsSync) === null || _d === void 0 ? void 0 : _d.bind(fs));
2799
2984
  }
2800
2985
  *lookup(file, type, sync, currentFile) {
2801
- const { fs } = this.options;
2802
2986
  const dirs = this.options[type];
2803
- for (const filepath of this.candidates(file, dirs, currentFile, type !== LookupType.Root)) {
2804
- if (sync ? fs.existsSync(filepath) : yield fs.exists(filepath))
2987
+ for (const filepath of this.candidates(file, dirs, currentFile)) {
2988
+ let allowed = false;
2989
+ for (const dir of dirs) {
2990
+ if (yield this.contains(!!sync, dir, filepath)) {
2991
+ allowed = true;
2992
+ break;
2993
+ }
2994
+ }
2995
+ if (!allowed)
2996
+ continue;
2997
+ if (yield this.exists(!!sync, filepath))
2805
2998
  return filepath;
2806
2999
  }
2807
3000
  throw this.lookupError(file, dirs);
2808
3001
  }
2809
- *candidates(file, dirs, currentFile, enforceRoot) {
3002
+ *candidates(file, dirs, currentFile) {
2810
3003
  const { fs, extname } = this.options;
2811
3004
  if (this.shouldLoadRelative(file) && currentFile) {
2812
3005
  const referenced = fs.resolve(this.dirname(currentFile), file, extname);
2813
- for (const dir of dirs) {
2814
- if (!enforceRoot || this.contains(dir, referenced)) {
2815
- // the relatively referenced file is within one of root dirs
2816
- yield referenced;
2817
- break;
2818
- }
2819
- }
3006
+ yield referenced;
2820
3007
  }
2821
3008
  for (const dir of dirs) {
2822
3009
  const referenced = fs.resolve(dir, file, extname);
2823
- if (!enforceRoot || this.contains(dir, referenced)) {
2824
- yield referenced;
2825
- }
3010
+ yield referenced;
2826
3011
  }
2827
3012
  if (fs.fallback !== undefined) {
2828
3013
  const filepath = fs.fallback(file);
@@ -2845,17 +3030,19 @@ class Loader {
2845
3030
 
2846
3031
  class Parser {
2847
3032
  constructor(liquid) {
3033
+ var _a, _b;
2848
3034
  this.liquid = liquid;
2849
3035
  this.cache = this.liquid.options.cache;
2850
3036
  this.fs = this.liquid.options.fs;
2851
3037
  this.parseFile = this.cache ? this._parseFileCached : this._parseFile;
2852
3038
  this.loader = new Loader(this.liquid.options);
2853
3039
  this.parseLimit = new Limiter('parse length', liquid.options.parseLimit);
3040
+ this.readFile = toLiquidAsync(((_a = this.fs.readFile) === null || _a === void 0 ? void 0 : _a.bind(this.fs)) || (() => __awaiter(this, void 0, void 0, function* () { throw new Error('readFile not implemented'); })), (_b = this.fs.readFileSync) === null || _b === void 0 ? void 0 : _b.bind(this.fs));
2854
3041
  }
2855
3042
  parse(html, filepath) {
2856
3043
  html = String(html);
2857
3044
  this.parseLimit.use(html.length);
2858
- const tokenizer = new Tokenizer(html, this.liquid.options.operators, filepath);
3045
+ const tokenizer = new Tokenizer(html, this.liquid.options.operators, filepath, undefined, this.liquid.options.groupedExpressions);
2859
3046
  const tokens = tokenizer.readTopLevelTokens(this.liquid.options);
2860
3047
  return this.parseTokens(tokens);
2861
3048
  }
@@ -2921,7 +3108,7 @@ class Parser {
2921
3108
  }
2922
3109
  *_parseFile(file, sync, type = LookupType.Root, currentFile) {
2923
3110
  const filepath = yield this.loader.lookup(file, type, sync, currentFile);
2924
- return this.parse(sync ? this.fs.readFileSync(filepath) : yield this.fs.readFile(filepath), filepath);
3111
+ return this.parse(yield this.readFile(!!sync, filepath), filepath);
2925
3112
  }
2926
3113
  }
2927
3114
 
@@ -2940,6 +3127,7 @@ var TokenKind;
2940
3127
  TokenKind[TokenKind["Quoted"] = 1024] = "Quoted";
2941
3128
  TokenKind[TokenKind["Operator"] = 2048] = "Operator";
2942
3129
  TokenKind[TokenKind["FilteredValue"] = 4096] = "FilteredValue";
3130
+ TokenKind[TokenKind["GroupedExpression"] = 8192] = "GroupedExpression";
2943
3131
  TokenKind[TokenKind["Delimited"] = 12] = "Delimited";
2944
3132
  })(TokenKind || (TokenKind = {}));
2945
3133
 
@@ -2976,9 +3164,12 @@ function isWordToken(val) {
2976
3164
  function isRangeToken(val) {
2977
3165
  return getKind(val) === TokenKind.Range;
2978
3166
  }
3167
+ function isFilteredValueToken(val) {
3168
+ return getKind(val) === TokenKind.FilteredValue;
3169
+ }
2979
3170
  function isValueToken(val) {
2980
- // valueTokenBitMask = TokenKind.Number | TokenKind.Literal | TokenKind.Quoted | TokenKind.PropertyAccess | TokenKind.Range
2981
- return (getKind(val) & 1667) > 0;
3171
+ // valueTokenBitMask = TokenKind.Number | TokenKind.Literal | TokenKind.Quoted | TokenKind.PropertyAccess | TokenKind.Range | TokenKind.FilteredValue
3172
+ return (getKind(val) & 5763) > 0;
2982
3173
  }
2983
3174
  function getKind(val) {
2984
3175
  return val ? val.kind : -1;
@@ -2997,17 +3188,25 @@ var typeGuards = /*#__PURE__*/Object.freeze({
2997
3188
  isPropertyAccessToken: isPropertyAccessToken,
2998
3189
  isWordToken: isWordToken,
2999
3190
  isRangeToken: isRangeToken,
3191
+ isFilteredValueToken: isFilteredValueToken,
3000
3192
  isValueToken: isValueToken
3001
3193
  });
3002
3194
 
3195
+ function createScope(from) {
3196
+ const scope = Object.create(null);
3197
+ if (from)
3198
+ Object.assign(scope, from);
3199
+ return scope;
3200
+ }
3201
+
3003
3202
  class Context {
3004
- constructor(env = {}, opts = defaultOptions, renderOptions = {}, { memoryLimit, renderLimit } = {}) {
3203
+ constructor(env = {}, opts = defaultOptions, renderOptions = {}, { memoryLimit, renderLimit, liquid } = {}) {
3005
3204
  var _a, _b, _c, _d, _e;
3006
3205
  /**
3007
3206
  * insert a Context-level empty scope,
3008
3207
  * for tags like `{% capture %}` `{% assign %}` to operate
3009
3208
  */
3010
- this.scopes = [{}];
3209
+ this.scopes = [createScope()];
3011
3210
  this.registers = {};
3012
3211
  this.breakCalled = false;
3013
3212
  this.continueCalled = false;
@@ -3019,9 +3218,10 @@ class Context {
3019
3218
  this.ownPropertyOnly = (_c = renderOptions.ownPropertyOnly) !== null && _c !== void 0 ? _c : opts.ownPropertyOnly;
3020
3219
  this.memoryLimit = memoryLimit !== null && memoryLimit !== void 0 ? memoryLimit : new Limiter('memory alloc', (_d = renderOptions.memoryLimit) !== null && _d !== void 0 ? _d : opts.memoryLimit);
3021
3220
  this.renderLimit = renderLimit !== null && renderLimit !== void 0 ? renderLimit : new Limiter('template render', getPerformance().now() + ((_e = renderOptions.renderLimit) !== null && _e !== void 0 ? _e : opts.renderLimit));
3221
+ this.liquid = liquid;
3022
3222
  }
3023
- getRegister(key) {
3024
- return (this.registers[key] = this.registers[key] || {});
3223
+ getRegister(key, defaultValue = undefined) {
3224
+ return (this.registers[key] = this.registers[key] || defaultValue);
3025
3225
  }
3026
3226
  setRegister(key, value) {
3027
3227
  return (this.registers[key] = value);
@@ -3059,7 +3259,7 @@ class Context {
3059
3259
  if (isString(paths))
3060
3260
  paths = paths.split('.');
3061
3261
  for (let i = 0; i < paths.length; i++) {
3062
- scope = yield readProperty(scope, paths[i], this.ownPropertyOnly);
3262
+ scope = yield this.readProperty(scope, paths[i]);
3063
3263
  if (strictVariables && isUndefined(scope)) {
3064
3264
  throw new InternalUndefinedVariableError(paths.slice(0, i + 1).join('.'));
3065
3265
  }
@@ -3079,10 +3279,12 @@ class Context {
3079
3279
  return new Context(scope, this.opts, {
3080
3280
  sync: this.sync,
3081
3281
  globals: this.globals,
3082
- strictVariables: this.strictVariables
3282
+ strictVariables: this.strictVariables,
3283
+ ownPropertyOnly: this.ownPropertyOnly
3083
3284
  }, {
3084
3285
  renderLimit: this.renderLimit,
3085
- memoryLimit: this.memoryLimit
3286
+ memoryLimit: this.memoryLimit,
3287
+ liquid: this.liquid
3086
3288
  });
3087
3289
  }
3088
3290
  findScope(key) {
@@ -3095,41 +3297,41 @@ class Context {
3095
3297
  return this.environments;
3096
3298
  return this.globals;
3097
3299
  }
3098
- }
3099
- function readProperty(obj, key, ownPropertyOnly) {
3100
- obj = toLiquid(obj);
3101
- key = toValue(key);
3102
- if (isNil(obj))
3103
- return obj;
3104
- if (isArray(obj) && key < 0)
3105
- return obj[obj.length + +key];
3106
- const value = readJSProperty(obj, key, ownPropertyOnly);
3107
- if (value === undefined && obj instanceof Drop)
3108
- return obj.liquidMethodMissing(key);
3109
- if (isFunction(value))
3110
- return value.call(obj);
3111
- if (key === 'size')
3112
- return readSize(obj);
3113
- else if (key === 'first')
3114
- return readFirst(obj);
3115
- else if (key === 'last')
3116
- return readLast(obj);
3117
- return value;
3300
+ readProperty(obj, key) {
3301
+ obj = toLiquid(obj);
3302
+ key = toValue(key);
3303
+ if (isNil(obj))
3304
+ return obj;
3305
+ if (isArray(obj) && isNumber(key))
3306
+ return readArrayElement(obj, key, this.ownPropertyOnly);
3307
+ const value = readJSProperty(obj, key, this.ownPropertyOnly);
3308
+ if (value === undefined && obj instanceof Drop)
3309
+ return obj.liquidMethodMissing(key, this);
3310
+ if (isFunction(value))
3311
+ return value.call(obj);
3312
+ if (key === 'size')
3313
+ return readSize(obj);
3314
+ else if (key === 'first')
3315
+ return readFirst(obj, this.ownPropertyOnly);
3316
+ else if (key === 'last')
3317
+ return readLast(obj, this.ownPropertyOnly);
3318
+ return value;
3319
+ }
3118
3320
  }
3119
3321
  function readJSProperty(obj, key, ownPropertyOnly) {
3120
3322
  if (ownPropertyOnly && !hasOwnProperty.call(obj, key) && !(obj instanceof Drop))
3121
3323
  return undefined;
3122
3324
  return obj[key];
3123
3325
  }
3124
- function readFirst(obj) {
3326
+ function readFirst(obj, ownPropertyOnly) {
3125
3327
  if (isArray(obj))
3126
- return obj[0];
3127
- return obj['first'];
3328
+ return readArrayElement(obj, 0, ownPropertyOnly);
3329
+ return readJSProperty(obj, 'first', ownPropertyOnly);
3128
3330
  }
3129
- function readLast(obj) {
3331
+ function readLast(obj, ownPropertyOnly) {
3130
3332
  if (isArray(obj))
3131
- return obj[obj.length - 1];
3132
- return obj['last'];
3333
+ return readArrayElement(obj, -1, ownPropertyOnly);
3334
+ return readJSProperty(obj, 'last', ownPropertyOnly);
3133
3335
  }
3134
3336
  function readSize(obj) {
3135
3337
  if (hasOwnProperty.call(obj, 'size') || obj['size'] !== undefined)
@@ -3148,25 +3350,28 @@ var BlockMode;
3148
3350
  BlockMode[BlockMode["STORE"] = 1] = "STORE";
3149
3351
  })(BlockMode || (BlockMode = {}));
3150
3352
 
3151
- const abs = argumentsToValue(Math.abs);
3152
- const at_least = argumentsToValue(Math.max);
3153
- const at_most = argumentsToValue(Math.min);
3154
- const ceil = argumentsToValue(Math.ceil);
3155
- const divided_by = argumentsToValue((dividend, divisor, integerArithmetic = false) => integerArithmetic ? Math.floor(dividend / divisor) : dividend / divisor);
3156
- const floor = argumentsToValue(Math.floor);
3157
- const minus = argumentsToValue((v, arg) => v - arg);
3158
- const modulo = argumentsToValue((v, arg) => v % arg);
3159
- const times = argumentsToValue((v, arg) => v * arg);
3160
- function round(v, arg = 0) {
3161
- v = toValue(v);
3162
- arg = toValue(arg);
3163
- const amp = Math.pow(10, arg);
3164
- return Math.round(v * amp) / amp;
3353
+ const abs = argumentsToNumber(Math.abs);
3354
+ const at_least = argumentsToNumber(Math.max);
3355
+ const at_most = argumentsToNumber(Math.min);
3356
+ const ceil = argumentsToNumber(Math.ceil);
3357
+ const divided_by = argumentsToNumber((dividend, divisor, integerArithmetic = false) => integerArithmetic ? Math.floor(dividend / divisor) : dividend / divisor);
3358
+ const floor = argumentsToNumber(Math.floor);
3359
+ const minus = argumentsToNumber((v, arg) => v - arg);
3360
+ const plus = argumentsToNumber((lhs, rhs) => lhs + rhs);
3361
+ const modulo = argumentsToNumber((v, arg) => ((v % arg) + arg) % arg);
3362
+ const times = argumentsToNumber((v, arg) => v * arg);
3363
+ function shiftDecimal(n, exponent) {
3364
+ const [mantissa, power = '0'] = String(n).split('e');
3365
+ return Number(`${mantissa}e${Number(power) + exponent}`);
3165
3366
  }
3166
- function plus(v, arg) {
3167
- v = toValue(v);
3168
- arg = toValue(arg);
3169
- return Number(v) + Number(arg);
3367
+ function round(v, arg = 0) {
3368
+ v = toNumber(v);
3369
+ arg = toNumber(arg);
3370
+ const scaled = shiftDecimal(Math.abs(v), arg);
3371
+ if (!(scaled <= Number.MAX_SAFE_INTEGER))
3372
+ return v;
3373
+ const rounded = shiftDecimal(Math.round(scaled), -arg);
3374
+ return v < 0 ? -rounded : rounded;
3170
3375
  }
3171
3376
 
3172
3377
  var mathFilters = /*#__PURE__*/Object.freeze({
@@ -3178,13 +3383,13 @@ var mathFilters = /*#__PURE__*/Object.freeze({
3178
3383
  divided_by: divided_by,
3179
3384
  floor: floor,
3180
3385
  minus: minus,
3386
+ plus: plus,
3181
3387
  modulo: modulo,
3182
3388
  times: times,
3183
- round: round,
3184
- plus: plus
3389
+ round: round
3185
3390
  });
3186
3391
 
3187
- const url_decode = (x) => decodeURIComponent(stringify(x)).replace(/\+/g, ' ');
3392
+ const url_decode = (x) => decodeURIComponent(stringify(x).replace(/\+/g, ' '));
3188
3393
  const url_encode = (x) => encodeURIComponent(stringify(x)).replace(/%20/g, '+');
3189
3394
  const cgi_escape = (x) => encodeURIComponent(stringify(x))
3190
3395
  .replace(/%20/g, '+')
@@ -3242,18 +3447,24 @@ var urlFilters = /*#__PURE__*/Object.freeze({
3242
3447
  const join = argumentsToValue(function (v, arg) {
3243
3448
  const array = toArray(v);
3244
3449
  const sep = isNil(arg) ? ' ' : stringify(arg);
3245
- const complexity = array.length * (1 + sep.length);
3246
- this.context.memoryLimit.use(complexity);
3247
- return array.join(sep);
3450
+ let outputSize = sep.length * Math.max(array.length - 1, 0);
3451
+ for (let i = 0; i < array.length; i++)
3452
+ outputSize += String(array[i]).length;
3453
+ this.context.memoryLimit.use(outputSize);
3454
+ return Array.prototype.join.call(array, sep);
3455
+ });
3456
+ const last$1 = argumentsToValue(function (v) {
3457
+ return isArrayLike(v) ? readArrayElement(v, -1, this.context.ownPropertyOnly) : '';
3458
+ });
3459
+ const first = argumentsToValue(function (v) {
3460
+ return isArrayLike(v) ? readArrayElement(v, 0, this.context.ownPropertyOnly) : '';
3248
3461
  });
3249
- const last$1 = argumentsToValue((v) => isArrayLike(v) ? last(v) : '');
3250
- const first = argumentsToValue((v) => isArrayLike(v) ? v[0] : '');
3251
3462
  const reverse = argumentsToValue(function (v) {
3252
3463
  const array = toArray(v);
3253
3464
  this.context.memoryLimit.use(array.length);
3254
3465
  return [...array].reverse();
3255
3466
  });
3256
- function* sort(arr, property) {
3467
+ function* sortBy(arr, property, comparator) {
3257
3468
  const values = [];
3258
3469
  const array = toArray(arr);
3259
3470
  this.context.memoryLimit.use(array.length);
@@ -3263,20 +3474,13 @@ function* sort(arr, property) {
3263
3474
  property ? yield this.context._getFromScope(item, stringify(property).split('.'), false) : item
3264
3475
  ]);
3265
3476
  }
3266
- return values.sort((lhs, rhs) => {
3267
- const lvalue = lhs[1];
3268
- const rvalue = rhs[1];
3269
- return lvalue < rvalue ? -1 : (lvalue > rvalue ? 1 : 0);
3270
- }).map(tuple => tuple[0]);
3271
- }
3272
- function sort_natural(input, property) {
3273
- const propertyString = stringify(property);
3274
- const compare = property === undefined
3275
- ? caseInsensitiveCompare
3276
- : (lhs, rhs) => caseInsensitiveCompare(lhs[propertyString], rhs[propertyString]);
3277
- const array = toArray(input);
3278
- this.context.memoryLimit.use(array.length);
3279
- return [...array].sort(compare);
3477
+ return values.sort((lhs, rhs) => comparator(lhs[1], rhs[1])).map(tuple => tuple[0]);
3478
+ }
3479
+ function* sort(arr, property) {
3480
+ return yield* sortBy.call(this, arr, property, orderedCompare);
3481
+ }
3482
+ function* sort_natural(arr, property) {
3483
+ return yield* sortBy.call(this, arr, property, caseInsensitiveCompare);
3280
3484
  }
3281
3485
  const size = (v) => (v && v.length) || 0;
3282
3486
  function* map(arr, property) {
@@ -3300,13 +3504,13 @@ function* sum(arr, property) {
3300
3504
  function compact(arr) {
3301
3505
  const array = toArray(arr);
3302
3506
  this.context.memoryLimit.use(array.length);
3303
- return array.filter(x => !isNil(toValue(x)));
3507
+ return Array.prototype.filter.call(array, x => !isNil(toValue(x)));
3304
3508
  }
3305
3509
  function concat(v, arg = []) {
3306
3510
  const lhs = toArray(v);
3307
3511
  const rhs = toArray(arg);
3308
3512
  this.context.memoryLimit.use(lhs.length + rhs.length);
3309
- return lhs.concat(rhs);
3513
+ return Array.prototype.concat.call(lhs, rhs);
3310
3514
  }
3311
3515
  function push(v, arg) {
3312
3516
  return concat.call(this, v, [arg]);
@@ -3319,7 +3523,9 @@ function unshift(v, arg) {
3319
3523
  return clone;
3320
3524
  }
3321
3525
  function pop(v) {
3322
- const clone = [...toArray(v)];
3526
+ const array = toArray(v);
3527
+ this.context.memoryLimit.use(array.length);
3528
+ const clone = [...array];
3323
3529
  clone.pop();
3324
3530
  return clone;
3325
3531
  }
@@ -3336,39 +3542,65 @@ function slice(v, begin, length = 1) {
3336
3542
  return [];
3337
3543
  if (!isArray(v))
3338
3544
  v = stringify(v);
3339
- begin = begin < 0 ? v.length + begin : begin;
3545
+ begin = begin < 0 ? (isArray(v) ? v.length : codePointLength(v)) + begin : begin;
3546
+ if (begin < 0 || length < 0)
3547
+ return isArray(v) ? [] : '';
3340
3548
  this.context.memoryLimit.use(length);
3341
- return v.slice(begin, begin + length);
3342
- }
3343
- function* where(arr, property, expected) {
3344
- const values = [];
3549
+ if (isArray(v))
3550
+ return Array.prototype.slice.call(v, begin, begin + length);
3551
+ const start = advanceCodePoints(v, 0, begin);
3552
+ return v.slice(start, advanceCodePoints(v, start, length));
3553
+ }
3554
+ function* matches(value, expected) {
3555
+ if (this.context.opts.jekyllWhere) {
3556
+ if (EmptyDrop.is(expected))
3557
+ return yield equals(value, expected);
3558
+ if (isArray(value))
3559
+ return yield arrayIncludes(value, expected);
3560
+ return yield equals(value, expected);
3561
+ }
3562
+ if (expected === undefined)
3563
+ return isTruthy(value, this.context);
3564
+ return yield equals(value, expected);
3565
+ }
3566
+ function* filter(include, arr, property, expected) {
3345
3567
  arr = toArray(arr);
3346
3568
  this.context.memoryLimit.use(arr.length);
3347
3569
  const token = new Tokenizer(stringify(property)).readScopeValue();
3570
+ const result = [];
3348
3571
  for (const item of arr) {
3349
- values.push(yield evalToken(token, this.context.spawn(item)));
3350
- }
3351
- const matcher = this.context.opts.jekyllWhere
3352
- ? (v) => EmptyDrop.is(expected) ? equals(v, expected) : (isArray(v) ? arrayIncludes(v, expected) : equals(v, expected))
3353
- : (v) => equals(v, expected);
3354
- return arr.filter((_, i) => {
3355
- if (expected === undefined)
3356
- return isTruthy(values[i], this.context);
3357
- return matcher(values[i]);
3358
- });
3572
+ const value = yield evalToken(token, this.context.spawn(item));
3573
+ if ((yield matches.call(this, value, expected)) === include)
3574
+ result.push(item);
3575
+ }
3576
+ return result;
3359
3577
  }
3360
- function* where_exp(arr, itemName, exp) {
3578
+ function* filter_exp(include, arr, itemName, exp) {
3361
3579
  const filtered = [];
3362
3580
  const keyTemplate = new Value(stringify(exp), this.liquid);
3363
3581
  const array = toArray(arr);
3364
3582
  this.context.memoryLimit.use(array.length);
3365
3583
  for (const item of array) {
3366
- const value = yield keyTemplate.value(this.context.spawn({ [itemName]: item }));
3367
- if (value)
3584
+ this.context.push({ [itemName]: item });
3585
+ const value = yield keyTemplate.value(this.context);
3586
+ this.context.pop();
3587
+ if (value === include)
3368
3588
  filtered.push(item);
3369
3589
  }
3370
3590
  return filtered;
3371
3591
  }
3592
+ function* where(arr, property, expected) {
3593
+ return yield* filter.call(this, true, arr, property, expected);
3594
+ }
3595
+ function* reject(arr, property, expected) {
3596
+ return yield* filter.call(this, false, arr, property, expected);
3597
+ }
3598
+ function* where_exp(arr, itemName, exp) {
3599
+ return yield* filter_exp.call(this, true, arr, itemName, exp);
3600
+ }
3601
+ function* reject_exp(arr, itemName, exp) {
3602
+ return yield* filter_exp.call(this, false, arr, itemName, exp);
3603
+ }
3372
3604
  function* group_by(arr, property) {
3373
3605
  const map = new Map();
3374
3606
  arr = toEnumerable(arr);
@@ -3388,31 +3620,59 @@ function* group_by_exp(arr, itemName, exp) {
3388
3620
  arr = toEnumerable(arr);
3389
3621
  this.context.memoryLimit.use(arr.length);
3390
3622
  for (const item of arr) {
3391
- const key = yield keyTemplate.value(this.context.spawn({ [itemName]: item }));
3623
+ this.context.push({ [itemName]: item });
3624
+ const key = yield keyTemplate.value(this.context);
3625
+ this.context.pop();
3392
3626
  if (!map.has(key))
3393
3627
  map.set(key, []);
3394
3628
  map.get(key).push(item);
3395
3629
  }
3396
3630
  return [...map.entries()].map(([name, items]) => ({ name, items }));
3397
3631
  }
3398
- function* find(arr, property, expected) {
3632
+ function* search(arr, property, expected) {
3399
3633
  const token = new Tokenizer(stringify(property)).readScopeValue();
3400
3634
  const array = toArray(arr);
3401
- for (const item of array) {
3402
- const value = yield evalToken(token, this.context.spawn(item));
3403
- if (equals(value, expected))
3404
- return item;
3635
+ for (let index = 0; index < array.length; index++) {
3636
+ const value = yield evalToken(token, this.context.spawn(array[index]));
3637
+ if (yield matches.call(this, value, expected))
3638
+ return [index, array[index]];
3405
3639
  }
3406
3640
  }
3407
- function* find_exp(arr, itemName, exp) {
3641
+ function* search_exp(arr, itemName, exp) {
3408
3642
  const predicate = new Value(stringify(exp), this.liquid);
3409
3643
  const array = toArray(arr);
3410
- for (const item of array) {
3411
- const value = yield predicate.value(this.context.spawn({ [itemName]: item }));
3644
+ for (let index = 0; index < array.length; index++) {
3645
+ this.context.push({ [itemName]: array[index] });
3646
+ const value = yield predicate.value(this.context);
3647
+ this.context.pop();
3412
3648
  if (value)
3413
- return item;
3649
+ return [index, array[index]];
3414
3650
  }
3415
3651
  }
3652
+ function* has(arr, property, expected) {
3653
+ const result = yield* search.call(this, arr, property, expected);
3654
+ return !!result;
3655
+ }
3656
+ function* has_exp(arr, itemName, exp) {
3657
+ const result = yield* search_exp.call(this, arr, itemName, exp);
3658
+ return !!result;
3659
+ }
3660
+ function* find_index(arr, property, expected) {
3661
+ const result = yield* search.call(this, arr, property, expected);
3662
+ return result ? result[0] : undefined;
3663
+ }
3664
+ function* find_index_exp(arr, itemName, exp) {
3665
+ const result = yield* search_exp.call(this, arr, itemName, exp);
3666
+ return result ? result[0] : undefined;
3667
+ }
3668
+ function* find(arr, property, expected) {
3669
+ const result = yield* search.call(this, arr, property, expected);
3670
+ return result ? result[1] : undefined;
3671
+ }
3672
+ function* find_exp(arr, itemName, exp) {
3673
+ const result = yield* search_exp.call(this, arr, itemName, exp);
3674
+ return result ? result[1] : undefined;
3675
+ }
3416
3676
  function uniq(arr) {
3417
3677
  arr = toArray(arr);
3418
3678
  this.context.memoryLimit.use(arr.length);
@@ -3424,7 +3684,7 @@ function sample(v, count = 1) {
3424
3684
  return [];
3425
3685
  if (!isArray(v))
3426
3686
  v = stringify(v);
3427
- this.context.memoryLimit.use(count);
3687
+ this.context.memoryLimit.use(v.length);
3428
3688
  const shuffled = [...v].sort(() => Math.random() - 0.5);
3429
3689
  if (count === 1)
3430
3690
  return shuffled[0];
@@ -3450,9 +3710,15 @@ var arrayFilters = /*#__PURE__*/Object.freeze({
3450
3710
  shift: shift,
3451
3711
  slice: slice,
3452
3712
  where: where,
3713
+ reject: reject,
3453
3714
  where_exp: where_exp,
3715
+ reject_exp: reject_exp,
3454
3716
  group_by: group_by,
3455
3717
  group_by_exp: group_by_exp,
3718
+ has: has,
3719
+ has_exp: has_exp,
3720
+ find_index: find_index,
3721
+ find_index_exp: find_index_exp,
3456
3722
  find: find,
3457
3723
  find_exp: find_exp,
3458
3724
  uniq: uniq,
@@ -3460,21 +3726,25 @@ var arrayFilters = /*#__PURE__*/Object.freeze({
3460
3726
  });
3461
3727
 
3462
3728
  function date(v, format, timezoneOffset) {
3463
- var _a, _b, _c;
3464
- const size = ((_a = v === null || v === void 0 ? void 0 : v.length) !== null && _a !== void 0 ? _a : 0) + ((_b = format === null || format === void 0 ? void 0 : format.length) !== null && _b !== void 0 ? _b : 0) + ((_c = timezoneOffset === null || timezoneOffset === void 0 ? void 0 : timezoneOffset.length) !== null && _c !== void 0 ? _c : 0);
3729
+ var _a, _b;
3730
+ const size = ((_a = v === null || v === void 0 ? void 0 : v.length) !== null && _a !== void 0 ? _a : 0) + ((_b = timezoneOffset === null || timezoneOffset === void 0 ? void 0 : timezoneOffset.length) !== null && _b !== void 0 ? _b : 0);
3465
3731
  this.context.memoryLimit.use(size);
3466
3732
  const date = parseDate(v, this.context.opts, timezoneOffset);
3467
3733
  if (!date)
3468
3734
  return v;
3469
3735
  format = toValue(format);
3470
3736
  format = isNil(format) ? this.context.opts.dateFormat : stringify(format);
3471
- return strftime(date, format);
3737
+ this.context.memoryLimit.use(format.length);
3738
+ return strftime(date, format, this.context.memoryLimit);
3472
3739
  }
3473
3740
  function date_to_xmlschema(v) {
3474
3741
  return date.call(this, v, '%Y-%m-%dT%H:%M:%S%:z');
3475
3742
  }
3476
3743
  function date_to_rfc822(v) {
3477
- return date.call(this, v, '%a, %d %b %Y %H:%M:%S %z');
3744
+ const date = parseDate(v, this.context.opts, undefined, 'en-US');
3745
+ if (!date)
3746
+ return v;
3747
+ return strftime(date, '%a, %d %b %Y %H:%M:%S %z', this.context.memoryLimit);
3478
3748
  }
3479
3749
  function date_to_string(v, type, style) {
3480
3750
  return stringify_date.call(this, v, '%b', type, style);
@@ -3486,20 +3756,23 @@ function stringify_date(v, month_type, type, style) {
3486
3756
  const date = parseDate(v, this.context.opts);
3487
3757
  if (!date)
3488
3758
  return v;
3759
+ const ml = this.context.memoryLimit;
3489
3760
  if (type === 'ordinal') {
3490
3761
  const d = date.getDate();
3491
3762
  return style === 'US'
3492
- ? strftime(date, `${month_type} ${d}%q, %Y`)
3493
- : strftime(date, `${d}%q ${month_type} %Y`);
3763
+ ? strftime(date, `${month_type} ${d}%q, %Y`, ml)
3764
+ : strftime(date, `${d}%q ${month_type} %Y`, ml);
3494
3765
  }
3495
- return strftime(date, `%d ${month_type} %Y`);
3766
+ return strftime(date, `%d ${month_type} %Y`, ml);
3496
3767
  }
3497
- function parseDate(v, opts, timezoneOffset) {
3768
+ function parseDate(v, opts, timezoneOffset, locale = opts.locale) {
3498
3769
  let date;
3499
3770
  const defaultTimezoneOffset = timezoneOffset !== null && timezoneOffset !== void 0 ? timezoneOffset : opts.timezoneOffset;
3500
- const locale = opts.locale;
3501
3771
  v = toValue(v);
3502
- if (v === 'now' || v === 'today') {
3772
+ if (isNil(v)) {
3773
+ return undefined;
3774
+ }
3775
+ else if (v === 'now' || v === 'today') {
3503
3776
  date = new LiquidDate(Date.now(), locale, defaultTimezoneOffset);
3504
3777
  }
3505
3778
  else if (isNumber(v)) {
@@ -3553,14 +3826,34 @@ function prepend(v, arg) {
3553
3826
  this.context.memoryLimit.use(lhs.length + rhs.length);
3554
3827
  return rhs + lhs;
3555
3828
  }
3829
+ function getTrimStart(str, chars) {
3830
+ let start = 0;
3831
+ for (const char of str) {
3832
+ if (!chars.has(char))
3833
+ break;
3834
+ start += char.length;
3835
+ }
3836
+ return start;
3837
+ }
3838
+ function getTrimEnd(str, chars, start = 0) {
3839
+ let end = str.length;
3840
+ while (end > start) {
3841
+ const size = end - start >= 2 && str.codePointAt(end - 2) > 0xFFFF ? 2 : 1;
3842
+ if (!chars.has(str.slice(end - size, end)))
3843
+ break;
3844
+ end -= size;
3845
+ }
3846
+ return end;
3847
+ }
3556
3848
  function lstrip(v, chars) {
3557
3849
  const str = stringify(v);
3558
3850
  this.context.memoryLimit.use(str.length);
3559
3851
  if (chars) {
3560
- chars = escapeRegExp(stringify(chars));
3561
- return str.replace(new RegExp(`^[${chars}]+`, 'g'), '');
3852
+ chars = stringify(chars);
3853
+ this.context.memoryLimit.use(chars.length);
3854
+ return str.slice(getTrimStart(str, new Set(chars)));
3562
3855
  }
3563
- return str.replace(/^\s+/, '');
3856
+ return str.trimStart();
3564
3857
  }
3565
3858
  function downcase(v) {
3566
3859
  const str = stringify(v);
@@ -3574,18 +3867,20 @@ function upcase(v) {
3574
3867
  }
3575
3868
  function remove(v, arg) {
3576
3869
  const str = stringify(v);
3577
- this.context.memoryLimit.use(str.length);
3578
- return str.split(stringify(arg)).join('');
3870
+ arg = stringify(arg);
3871
+ this.context.memoryLimit.use(str.length + arg.length);
3872
+ return str.split(arg).join('');
3579
3873
  }
3580
3874
  function remove_first(v, l) {
3581
3875
  const str = stringify(v);
3582
- this.context.memoryLimit.use(str.length);
3583
- return str.replace(stringify(l), '');
3876
+ l = stringify(l);
3877
+ this.context.memoryLimit.use(str.length + l.length);
3878
+ return str.replace(l, '');
3584
3879
  }
3585
3880
  function remove_last(v, l) {
3586
3881
  const str = stringify(v);
3587
- this.context.memoryLimit.use(str.length);
3588
3882
  const pattern = stringify(l);
3883
+ this.context.memoryLimit.use(str.length + pattern.length);
3589
3884
  const index = str.lastIndexOf(pattern);
3590
3885
  if (index === -1)
3591
3886
  return str;
@@ -3595,10 +3890,11 @@ function rstrip(str, chars) {
3595
3890
  str = stringify(str);
3596
3891
  this.context.memoryLimit.use(str.length);
3597
3892
  if (chars) {
3598
- chars = escapeRegExp(stringify(chars));
3599
- return str.replace(new RegExp(`[${chars}]+$`, 'g'), '');
3893
+ chars = stringify(chars);
3894
+ this.context.memoryLimit.use(chars.length);
3895
+ return str.slice(0, getTrimEnd(str, new Set(chars)));
3600
3896
  }
3601
- return str.replace(/\s+$/, '');
3897
+ return str.trimEnd();
3602
3898
  }
3603
3899
  function split(v, arg) {
3604
3900
  const str = stringify(v);
@@ -3614,10 +3910,10 @@ function strip(v, chars) {
3614
3910
  const str = stringify(v);
3615
3911
  this.context.memoryLimit.use(str.length);
3616
3912
  if (chars) {
3617
- chars = escapeRegExp(stringify(chars));
3618
- return str
3619
- .replace(new RegExp(`^[${chars}]+`, 'g'), '')
3620
- .replace(new RegExp(`[${chars}]+$`, 'g'), '');
3913
+ const set = new Set(stringify(chars));
3914
+ this.context.memoryLimit.use(set.size);
3915
+ const start = getTrimStart(str, set);
3916
+ return str.slice(start, getTrimEnd(str, set, start));
3621
3917
  }
3622
3918
  return str.trim();
3623
3919
  }
@@ -3626,6 +3922,11 @@ function strip_newlines(v) {
3626
3922
  this.context.memoryLimit.use(str.length);
3627
3923
  return str.replace(/\r?\n/gm, '');
3628
3924
  }
3925
+ function squish(v) {
3926
+ const str = stringify(v);
3927
+ this.context.memoryLimit.use(str.length);
3928
+ return str.replace(/\s+/g, ' ').trim();
3929
+ }
3629
3930
  function capitalize(str) {
3630
3931
  str = stringify(str);
3631
3932
  this.context.memoryLimit.use(str.length);
@@ -3633,35 +3934,43 @@ function capitalize(str) {
3633
3934
  }
3634
3935
  function replace(v, pattern, replacement) {
3635
3936
  const str = stringify(v);
3636
- this.context.memoryLimit.use(str.length);
3637
- return str.split(stringify(pattern)).join(replacement);
3937
+ pattern = stringify(pattern);
3938
+ replacement = stringify(replacement);
3939
+ const parts = str.split(pattern);
3940
+ const outputSize = str.length + (parts.length - 1) * (replacement.length - pattern.length);
3941
+ this.context.memoryLimit.use(outputSize);
3942
+ return parts.join(replacement);
3638
3943
  }
3639
3944
  function replace_first(v, arg1, arg2) {
3640
3945
  const str = stringify(v);
3641
- this.context.memoryLimit.use(str.length);
3642
- return str.replace(stringify(arg1), arg2);
3946
+ arg1 = stringify(arg1);
3947
+ arg2 = stringify(arg2);
3948
+ this.context.memoryLimit.use(str.length + arg1.length + arg2.length);
3949
+ return str.replace(arg1, () => arg2);
3643
3950
  }
3644
3951
  function replace_last(v, arg1, arg2) {
3645
3952
  const str = stringify(v);
3646
- this.context.memoryLimit.use(str.length);
3647
3953
  const pattern = stringify(arg1);
3954
+ const replacement = stringify(arg2);
3955
+ this.context.memoryLimit.use(str.length + pattern.length + replacement.length);
3648
3956
  const index = str.lastIndexOf(pattern);
3649
3957
  if (index === -1)
3650
3958
  return str;
3651
- const replacement = stringify(arg2);
3652
3959
  return str.substring(0, index) + replacement + str.substring(index + pattern.length);
3653
3960
  }
3654
3961
  function truncate(v, l = 50, o = '...') {
3655
3962
  const str = stringify(v);
3656
- this.context.memoryLimit.use(str.length);
3657
- if (str.length <= l)
3963
+ o = stringify(o);
3964
+ this.context.memoryLimit.use(str.length + o.length);
3965
+ if (str.length <= l || advanceCodePoints(str, 0, l) >= str.length)
3658
3966
  return v;
3659
- return str.substring(0, l - o.length) + o;
3967
+ return str.slice(0, advanceCodePoints(str, 0, Math.max(l - codePointLength(o), 0))) + o;
3660
3968
  }
3661
3969
  function truncatewords(v, words = 15, o = '...') {
3662
3970
  const str = stringify(v);
3663
- this.context.memoryLimit.use(str.length);
3664
- const arr = str.split(/\s+/);
3971
+ o = stringify(o);
3972
+ this.context.memoryLimit.use(str.length + o.length);
3973
+ const arr = str.trimStart().split(/\s+/);
3665
3974
  if (words <= 0)
3666
3975
  words = 1;
3667
3976
  let ret = arr.slice(0, words).join(' ');
@@ -3694,8 +4003,13 @@ function number_of_words(input, mode) {
3694
4003
  return input.split(/\s+/).length;
3695
4004
  }
3696
4005
  }
3697
- function array_to_sentence_string(array, connector = 'and') {
3698
- this.context.memoryLimit.use(array.length);
4006
+ function array_to_sentence_string(input, connector = 'and') {
4007
+ const array = toArray(input);
4008
+ connector = stringify(connector);
4009
+ let outputSize = connector.length + array.length * 2;
4010
+ for (let i = 0; i < array.length; i++)
4011
+ outputSize += stringify(array[i]).length;
4012
+ this.context.memoryLimit.use(outputSize);
3699
4013
  switch (array.length) {
3700
4014
  case 0:
3701
4015
  return '';
@@ -3722,6 +4036,7 @@ var stringFilters = /*#__PURE__*/Object.freeze({
3722
4036
  split: split,
3723
4037
  strip: strip,
3724
4038
  strip_newlines: strip_newlines,
4039
+ squish: squish,
3725
4040
  capitalize: capitalize,
3726
4041
  replace: replace,
3727
4042
  replace_first: replace_first,
@@ -3733,7 +4048,99 @@ var stringFilters = /*#__PURE__*/Object.freeze({
3733
4048
  array_to_sentence_string: array_to_sentence_string
3734
4049
  });
3735
4050
 
3736
- const filters = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, htmlFilters), mathFilters), urlFilters), arrayFilters), dateFilters), stringFilters), misc);
4051
+ function base64Encode(str) {
4052
+ const bytes = new TextEncoder().encode(str);
4053
+ let binary = '';
4054
+ for (let i = 0; i < bytes.length; i += 0x8000) {
4055
+ binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
4056
+ }
4057
+ return btoa(binary);
4058
+ }
4059
+ function base64Decode(str) {
4060
+ return new TextDecoder().decode(Uint8Array.from(atob(str), c => c.charCodeAt(0)));
4061
+ }
4062
+
4063
+ /**
4064
+ * Base64 related filters
4065
+ *
4066
+ * Implements base64_encode and base64_decode filters for Shopify compatibility
4067
+ */
4068
+ function base64_encode(value) {
4069
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
4070
+ this.context.memoryLimit.use(value.byteLength);
4071
+ return value.toString('base64');
4072
+ }
4073
+ const str = stringify(value);
4074
+ this.context.memoryLimit.use(str.length);
4075
+ return base64Encode(str);
4076
+ }
4077
+ function base64_decode(value) {
4078
+ const str = stringify(value);
4079
+ this.context.memoryLimit.use(str.length);
4080
+ assert(isBase64(str), 'invalid base64 provided to base64_decode');
4081
+ return base64Decode(str);
4082
+ }
4083
+ function isBase64(str) {
4084
+ str = str.replace(/[\t\n\f\r ]/g, '');
4085
+ if (str.length % 4 === 0)
4086
+ str = str.replace(/==?$/, '');
4087
+ return str.length % 4 !== 1 && /^[A-Za-z0-9+/]*$/.test(str);
4088
+ }
4089
+
4090
+ var base64Filters = /*#__PURE__*/Object.freeze({
4091
+ __proto__: null,
4092
+ base64_encode: base64_encode,
4093
+ base64_decode: base64_decode
4094
+ });
4095
+
4096
+ function bufferToHex(buffer) {
4097
+ const bytes = new Uint8Array(buffer);
4098
+ let hex = '';
4099
+ for (let i = 0; i < bytes.length; i++) {
4100
+ hex += bytes[i].toString(16).padStart(2, '0');
4101
+ }
4102
+ return hex;
4103
+ }
4104
+ function sha256(str) {
4105
+ return __awaiter(this, void 0, void 0, function* () {
4106
+ const data = new TextEncoder().encode(str);
4107
+ const digest = yield crypto.subtle.digest('SHA-256', data);
4108
+ return bufferToHex(digest);
4109
+ });
4110
+ }
4111
+ function hmacSha256(str, key) {
4112
+ return __awaiter(this, void 0, void 0, function* () {
4113
+ const encoder = new TextEncoder();
4114
+ const cryptoKey = yield crypto.subtle.importKey('raw', encoder.encode(key), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
4115
+ const signature = yield crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(str));
4116
+ return bufferToHex(signature);
4117
+ });
4118
+ }
4119
+
4120
+ /**
4121
+ * Crypto related filters
4122
+ *
4123
+ * Implements sha256 and hmac_sha256 filters for Shopify compatibility
4124
+ */
4125
+ function sha256$1(value) {
4126
+ const str = stringify(value);
4127
+ this.context.memoryLimit.use(str.length);
4128
+ return sha256(str);
4129
+ }
4130
+ function hmac_sha256(value, key) {
4131
+ const str = stringify(value);
4132
+ const keyStr = stringify(key);
4133
+ this.context.memoryLimit.use(str.length + keyStr.length);
4134
+ return hmacSha256(str, keyStr);
4135
+ }
4136
+
4137
+ var cryptoFilters = /*#__PURE__*/Object.freeze({
4138
+ __proto__: null,
4139
+ sha256: sha256$1,
4140
+ hmac_sha256: hmac_sha256
4141
+ });
4142
+
4143
+ const filters = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, htmlFilters), mathFilters), urlFilters), arrayFilters), dateFilters), stringFilters), base64Filters), cryptoFilters), misc);
3737
4144
 
3738
4145
  class AssignTag extends Tag {
3739
4146
  constructor(token, remainTokens, liquid) {
@@ -3789,8 +4196,8 @@ class ForTag extends Tag {
3789
4196
  return;
3790
4197
  }
3791
4198
  const continueKey = 'continue-' + this.variable + '-' + this.collection.getText();
3792
- ctx.push({ continue: ctx.getRegister(continueKey) });
3793
- const hash = yield this.hash.render(ctx);
4199
+ ctx.push(createScope({ continue: ctx.getRegister(continueKey, {}) }));
4200
+ const hash = (yield this.hash.render(ctx));
3794
4201
  ctx.pop();
3795
4202
  const modifiers = this.liquid.options.orderedFilterParameters
3796
4203
  ? Object.keys(hash).filter(x => MODIFIERS.includes(x))
@@ -3803,7 +4210,7 @@ class ForTag extends Tag {
3803
4210
  return reversed(collection);
3804
4211
  }, collection);
3805
4212
  ctx.setRegister(continueKey, (hash['offset'] || 0) + collection.length);
3806
- const scope = { forloop: new ForloopDrop(collection.length, this.collection.getText(), this.variable) };
4213
+ const scope = createScope({ forloop: new ForloopDrop(collection.length, this.collection.getText(), this.variable) });
3807
4214
  ctx.push(scope);
3808
4215
  for (const item of collection) {
3809
4216
  scope[this.variable] = item;
@@ -3929,12 +4336,12 @@ class CaseTag extends Tag {
3929
4336
  }
3930
4337
  *render(ctx, emitter) {
3931
4338
  const r = this.liquid.renderer;
3932
- const target = toValue(yield this.value.value(ctx, ctx.opts.lenientIf));
4339
+ const target = yield toValue(yield this.value.value(ctx, ctx.opts.lenientIf));
3933
4340
  let branchHit = false;
3934
4341
  for (const branch of this.branches) {
3935
4342
  for (const valueToken of branch.values) {
3936
4343
  const value = yield evalToken(valueToken, ctx, ctx.opts.lenientIf);
3937
- if (equals(target, value)) {
4344
+ if (yield equals(target, value)) {
3938
4345
  yield r.renderTemplates(branch.templates, ctx, emitter);
3939
4346
  branchHit = true;
3940
4347
  break;
@@ -3995,7 +4402,11 @@ class RenderTag extends Tag {
3995
4402
  alias = tokenizer.readIdentifier();
3996
4403
  else
3997
4404
  tokenizer.p = beforeAs;
3998
- this[keyword.content] = { value, alias: alias && alias.content };
4405
+ const binding = { value, alias: alias && alias.content };
4406
+ if (keyword.content === 'with')
4407
+ this.with = binding;
4408
+ else
4409
+ this.forBinding = binding;
3999
4410
  tokenizer.skipBlank();
4000
4411
  if (tokenizer.peek() === ',')
4001
4412
  tokenizer.advance();
@@ -4013,42 +4424,42 @@ class RenderTag extends Tag {
4013
4424
  }
4014
4425
  *render(ctx, emitter) {
4015
4426
  const { liquid, hash } = this;
4016
- const filepath = (yield renderFilePath(this['file'], ctx, liquid));
4427
+ const filepath = (yield renderFilePath(this.file, ctx, liquid));
4017
4428
  assert(filepath, () => `illegal file path "${filepath}"`);
4018
4429
  const childCtx = ctx.spawn();
4019
4430
  const scope = childCtx.bottom();
4020
4431
  __assign(scope, yield hash.render(ctx));
4021
- if (this['with']) {
4022
- const { value, alias } = this['with'];
4432
+ if (this.with) {
4433
+ const { value, alias } = this.with;
4023
4434
  scope[alias || filepath] = yield evalToken(value, ctx);
4024
4435
  }
4025
- if (this['for']) {
4026
- const { value, alias } = this['for'];
4436
+ if (this.forBinding) {
4437
+ const { value, alias } = this.forBinding;
4027
4438
  const collection = toEnumerable(yield evalToken(value, ctx));
4028
4439
  scope['forloop'] = new ForloopDrop(collection.length, value.getText(), alias);
4029
4440
  for (const item of collection) {
4030
4441
  scope[alias] = item;
4031
- const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile']));
4442
+ const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this.currentFile));
4032
4443
  yield liquid.renderer.renderTemplates(templates, childCtx, emitter);
4033
4444
  scope['forloop'].next();
4034
4445
  }
4035
4446
  }
4036
4447
  else {
4037
- const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile']));
4448
+ const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this.currentFile));
4038
4449
  yield liquid.renderer.renderTemplates(templates, childCtx, emitter);
4039
4450
  }
4040
4451
  }
4041
4452
  *children(partials, sync) {
4042
- if (partials && isString(this['file'])) {
4043
- return (yield this.liquid._parsePartialFile(this['file'], sync, this['currentFile']));
4453
+ if (partials && isString(this.file)) {
4454
+ return (yield this.liquid._parsePartialFile(this.file, sync, this.currentFile));
4044
4455
  }
4045
4456
  return [];
4046
4457
  }
4047
4458
  partialScope() {
4048
- if (isString(this['file'])) {
4459
+ if (isString(this.file)) {
4049
4460
  const names = Object.keys(this.hash.hash);
4050
- if (this['with']) {
4051
- const { value, alias } = this['with'];
4461
+ if (this.with) {
4462
+ const { value, alias } = this.with;
4052
4463
  if (isString(alias)) {
4053
4464
  names.push([alias, value]);
4054
4465
  }
@@ -4056,8 +4467,8 @@ class RenderTag extends Tag {
4056
4467
  names.push([this.file, value]);
4057
4468
  }
4058
4469
  }
4059
- if (this['for']) {
4060
- const { value, alias } = this['for'];
4470
+ if (this.forBinding) {
4471
+ const { value, alias } = this.forBinding;
4061
4472
  if (isString(alias)) {
4062
4473
  names.push([alias, value]);
4063
4474
  }
@@ -4065,7 +4476,7 @@ class RenderTag extends Tag {
4065
4476
  names.push([this.file, value]);
4066
4477
  }
4067
4478
  }
4068
- return { name: this['file'], isolated: true, scope: names };
4479
+ return { name: this.file, isolated: true, scope: names };
4069
4480
  }
4070
4481
  }
4071
4482
  *arguments() {
@@ -4074,14 +4485,14 @@ class RenderTag extends Tag {
4074
4485
  yield v;
4075
4486
  }
4076
4487
  }
4077
- if (this['with']) {
4078
- const { value } = this['with'];
4488
+ if (this.with) {
4489
+ const { value } = this.with;
4079
4490
  if (isValueToken(value)) {
4080
4491
  yield value;
4081
4492
  }
4082
4493
  }
4083
- if (this['for']) {
4084
- const { value } = this['for'];
4494
+ if (this.forBinding) {
4495
+ const { value } = this.forBinding;
4085
4496
  if (isValueToken(value)) {
4086
4497
  yield value;
4087
4498
  }
@@ -4129,8 +4540,8 @@ class IncludeTag extends Tag {
4129
4540
  constructor(token, remainTokens, liquid, parser) {
4130
4541
  super(token, remainTokens, liquid);
4131
4542
  const { tokenizer } = token;
4132
- this['file'] = parseFilePath(tokenizer, this.liquid, parser);
4133
- this['currentFile'] = token.file;
4543
+ this.file = parseFilePath(tokenizer, this.liquid, parser);
4544
+ this.currentFile = token.file;
4134
4545
  const begin = tokenizer.p;
4135
4546
  const withStr = tokenizer.readIdentifier();
4136
4547
  if (withStr.content === 'with') {
@@ -4148,28 +4559,28 @@ class IncludeTag extends Tag {
4148
4559
  *render(ctx, emitter) {
4149
4560
  const { liquid, hash, withVar } = this;
4150
4561
  const { renderer } = liquid;
4151
- const filepath = (yield renderFilePath(this['file'], ctx, liquid));
4562
+ const filepath = (yield renderFilePath(this.file, ctx, liquid));
4152
4563
  assert(filepath, () => `illegal file path "${filepath}"`);
4153
4564
  const saved = ctx.saveRegister('blocks', 'blockMode');
4154
4565
  ctx.setRegister('blocks', {});
4155
4566
  ctx.setRegister('blockMode', BlockMode.OUTPUT);
4156
- const scope = (yield hash.render(ctx));
4567
+ const scope = createScope((yield hash.render(ctx)));
4157
4568
  if (withVar)
4158
4569
  scope[filepath] = yield evalToken(withVar, ctx);
4159
- const templates = (yield liquid._parsePartialFile(filepath, ctx.sync, this['currentFile']));
4160
- ctx.push(ctx.opts.jekyllInclude ? { include: scope } : scope);
4570
+ const templates = (yield liquid._parsePartialFile(filepath, ctx.sync, this.currentFile));
4571
+ ctx.push(ctx.opts.jekyllInclude ? createScope({ include: scope }) : scope);
4161
4572
  yield renderer.renderTemplates(templates, ctx, emitter);
4162
4573
  ctx.pop();
4163
4574
  ctx.restoreRegister(saved);
4164
4575
  }
4165
4576
  *children(partials, sync) {
4166
- if (partials && isString(this['file'])) {
4167
- return (yield this.liquid._parsePartialFile(this['file'], sync, this['currentFile']));
4577
+ if (partials && isString(this.file)) {
4578
+ return (yield this.liquid._parsePartialFile(this.file, sync, this.currentFile));
4168
4579
  }
4169
4580
  return [];
4170
4581
  }
4171
4582
  partialScope() {
4172
- if (isString(this['file'])) {
4583
+ if (isString(this.file)) {
4173
4584
  let names;
4174
4585
  if (this.liquid.options.jekyllInclude) {
4175
4586
  names = ['include'];
@@ -4177,16 +4588,16 @@ class IncludeTag extends Tag {
4177
4588
  else {
4178
4589
  names = Object.keys(this.hash.hash);
4179
4590
  if (this.withVar) {
4180
- names.push([this['file'], this.withVar]);
4591
+ names.push([this.file, this.withVar]);
4181
4592
  }
4182
4593
  }
4183
- return { name: this['file'], isolated: false, scope: names };
4594
+ return { name: this.file, isolated: false, scope: names };
4184
4595
  }
4185
4596
  }
4186
4597
  *arguments() {
4187
4598
  yield* Object.values(this.hash.hash).filter(isValueToken);
4188
- if (isValueToken(this['file'])) {
4189
- yield this['file'];
4599
+ if (isValueToken(this.file)) {
4600
+ yield this.file;
4190
4601
  }
4191
4602
  if (isValueToken(this.withVar)) {
4192
4603
  yield this.withVar;
@@ -4216,6 +4627,7 @@ class CycleTag extends Tag {
4216
4627
  constructor(token, remainTokens, liquid) {
4217
4628
  super(token, remainTokens, liquid);
4218
4629
  this.candidates = [];
4630
+ const begin = this.tokenizer.p;
4219
4631
  const group = this.tokenizer.readValue();
4220
4632
  this.tokenizer.skipBlank();
4221
4633
  if (group) {
@@ -4224,28 +4636,33 @@ class CycleTag extends Tag {
4224
4636
  this.tokenizer.advance();
4225
4637
  }
4226
4638
  else
4227
- this.candidates.push(group);
4639
+ this.tokenizer.p = begin;
4228
4640
  }
4229
4641
  while (!this.tokenizer.end()) {
4230
- const value = this.tokenizer.readValue();
4231
- if (value)
4232
- this.candidates.push(value);
4642
+ const value = this.tokenizer.readFilteredValue();
4643
+ this.candidates.push(value);
4233
4644
  this.tokenizer.readTo(',');
4234
4645
  }
4235
4646
  this.tokenizer.assert(this.candidates.length, () => `empty candidates: "${token.getText()}"`);
4236
4647
  }
4237
4648
  *render(ctx, emitter) {
4649
+ var _a;
4238
4650
  const group = (yield evalToken(this.group, ctx));
4239
- const fingerprint = `cycle:${group}:` + this.candidates.join(',');
4240
- const groups = ctx.getRegister('cycle');
4651
+ const fingerprint = `cycle:${group}:` + this.candidates.map(c => c.getText()).join(',');
4652
+ const groups = ctx.getRegister('cycle', {});
4241
4653
  let idx = groups[fingerprint];
4242
4654
  if (idx === undefined) {
4243
4655
  idx = groups[fingerprint] = 0;
4244
4656
  }
4245
- const candidate = this.candidates[idx];
4657
+ const candidate = new Value(this.candidates[idx], this.liquid);
4246
4658
  idx = (idx + 1) % this.candidates.length;
4247
4659
  groups[fingerprint] = idx;
4248
- return yield evalToken(candidate, ctx);
4660
+ let val = yield toValue(yield candidate.value(ctx, false));
4661
+ const outputEscape = this.liquid.options.outputEscape;
4662
+ if (outputEscape && !((_a = candidate.filters[candidate.filters.length - 1]) === null || _a === void 0 ? void 0 : _a.raw)) {
4663
+ val = yield outputEscape.call({ context: ctx, token: this.token, liquid: this.liquid }, val);
4664
+ }
4665
+ return val;
4249
4666
  }
4250
4667
  *arguments() {
4251
4668
  yield* this.candidates;
@@ -4285,7 +4702,7 @@ class IfTag extends Tag {
4285
4702
  *render(ctx, emitter) {
4286
4703
  const r = this.liquid.renderer;
4287
4704
  for (const { value, templates } of this.branches) {
4288
- const v = yield value.value(ctx, ctx.opts.lenientIf);
4705
+ const v = yield toValue(yield value.value(ctx, ctx.opts.lenientIf));
4289
4706
  if (isTruthy(v, ctx)) {
4290
4707
  yield r.renderTemplates(templates, ctx, emitter);
4291
4708
  return;
@@ -4329,7 +4746,7 @@ class LayoutTag extends Tag {
4329
4746
  constructor(token, remainTokens, liquid, parser) {
4330
4747
  super(token, remainTokens, liquid);
4331
4748
  this.file = parseFilePath(this.tokenizer, this.liquid, parser);
4332
- this['currentFile'] = token.file;
4749
+ this.currentFile = token.file;
4333
4750
  this.args = new Hash(this.tokenizer, liquid.options.keyValueSeparator);
4334
4751
  this.templates = parser.parseTokens(remainTokens);
4335
4752
  }
@@ -4343,24 +4760,24 @@ class LayoutTag extends Tag {
4343
4760
  }
4344
4761
  const filepath = (yield renderFilePath(this.file, ctx, liquid));
4345
4762
  assert(filepath, () => `illegal file path "${filepath}"`);
4346
- const templates = (yield liquid._parseLayoutFile(filepath, ctx.sync, this['currentFile']));
4763
+ const templates = (yield liquid._parseLayoutFile(filepath, ctx.sync, this.currentFile));
4347
4764
  // render remaining contents and store rendered results
4348
4765
  ctx.setRegister('blockMode', BlockMode.STORE);
4349
4766
  const html = yield renderer.renderTemplates(this.templates, ctx);
4350
- const blocks = ctx.getRegister('blocks');
4767
+ const blocks = ctx.getRegister('blocks', {});
4351
4768
  // set whole content to anonymous block if anonymous doesn't specified
4352
4769
  if (blocks[''] === undefined)
4353
4770
  blocks[''] = (parent, emitter) => emitter.write(html);
4354
4771
  ctx.setRegister('blockMode', BlockMode.OUTPUT);
4355
4772
  // render the layout file use stored blocks
4356
- ctx.push((yield args.render(ctx)));
4773
+ ctx.push(createScope((yield args.render(ctx))));
4357
4774
  yield renderer.renderTemplates(templates, ctx, emitter);
4358
4775
  ctx.pop();
4359
4776
  }
4360
4777
  *children(partials) {
4361
4778
  const templates = this.templates.slice();
4362
4779
  if (partials && isString(this.file)) {
4363
- templates.push(...(yield this.liquid._parsePartialFile(this.file, true, this['currentFile'])));
4780
+ templates.push(...(yield this.liquid._parsePartialFile(this.file, true, this.currentFile)));
4364
4781
  }
4365
4782
  return templates;
4366
4783
  }
@@ -4399,23 +4816,28 @@ class BlockTag extends Tag {
4399
4816
  *render(ctx, emitter) {
4400
4817
  const blockRender = this.getBlockRender(ctx);
4401
4818
  if (ctx.getRegister('blockMode') === BlockMode.STORE) {
4402
- ctx.getRegister('blocks')[this.block] = blockRender;
4819
+ ctx.getRegister('blocks', {})[this.block] = blockRender;
4403
4820
  }
4404
4821
  else {
4405
4822
  yield blockRender(new BlockDrop(), emitter);
4406
4823
  }
4407
4824
  }
4408
4825
  getBlockRender(ctx) {
4826
+ const self = this;
4409
4827
  const { liquid, templates } = this;
4410
- const renderChild = ctx.getRegister('blocks')[this.block];
4828
+ const renderChild = ctx.getRegister('blocks', {})[this.block];
4411
4829
  const renderCurrent = function* (superBlock, emitter) {
4412
- // add {{ block.super }} support when rendering
4413
- ctx.push({ block: superBlock });
4830
+ const stack = ctx.getRegister('blockStack', []);
4831
+ if (stack.includes(self))
4832
+ throw new Error('block tag cannot be nested');
4833
+ stack.push(self);
4834
+ ctx.push(createScope({ block: superBlock }));
4414
4835
  yield liquid.renderer.renderTemplates(templates, ctx, emitter);
4415
4836
  ctx.pop();
4837
+ stack.pop();
4416
4838
  };
4417
4839
  return renderChild
4418
- ? (superBlock, emitter) => renderChild(new BlockDrop(() => renderCurrent(superBlock, emitter)), emitter)
4840
+ ? (superBlock, emitter) => renderChild(new BlockDrop((emitter) => renderCurrent(superBlock, emitter)), emitter)
4419
4841
  : renderCurrent;
4420
4842
  }
4421
4843
  *children() {
@@ -4499,7 +4921,7 @@ class TablerowTag extends Tag {
4499
4921
  const cols = args.cols || collection.length;
4500
4922
  const r = this.liquid.renderer;
4501
4923
  const tablerowloop = new TablerowloopDrop(collection.length, cols, this.collection.getText(), this.variable);
4502
- const scope = { tablerowloop };
4924
+ const scope = createScope({ tablerowloop });
4503
4925
  ctx.push(scope);
4504
4926
  for (let idx = 0; idx < collection.length; idx++, tablerowloop.next()) {
4505
4927
  scope[this.variable] = collection[idx];
@@ -4572,7 +4994,7 @@ class UnlessTag extends Tag {
4572
4994
  *render(ctx, emitter) {
4573
4995
  const r = this.liquid.renderer;
4574
4996
  for (const { value, test, templates } of this.branches) {
4575
- const v = yield value.value(ctx, ctx.opts.lenientIf);
4997
+ const v = yield toValue(yield value.value(ctx, ctx.opts.lenientIf));
4576
4998
  if (test(v, ctx)) {
4577
4999
  yield r.renderTemplates(templates, ctx, emitter);
4578
5000
  return;
@@ -4613,9 +5035,14 @@ class EchoTag extends Tag {
4613
5035
  }
4614
5036
  }
4615
5037
  *render(ctx, emitter) {
5038
+ var _a;
4616
5039
  if (!this.value)
4617
5040
  return;
4618
- const val = yield this.value.value(ctx, false);
5041
+ let val = yield toValue(yield this.value.value(ctx, false));
5042
+ const outputEscape = this.liquid.options.outputEscape;
5043
+ if (outputEscape && !((_a = this.value.filters[this.value.filters.length - 1]) === null || _a === void 0 ? void 0 : _a.raw)) {
5044
+ val = yield outputEscape.call({ context: ctx, token: this.token, liquid: this.liquid }, val);
5045
+ }
4619
5046
  emitter.write(val);
4620
5047
  }
4621
5048
  *arguments() {
@@ -4676,8 +5103,8 @@ const tags = {
4676
5103
  class Liquid {
4677
5104
  constructor(opts = {}) {
4678
5105
  this.renderer = new Render();
4679
- this.filters = {};
4680
- this.tags = {};
5106
+ this.filters = Object.create(null);
5107
+ this.tags = Object.create(null);
4681
5108
  this.options = normalize(opts);
4682
5109
  // eslint-disable-next-line deprecation/deprecation
4683
5110
  this.parser = new Parser(this);
@@ -4689,7 +5116,7 @@ class Liquid {
4689
5116
  return parser.parse(html, filepath);
4690
5117
  }
4691
5118
  _render(tpl, scope, renderOptions) {
4692
- const ctx = scope instanceof Context ? scope : new Context(scope, this.options, renderOptions);
5119
+ const ctx = scope instanceof Context ? scope : new Context(scope, this.options, renderOptions, { liquid: this });
4693
5120
  return this.renderer.renderTemplates(tpl, ctx);
4694
5121
  }
4695
5122
  render(tpl, scope, renderOptions) {
@@ -4701,7 +5128,7 @@ class Liquid {
4701
5128
  return toValueSync(this._render(tpl, scope, Object.assign(Object.assign({}, renderOptions), { sync: true })));
4702
5129
  }
4703
5130
  renderToNodeStream(tpl, scope, renderOptions = {}) {
4704
- const ctx = new Context(scope, this.options, renderOptions);
5131
+ const ctx = new Context(scope, this.options, renderOptions, { liquid: this });
4705
5132
  return this.renderer.renderTemplatesToNodeStream(tpl, ctx);
4706
5133
  }
4707
5134
  _parseAndRender(html, scope, renderOptions) {
@@ -4753,7 +5180,7 @@ class Liquid {
4753
5180
  }
4754
5181
  _evalValue(str, scope) {
4755
5182
  const value = new Value(str, this);
4756
- const ctx = scope instanceof Context ? scope : new Context(scope, this.options);
5183
+ const ctx = scope instanceof Context ? scope : new Context(scope, this.options, {}, { liquid: this });
4757
5184
  return value.value(ctx);
4758
5185
  }
4759
5186
  evalValue(str, scope) {
@@ -4767,6 +5194,9 @@ class Liquid {
4767
5194
  registerFilter(name, filter) {
4768
5195
  this.filters[name] = filter;
4769
5196
  }
5197
+ unregisterFilter(name) {
5198
+ delete this.filters[name];
5199
+ }
4770
5200
  registerTag(name, tag) {
4771
5201
  this.tags[name] = isFunction(tag) ? tag : createTagClass(tag);
4772
5202
  }
@@ -4880,4 +5310,4 @@ class Liquid {
4880
5310
  /* istanbul ignore file */
4881
5311
  const version = '[VI]{version}[/VI]';
4882
5312
 
4883
- export { AssertionError, AssignTag, BlockTag, BreakTag, CaptureTag, CaseTag, CommentTag, Context, ContinueTag, CycleTag, DecrementTag, Drop, EchoTag, Expression, Filter, ForTag, Hash, IfTag, IncludeTag, IncrementTag, InlineCommentTag, LayoutTag, Liquid, LiquidError, LiquidTag, Output, ParseError, ParseStream, Parser, RawTag, RenderError, RenderTag, TablerowTag, Tag, TagToken, Token, TokenKind, TokenizationError, Tokenizer, typeGuards as TypeGuards, UndefinedVariableError, UnlessTag, Value, Variable, analyze, analyzeSync, assert, createTrie, defaultOperators, defaultOptions, evalQuotedToken, evalToken, filters, isFalsy, isTruthy, tags, toPromise, toValue, toValueSync, version };
5313
+ export { AssertionError, AssignTag, BlockTag, BreakTag, CaptureTag, CaseTag, CommentTag, Context, ContinueTag, CycleTag, DecrementTag, Drop, EchoTag, Expression, Filter, ForTag, Hash, IfTag, IncludeTag, IncrementTag, InlineCommentTag, LayoutTag, Liquid, LiquidError, LiquidTag, LookupType, Output, ParseError, ParseStream, Parser, RawTag, RenderError, RenderTag, TablerowTag, Tag, TagToken, Token, TokenKind, TokenizationError, Tokenizer, typeGuards as TypeGuards, UndefinedVariableError, UnlessTag, Value, Variable, analyze, analyzeSync, assert, createTrie, defaultOperators, defaultOptions, evalQuotedToken, evalToken, evalTokenValue, filters, isFalsy, isTruthy, tags, toPromise, toValue, toValueSync, version };