@skeptools/skep-organization-project 0.0.15 → 0.0.17
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.
- package/.jsii +138 -3
- package/API.md +137 -44
- package/lib/skep-organization-project.js +1 -1
- package/node_modules/liquidjs/README.md +29 -17
- package/node_modules/liquidjs/dist/context/context.d.ts +13 -4
- package/node_modules/liquidjs/dist/drop/blank-drop.d.ts +1 -0
- package/node_modules/liquidjs/dist/drop/drop.d.ts +1 -1
- package/node_modules/liquidjs/dist/drop/empty-drop.d.ts +1 -0
- package/node_modules/liquidjs/dist/filters/array.d.ts +14 -14
- package/node_modules/liquidjs/dist/filters/html.d.ts +6 -5
- package/node_modules/liquidjs/dist/filters/math.d.ts +9 -9
- package/node_modules/liquidjs/dist/filters/string.d.ts +22 -21
- package/node_modules/liquidjs/dist/fs/map-fs.d.ts +13 -0
- package/node_modules/liquidjs/dist/fs/node-require.d.ts +1 -2
- package/node_modules/liquidjs/dist/index.d.ts +3 -3
- package/node_modules/liquidjs/dist/liquid-options.d.ts +30 -2
- package/node_modules/liquidjs/dist/liquid.browser.min.js +1 -1
- package/node_modules/liquidjs/dist/liquid.browser.min.js.map +1 -1
- package/node_modules/liquidjs/dist/{liquid.browser.esm.js → liquid.browser.mjs} +1147 -264
- package/node_modules/liquidjs/dist/liquid.browser.umd.js +2072 -478
- package/node_modules/liquidjs/dist/liquid.browser.umd.js.map +1 -1
- package/node_modules/liquidjs/dist/liquid.d.ts +32 -1
- package/node_modules/liquidjs/dist/{liquid.node.cjs.js → liquid.node.js} +1123 -264
- package/node_modules/liquidjs/dist/{liquid.node.esm.js → liquid.node.mjs} +1152 -265
- package/node_modules/liquidjs/dist/parser/parser.d.ts +1 -0
- package/node_modules/liquidjs/dist/parser/parser.spec.d.ts +1 -0
- package/node_modules/liquidjs/dist/parser/tokenizer.d.ts +4 -3
- package/node_modules/liquidjs/dist/render/expression.d.ts +1 -1
- package/node_modules/liquidjs/dist/render/operator.d.ts +1 -0
- package/node_modules/liquidjs/dist/tags/assign.d.ts +5 -0
- package/node_modules/liquidjs/dist/tags/block.d.ts +4 -1
- package/node_modules/liquidjs/dist/tags/break.d.ts +1 -1
- package/node_modules/liquidjs/dist/tags/capture.d.ts +7 -2
- package/node_modules/liquidjs/dist/tags/case.d.ts +5 -1
- package/node_modules/liquidjs/dist/tags/continue.d.ts +1 -1
- package/node_modules/liquidjs/dist/tags/cycle.d.ts +2 -0
- package/node_modules/liquidjs/dist/tags/decrement.d.ts +3 -0
- package/node_modules/liquidjs/dist/tags/echo.d.ts +2 -0
- package/node_modules/liquidjs/dist/tags/for.d.ts +6 -1
- package/node_modules/liquidjs/dist/tags/if.d.ts +6 -2
- package/node_modules/liquidjs/dist/tags/include.d.ts +7 -2
- package/node_modules/liquidjs/dist/tags/increment.d.ts +3 -0
- package/node_modules/liquidjs/dist/tags/layout.d.ts +6 -1
- package/node_modules/liquidjs/dist/tags/liquid.d.ts +3 -1
- package/node_modules/liquidjs/dist/tags/render.d.ts +7 -2
- package/node_modules/liquidjs/dist/tags/tablerow.d.ts +6 -1
- package/node_modules/liquidjs/dist/tags/unless.d.ts +5 -1
- package/node_modules/liquidjs/dist/template/analysis.d.ts +85 -0
- package/node_modules/liquidjs/dist/template/analysis.spec.d.ts +1 -0
- package/node_modules/liquidjs/dist/template/filter-impl-options.d.ts +2 -0
- package/node_modules/liquidjs/dist/template/filter.d.ts +3 -1
- package/node_modules/liquidjs/dist/template/hash.d.ts +2 -1
- package/node_modules/liquidjs/dist/template/index.d.ts +1 -0
- package/node_modules/liquidjs/dist/template/output.d.ts +2 -1
- package/node_modules/liquidjs/dist/template/tag.d.ts +2 -2
- package/node_modules/liquidjs/dist/template/template.d.ts +29 -0
- package/node_modules/liquidjs/dist/tokens/identifier-token.d.ts +0 -1
- package/node_modules/liquidjs/dist/tokens/liquid-tag-token.d.ts +1 -1
- package/node_modules/liquidjs/dist/util/assert.d.ts +1 -0
- package/node_modules/liquidjs/dist/util/error.d.ts +7 -2
- package/node_modules/liquidjs/dist/util/error.spec.d.ts +1 -0
- package/node_modules/liquidjs/dist/util/index.d.ts +2 -1
- package/node_modules/liquidjs/dist/util/intl.d.ts +6 -0
- package/node_modules/liquidjs/dist/util/limiter.d.ts +8 -0
- package/node_modules/liquidjs/dist/util/liquid-date.d.ts +39 -7
- package/node_modules/liquidjs/dist/util/liquid-date.spec.d.ts +1 -0
- package/node_modules/liquidjs/dist/util/performance.d.ts +5 -0
- package/node_modules/liquidjs/dist/util/performance.spec.d.ts +1 -0
- package/node_modules/liquidjs/dist/util/type-guards.d.ts +2 -1
- package/node_modules/liquidjs/dist/util/type-guards.spec.d.ts +1 -0
- package/node_modules/liquidjs/dist/util/underscore.d.ts +5 -1
- package/node_modules/liquidjs/package.json +11 -7
- package/package.json +17 -13
- package/node_modules/liquidjs/dist/util/timezone-date.d.ts +0 -44
- /package/node_modules/liquidjs/dist/{util/timezone-date.spec.d.ts → fs/map-fs.spec.d.ts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* liquidjs@10.
|
|
3
|
-
* (c) 2016-
|
|
2
|
+
* liquidjs@10.20.1, https://github.com/harttle/liquidjs
|
|
3
|
+
* (c) 2016-2025 harttle
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
6
6
|
'use strict';
|
|
@@ -106,6 +106,10 @@ function toArray(val) {
|
|
|
106
106
|
function toValue(value) {
|
|
107
107
|
return (value instanceof Drop && isFunction(value.valueOf)) ? value.valueOf() : value;
|
|
108
108
|
}
|
|
109
|
+
function toNumber(value) {
|
|
110
|
+
value = Number(value);
|
|
111
|
+
return isNaN(value) ? 0 : value;
|
|
112
|
+
}
|
|
109
113
|
function isNumber(value) {
|
|
110
114
|
return typeof value === 'number';
|
|
111
115
|
}
|
|
@@ -124,6 +128,9 @@ function isArray(value) {
|
|
|
124
128
|
// be compatible with IE 8
|
|
125
129
|
return toString$1.call(value) === '[object Array]';
|
|
126
130
|
}
|
|
131
|
+
function isArrayLike(value) {
|
|
132
|
+
return value && isNumber(value.length);
|
|
133
|
+
}
|
|
127
134
|
function isIterable(value) {
|
|
128
135
|
return isObject(value) && Symbol.iterator in value;
|
|
129
136
|
}
|
|
@@ -205,14 +212,25 @@ function caseInsensitiveCompare(a, b) {
|
|
|
205
212
|
return 0;
|
|
206
213
|
}
|
|
207
214
|
function argumentsToValue(fn) {
|
|
208
|
-
return (...args)
|
|
215
|
+
return function (...args) { return fn.call(this, ...args.map(toValue)); };
|
|
209
216
|
}
|
|
210
217
|
function escapeRegExp(text) {
|
|
211
218
|
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
|
219
|
+
}
|
|
220
|
+
/** Return an array containing unique elements from _array_. Works with nested arrays and objects. */
|
|
221
|
+
function* strictUniq(array) {
|
|
222
|
+
const seen = new Set();
|
|
223
|
+
for (const element of array) {
|
|
224
|
+
const key = JSON.stringify(element);
|
|
225
|
+
if (!seen.has(key)) {
|
|
226
|
+
seen.add(key);
|
|
227
|
+
yield element;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
212
230
|
}
|
|
213
231
|
|
|
214
232
|
/**
|
|
215
|
-
* targeting ES5, extends Error won't create a proper prototype chain, need a trait to
|
|
233
|
+
* targeting ES5, extends Error won't create a proper prototype chain, need a trait to keep track of classes
|
|
216
234
|
*/
|
|
217
235
|
const TRAIT = '__liquidClass__';
|
|
218
236
|
class LiquidError extends Error {
|
|
@@ -266,6 +284,19 @@ class RenderError extends LiquidError {
|
|
|
266
284
|
return obj.name === 'RenderError';
|
|
267
285
|
}
|
|
268
286
|
}
|
|
287
|
+
class LiquidErrors extends LiquidError {
|
|
288
|
+
constructor(errors) {
|
|
289
|
+
super(errors[0], errors[0].token);
|
|
290
|
+
this.errors = errors;
|
|
291
|
+
this.name = 'LiquidErrors';
|
|
292
|
+
const s = errors.length > 1 ? 's' : '';
|
|
293
|
+
this.message = `${errors.length} error${s} found`;
|
|
294
|
+
super.update();
|
|
295
|
+
}
|
|
296
|
+
static is(obj) {
|
|
297
|
+
return obj.name === 'LiquidErrors';
|
|
298
|
+
}
|
|
299
|
+
}
|
|
269
300
|
class UndefinedVariableError extends LiquidError {
|
|
270
301
|
constructor(err, token) {
|
|
271
302
|
super(err, token);
|
|
@@ -344,6 +375,9 @@ function assert(predicate, message) {
|
|
|
344
375
|
: (message || `expect ${predicate} to be true`);
|
|
345
376
|
throw new AssertionError(msg);
|
|
346
377
|
}
|
|
378
|
+
}
|
|
379
|
+
function assertEmpty(predicate, message = `unexpected ${JSON.stringify(predicate)}`) {
|
|
380
|
+
assert(!predicate, message);
|
|
347
381
|
}
|
|
348
382
|
|
|
349
383
|
class NullDrop extends Drop {
|
|
@@ -393,6 +427,9 @@ class EmptyDrop extends Drop {
|
|
|
393
427
|
valueOf() {
|
|
394
428
|
return '';
|
|
395
429
|
}
|
|
430
|
+
static is(value) {
|
|
431
|
+
return value instanceof EmptyDrop;
|
|
432
|
+
}
|
|
396
433
|
}
|
|
397
434
|
|
|
398
435
|
class BlankDrop extends EmptyDrop {
|
|
@@ -405,6 +442,9 @@ class BlankDrop extends EmptyDrop {
|
|
|
405
442
|
return /^\s*$/.test(value);
|
|
406
443
|
return super.equals(value);
|
|
407
444
|
}
|
|
445
|
+
static is(value) {
|
|
446
|
+
return value instanceof BlankDrop;
|
|
447
|
+
}
|
|
408
448
|
}
|
|
409
449
|
|
|
410
450
|
class ForloopDrop extends Drop {
|
|
@@ -457,7 +497,12 @@ class BlockDrop extends Drop {
|
|
|
457
497
|
}
|
|
458
498
|
|
|
459
499
|
function isComparable(arg) {
|
|
460
|
-
return arg &&
|
|
500
|
+
return (arg &&
|
|
501
|
+
isFunction(arg.equals) &&
|
|
502
|
+
isFunction(arg.gt) &&
|
|
503
|
+
isFunction(arg.geq) &&
|
|
504
|
+
isFunction(arg.lt) &&
|
|
505
|
+
isFunction(arg.leq));
|
|
461
506
|
}
|
|
462
507
|
|
|
463
508
|
const nil = new NullDrop();
|
|
@@ -539,18 +584,6 @@ function toValueSync(val) {
|
|
|
539
584
|
}
|
|
540
585
|
|
|
541
586
|
const rFormat = /%([-_0^#:]+)?(\d+)?([EO])?(.)/;
|
|
542
|
-
const monthNames = [
|
|
543
|
-
'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
|
|
544
|
-
'September', 'October', 'November', 'December'
|
|
545
|
-
];
|
|
546
|
-
const dayNames = [
|
|
547
|
-
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
|
|
548
|
-
];
|
|
549
|
-
const monthNamesShort = monthNames.map(abbr);
|
|
550
|
-
const dayNamesShort = dayNames.map(abbr);
|
|
551
|
-
function abbr(str) {
|
|
552
|
-
return str.slice(0, 3);
|
|
553
|
-
}
|
|
554
587
|
// prototype extensions
|
|
555
588
|
function daysInMonth(d) {
|
|
556
589
|
const feb = isLeapYear(d) ? 29 : 28;
|
|
@@ -605,19 +638,7 @@ const padWidths = {
|
|
|
605
638
|
U: 2,
|
|
606
639
|
W: 2
|
|
607
640
|
};
|
|
608
|
-
|
|
609
|
-
const padChars = {
|
|
610
|
-
a: ' ',
|
|
611
|
-
A: ' ',
|
|
612
|
-
b: ' ',
|
|
613
|
-
B: ' ',
|
|
614
|
-
c: ' ',
|
|
615
|
-
e: ' ',
|
|
616
|
-
k: ' ',
|
|
617
|
-
l: ' ',
|
|
618
|
-
p: ' ',
|
|
619
|
-
P: ' '
|
|
620
|
-
};
|
|
641
|
+
const padSpaceChars = new Set('aAbBceklpP');
|
|
621
642
|
function getTimezoneOffset(d, opts) {
|
|
622
643
|
const nOffset = Math.abs(d.getTimezoneOffset());
|
|
623
644
|
const h = Math.floor(nOffset / 60);
|
|
@@ -628,10 +649,10 @@ function getTimezoneOffset(d, opts) {
|
|
|
628
649
|
padStart(m, 2, '0');
|
|
629
650
|
}
|
|
630
651
|
const formatCodes = {
|
|
631
|
-
a: (d) =>
|
|
632
|
-
A: (d) =>
|
|
633
|
-
b: (d) =>
|
|
634
|
-
B: (d) =>
|
|
652
|
+
a: (d) => d.getShortWeekdayName(),
|
|
653
|
+
A: (d) => d.getLongWeekdayName(),
|
|
654
|
+
b: (d) => d.getShortMonthName(),
|
|
655
|
+
B: (d) => d.getLongMonthName(),
|
|
635
656
|
c: (d) => d.toLocaleString(),
|
|
636
657
|
C: (d) => century(d),
|
|
637
658
|
d: (d) => d.getDate(),
|
|
@@ -663,12 +684,7 @@ const formatCodes = {
|
|
|
663
684
|
y: (d) => d.getFullYear().toString().slice(2, 4),
|
|
664
685
|
Y: (d) => d.getFullYear(),
|
|
665
686
|
z: getTimezoneOffset,
|
|
666
|
-
Z: (d, opts) =>
|
|
667
|
-
if (d.getTimezoneName) {
|
|
668
|
-
return d.getTimezoneName() || getTimezoneOffset(d, opts);
|
|
669
|
-
}
|
|
670
|
-
return (typeof Intl !== 'undefined' ? Intl.DateTimeFormat().resolvedOptions().timeZone : '');
|
|
671
|
-
},
|
|
687
|
+
Z: (d, opts) => d.getTimeZoneName() || getTimezoneOffset(d, opts),
|
|
672
688
|
't': () => '\t',
|
|
673
689
|
'n': () => '\n',
|
|
674
690
|
'%': () => '%'
|
|
@@ -694,7 +710,7 @@ function format(d, match) {
|
|
|
694
710
|
for (const flag of flagStr)
|
|
695
711
|
flags[flag] = true;
|
|
696
712
|
let ret = String(convert(d, { flags, width, modifier }));
|
|
697
|
-
let padChar =
|
|
713
|
+
let padChar = padSpaceChars.has(conversion) ? ' ' : '0';
|
|
698
714
|
let padWidth = width || padWidths[conversion] || 0;
|
|
699
715
|
if (flags['^'])
|
|
700
716
|
ret = ret.toUpperCase();
|
|
@@ -709,9 +725,26 @@ function format(d, match) {
|
|
|
709
725
|
return padStart(ret, padWidth, padChar);
|
|
710
726
|
}
|
|
711
727
|
|
|
728
|
+
function getDateTimeFormat() {
|
|
729
|
+
return (typeof Intl !== 'undefined' ? Intl.DateTimeFormat : undefined);
|
|
730
|
+
}
|
|
731
|
+
|
|
712
732
|
// one minute in milliseconds
|
|
713
733
|
const OneMinute = 60000;
|
|
714
|
-
|
|
734
|
+
/**
|
|
735
|
+
* Need support both ISO8601 and RFC2822 as in major browsers & NodeJS
|
|
736
|
+
* RFC2822: https://datatracker.ietf.org/doc/html/rfc2822#section-3.3
|
|
737
|
+
*/
|
|
738
|
+
const TIMEZONE_PATTERN = /([zZ]|([+-])(\d{2}):?(\d{2}))$/;
|
|
739
|
+
const monthNames = [
|
|
740
|
+
'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
|
|
741
|
+
'September', 'October', 'November', 'December'
|
|
742
|
+
];
|
|
743
|
+
const monthNamesShort = monthNames.map(name => name.slice(0, 3));
|
|
744
|
+
const dayNames = [
|
|
745
|
+
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
|
|
746
|
+
];
|
|
747
|
+
const dayNamesShort = dayNames.map(name => name.slice(0, 3));
|
|
715
748
|
/**
|
|
716
749
|
* A date implementation with timezone info, just like Ruby date
|
|
717
750
|
*
|
|
@@ -719,12 +752,16 @@ const ISO8601_TIMEZONE_PATTERN = /([zZ]|([+-])(\d{2}):(\d{2}))$/;
|
|
|
719
752
|
* - create a Date offset by it's timezone difference, avoiding overriding a bunch of methods
|
|
720
753
|
* - rewrite getTimezoneOffset() to trick strftime
|
|
721
754
|
*/
|
|
722
|
-
class
|
|
723
|
-
constructor(init, timezone) {
|
|
724
|
-
this.
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
this.
|
|
755
|
+
class LiquidDate {
|
|
756
|
+
constructor(init, locale, timezone) {
|
|
757
|
+
this.locale = locale;
|
|
758
|
+
this.DateTimeFormat = getDateTimeFormat();
|
|
759
|
+
this.date = new Date(init);
|
|
760
|
+
this.timezoneFixed = timezone !== undefined;
|
|
761
|
+
if (timezone === undefined) {
|
|
762
|
+
timezone = this.date.getTimezoneOffset();
|
|
763
|
+
}
|
|
764
|
+
this.timezoneOffset = isString(timezone) ? LiquidDate.getTimezoneOffset(timezone, this.date) : timezone;
|
|
728
765
|
this.timezoneName = isString(timezone) ? timezone : '';
|
|
729
766
|
const diff = (this.date.getTimezoneOffset() - this.timezoneOffset) * OneMinute;
|
|
730
767
|
const time = this.date.getTime() + diff;
|
|
@@ -772,8 +809,30 @@ class TimezoneDate {
|
|
|
772
809
|
getTimezoneOffset() {
|
|
773
810
|
return this.timezoneOffset;
|
|
774
811
|
}
|
|
775
|
-
|
|
776
|
-
|
|
812
|
+
getTimeZoneName() {
|
|
813
|
+
if (this.timezoneFixed)
|
|
814
|
+
return this.timezoneName;
|
|
815
|
+
if (!this.DateTimeFormat)
|
|
816
|
+
return;
|
|
817
|
+
return this.DateTimeFormat().resolvedOptions().timeZone;
|
|
818
|
+
}
|
|
819
|
+
getLongMonthName() {
|
|
820
|
+
return this.format({ month: 'long' }) ?? monthNames[this.getMonth()];
|
|
821
|
+
}
|
|
822
|
+
getShortMonthName() {
|
|
823
|
+
return this.format({ month: 'short' }) ?? monthNamesShort[this.getMonth()];
|
|
824
|
+
}
|
|
825
|
+
getLongWeekdayName() {
|
|
826
|
+
return this.format({ weekday: 'long' }) ?? dayNames[this.displayDate.getDay()];
|
|
827
|
+
}
|
|
828
|
+
getShortWeekdayName() {
|
|
829
|
+
return this.format({ weekday: 'short' }) ?? dayNamesShort[this.displayDate.getDay()];
|
|
830
|
+
}
|
|
831
|
+
valid() {
|
|
832
|
+
return !isNaN(this.getTime());
|
|
833
|
+
}
|
|
834
|
+
format(options) {
|
|
835
|
+
return this.DateTimeFormat && this.DateTimeFormat(this.locale, options).format(this.displayDate);
|
|
777
836
|
}
|
|
778
837
|
/**
|
|
779
838
|
* Create a Date object fixed to it's declared Timezone. Both
|
|
@@ -788,21 +847,21 @@ class TimezoneDate {
|
|
|
788
847
|
* we create a different Date to trick strftime, it's both simpler and more performant.
|
|
789
848
|
* Given that a template is expected to be parsed fewer times than rendered.
|
|
790
849
|
*/
|
|
791
|
-
static createDateFixedToTimezone(dateString) {
|
|
792
|
-
const m = dateString.match(
|
|
850
|
+
static createDateFixedToTimezone(dateString, locale) {
|
|
851
|
+
const m = dateString.match(TIMEZONE_PATTERN);
|
|
793
852
|
// representing a UTC timestamp
|
|
794
853
|
if (m && m[1] === 'Z') {
|
|
795
|
-
return new
|
|
854
|
+
return new LiquidDate(+new Date(dateString), locale, 0);
|
|
796
855
|
}
|
|
797
856
|
// has a timezone specified
|
|
798
857
|
if (m && m[2] && m[3] && m[4]) {
|
|
799
858
|
const [, , sign, hours, minutes] = m;
|
|
800
859
|
const offset = (sign === '+' ? -1 : 1) * (parseInt(hours, 10) * 60 + parseInt(minutes, 10));
|
|
801
|
-
return new
|
|
860
|
+
return new LiquidDate(+new Date(dateString), locale, offset);
|
|
802
861
|
}
|
|
803
|
-
return new
|
|
862
|
+
return new LiquidDate(dateString, locale);
|
|
804
863
|
}
|
|
805
|
-
static getTimezoneOffset(timezoneName, date
|
|
864
|
+
static getTimezoneOffset(timezoneName, date) {
|
|
806
865
|
const localDateString = date.toLocaleString('en-US', { timeZone: timezoneName });
|
|
807
866
|
const utcDateString = date.toLocaleString('en-US', { timeZone: 'UTC' });
|
|
808
867
|
const localDate = new Date(localDateString);
|
|
@@ -811,6 +870,23 @@ class TimezoneDate {
|
|
|
811
870
|
}
|
|
812
871
|
}
|
|
813
872
|
|
|
873
|
+
class Limiter {
|
|
874
|
+
constructor(resource, limit) {
|
|
875
|
+
this.base = 0;
|
|
876
|
+
this.message = `${resource} limit exceeded`;
|
|
877
|
+
this.limit = limit;
|
|
878
|
+
}
|
|
879
|
+
use(count) {
|
|
880
|
+
count = toNumber(count);
|
|
881
|
+
assert(this.base + count <= this.limit, this.message);
|
|
882
|
+
this.base += count;
|
|
883
|
+
}
|
|
884
|
+
check(count) {
|
|
885
|
+
count = toNumber(count);
|
|
886
|
+
assert(count <= this.limit, this.message);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
814
890
|
class DelimitedToken extends Token {
|
|
815
891
|
constructor(kind, [contentBegin, contentEnd], input, begin, end, trimLeft, trimRight, file) {
|
|
816
892
|
super(kind, input, begin, end, file);
|
|
@@ -891,16 +967,6 @@ class IdentifierToken extends Token {
|
|
|
891
967
|
this.file = file;
|
|
892
968
|
this.content = this.getText();
|
|
893
969
|
}
|
|
894
|
-
isNumber(allowSign = false) {
|
|
895
|
-
const begin = allowSign && TYPES[this.input.charCodeAt(this.begin)] & SIGN
|
|
896
|
-
? this.begin + 1
|
|
897
|
-
: this.begin;
|
|
898
|
-
for (let i = begin; i < this.end; i++) {
|
|
899
|
-
if (!(TYPES[this.input.charCodeAt(i)] & NUMBER))
|
|
900
|
-
return false;
|
|
901
|
-
}
|
|
902
|
-
return true;
|
|
903
|
-
}
|
|
904
970
|
}
|
|
905
971
|
|
|
906
972
|
class LiteralToken extends Token {
|
|
@@ -1068,7 +1134,9 @@ class LiquidTagToken extends DelimitedToken {
|
|
|
1068
1134
|
this.name = this.tokenizer.readTagName();
|
|
1069
1135
|
this.tokenizer.assert(this.name, 'illegal liquid tag syntax');
|
|
1070
1136
|
this.tokenizer.skipBlank();
|
|
1071
|
-
|
|
1137
|
+
}
|
|
1138
|
+
get args() {
|
|
1139
|
+
return this.tokenizer.input.slice(this.tokenizer.p, this.contentRange[1]);
|
|
1072
1140
|
}
|
|
1073
1141
|
}
|
|
1074
1142
|
|
|
@@ -1089,6 +1157,15 @@ class FilteredValueToken extends Token {
|
|
|
1089
1157
|
}
|
|
1090
1158
|
}
|
|
1091
1159
|
|
|
1160
|
+
const polyfill = {
|
|
1161
|
+
now: () => Date.now()
|
|
1162
|
+
};
|
|
1163
|
+
function getPerformance() {
|
|
1164
|
+
return (typeof global === 'object' && global.performance) ||
|
|
1165
|
+
(typeof window === 'object' && window.performance) ||
|
|
1166
|
+
polyfill;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1092
1169
|
class SimpleEmitter {
|
|
1093
1170
|
constructor() {
|
|
1094
1171
|
this.buffer = '';
|
|
@@ -1144,20 +1221,28 @@ class Render {
|
|
|
1144
1221
|
if (!emitter) {
|
|
1145
1222
|
emitter = ctx.opts.keepOutputType ? new KeepingTypeEmitter() : new SimpleEmitter();
|
|
1146
1223
|
}
|
|
1224
|
+
const errors = [];
|
|
1147
1225
|
for (const tpl of templates) {
|
|
1226
|
+
ctx.renderLimit.check(getPerformance().now());
|
|
1148
1227
|
try {
|
|
1149
1228
|
// if tpl.render supports emitter, it'll return empty `html`
|
|
1150
1229
|
const html = yield tpl.render(ctx, emitter);
|
|
1151
1230
|
// if not, it'll return an `html`, write to the emitter for it
|
|
1152
1231
|
html && emitter.write(html);
|
|
1153
|
-
if (
|
|
1232
|
+
if (ctx.breakCalled || ctx.continueCalled)
|
|
1154
1233
|
break;
|
|
1155
1234
|
}
|
|
1156
1235
|
catch (e) {
|
|
1157
|
-
const err =
|
|
1158
|
-
|
|
1236
|
+
const err = LiquidError.is(e) ? e : new RenderError(e, tpl);
|
|
1237
|
+
if (ctx.opts.catchAllErrors)
|
|
1238
|
+
errors.push(err);
|
|
1239
|
+
else
|
|
1240
|
+
throw err;
|
|
1159
1241
|
}
|
|
1160
1242
|
}
|
|
1243
|
+
if (errors.length) {
|
|
1244
|
+
throw new LiquidErrors(errors);
|
|
1245
|
+
}
|
|
1161
1246
|
return emitter.buffer;
|
|
1162
1247
|
}
|
|
1163
1248
|
}
|
|
@@ -1228,6 +1313,7 @@ function evalQuotedToken(token) {
|
|
|
1228
1313
|
function* evalRangeToken(token, ctx) {
|
|
1229
1314
|
const low = yield evalToken(token.lhs, ctx);
|
|
1230
1315
|
const high = yield evalToken(token.rhs, ctx);
|
|
1316
|
+
ctx.memoryLimit.use(high - low + 1);
|
|
1231
1317
|
return range(+low, +high + 1);
|
|
1232
1318
|
}
|
|
1233
1319
|
function* toPostfix(tokens) {
|
|
@@ -1251,6 +1337,7 @@ function isTruthy(val, ctx) {
|
|
|
1251
1337
|
return !isFalsy(val, ctx);
|
|
1252
1338
|
}
|
|
1253
1339
|
function isFalsy(val, ctx) {
|
|
1340
|
+
val = toValue(val);
|
|
1254
1341
|
if (ctx.opts.jsTruthy) {
|
|
1255
1342
|
return !val;
|
|
1256
1343
|
}
|
|
@@ -1318,6 +1405,9 @@ function arrayEquals(lhs, rhs) {
|
|
|
1318
1405
|
if (lhs.length !== rhs.length)
|
|
1319
1406
|
return false;
|
|
1320
1407
|
return !lhs.some((value, i) => !equals(value, rhs[i]));
|
|
1408
|
+
}
|
|
1409
|
+
function arrayIncludes(arr, item) {
|
|
1410
|
+
return arr.some(value => equals(value, item));
|
|
1321
1411
|
}
|
|
1322
1412
|
|
|
1323
1413
|
class Node {
|
|
@@ -1378,7 +1468,7 @@ class LRU {
|
|
|
1378
1468
|
}
|
|
1379
1469
|
}
|
|
1380
1470
|
|
|
1381
|
-
const requireResolve = require.resolve;
|
|
1471
|
+
const requireResolve = (partial) => require.resolve(partial, { paths: ['.'] });
|
|
1382
1472
|
|
|
1383
1473
|
const statAsync = promisify(fs$1.stat);
|
|
1384
1474
|
const readFileAsync = promisify(fs$1.readFile);
|
|
@@ -1495,22 +1585,30 @@ const unescapeMap = {
|
|
|
1495
1585
|
''': "'"
|
|
1496
1586
|
};
|
|
1497
1587
|
function escape(str) {
|
|
1498
|
-
|
|
1588
|
+
str = stringify(str);
|
|
1589
|
+
this.context.memoryLimit.use(str.length);
|
|
1590
|
+
return str.replace(/&|<|>|"|'/g, m => escapeMap[m]);
|
|
1499
1591
|
}
|
|
1500
1592
|
function xml_escape(str) {
|
|
1501
|
-
return escape(str);
|
|
1593
|
+
return escape.call(this, str);
|
|
1502
1594
|
}
|
|
1503
1595
|
function unescape(str) {
|
|
1504
|
-
|
|
1596
|
+
str = stringify(str);
|
|
1597
|
+
this.context.memoryLimit.use(str.length);
|
|
1598
|
+
return str.replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m]);
|
|
1505
1599
|
}
|
|
1506
1600
|
function escape_once(str) {
|
|
1507
|
-
return escape(unescape(
|
|
1601
|
+
return escape.call(this, unescape.call(this, str));
|
|
1508
1602
|
}
|
|
1509
1603
|
function newline_to_br(v) {
|
|
1510
|
-
|
|
1604
|
+
const str = stringify(v);
|
|
1605
|
+
this.context.memoryLimit.use(str.length);
|
|
1606
|
+
return str.replace(/\r?\n/gm, '<br />\n');
|
|
1511
1607
|
}
|
|
1512
1608
|
function strip_html(v) {
|
|
1513
|
-
|
|
1609
|
+
const str = stringify(v);
|
|
1610
|
+
this.context.memoryLimit.use(str.length);
|
|
1611
|
+
return str.replace(/<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>|<.*?>|<!--[\s\S]*?-->/g, '');
|
|
1514
1612
|
}
|
|
1515
1613
|
|
|
1516
1614
|
var htmlFilters = /*#__PURE__*/Object.freeze({
|
|
@@ -1522,18 +1620,64 @@ var htmlFilters = /*#__PURE__*/Object.freeze({
|
|
|
1522
1620
|
strip_html: strip_html
|
|
1523
1621
|
});
|
|
1524
1622
|
|
|
1623
|
+
class MapFS {
|
|
1624
|
+
constructor(mapping) {
|
|
1625
|
+
this.mapping = mapping;
|
|
1626
|
+
this.sep = '/';
|
|
1627
|
+
}
|
|
1628
|
+
async exists(filepath) {
|
|
1629
|
+
return this.existsSync(filepath);
|
|
1630
|
+
}
|
|
1631
|
+
existsSync(filepath) {
|
|
1632
|
+
return !isNil(this.mapping[filepath]);
|
|
1633
|
+
}
|
|
1634
|
+
async readFile(filepath) {
|
|
1635
|
+
return this.readFileSync(filepath);
|
|
1636
|
+
}
|
|
1637
|
+
readFileSync(filepath) {
|
|
1638
|
+
const content = this.mapping[filepath];
|
|
1639
|
+
if (isNil(content))
|
|
1640
|
+
throw new Error(`ENOENT: ${filepath}`);
|
|
1641
|
+
return content;
|
|
1642
|
+
}
|
|
1643
|
+
dirname(filepath) {
|
|
1644
|
+
const segments = filepath.split(this.sep);
|
|
1645
|
+
segments.pop();
|
|
1646
|
+
return segments.join(this.sep);
|
|
1647
|
+
}
|
|
1648
|
+
resolve(dir, file, ext) {
|
|
1649
|
+
file += ext;
|
|
1650
|
+
if (dir === '.')
|
|
1651
|
+
return file;
|
|
1652
|
+
const segments = dir.split(/\/+/);
|
|
1653
|
+
for (const segment of file.split(this.sep)) {
|
|
1654
|
+
if (segment === '.' || segment === '')
|
|
1655
|
+
continue;
|
|
1656
|
+
else if (segment === '..') {
|
|
1657
|
+
if (segments.length > 1 || segments[0] !== '')
|
|
1658
|
+
segments.pop();
|
|
1659
|
+
}
|
|
1660
|
+
else
|
|
1661
|
+
segments.push(segment);
|
|
1662
|
+
}
|
|
1663
|
+
return segments.join(this.sep);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1525
1667
|
const defaultOptions = {
|
|
1526
1668
|
root: ['.'],
|
|
1527
1669
|
layouts: ['.'],
|
|
1528
1670
|
partials: ['.'],
|
|
1529
1671
|
relativeReference: true,
|
|
1530
1672
|
jekyllInclude: false,
|
|
1673
|
+
keyValueSeparator: ':',
|
|
1531
1674
|
cache: undefined,
|
|
1532
1675
|
extname: '',
|
|
1533
1676
|
fs: fs,
|
|
1534
1677
|
dynamicPartials: true,
|
|
1535
1678
|
jsTruthy: false,
|
|
1536
1679
|
dateFormat: '%A, %B %-e, %Y at %-l:%M %P %z',
|
|
1680
|
+
locale: '',
|
|
1537
1681
|
trimTagRight: false,
|
|
1538
1682
|
trimTagLeft: false,
|
|
1539
1683
|
trimOutputRight: false,
|
|
@@ -1550,7 +1694,10 @@ const defaultOptions = {
|
|
|
1550
1694
|
lenientIf: false,
|
|
1551
1695
|
globals: {},
|
|
1552
1696
|
keepOutputType: false,
|
|
1553
|
-
operators: defaultOperators
|
|
1697
|
+
operators: defaultOperators,
|
|
1698
|
+
memoryLimit: Infinity,
|
|
1699
|
+
parseLimit: Infinity,
|
|
1700
|
+
renderLimit: Infinity
|
|
1554
1701
|
};
|
|
1555
1702
|
function normalize(options) {
|
|
1556
1703
|
if (options.hasOwnProperty('root')) {
|
|
@@ -1578,6 +1725,14 @@ function normalize(options) {
|
|
|
1578
1725
|
options.partials = normalizeDirectoryList(options.partials);
|
|
1579
1726
|
options.layouts = normalizeDirectoryList(options.layouts);
|
|
1580
1727
|
options.outputEscape = options.outputEscape && getOutputEscapeFunction(options.outputEscape);
|
|
1728
|
+
if (!options.locale) {
|
|
1729
|
+
options.locale = getDateTimeFormat()?.().resolvedOptions().locale ?? 'en-US';
|
|
1730
|
+
}
|
|
1731
|
+
if (options.templates) {
|
|
1732
|
+
options.fs = new MapFS(options.templates);
|
|
1733
|
+
options.relativeReference = true;
|
|
1734
|
+
options.root = options.partials = options.layouts = '.';
|
|
1735
|
+
}
|
|
1581
1736
|
return options;
|
|
1582
1737
|
}
|
|
1583
1738
|
function getOutputEscapeFunction(nameOrFunction) {
|
|
@@ -1704,9 +1859,7 @@ class Tokenizer {
|
|
|
1704
1859
|
this.skipBlank();
|
|
1705
1860
|
if (this.end())
|
|
1706
1861
|
return null;
|
|
1707
|
-
this.assert(this.
|
|
1708
|
-
this.p++;
|
|
1709
|
-
const begin = this.p;
|
|
1862
|
+
this.assert(this.read() === '|', `expected "|" before filter`);
|
|
1710
1863
|
const name = this.readIdentifier();
|
|
1711
1864
|
if (!name.size()) {
|
|
1712
1865
|
this.assert(this.end(), `expected filter name`);
|
|
@@ -1727,7 +1880,7 @@ class Tokenizer {
|
|
|
1727
1880
|
else {
|
|
1728
1881
|
throw this.error('expected ":" after filter name');
|
|
1729
1882
|
}
|
|
1730
|
-
return new FilterToken(name.getText(), args, this.input, begin, this.p, this.file);
|
|
1883
|
+
return new FilterToken(name.getText(), args, this.input, name.begin, this.p, this.file);
|
|
1731
1884
|
}
|
|
1732
1885
|
readFilterArg() {
|
|
1733
1886
|
const key = this.readValue();
|
|
@@ -1768,7 +1921,7 @@ class Tokenizer {
|
|
|
1768
1921
|
}
|
|
1769
1922
|
return new HTMLToken(this.input, begin, this.p, this.file);
|
|
1770
1923
|
}
|
|
1771
|
-
readTagToken(options
|
|
1924
|
+
readTagToken(options) {
|
|
1772
1925
|
const { file, input } = this;
|
|
1773
1926
|
const begin = this.p;
|
|
1774
1927
|
if (this.readToDelimiter(options.tagDelimiterRight) === -1) {
|
|
@@ -1899,7 +2052,7 @@ class Tokenizer {
|
|
|
1899
2052
|
return;
|
|
1900
2053
|
let value;
|
|
1901
2054
|
this.skipBlank();
|
|
1902
|
-
const sep = jekyllStyle ? '=' : ':';
|
|
2055
|
+
const sep = isString(jekyllStyle) ? jekyllStyle : (jekyllStyle ? '=' : ':');
|
|
1903
2056
|
if (this.peek() === sep) {
|
|
1904
2057
|
++this.p;
|
|
1905
2058
|
value = this.readValue();
|
|
@@ -1915,6 +2068,9 @@ class Tokenizer {
|
|
|
1915
2068
|
end() {
|
|
1916
2069
|
return this.p >= this.N;
|
|
1917
2070
|
}
|
|
2071
|
+
read() {
|
|
2072
|
+
return this.input[this.p++];
|
|
2073
|
+
}
|
|
1918
2074
|
readTo(end) {
|
|
1919
2075
|
while (this.p < this.N) {
|
|
1920
2076
|
++this.p;
|
|
@@ -2138,9 +2294,9 @@ class Tag extends TemplateImpl {
|
|
|
2138
2294
|
* hash['reversed'] === undefined
|
|
2139
2295
|
*/
|
|
2140
2296
|
class Hash {
|
|
2141
|
-
constructor(
|
|
2297
|
+
constructor(input, jekyllStyle) {
|
|
2142
2298
|
this.hash = {};
|
|
2143
|
-
const tokenizer = new Tokenizer(
|
|
2299
|
+
const tokenizer = input instanceof Tokenizer ? input : new Tokenizer(input, {});
|
|
2144
2300
|
for (const hash of tokenizer.readHashes(jekyllStyle)) {
|
|
2145
2301
|
this.hash[hash.name.content] = hash.value;
|
|
2146
2302
|
}
|
|
@@ -2163,7 +2319,7 @@ function createTagClass(options) {
|
|
|
2163
2319
|
}
|
|
2164
2320
|
}
|
|
2165
2321
|
*render(ctx, emitter) {
|
|
2166
|
-
const hash = (yield new Hash(this.token.args).render(ctx));
|
|
2322
|
+
const hash = (yield new Hash(this.token.args, ctx.opts.keyValueSeparator).render(ctx));
|
|
2167
2323
|
return yield options.render.call(this, ctx, emitter, hash);
|
|
2168
2324
|
}
|
|
2169
2325
|
};
|
|
@@ -2174,13 +2330,14 @@ function isKeyValuePair(arr) {
|
|
|
2174
2330
|
}
|
|
2175
2331
|
|
|
2176
2332
|
class Filter {
|
|
2177
|
-
constructor(
|
|
2178
|
-
this.
|
|
2333
|
+
constructor(token, options, liquid) {
|
|
2334
|
+
this.token = token;
|
|
2335
|
+
this.name = token.name;
|
|
2179
2336
|
this.handler = isFunction(options)
|
|
2180
2337
|
? options
|
|
2181
2338
|
: (isFunction(options?.handler) ? options.handler : identify);
|
|
2182
2339
|
this.raw = !isFunction(options) && !!options?.raw;
|
|
2183
|
-
this.args = args;
|
|
2340
|
+
this.args = token.args;
|
|
2184
2341
|
this.liquid = liquid;
|
|
2185
2342
|
}
|
|
2186
2343
|
*render(value, context) {
|
|
@@ -2191,7 +2348,7 @@ class Filter {
|
|
|
2191
2348
|
else
|
|
2192
2349
|
argv.push(yield evalToken(arg, context));
|
|
2193
2350
|
}
|
|
2194
|
-
return yield this.handler.apply({ context, liquid: this.liquid }, [value, ...argv]);
|
|
2351
|
+
return yield this.handler.apply({ context, token: this.token, liquid: this.liquid }, [value, ...argv]);
|
|
2195
2352
|
}
|
|
2196
2353
|
}
|
|
2197
2354
|
|
|
@@ -2205,7 +2362,7 @@ class Value {
|
|
|
2205
2362
|
? new Tokenizer(input, liquid.options.operators).readFilteredValue()
|
|
2206
2363
|
: input;
|
|
2207
2364
|
this.initial = token.initial;
|
|
2208
|
-
this.filters = token.filters.map(
|
|
2365
|
+
this.filters = token.filters.map(token => new Filter(token, this.getFilter(liquid, token.name), liquid));
|
|
2209
2366
|
}
|
|
2210
2367
|
*value(ctx, lenient) {
|
|
2211
2368
|
lenient = lenient || (ctx.opts.lenientIf && this.filters.length > 0 && this.filters[0].name === 'default');
|
|
@@ -2230,13 +2387,17 @@ class Output extends TemplateImpl {
|
|
|
2230
2387
|
const filters = this.value.filters;
|
|
2231
2388
|
const outputEscape = liquid.options.outputEscape;
|
|
2232
2389
|
if (!filters[filters.length - 1]?.raw && outputEscape) {
|
|
2233
|
-
|
|
2390
|
+
const token = new FilterToken(toString.call(outputEscape), [], '', 0, 0);
|
|
2391
|
+
filters.push(new Filter(token, outputEscape, liquid));
|
|
2234
2392
|
}
|
|
2235
2393
|
}
|
|
2236
2394
|
*render(ctx, emitter) {
|
|
2237
2395
|
const val = yield this.value.value(ctx, false);
|
|
2238
2396
|
emitter.write(val);
|
|
2239
2397
|
}
|
|
2398
|
+
*arguments() {
|
|
2399
|
+
yield this.value;
|
|
2400
|
+
}
|
|
2240
2401
|
}
|
|
2241
2402
|
|
|
2242
2403
|
class HTML extends TemplateImpl {
|
|
@@ -2249,6 +2410,335 @@ class HTML extends TemplateImpl {
|
|
|
2249
2410
|
}
|
|
2250
2411
|
}
|
|
2251
2412
|
|
|
2413
|
+
/**
|
|
2414
|
+
* A variable's segments and location, which can be coerced to a string.
|
|
2415
|
+
*/
|
|
2416
|
+
class Variable {
|
|
2417
|
+
constructor(segments, location) {
|
|
2418
|
+
this.segments = segments;
|
|
2419
|
+
this.location = location;
|
|
2420
|
+
}
|
|
2421
|
+
toString() {
|
|
2422
|
+
return segmentsString(this.segments, true);
|
|
2423
|
+
}
|
|
2424
|
+
/** Return this variable's segments as an array, possibly with nested arrays for nested paths. */
|
|
2425
|
+
toArray() {
|
|
2426
|
+
function* _visit(...segments) {
|
|
2427
|
+
for (const segment of segments) {
|
|
2428
|
+
if (segment instanceof Variable) {
|
|
2429
|
+
yield Array.from(_visit(...segment.segments));
|
|
2430
|
+
}
|
|
2431
|
+
else {
|
|
2432
|
+
yield segment;
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
return Array.from(_visit(...this.segments));
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Group variables by the string representation of their root segment.
|
|
2441
|
+
*/
|
|
2442
|
+
class VariableMap {
|
|
2443
|
+
constructor() {
|
|
2444
|
+
this.map = new Map();
|
|
2445
|
+
}
|
|
2446
|
+
get(key) {
|
|
2447
|
+
const k = segmentsString([key.segments[0]]);
|
|
2448
|
+
if (!this.map.has(k)) {
|
|
2449
|
+
this.map.set(k, []);
|
|
2450
|
+
}
|
|
2451
|
+
return this.map.get(k);
|
|
2452
|
+
}
|
|
2453
|
+
has(key) {
|
|
2454
|
+
return this.map.has(segmentsString([key.segments[0]]));
|
|
2455
|
+
}
|
|
2456
|
+
push(variable) {
|
|
2457
|
+
this.get(variable).push(variable);
|
|
2458
|
+
}
|
|
2459
|
+
asObject() {
|
|
2460
|
+
return Object.fromEntries(this.map);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
const defaultStaticAnalysisOptions = {
|
|
2464
|
+
partials: true
|
|
2465
|
+
};
|
|
2466
|
+
function* _analyze(templates, partials, sync) {
|
|
2467
|
+
const variables = new VariableMap();
|
|
2468
|
+
const globals = new VariableMap();
|
|
2469
|
+
const locals = new VariableMap();
|
|
2470
|
+
const rootScope = new DummyScope(new Set());
|
|
2471
|
+
// Names of partial templates that we've already analyzed.
|
|
2472
|
+
const seen = new Set();
|
|
2473
|
+
function updateVariables(variable, scope) {
|
|
2474
|
+
variables.push(variable);
|
|
2475
|
+
const aliased = scope.alias(variable);
|
|
2476
|
+
if (aliased !== undefined) {
|
|
2477
|
+
const root = aliased.segments[0];
|
|
2478
|
+
// TODO: What if a a template renders a rendered template? Do we need scope.parent?
|
|
2479
|
+
if (isString(root) && !rootScope.has(root)) {
|
|
2480
|
+
globals.push(aliased);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
else {
|
|
2484
|
+
const root = variable.segments[0];
|
|
2485
|
+
if (isString(root) && !scope.has(root)) {
|
|
2486
|
+
globals.push(variable);
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
// Recurse for nested Variables
|
|
2490
|
+
for (const segment of variable.segments) {
|
|
2491
|
+
if (segment instanceof Variable) {
|
|
2492
|
+
updateVariables(segment, scope);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
function* visit(template, scope) {
|
|
2497
|
+
if (template.arguments) {
|
|
2498
|
+
for (const arg of template.arguments()) {
|
|
2499
|
+
for (const variable of extractVariables(arg)) {
|
|
2500
|
+
updateVariables(variable, scope);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
if (template.localScope) {
|
|
2505
|
+
for (const ident of template.localScope()) {
|
|
2506
|
+
scope.add(ident.content);
|
|
2507
|
+
scope.deleteAlias(ident.content);
|
|
2508
|
+
const [row, col] = ident.getPosition();
|
|
2509
|
+
locals.push(new Variable([ident.content], { row, col, file: ident.file }));
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
if (template.children) {
|
|
2513
|
+
if (template.partialScope) {
|
|
2514
|
+
const partial = template.partialScope();
|
|
2515
|
+
if (partial === undefined) {
|
|
2516
|
+
// Layouts, for example, can have children that are not partials.
|
|
2517
|
+
for (const child of (yield template.children(partials, sync))) {
|
|
2518
|
+
yield visit(child, scope);
|
|
2519
|
+
}
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
if (seen.has(partial.name))
|
|
2523
|
+
return;
|
|
2524
|
+
const partialScopeNames = new Set();
|
|
2525
|
+
const partialScope = partial.isolated
|
|
2526
|
+
? new DummyScope(partialScopeNames)
|
|
2527
|
+
: scope.push(partialScopeNames);
|
|
2528
|
+
for (const name of partial.scope) {
|
|
2529
|
+
if (isString(name)) {
|
|
2530
|
+
partialScopeNames.add(name);
|
|
2531
|
+
}
|
|
2532
|
+
else {
|
|
2533
|
+
const [alias, argument] = name;
|
|
2534
|
+
partialScopeNames.add(alias);
|
|
2535
|
+
const variables = Array.from(extractVariables(argument));
|
|
2536
|
+
if (variables.length) {
|
|
2537
|
+
partialScope.setAlias(alias, variables[0].segments);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
for (const child of (yield template.children(partials, sync))) {
|
|
2542
|
+
yield visit(child, partialScope);
|
|
2543
|
+
seen.add(partial.name);
|
|
2544
|
+
}
|
|
2545
|
+
partialScope.pop();
|
|
2546
|
+
}
|
|
2547
|
+
else {
|
|
2548
|
+
if (template.blockScope) {
|
|
2549
|
+
scope.push(new Set(template.blockScope()));
|
|
2550
|
+
}
|
|
2551
|
+
for (const child of (yield template.children(partials, sync))) {
|
|
2552
|
+
yield visit(child, scope);
|
|
2553
|
+
}
|
|
2554
|
+
if (template.blockScope) {
|
|
2555
|
+
scope.pop();
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
for (const template of templates) {
|
|
2561
|
+
yield visit(template, rootScope);
|
|
2562
|
+
}
|
|
2563
|
+
return {
|
|
2564
|
+
variables: variables.asObject(),
|
|
2565
|
+
globals: globals.asObject(),
|
|
2566
|
+
locals: locals.asObject()
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
/**
|
|
2570
|
+
* Statically analyze a template and report variable usage.
|
|
2571
|
+
*/
|
|
2572
|
+
function analyze(template, options = {}) {
|
|
2573
|
+
const opts = { ...defaultStaticAnalysisOptions, ...options };
|
|
2574
|
+
return toPromise(_analyze(template, opts.partials, false));
|
|
2575
|
+
}
|
|
2576
|
+
/**
|
|
2577
|
+
* Statically analyze a template and report variable usage.
|
|
2578
|
+
*/
|
|
2579
|
+
function analyzeSync(template, options = {}) {
|
|
2580
|
+
const opts = { ...defaultStaticAnalysisOptions, ...options };
|
|
2581
|
+
return toValueSync(_analyze(template, opts.partials, true));
|
|
2582
|
+
}
|
|
2583
|
+
/**
|
|
2584
|
+
* A stack to manage scopes while traversing templates during static analysis.
|
|
2585
|
+
*/
|
|
2586
|
+
class DummyScope {
|
|
2587
|
+
constructor(globals) {
|
|
2588
|
+
this.stack = [{ names: globals, aliases: new Map() }];
|
|
2589
|
+
}
|
|
2590
|
+
/** Return true if `name` is in scope. */
|
|
2591
|
+
has(name) {
|
|
2592
|
+
for (const scope of this.stack) {
|
|
2593
|
+
if (scope.names.has(name)) {
|
|
2594
|
+
return true;
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
return false;
|
|
2598
|
+
}
|
|
2599
|
+
push(scope) {
|
|
2600
|
+
this.stack.push({ names: scope, aliases: new Map() });
|
|
2601
|
+
return this;
|
|
2602
|
+
}
|
|
2603
|
+
pop() {
|
|
2604
|
+
return this.stack.pop()?.names;
|
|
2605
|
+
}
|
|
2606
|
+
// Add a name to the template scope.
|
|
2607
|
+
add(name) {
|
|
2608
|
+
this.stack[0].names.add(name);
|
|
2609
|
+
}
|
|
2610
|
+
/** Return the variable that `variable` aliases, or `variable` if it doesn't alias anything. */
|
|
2611
|
+
alias(variable) {
|
|
2612
|
+
const root = variable.segments[0];
|
|
2613
|
+
if (!isString(root))
|
|
2614
|
+
return undefined;
|
|
2615
|
+
const alias = this.getAlias(root);
|
|
2616
|
+
if (alias === undefined)
|
|
2617
|
+
return undefined;
|
|
2618
|
+
return new Variable([...alias, ...variable.segments.slice(1)], variable.location);
|
|
2619
|
+
}
|
|
2620
|
+
// TODO: `from` could be a path with multiple segments, like `include.x`.
|
|
2621
|
+
setAlias(from, to) {
|
|
2622
|
+
this.stack[this.stack.length - 1].aliases.set(from, to);
|
|
2623
|
+
}
|
|
2624
|
+
deleteAlias(name) {
|
|
2625
|
+
this.stack[this.stack.length - 1].aliases.delete(name);
|
|
2626
|
+
}
|
|
2627
|
+
getAlias(name) {
|
|
2628
|
+
for (const scope of this.stack) {
|
|
2629
|
+
if (scope.aliases.has(name)) {
|
|
2630
|
+
return scope.aliases.get(name);
|
|
2631
|
+
}
|
|
2632
|
+
// If a scope has defined `name`, then it masks aliases in parent scopes.
|
|
2633
|
+
if (scope.names.has(name)) {
|
|
2634
|
+
return undefined;
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
return undefined;
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
function* extractVariables(value) {
|
|
2641
|
+
if (isValueToken(value)) {
|
|
2642
|
+
yield* extractValueTokenVariables(value);
|
|
2643
|
+
}
|
|
2644
|
+
else if (value instanceof Value) {
|
|
2645
|
+
yield* extractFilteredValueVariables(value);
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
function* extractFilteredValueVariables(value) {
|
|
2649
|
+
for (const token of value.initial.postfix) {
|
|
2650
|
+
if (isValueToken(token)) {
|
|
2651
|
+
yield* extractValueTokenVariables(token);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
for (const filter of value.filters) {
|
|
2655
|
+
for (const arg of filter.args) {
|
|
2656
|
+
if (isKeyValuePair(arg) && arg[1]) {
|
|
2657
|
+
yield* extractValueTokenVariables(arg[1]);
|
|
2658
|
+
}
|
|
2659
|
+
else if (isValueToken(arg)) {
|
|
2660
|
+
yield* extractValueTokenVariables(arg);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
function* extractValueTokenVariables(token) {
|
|
2666
|
+
if (isRangeToken(token)) {
|
|
2667
|
+
yield* extractValueTokenVariables(token.lhs);
|
|
2668
|
+
yield* extractValueTokenVariables(token.rhs);
|
|
2669
|
+
}
|
|
2670
|
+
else if (isPropertyAccessToken(token)) {
|
|
2671
|
+
yield extractPropertyAccessVariable(token);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
function extractPropertyAccessVariable(token) {
|
|
2675
|
+
const segments = [];
|
|
2676
|
+
// token is not guaranteed to have `file` set. We'll try to get it from a prop if not.
|
|
2677
|
+
let file = token.file;
|
|
2678
|
+
// Here we're flattening the first segment of a path if it is a nested path.
|
|
2679
|
+
const root = token.props[0];
|
|
2680
|
+
file = file || root.file;
|
|
2681
|
+
if (isQuotedToken(root) || isNumberToken(root) || isWordToken(root)) {
|
|
2682
|
+
segments.push(root.content);
|
|
2683
|
+
}
|
|
2684
|
+
else if (isPropertyAccessToken(root)) {
|
|
2685
|
+
// Flatten paths that start with a nested path.
|
|
2686
|
+
segments.push(...extractPropertyAccessVariable(root).segments);
|
|
2687
|
+
}
|
|
2688
|
+
for (const prop of token.props.slice(1)) {
|
|
2689
|
+
file = file || prop.file;
|
|
2690
|
+
if (isQuotedToken(prop) || isNumberToken(prop) || isWordToken(prop)) {
|
|
2691
|
+
segments.push(prop.content);
|
|
2692
|
+
}
|
|
2693
|
+
else if (isPropertyAccessToken(prop)) {
|
|
2694
|
+
segments.push(extractPropertyAccessVariable(prop));
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
const [row, col] = token.getPosition();
|
|
2698
|
+
return new Variable(segments, {
|
|
2699
|
+
row,
|
|
2700
|
+
col,
|
|
2701
|
+
file
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
// This is used to detect segments that can be represented with dot notation
|
|
2705
|
+
// when creating a string representation of VariableSegments.
|
|
2706
|
+
const RE_PROPERTY = /^[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*$/;
|
|
2707
|
+
/**
|
|
2708
|
+
* Return a string representation of segments using dot notation where possible.
|
|
2709
|
+
* @param segments - The property names and array indices that make up a path to a variable.
|
|
2710
|
+
* @param bracketedRoot - If false (the default), don't surround the root segment with square brackets.
|
|
2711
|
+
*/
|
|
2712
|
+
function segmentsString(segments, bracketedRoot = false) {
|
|
2713
|
+
const buf = [];
|
|
2714
|
+
const root = segments[0];
|
|
2715
|
+
if (isString(root)) {
|
|
2716
|
+
if (!bracketedRoot || root.match(RE_PROPERTY)) {
|
|
2717
|
+
buf.push(`${root}`);
|
|
2718
|
+
}
|
|
2719
|
+
else {
|
|
2720
|
+
buf.push(`['${root}']`);
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
for (const segment of segments.slice(1)) {
|
|
2724
|
+
if (segment instanceof Variable) {
|
|
2725
|
+
buf.push(`[${segmentsString(segment.segments)}]`);
|
|
2726
|
+
}
|
|
2727
|
+
else if (isString(segment)) {
|
|
2728
|
+
if (segment.match(RE_PROPERTY)) {
|
|
2729
|
+
buf.push(`.${segment}`);
|
|
2730
|
+
}
|
|
2731
|
+
else {
|
|
2732
|
+
buf.push(`['${segment}']`);
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
else {
|
|
2736
|
+
buf.push(`[${segment}]`);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
return buf.join('');
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2252
2742
|
var LookupType;
|
|
2253
2743
|
(function (LookupType) {
|
|
2254
2744
|
LookupType["Partials"] = "partials";
|
|
@@ -2265,7 +2755,7 @@ class Loader {
|
|
|
2265
2755
|
this.shouldLoadRelative = (referencedFile) => rRelativePath.test(referencedFile);
|
|
2266
2756
|
}
|
|
2267
2757
|
else {
|
|
2268
|
-
this.shouldLoadRelative = (
|
|
2758
|
+
this.shouldLoadRelative = (_referencedFile) => false;
|
|
2269
2759
|
}
|
|
2270
2760
|
this.contains = this.options.fs.contains || (() => true);
|
|
2271
2761
|
}
|
|
@@ -2322,8 +2812,11 @@ class Parser {
|
|
|
2322
2812
|
this.fs = this.liquid.options.fs;
|
|
2323
2813
|
this.parseFile = this.cache ? this._parseFileCached : this._parseFile;
|
|
2324
2814
|
this.loader = new Loader(this.liquid.options);
|
|
2815
|
+
this.parseLimit = new Limiter('parse length', liquid.options.parseLimit);
|
|
2325
2816
|
}
|
|
2326
2817
|
parse(html, filepath) {
|
|
2818
|
+
html = String(html);
|
|
2819
|
+
this.parseLimit.use(html.length);
|
|
2327
2820
|
const tokenizer = new Tokenizer(html, this.liquid.options.operators, filepath);
|
|
2328
2821
|
const tokens = tokenizer.readTopLevelTokens(this.liquid.options);
|
|
2329
2822
|
return this.parseTokens(tokens);
|
|
@@ -2331,9 +2824,20 @@ class Parser {
|
|
|
2331
2824
|
parseTokens(tokens) {
|
|
2332
2825
|
let token;
|
|
2333
2826
|
const templates = [];
|
|
2827
|
+
const errors = [];
|
|
2334
2828
|
while ((token = tokens.shift())) {
|
|
2335
|
-
|
|
2829
|
+
try {
|
|
2830
|
+
templates.push(this.parseToken(token, tokens));
|
|
2831
|
+
}
|
|
2832
|
+
catch (err) {
|
|
2833
|
+
if (this.liquid.options.catchAllErrors)
|
|
2834
|
+
errors.push(err);
|
|
2835
|
+
else
|
|
2836
|
+
throw err;
|
|
2837
|
+
}
|
|
2336
2838
|
}
|
|
2839
|
+
if (errors.length)
|
|
2840
|
+
throw new LiquidErrors(errors);
|
|
2337
2841
|
return templates;
|
|
2338
2842
|
}
|
|
2339
2843
|
parseToken(token, remainTokens) {
|
|
@@ -2341,7 +2845,7 @@ class Parser {
|
|
|
2341
2845
|
if (isTagToken(token)) {
|
|
2342
2846
|
const TagClass = this.liquid.tags[token.name];
|
|
2343
2847
|
assert(TagClass, `tag "${token.name}" not found`);
|
|
2344
|
-
return new TagClass(token, remainTokens, this.liquid);
|
|
2848
|
+
return new TagClass(token, remainTokens, this.liquid, this);
|
|
2345
2849
|
}
|
|
2346
2850
|
if (isOutputToken(token)) {
|
|
2347
2851
|
return new Output(token, this.liquid);
|
|
@@ -2379,7 +2883,7 @@ class Parser {
|
|
|
2379
2883
|
}
|
|
2380
2884
|
*_parseFile(file, sync, type = LookupType.Root, currentFile) {
|
|
2381
2885
|
const filepath = yield this.loader.lookup(file, type, sync, currentFile);
|
|
2382
|
-
return this.
|
|
2886
|
+
return this.parse(sync ? this.fs.readFileSync(filepath) : yield this.fs.readFile(filepath), filepath);
|
|
2383
2887
|
}
|
|
2384
2888
|
}
|
|
2385
2889
|
|
|
@@ -2433,6 +2937,10 @@ function isWordToken(val) {
|
|
|
2433
2937
|
function isRangeToken(val) {
|
|
2434
2938
|
return getKind(val) === exports.TokenKind.Range;
|
|
2435
2939
|
}
|
|
2940
|
+
function isValueToken(val) {
|
|
2941
|
+
// valueTokenBitMask = TokenKind.Number | TokenKind.Literal | TokenKind.Quoted | TokenKind.PropertyAccess | TokenKind.Range
|
|
2942
|
+
return (getKind(val) & 1667) > 0;
|
|
2943
|
+
}
|
|
2436
2944
|
function getKind(val) {
|
|
2437
2945
|
return val ? val.kind : -1;
|
|
2438
2946
|
}
|
|
@@ -2449,7 +2957,8 @@ var typeGuards = /*#__PURE__*/Object.freeze({
|
|
|
2449
2957
|
isNumberToken: isNumberToken,
|
|
2450
2958
|
isPropertyAccessToken: isPropertyAccessToken,
|
|
2451
2959
|
isWordToken: isWordToken,
|
|
2452
|
-
isRangeToken: isRangeToken
|
|
2960
|
+
isRangeToken: isRangeToken,
|
|
2961
|
+
isValueToken: isValueToken
|
|
2453
2962
|
});
|
|
2454
2963
|
|
|
2455
2964
|
/******************************************************************************
|
|
@@ -2479,19 +2988,23 @@ var __assign = function() {
|
|
|
2479
2988
|
};
|
|
2480
2989
|
|
|
2481
2990
|
class Context {
|
|
2482
|
-
constructor(env = {}, opts = defaultOptions, renderOptions = {}) {
|
|
2991
|
+
constructor(env = {}, opts = defaultOptions, renderOptions = {}, { memoryLimit, renderLimit } = {}) {
|
|
2483
2992
|
/**
|
|
2484
2993
|
* insert a Context-level empty scope,
|
|
2485
2994
|
* for tags like `{% capture %}` `{% assign %}` to operate
|
|
2486
2995
|
*/
|
|
2487
2996
|
this.scopes = [{}];
|
|
2488
2997
|
this.registers = {};
|
|
2998
|
+
this.breakCalled = false;
|
|
2999
|
+
this.continueCalled = false;
|
|
2489
3000
|
this.sync = !!renderOptions.sync;
|
|
2490
3001
|
this.opts = opts;
|
|
2491
3002
|
this.globals = renderOptions.globals ?? opts.globals;
|
|
2492
3003
|
this.environments = isObject(env) ? env : Object(env);
|
|
2493
3004
|
this.strictVariables = renderOptions.strictVariables ?? this.opts.strictVariables;
|
|
2494
3005
|
this.ownPropertyOnly = renderOptions.ownPropertyOnly ?? opts.ownPropertyOnly;
|
|
3006
|
+
this.memoryLimit = memoryLimit ?? new Limiter('memory alloc', renderOptions.memoryLimit ?? opts.memoryLimit);
|
|
3007
|
+
this.renderLimit = renderLimit ?? new Limiter('template render', getPerformance().now() + (renderOptions.renderLimit ?? opts.renderLimit));
|
|
2495
3008
|
}
|
|
2496
3009
|
getRegister(key) {
|
|
2497
3010
|
return (this.registers[key] = this.registers[key] || {});
|
|
@@ -2519,7 +3032,7 @@ class Context {
|
|
|
2519
3032
|
return toValueSync(this._get(paths));
|
|
2520
3033
|
}
|
|
2521
3034
|
*_get(paths) {
|
|
2522
|
-
const scope = this.findScope(paths[0]);
|
|
3035
|
+
const scope = this.findScope(paths[0]); // first prop should always be a string
|
|
2523
3036
|
return yield this._getFromScope(scope, paths);
|
|
2524
3037
|
}
|
|
2525
3038
|
/**
|
|
@@ -2548,6 +3061,16 @@ class Context {
|
|
|
2548
3061
|
bottom() {
|
|
2549
3062
|
return this.scopes[0];
|
|
2550
3063
|
}
|
|
3064
|
+
spawn(scope = {}) {
|
|
3065
|
+
return new Context(scope, this.opts, {
|
|
3066
|
+
sync: this.sync,
|
|
3067
|
+
globals: this.globals,
|
|
3068
|
+
strictVariables: this.strictVariables
|
|
3069
|
+
}, {
|
|
3070
|
+
renderLimit: this.renderLimit,
|
|
3071
|
+
memoryLimit: this.memoryLimit
|
|
3072
|
+
});
|
|
3073
|
+
}
|
|
2551
3074
|
findScope(key) {
|
|
2552
3075
|
for (let i = this.scopes.length - 1; i >= 0; i--) {
|
|
2553
3076
|
const candidate = this.scopes[i];
|
|
@@ -2561,6 +3084,7 @@ class Context {
|
|
|
2561
3084
|
}
|
|
2562
3085
|
function readProperty(obj, key, ownPropertyOnly) {
|
|
2563
3086
|
obj = toLiquid(obj);
|
|
3087
|
+
key = toValue(key);
|
|
2564
3088
|
if (isNil(obj))
|
|
2565
3089
|
return obj;
|
|
2566
3090
|
if (isArray(obj) && key < 0)
|
|
@@ -2579,7 +3103,7 @@ function readProperty(obj, key, ownPropertyOnly) {
|
|
|
2579
3103
|
return value;
|
|
2580
3104
|
}
|
|
2581
3105
|
function readJSProperty(obj, key, ownPropertyOnly) {
|
|
2582
|
-
if (ownPropertyOnly && !
|
|
3106
|
+
if (ownPropertyOnly && !hasOwnProperty.call(obj, key) && !(obj instanceof Drop))
|
|
2583
3107
|
return undefined;
|
|
2584
3108
|
return obj[key];
|
|
2585
3109
|
}
|
|
@@ -2594,7 +3118,7 @@ function readLast(obj) {
|
|
|
2594
3118
|
return obj['last'];
|
|
2595
3119
|
}
|
|
2596
3120
|
function readSize(obj) {
|
|
2597
|
-
if (
|
|
3121
|
+
if (hasOwnProperty.call(obj, 'size') || obj['size'] !== undefined)
|
|
2598
3122
|
return obj['size'];
|
|
2599
3123
|
if (isArray(obj) || isString(obj))
|
|
2600
3124
|
return obj.length;
|
|
@@ -2701,13 +3225,25 @@ var urlFilters = /*#__PURE__*/Object.freeze({
|
|
|
2701
3225
|
slugify: slugify
|
|
2702
3226
|
});
|
|
2703
3227
|
|
|
2704
|
-
const join = argumentsToValue((v, arg)
|
|
2705
|
-
const
|
|
2706
|
-
const
|
|
2707
|
-
const
|
|
3228
|
+
const join = argumentsToValue(function (v, arg) {
|
|
3229
|
+
const array = toArray(v);
|
|
3230
|
+
const sep = isNil(arg) ? ' ' : stringify(arg);
|
|
3231
|
+
const complexity = array.length * (1 + sep.length);
|
|
3232
|
+
this.context.memoryLimit.use(complexity);
|
|
3233
|
+
return array.join(sep);
|
|
3234
|
+
});
|
|
3235
|
+
const last$1 = argumentsToValue((v) => isArrayLike(v) ? last(v) : '');
|
|
3236
|
+
const first = argumentsToValue((v) => isArrayLike(v) ? v[0] : '');
|
|
3237
|
+
const reverse = argumentsToValue(function (v) {
|
|
3238
|
+
const array = toArray(v);
|
|
3239
|
+
this.context.memoryLimit.use(array.length);
|
|
3240
|
+
return [...array].reverse();
|
|
3241
|
+
});
|
|
2708
3242
|
function* sort(arr, property) {
|
|
2709
3243
|
const values = [];
|
|
2710
|
-
|
|
3244
|
+
const array = toArray(arr);
|
|
3245
|
+
this.context.memoryLimit.use(array.length);
|
|
3246
|
+
for (const item of array) {
|
|
2711
3247
|
values.push([
|
|
2712
3248
|
item,
|
|
2713
3249
|
property ? yield this.context._getFromScope(item, stringify(property).split('.'), false) : item
|
|
@@ -2724,35 +3260,47 @@ function sort_natural(input, property) {
|
|
|
2724
3260
|
const compare = property === undefined
|
|
2725
3261
|
? caseInsensitiveCompare
|
|
2726
3262
|
: (lhs, rhs) => caseInsensitiveCompare(lhs[propertyString], rhs[propertyString]);
|
|
2727
|
-
|
|
3263
|
+
const array = toArray(input);
|
|
3264
|
+
this.context.memoryLimit.use(array.length);
|
|
3265
|
+
return [...array].sort(compare);
|
|
2728
3266
|
}
|
|
2729
3267
|
const size = (v) => (v && v.length) || 0;
|
|
2730
3268
|
function* map(arr, property) {
|
|
2731
3269
|
const results = [];
|
|
2732
|
-
|
|
3270
|
+
const array = toArray(arr);
|
|
3271
|
+
this.context.memoryLimit.use(array.length);
|
|
3272
|
+
for (const item of array) {
|
|
2733
3273
|
results.push(yield this.context._getFromScope(item, stringify(property), false));
|
|
2734
3274
|
}
|
|
2735
3275
|
return results;
|
|
2736
3276
|
}
|
|
2737
3277
|
function* sum(arr, property) {
|
|
2738
3278
|
let sum = 0;
|
|
2739
|
-
|
|
3279
|
+
const array = toArray(arr);
|
|
3280
|
+
for (const item of array) {
|
|
2740
3281
|
const data = Number(property ? yield this.context._getFromScope(item, stringify(property), false) : item);
|
|
2741
3282
|
sum += Number.isNaN(data) ? 0 : data;
|
|
2742
3283
|
}
|
|
2743
3284
|
return sum;
|
|
2744
3285
|
}
|
|
2745
3286
|
function compact(arr) {
|
|
2746
|
-
|
|
3287
|
+
const array = toArray(arr);
|
|
3288
|
+
this.context.memoryLimit.use(array.length);
|
|
3289
|
+
return array.filter(x => !isNil(toValue(x)));
|
|
2747
3290
|
}
|
|
2748
3291
|
function concat(v, arg = []) {
|
|
2749
|
-
|
|
3292
|
+
const lhs = toArray(v);
|
|
3293
|
+
const rhs = toArray(arg);
|
|
3294
|
+
this.context.memoryLimit.use(lhs.length + rhs.length);
|
|
3295
|
+
return lhs.concat(rhs);
|
|
2750
3296
|
}
|
|
2751
3297
|
function push(v, arg) {
|
|
2752
|
-
return concat(v, [arg]);
|
|
3298
|
+
return concat.call(this, v, [arg]);
|
|
2753
3299
|
}
|
|
2754
3300
|
function unshift(v, arg) {
|
|
2755
|
-
const
|
|
3301
|
+
const array = toArray(v);
|
|
3302
|
+
this.context.memoryLimit.use(array.length);
|
|
3303
|
+
const clone = [...array];
|
|
2756
3304
|
clone.unshift(arg);
|
|
2757
3305
|
return clone;
|
|
2758
3306
|
}
|
|
@@ -2762,7 +3310,9 @@ function pop(v) {
|
|
|
2762
3310
|
return clone;
|
|
2763
3311
|
}
|
|
2764
3312
|
function shift(v) {
|
|
2765
|
-
const
|
|
3313
|
+
const array = toArray(v);
|
|
3314
|
+
this.context.memoryLimit.use(array.length);
|
|
3315
|
+
const clone = [...array];
|
|
2766
3316
|
clone.shift();
|
|
2767
3317
|
return clone;
|
|
2768
3318
|
}
|
|
@@ -2773,26 +3323,33 @@ function slice(v, begin, length = 1) {
|
|
|
2773
3323
|
if (!isArray(v))
|
|
2774
3324
|
v = stringify(v);
|
|
2775
3325
|
begin = begin < 0 ? v.length + begin : begin;
|
|
3326
|
+
this.context.memoryLimit.use(length);
|
|
2776
3327
|
return v.slice(begin, begin + length);
|
|
2777
3328
|
}
|
|
2778
3329
|
function* where(arr, property, expected) {
|
|
2779
3330
|
const values = [];
|
|
2780
3331
|
arr = toArray(arr);
|
|
3332
|
+
this.context.memoryLimit.use(arr.length);
|
|
2781
3333
|
const token = new Tokenizer(stringify(property)).readScopeValue();
|
|
2782
3334
|
for (const item of arr) {
|
|
2783
|
-
values.push(yield evalToken(token,
|
|
3335
|
+
values.push(yield evalToken(token, this.context.spawn(item)));
|
|
2784
3336
|
}
|
|
3337
|
+
const matcher = this.context.opts.jekyllWhere
|
|
3338
|
+
? (v) => EmptyDrop.is(expected) ? equals(v, expected) : (isArray(v) ? arrayIncludes(v, expected) : equals(v, expected))
|
|
3339
|
+
: (v) => equals(v, expected);
|
|
2785
3340
|
return arr.filter((_, i) => {
|
|
2786
3341
|
if (expected === undefined)
|
|
2787
3342
|
return isTruthy(values[i], this.context);
|
|
2788
|
-
return
|
|
3343
|
+
return matcher(values[i]);
|
|
2789
3344
|
});
|
|
2790
3345
|
}
|
|
2791
3346
|
function* where_exp(arr, itemName, exp) {
|
|
2792
3347
|
const filtered = [];
|
|
2793
3348
|
const keyTemplate = new Value(stringify(exp), this.liquid);
|
|
2794
|
-
|
|
2795
|
-
|
|
3349
|
+
const array = toArray(arr);
|
|
3350
|
+
this.context.memoryLimit.use(array.length);
|
|
3351
|
+
for (const item of array) {
|
|
3352
|
+
const value = yield keyTemplate.value(this.context.spawn({ [itemName]: item }));
|
|
2796
3353
|
if (value)
|
|
2797
3354
|
filtered.push(item);
|
|
2798
3355
|
}
|
|
@@ -2800,10 +3357,11 @@ function* where_exp(arr, itemName, exp) {
|
|
|
2800
3357
|
}
|
|
2801
3358
|
function* group_by(arr, property) {
|
|
2802
3359
|
const map = new Map();
|
|
2803
|
-
arr =
|
|
3360
|
+
arr = toEnumerable(arr);
|
|
2804
3361
|
const token = new Tokenizer(stringify(property)).readScopeValue();
|
|
3362
|
+
this.context.memoryLimit.use(arr.length);
|
|
2805
3363
|
for (const item of arr) {
|
|
2806
|
-
const key = yield evalToken(token,
|
|
3364
|
+
const key = yield evalToken(token, this.context.spawn(item));
|
|
2807
3365
|
if (!map.has(key))
|
|
2808
3366
|
map.set(key, []);
|
|
2809
3367
|
map.get(key).push(item);
|
|
@@ -2813,8 +3371,10 @@ function* group_by(arr, property) {
|
|
|
2813
3371
|
function* group_by_exp(arr, itemName, exp) {
|
|
2814
3372
|
const map = new Map();
|
|
2815
3373
|
const keyTemplate = new Value(stringify(exp), this.liquid);
|
|
2816
|
-
|
|
2817
|
-
|
|
3374
|
+
arr = toEnumerable(arr);
|
|
3375
|
+
this.context.memoryLimit.use(arr.length);
|
|
3376
|
+
for (const item of arr) {
|
|
3377
|
+
const key = yield keyTemplate.value(this.context.spawn({ [itemName]: item }));
|
|
2818
3378
|
if (!map.has(key))
|
|
2819
3379
|
map.set(key, []);
|
|
2820
3380
|
map.get(key).push(item);
|
|
@@ -2823,31 +3383,26 @@ function* group_by_exp(arr, itemName, exp) {
|
|
|
2823
3383
|
}
|
|
2824
3384
|
function* find(arr, property, expected) {
|
|
2825
3385
|
const token = new Tokenizer(stringify(property)).readScopeValue();
|
|
2826
|
-
|
|
2827
|
-
|
|
3386
|
+
const array = toArray(arr);
|
|
3387
|
+
for (const item of array) {
|
|
3388
|
+
const value = yield evalToken(token, this.context.spawn(item));
|
|
2828
3389
|
if (equals(value, expected))
|
|
2829
3390
|
return item;
|
|
2830
3391
|
}
|
|
2831
|
-
return null;
|
|
2832
3392
|
}
|
|
2833
3393
|
function* find_exp(arr, itemName, exp) {
|
|
2834
3394
|
const predicate = new Value(stringify(exp), this.liquid);
|
|
2835
|
-
|
|
2836
|
-
|
|
3395
|
+
const array = toArray(arr);
|
|
3396
|
+
for (const item of array) {
|
|
3397
|
+
const value = yield predicate.value(this.context.spawn({ [itemName]: item }));
|
|
2837
3398
|
if (value)
|
|
2838
3399
|
return item;
|
|
2839
3400
|
}
|
|
2840
|
-
return null;
|
|
2841
3401
|
}
|
|
2842
3402
|
function uniq(arr) {
|
|
2843
|
-
arr =
|
|
2844
|
-
|
|
2845
|
-
return (arr
|
|
2846
|
-
if (hasOwnProperty.call(u, String(val)))
|
|
2847
|
-
return false;
|
|
2848
|
-
u[String(val)] = true;
|
|
2849
|
-
return true;
|
|
2850
|
-
});
|
|
3403
|
+
arr = toArray(arr);
|
|
3404
|
+
this.context.memoryLimit.use(arr.length);
|
|
3405
|
+
return [...new Set(arr)];
|
|
2851
3406
|
}
|
|
2852
3407
|
function sample(v, count = 1) {
|
|
2853
3408
|
v = toValue(v);
|
|
@@ -2855,6 +3410,7 @@ function sample(v, count = 1) {
|
|
|
2855
3410
|
return [];
|
|
2856
3411
|
if (!isArray(v))
|
|
2857
3412
|
v = stringify(v);
|
|
3413
|
+
this.context.memoryLimit.use(count);
|
|
2858
3414
|
const shuffled = [...v].sort(() => Math.random() - 0.5);
|
|
2859
3415
|
if (count === 1)
|
|
2860
3416
|
return shuffled[0];
|
|
@@ -2890,6 +3446,8 @@ var arrayFilters = /*#__PURE__*/Object.freeze({
|
|
|
2890
3446
|
});
|
|
2891
3447
|
|
|
2892
3448
|
function date(v, format, timezoneOffset) {
|
|
3449
|
+
const size = (v?.length ?? 0) + (format?.length ?? 0) + (timezoneOffset?.length ?? 0);
|
|
3450
|
+
this.context.memoryLimit.use(size);
|
|
2893
3451
|
const date = parseDate(v, this.context.opts, timezoneOffset);
|
|
2894
3452
|
if (!date)
|
|
2895
3453
|
return v;
|
|
@@ -2923,39 +3481,30 @@ function stringify_date(v, month_type, type, style) {
|
|
|
2923
3481
|
}
|
|
2924
3482
|
function parseDate(v, opts, timezoneOffset) {
|
|
2925
3483
|
let date;
|
|
3484
|
+
const defaultTimezoneOffset = timezoneOffset ?? opts.timezoneOffset;
|
|
3485
|
+
const locale = opts.locale;
|
|
2926
3486
|
v = toValue(v);
|
|
2927
3487
|
if (v === 'now' || v === 'today') {
|
|
2928
|
-
date = new Date();
|
|
3488
|
+
date = new LiquidDate(Date.now(), locale, defaultTimezoneOffset);
|
|
2929
3489
|
}
|
|
2930
3490
|
else if (isNumber(v)) {
|
|
2931
|
-
date = new
|
|
3491
|
+
date = new LiquidDate(v * 1000, locale, defaultTimezoneOffset);
|
|
2932
3492
|
}
|
|
2933
3493
|
else if (isString(v)) {
|
|
2934
3494
|
if (/^\d+$/.test(v)) {
|
|
2935
|
-
date = new
|
|
3495
|
+
date = new LiquidDate(+v * 1000, locale, defaultTimezoneOffset);
|
|
2936
3496
|
}
|
|
2937
|
-
else if (opts.preserveTimezones) {
|
|
2938
|
-
date =
|
|
3497
|
+
else if (opts.preserveTimezones && timezoneOffset === undefined) {
|
|
3498
|
+
date = LiquidDate.createDateFixedToTimezone(v, locale);
|
|
2939
3499
|
}
|
|
2940
3500
|
else {
|
|
2941
|
-
date = new
|
|
3501
|
+
date = new LiquidDate(v, locale, defaultTimezoneOffset);
|
|
2942
3502
|
}
|
|
2943
3503
|
}
|
|
2944
3504
|
else {
|
|
2945
|
-
date = v;
|
|
2946
|
-
}
|
|
2947
|
-
if (!isValidDate(date))
|
|
2948
|
-
return;
|
|
2949
|
-
if (timezoneOffset !== undefined) {
|
|
2950
|
-
date = new TimezoneDate(date, timezoneOffset);
|
|
2951
|
-
}
|
|
2952
|
-
else if (!(date instanceof TimezoneDate) && opts.timezoneOffset !== undefined) {
|
|
2953
|
-
date = new TimezoneDate(date, opts.timezoneOffset);
|
|
3505
|
+
date = new LiquidDate(v, locale, defaultTimezoneOffset);
|
|
2954
3506
|
}
|
|
2955
|
-
return date;
|
|
2956
|
-
}
|
|
2957
|
-
function isValidDate(date) {
|
|
2958
|
-
return (date instanceof Date || date instanceof TimezoneDate) && !isNaN(date.getTime());
|
|
3507
|
+
return date.valid() ? date : undefined;
|
|
2959
3508
|
}
|
|
2960
3509
|
|
|
2961
3510
|
var dateFilters = /*#__PURE__*/Object.freeze({
|
|
@@ -2977,33 +3526,50 @@ const rCJKWord = /[\u4E00-\u9FFF\uF900-\uFAFF\u3400-\u4DBF\u3040-\u309F\u30A0-\u
|
|
|
2977
3526
|
const rNonCJKWord = /[^\u4E00-\u9FFF\uF900-\uFAFF\u3400-\u4DBF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF\s]+/gu;
|
|
2978
3527
|
function append(v, arg) {
|
|
2979
3528
|
assert(arguments.length === 2, 'append expect 2 arguments');
|
|
2980
|
-
|
|
3529
|
+
const lhs = stringify(v);
|
|
3530
|
+
const rhs = stringify(arg);
|
|
3531
|
+
this.context.memoryLimit.use(lhs.length + rhs.length);
|
|
3532
|
+
return lhs + rhs;
|
|
2981
3533
|
}
|
|
2982
3534
|
function prepend(v, arg) {
|
|
2983
3535
|
assert(arguments.length === 2, 'prepend expect 2 arguments');
|
|
2984
|
-
|
|
3536
|
+
const lhs = stringify(v);
|
|
3537
|
+
const rhs = stringify(arg);
|
|
3538
|
+
this.context.memoryLimit.use(lhs.length + rhs.length);
|
|
3539
|
+
return rhs + lhs;
|
|
2985
3540
|
}
|
|
2986
3541
|
function lstrip(v, chars) {
|
|
3542
|
+
const str = stringify(v);
|
|
3543
|
+
this.context.memoryLimit.use(str.length);
|
|
2987
3544
|
if (chars) {
|
|
2988
3545
|
chars = escapeRegExp(stringify(chars));
|
|
2989
|
-
return
|
|
3546
|
+
return str.replace(new RegExp(`^[${chars}]+`, 'g'), '');
|
|
2990
3547
|
}
|
|
2991
|
-
return
|
|
3548
|
+
return str.replace(/^\s+/, '');
|
|
2992
3549
|
}
|
|
2993
3550
|
function downcase(v) {
|
|
2994
|
-
|
|
3551
|
+
const str = stringify(v);
|
|
3552
|
+
this.context.memoryLimit.use(str.length);
|
|
3553
|
+
return str.toLowerCase();
|
|
2995
3554
|
}
|
|
2996
|
-
function upcase(
|
|
3555
|
+
function upcase(v) {
|
|
3556
|
+
const str = stringify(v);
|
|
3557
|
+
this.context.memoryLimit.use(str.length);
|
|
2997
3558
|
return stringify(str).toUpperCase();
|
|
2998
3559
|
}
|
|
2999
3560
|
function remove(v, arg) {
|
|
3000
|
-
|
|
3561
|
+
const str = stringify(v);
|
|
3562
|
+
this.context.memoryLimit.use(str.length);
|
|
3563
|
+
return str.split(stringify(arg)).join('');
|
|
3001
3564
|
}
|
|
3002
3565
|
function remove_first(v, l) {
|
|
3003
|
-
|
|
3566
|
+
const str = stringify(v);
|
|
3567
|
+
this.context.memoryLimit.use(str.length);
|
|
3568
|
+
return str.replace(stringify(l), '');
|
|
3004
3569
|
}
|
|
3005
3570
|
function remove_last(v, l) {
|
|
3006
3571
|
const str = stringify(v);
|
|
3572
|
+
this.context.memoryLimit.use(str.length);
|
|
3007
3573
|
const pattern = stringify(l);
|
|
3008
3574
|
const index = str.lastIndexOf(pattern);
|
|
3009
3575
|
if (index === -1)
|
|
@@ -3011,14 +3577,18 @@ function remove_last(v, l) {
|
|
|
3011
3577
|
return str.substring(0, index) + str.substring(index + pattern.length);
|
|
3012
3578
|
}
|
|
3013
3579
|
function rstrip(str, chars) {
|
|
3580
|
+
str = stringify(str);
|
|
3581
|
+
this.context.memoryLimit.use(str.length);
|
|
3014
3582
|
if (chars) {
|
|
3015
3583
|
chars = escapeRegExp(stringify(chars));
|
|
3016
|
-
return
|
|
3584
|
+
return str.replace(new RegExp(`[${chars}]+$`, 'g'), '');
|
|
3017
3585
|
}
|
|
3018
|
-
return
|
|
3586
|
+
return str.replace(/\s+$/, '');
|
|
3019
3587
|
}
|
|
3020
3588
|
function split(v, arg) {
|
|
3021
|
-
const
|
|
3589
|
+
const str = stringify(v);
|
|
3590
|
+
this.context.memoryLimit.use(str.length);
|
|
3591
|
+
const arr = str.split(stringify(arg));
|
|
3022
3592
|
// align to ruby split, which is the behavior of shopify/liquid
|
|
3023
3593
|
// see: https://ruby-doc.org/core-2.4.0/String.html#method-i-split
|
|
3024
3594
|
while (arr.length && arr[arr.length - 1] === '')
|
|
@@ -3026,29 +3596,39 @@ function split(v, arg) {
|
|
|
3026
3596
|
return arr;
|
|
3027
3597
|
}
|
|
3028
3598
|
function strip(v, chars) {
|
|
3599
|
+
const str = stringify(v);
|
|
3600
|
+
this.context.memoryLimit.use(str.length);
|
|
3029
3601
|
if (chars) {
|
|
3030
3602
|
chars = escapeRegExp(stringify(chars));
|
|
3031
|
-
return
|
|
3603
|
+
return str
|
|
3032
3604
|
.replace(new RegExp(`^[${chars}]+`, 'g'), '')
|
|
3033
3605
|
.replace(new RegExp(`[${chars}]+$`, 'g'), '');
|
|
3034
3606
|
}
|
|
3035
|
-
return
|
|
3607
|
+
return str.trim();
|
|
3036
3608
|
}
|
|
3037
3609
|
function strip_newlines(v) {
|
|
3038
|
-
|
|
3610
|
+
const str = stringify(v);
|
|
3611
|
+
this.context.memoryLimit.use(str.length);
|
|
3612
|
+
return str.replace(/\r?\n/gm, '');
|
|
3039
3613
|
}
|
|
3040
3614
|
function capitalize(str) {
|
|
3041
3615
|
str = stringify(str);
|
|
3616
|
+
this.context.memoryLimit.use(str.length);
|
|
3042
3617
|
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
3043
3618
|
}
|
|
3044
3619
|
function replace(v, pattern, replacement) {
|
|
3045
|
-
|
|
3620
|
+
const str = stringify(v);
|
|
3621
|
+
this.context.memoryLimit.use(str.length);
|
|
3622
|
+
return str.split(stringify(pattern)).join(replacement);
|
|
3046
3623
|
}
|
|
3047
3624
|
function replace_first(v, arg1, arg2) {
|
|
3048
|
-
|
|
3625
|
+
const str = stringify(v);
|
|
3626
|
+
this.context.memoryLimit.use(str.length);
|
|
3627
|
+
return str.replace(stringify(arg1), arg2);
|
|
3049
3628
|
}
|
|
3050
3629
|
function replace_last(v, arg1, arg2) {
|
|
3051
3630
|
const str = stringify(v);
|
|
3631
|
+
this.context.memoryLimit.use(str.length);
|
|
3052
3632
|
const pattern = stringify(arg1);
|
|
3053
3633
|
const index = str.lastIndexOf(pattern);
|
|
3054
3634
|
if (index === -1)
|
|
@@ -3057,13 +3637,16 @@ function replace_last(v, arg1, arg2) {
|
|
|
3057
3637
|
return str.substring(0, index) + replacement + str.substring(index + pattern.length);
|
|
3058
3638
|
}
|
|
3059
3639
|
function truncate(v, l = 50, o = '...') {
|
|
3060
|
-
|
|
3061
|
-
|
|
3640
|
+
const str = stringify(v);
|
|
3641
|
+
this.context.memoryLimit.use(str.length);
|
|
3642
|
+
if (str.length <= l)
|
|
3062
3643
|
return v;
|
|
3063
|
-
return
|
|
3644
|
+
return str.substring(0, l - o.length) + o;
|
|
3064
3645
|
}
|
|
3065
3646
|
function truncatewords(v, words = 15, o = '...') {
|
|
3066
|
-
const
|
|
3647
|
+
const str = stringify(v);
|
|
3648
|
+
this.context.memoryLimit.use(str.length);
|
|
3649
|
+
const arr = str.split(/\s+/);
|
|
3067
3650
|
if (words <= 0)
|
|
3068
3651
|
words = 1;
|
|
3069
3652
|
let ret = arr.slice(0, words).join(' ');
|
|
@@ -3072,11 +3655,14 @@ function truncatewords(v, words = 15, o = '...') {
|
|
|
3072
3655
|
return ret;
|
|
3073
3656
|
}
|
|
3074
3657
|
function normalize_whitespace(v) {
|
|
3075
|
-
|
|
3076
|
-
|
|
3658
|
+
const str = stringify(v);
|
|
3659
|
+
this.context.memoryLimit.use(str.length);
|
|
3660
|
+
return str.replace(/\s+/g, ' ');
|
|
3077
3661
|
}
|
|
3078
3662
|
function number_of_words(input, mode) {
|
|
3079
|
-
|
|
3663
|
+
const str = stringify(input);
|
|
3664
|
+
this.context.memoryLimit.use(str.length);
|
|
3665
|
+
input = str.trim();
|
|
3080
3666
|
if (!input)
|
|
3081
3667
|
return 0;
|
|
3082
3668
|
switch (mode) {
|
|
@@ -3094,6 +3680,7 @@ function number_of_words(input, mode) {
|
|
|
3094
3680
|
}
|
|
3095
3681
|
}
|
|
3096
3682
|
function array_to_sentence_string(array, connector = 'and') {
|
|
3683
|
+
this.context.memoryLimit.use(array.length);
|
|
3097
3684
|
switch (array.length) {
|
|
3098
3685
|
case 0:
|
|
3099
3686
|
return '';
|
|
@@ -3144,7 +3731,8 @@ const filters = {
|
|
|
3144
3731
|
class AssignTag extends Tag {
|
|
3145
3732
|
constructor(token, remainTokens, liquid) {
|
|
3146
3733
|
super(token, remainTokens, liquid);
|
|
3147
|
-
this.
|
|
3734
|
+
this.identifier = this.tokenizer.readIdentifier();
|
|
3735
|
+
this.key = this.identifier.content;
|
|
3148
3736
|
this.tokenizer.assert(this.key, 'expected variable name');
|
|
3149
3737
|
this.tokenizer.skipBlank();
|
|
3150
3738
|
this.tokenizer.assert(this.tokenizer.peek() === '=', 'expected "="');
|
|
@@ -3154,11 +3742,17 @@ class AssignTag extends Tag {
|
|
|
3154
3742
|
*render(ctx) {
|
|
3155
3743
|
ctx.bottom()[this.key] = yield this.value.value(ctx, this.liquid.options.lenientIf);
|
|
3156
3744
|
}
|
|
3745
|
+
*arguments() {
|
|
3746
|
+
yield this.value;
|
|
3747
|
+
}
|
|
3748
|
+
*localScope() {
|
|
3749
|
+
yield this.identifier;
|
|
3750
|
+
}
|
|
3157
3751
|
}
|
|
3158
3752
|
|
|
3159
3753
|
const MODIFIERS = ['offset', 'limit', 'reversed'];
|
|
3160
3754
|
class ForTag extends Tag {
|
|
3161
|
-
constructor(token, remainTokens, liquid) {
|
|
3755
|
+
constructor(token, remainTokens, liquid, parser) {
|
|
3162
3756
|
super(token, remainTokens, liquid);
|
|
3163
3757
|
const variable = this.tokenizer.readIdentifier();
|
|
3164
3758
|
const inStr = this.tokenizer.readIdentifier();
|
|
@@ -3168,18 +3762,16 @@ class ForTag extends Tag {
|
|
|
3168
3762
|
}
|
|
3169
3763
|
this.variable = variable.content;
|
|
3170
3764
|
this.collection = collection;
|
|
3171
|
-
this.hash = new Hash(this.tokenizer.
|
|
3765
|
+
this.hash = new Hash(this.tokenizer, liquid.options.keyValueSeparator);
|
|
3172
3766
|
this.templates = [];
|
|
3173
3767
|
this.elseTemplates = [];
|
|
3174
3768
|
let p;
|
|
3175
|
-
const stream =
|
|
3769
|
+
const stream = parser.parseStream(remainTokens)
|
|
3176
3770
|
.on('start', () => (p = this.templates))
|
|
3177
|
-
.on('tag:else',
|
|
3178
|
-
.on('tag:endfor', ()
|
|
3771
|
+
.on('tag:else', tag => { assertEmpty(tag.args); p = this.elseTemplates; })
|
|
3772
|
+
.on('tag:endfor', tag => { assertEmpty(tag.args); stream.stop(); })
|
|
3179
3773
|
.on('template', (tpl) => p.push(tpl))
|
|
3180
|
-
.on('end', () => {
|
|
3181
|
-
throw new Error(`tag ${token.getText()} not closed`);
|
|
3182
|
-
});
|
|
3774
|
+
.on('end', () => { throw new Error(`tag ${token.getText()} not closed`); });
|
|
3183
3775
|
stream.start();
|
|
3184
3776
|
}
|
|
3185
3777
|
*render(ctx, emitter) {
|
|
@@ -3208,16 +3800,33 @@ class ForTag extends Tag {
|
|
|
3208
3800
|
ctx.push(scope);
|
|
3209
3801
|
for (const item of collection) {
|
|
3210
3802
|
scope[this.variable] = item;
|
|
3803
|
+
ctx.continueCalled = ctx.breakCalled = false;
|
|
3211
3804
|
yield r.renderTemplates(this.templates, ctx, emitter);
|
|
3212
|
-
if (
|
|
3213
|
-
emitter['break'] = false;
|
|
3805
|
+
if (ctx.breakCalled)
|
|
3214
3806
|
break;
|
|
3215
|
-
}
|
|
3216
|
-
emitter['continue'] = false;
|
|
3217
3807
|
scope.forloop.next();
|
|
3218
3808
|
}
|
|
3809
|
+
ctx.continueCalled = ctx.breakCalled = false;
|
|
3219
3810
|
ctx.pop();
|
|
3220
3811
|
}
|
|
3812
|
+
*children() {
|
|
3813
|
+
const templates = this.templates.slice();
|
|
3814
|
+
if (this.elseTemplates) {
|
|
3815
|
+
templates.push(...this.elseTemplates);
|
|
3816
|
+
}
|
|
3817
|
+
return templates;
|
|
3818
|
+
}
|
|
3819
|
+
*arguments() {
|
|
3820
|
+
yield this.collection;
|
|
3821
|
+
for (const v of Object.values(this.hash.hash)) {
|
|
3822
|
+
if (isValueToken(v)) {
|
|
3823
|
+
yield v;
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
blockScope() {
|
|
3828
|
+
return [this.variable, 'forloop'];
|
|
3829
|
+
}
|
|
3221
3830
|
}
|
|
3222
3831
|
function reversed(arr) {
|
|
3223
3832
|
return [...arr].reverse();
|
|
@@ -3230,36 +3839,43 @@ function limit(arr, count) {
|
|
|
3230
3839
|
}
|
|
3231
3840
|
|
|
3232
3841
|
class CaptureTag extends Tag {
|
|
3233
|
-
constructor(tagToken, remainTokens, liquid) {
|
|
3842
|
+
constructor(tagToken, remainTokens, liquid, parser) {
|
|
3234
3843
|
super(tagToken, remainTokens, liquid);
|
|
3235
3844
|
this.templates = [];
|
|
3236
|
-
this.
|
|
3845
|
+
this.identifier = this.readVariable();
|
|
3846
|
+
this.variable = this.identifier.content;
|
|
3237
3847
|
while (remainTokens.length) {
|
|
3238
3848
|
const token = remainTokens.shift();
|
|
3239
3849
|
if (isTagToken(token) && token.name === 'endcapture')
|
|
3240
3850
|
return;
|
|
3241
|
-
this.templates.push(
|
|
3851
|
+
this.templates.push(parser.parseToken(token, remainTokens));
|
|
3242
3852
|
}
|
|
3243
3853
|
throw new Error(`tag ${tagToken.getText()} not closed`);
|
|
3244
3854
|
}
|
|
3855
|
+
readVariable() {
|
|
3856
|
+
let ident = this.tokenizer.readIdentifier();
|
|
3857
|
+
if (ident.content)
|
|
3858
|
+
return ident;
|
|
3859
|
+
ident = this.tokenizer.readQuoted();
|
|
3860
|
+
if (ident)
|
|
3861
|
+
return ident;
|
|
3862
|
+
throw this.tokenizer.error('invalid capture name');
|
|
3863
|
+
}
|
|
3245
3864
|
*render(ctx) {
|
|
3246
3865
|
const r = this.liquid.renderer;
|
|
3247
3866
|
const html = yield r.renderTemplates(this.templates, ctx);
|
|
3248
3867
|
ctx.bottom()[this.variable] = html;
|
|
3249
3868
|
}
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
if (quoted)
|
|
3256
|
-
return evalQuotedToken(quoted);
|
|
3257
|
-
throw this.tokenizer.error('invalid capture name');
|
|
3869
|
+
*children() {
|
|
3870
|
+
return this.templates;
|
|
3871
|
+
}
|
|
3872
|
+
*localScope() {
|
|
3873
|
+
yield this.identifier;
|
|
3258
3874
|
}
|
|
3259
3875
|
}
|
|
3260
3876
|
|
|
3261
3877
|
class CaseTag extends Tag {
|
|
3262
|
-
constructor(tagToken, remainTokens, liquid) {
|
|
3878
|
+
constructor(tagToken, remainTokens, liquid, parser) {
|
|
3263
3879
|
super(tagToken, remainTokens, liquid);
|
|
3264
3880
|
this.branches = [];
|
|
3265
3881
|
this.elseTemplates = [];
|
|
@@ -3267,7 +3883,7 @@ class CaseTag extends Tag {
|
|
|
3267
3883
|
this.elseTemplates = [];
|
|
3268
3884
|
let p = [];
|
|
3269
3885
|
let elseCount = 0;
|
|
3270
|
-
const stream =
|
|
3886
|
+
const stream = parser.parseStream(remainTokens)
|
|
3271
3887
|
.on('tag:when', (token) => {
|
|
3272
3888
|
if (elseCount > 0) {
|
|
3273
3889
|
return;
|
|
@@ -3322,6 +3938,17 @@ class CaseTag extends Tag {
|
|
|
3322
3938
|
yield r.renderTemplates(this.elseTemplates, ctx, emitter);
|
|
3323
3939
|
}
|
|
3324
3940
|
}
|
|
3941
|
+
*arguments() {
|
|
3942
|
+
yield this.value;
|
|
3943
|
+
yield* this.branches.flatMap(b => b.values);
|
|
3944
|
+
}
|
|
3945
|
+
*children() {
|
|
3946
|
+
const templates = this.branches.flatMap(b => b.templates);
|
|
3947
|
+
if (this.elseTemplates) {
|
|
3948
|
+
templates.push(...this.elseTemplates);
|
|
3949
|
+
}
|
|
3950
|
+
return templates;
|
|
3951
|
+
}
|
|
3325
3952
|
}
|
|
3326
3953
|
|
|
3327
3954
|
class CommentTag extends Tag {
|
|
@@ -3338,10 +3965,10 @@ class CommentTag extends Tag {
|
|
|
3338
3965
|
}
|
|
3339
3966
|
|
|
3340
3967
|
class RenderTag extends Tag {
|
|
3341
|
-
constructor(token, remainTokens, liquid) {
|
|
3968
|
+
constructor(token, remainTokens, liquid, parser) {
|
|
3342
3969
|
super(token, remainTokens, liquid);
|
|
3343
3970
|
const tokenizer = this.tokenizer;
|
|
3344
|
-
this.file = parseFilePath(tokenizer, this.liquid);
|
|
3971
|
+
this.file = parseFilePath(tokenizer, this.liquid, parser);
|
|
3345
3972
|
this.currentFile = token.file;
|
|
3346
3973
|
while (!tokenizer.end()) {
|
|
3347
3974
|
tokenizer.skipBlank();
|
|
@@ -3375,13 +4002,13 @@ class RenderTag extends Tag {
|
|
|
3375
4002
|
tokenizer.p = begin;
|
|
3376
4003
|
break;
|
|
3377
4004
|
}
|
|
3378
|
-
this.hash = new Hash(tokenizer.
|
|
4005
|
+
this.hash = new Hash(tokenizer, liquid.options.keyValueSeparator);
|
|
3379
4006
|
}
|
|
3380
4007
|
*render(ctx, emitter) {
|
|
3381
4008
|
const { liquid, hash } = this;
|
|
3382
4009
|
const filepath = (yield renderFilePath(this['file'], ctx, liquid));
|
|
3383
4010
|
assert(filepath, () => `illegal file path "${filepath}"`);
|
|
3384
|
-
const childCtx =
|
|
4011
|
+
const childCtx = ctx.spawn();
|
|
3385
4012
|
const scope = childCtx.bottom();
|
|
3386
4013
|
__assign(scope, yield hash.render(ctx));
|
|
3387
4014
|
if (this['with']) {
|
|
@@ -3404,6 +4031,55 @@ class RenderTag extends Tag {
|
|
|
3404
4031
|
yield liquid.renderer.renderTemplates(templates, childCtx, emitter);
|
|
3405
4032
|
}
|
|
3406
4033
|
}
|
|
4034
|
+
*children(partials, sync) {
|
|
4035
|
+
if (partials && isString(this['file'])) {
|
|
4036
|
+
return (yield this.liquid._parsePartialFile(this['file'], sync, this['currentFile']));
|
|
4037
|
+
}
|
|
4038
|
+
return [];
|
|
4039
|
+
}
|
|
4040
|
+
partialScope() {
|
|
4041
|
+
if (isString(this['file'])) {
|
|
4042
|
+
const names = Object.keys(this.hash.hash);
|
|
4043
|
+
if (this['with']) {
|
|
4044
|
+
const { value, alias } = this['with'];
|
|
4045
|
+
if (isString(alias)) {
|
|
4046
|
+
names.push([alias, value]);
|
|
4047
|
+
}
|
|
4048
|
+
else if (isString(this.file)) {
|
|
4049
|
+
names.push([this.file, value]);
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
if (this['for']) {
|
|
4053
|
+
const { value, alias } = this['for'];
|
|
4054
|
+
if (isString(alias)) {
|
|
4055
|
+
names.push([alias, value]);
|
|
4056
|
+
}
|
|
4057
|
+
else if (isString(this.file)) {
|
|
4058
|
+
names.push([this.file, value]);
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
return { name: this['file'], isolated: true, scope: names };
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
*arguments() {
|
|
4065
|
+
for (const v of Object.values(this.hash.hash)) {
|
|
4066
|
+
if (isValueToken(v)) {
|
|
4067
|
+
yield v;
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
if (this['with']) {
|
|
4071
|
+
const { value } = this['with'];
|
|
4072
|
+
if (isValueToken(value)) {
|
|
4073
|
+
yield value;
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
if (this['for']) {
|
|
4077
|
+
const { value } = this['for'];
|
|
4078
|
+
if (isValueToken(value)) {
|
|
4079
|
+
yield value;
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
3407
4083
|
}
|
|
3408
4084
|
/**
|
|
3409
4085
|
* @return null for "none",
|
|
@@ -3411,7 +4087,7 @@ class RenderTag extends Tag {
|
|
|
3411
4087
|
* @return Token for expression (not quoted)
|
|
3412
4088
|
* @throws TypeError if cannot read next token
|
|
3413
4089
|
*/
|
|
3414
|
-
function parseFilePath(tokenizer, liquid) {
|
|
4090
|
+
function parseFilePath(tokenizer, liquid, parser) {
|
|
3415
4091
|
if (liquid.options.dynamicPartials) {
|
|
3416
4092
|
const file = tokenizer.readValue();
|
|
3417
4093
|
tokenizer.assert(file, 'illegal file path');
|
|
@@ -3419,13 +4095,13 @@ function parseFilePath(tokenizer, liquid) {
|
|
|
3419
4095
|
return;
|
|
3420
4096
|
if (isQuotedToken(file)) {
|
|
3421
4097
|
// for filenames like "files/{{file}}", eval as liquid template
|
|
3422
|
-
const templates =
|
|
4098
|
+
const templates = parser.parse(evalQuotedToken(file));
|
|
3423
4099
|
return optimize(templates);
|
|
3424
4100
|
}
|
|
3425
4101
|
return file;
|
|
3426
4102
|
}
|
|
3427
4103
|
const tokens = [...tokenizer.readFileNameTemplate(liquid.options)];
|
|
3428
|
-
const templates = optimize(
|
|
4104
|
+
const templates = optimize(parser.parseTokens(tokens));
|
|
3429
4105
|
return templates === 'none' ? undefined : templates;
|
|
3430
4106
|
}
|
|
3431
4107
|
function optimize(templates) {
|
|
@@ -3443,10 +4119,10 @@ function* renderFilePath(file, ctx, liquid) {
|
|
|
3443
4119
|
}
|
|
3444
4120
|
|
|
3445
4121
|
class IncludeTag extends Tag {
|
|
3446
|
-
constructor(token, remainTokens, liquid) {
|
|
4122
|
+
constructor(token, remainTokens, liquid, parser) {
|
|
3447
4123
|
super(token, remainTokens, liquid);
|
|
3448
4124
|
const { tokenizer } = token;
|
|
3449
|
-
this['file'] = parseFilePath(tokenizer, this.liquid);
|
|
4125
|
+
this['file'] = parseFilePath(tokenizer, this.liquid, parser);
|
|
3450
4126
|
this['currentFile'] = token.file;
|
|
3451
4127
|
const begin = tokenizer.p;
|
|
3452
4128
|
const withStr = tokenizer.readIdentifier();
|
|
@@ -3460,7 +4136,7 @@ class IncludeTag extends Tag {
|
|
|
3460
4136
|
}
|
|
3461
4137
|
else
|
|
3462
4138
|
tokenizer.p = begin;
|
|
3463
|
-
this.hash = new Hash(tokenizer
|
|
4139
|
+
this.hash = new Hash(tokenizer, liquid.options.jekyllInclude || liquid.options.keyValueSeparator);
|
|
3464
4140
|
}
|
|
3465
4141
|
*render(ctx, emitter) {
|
|
3466
4142
|
const { liquid, hash, withVar } = this;
|
|
@@ -3479,12 +4155,43 @@ class IncludeTag extends Tag {
|
|
|
3479
4155
|
ctx.pop();
|
|
3480
4156
|
ctx.restoreRegister(saved);
|
|
3481
4157
|
}
|
|
4158
|
+
*children(partials, sync) {
|
|
4159
|
+
if (partials && isString(this['file'])) {
|
|
4160
|
+
return (yield this.liquid._parsePartialFile(this['file'], sync, this['currentFile']));
|
|
4161
|
+
}
|
|
4162
|
+
return [];
|
|
4163
|
+
}
|
|
4164
|
+
partialScope() {
|
|
4165
|
+
if (isString(this['file'])) {
|
|
4166
|
+
let names;
|
|
4167
|
+
if (this.liquid.options.jekyllInclude) {
|
|
4168
|
+
names = ['include'];
|
|
4169
|
+
}
|
|
4170
|
+
else {
|
|
4171
|
+
names = Object.keys(this.hash.hash);
|
|
4172
|
+
if (this.withVar) {
|
|
4173
|
+
names.push([this['file'], this.withVar]);
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
4176
|
+
return { name: this['file'], isolated: false, scope: names };
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
*arguments() {
|
|
4180
|
+
yield* Object.values(this.hash.hash).filter(isValueToken);
|
|
4181
|
+
if (isValueToken(this['file'])) {
|
|
4182
|
+
yield this['file'];
|
|
4183
|
+
}
|
|
4184
|
+
if (isValueToken(this.withVar)) {
|
|
4185
|
+
yield this.withVar;
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
3482
4188
|
}
|
|
3483
4189
|
|
|
3484
4190
|
class DecrementTag extends Tag {
|
|
3485
4191
|
constructor(token, remainTokens, liquid) {
|
|
3486
4192
|
super(token, remainTokens, liquid);
|
|
3487
|
-
this.
|
|
4193
|
+
this.identifier = this.tokenizer.readIdentifier();
|
|
4194
|
+
this.variable = this.identifier.content;
|
|
3488
4195
|
}
|
|
3489
4196
|
render(context, emitter) {
|
|
3490
4197
|
const scope = context.environments;
|
|
@@ -3493,6 +4200,9 @@ class DecrementTag extends Tag {
|
|
|
3493
4200
|
}
|
|
3494
4201
|
emitter.write(stringify(--scope[this.variable]));
|
|
3495
4202
|
}
|
|
4203
|
+
*localScope() {
|
|
4204
|
+
yield this.identifier;
|
|
4205
|
+
}
|
|
3496
4206
|
}
|
|
3497
4207
|
|
|
3498
4208
|
class CycleTag extends Tag {
|
|
@@ -3530,40 +4240,38 @@ class CycleTag extends Tag {
|
|
|
3530
4240
|
groups[fingerprint] = idx;
|
|
3531
4241
|
return yield evalToken(candidate, ctx);
|
|
3532
4242
|
}
|
|
4243
|
+
*arguments() {
|
|
4244
|
+
yield* this.candidates;
|
|
4245
|
+
if (this.group) {
|
|
4246
|
+
yield this.group;
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
3533
4249
|
}
|
|
3534
4250
|
|
|
3535
4251
|
class IfTag extends Tag {
|
|
3536
|
-
constructor(tagToken, remainTokens, liquid) {
|
|
4252
|
+
constructor(tagToken, remainTokens, liquid, parser) {
|
|
3537
4253
|
super(tagToken, remainTokens, liquid);
|
|
3538
4254
|
this.branches = [];
|
|
3539
|
-
this.elseTemplates = [];
|
|
3540
4255
|
let p = [];
|
|
3541
|
-
|
|
3542
|
-
liquid.parser.parseStream(remainTokens)
|
|
4256
|
+
parser.parseStream(remainTokens)
|
|
3543
4257
|
.on('start', () => this.branches.push({
|
|
3544
|
-
value: new Value(tagToken.
|
|
4258
|
+
value: new Value(tagToken.tokenizer.readFilteredValue(), this.liquid),
|
|
3545
4259
|
templates: (p = [])
|
|
3546
4260
|
}))
|
|
3547
4261
|
.on('tag:elsif', (token) => {
|
|
3548
|
-
|
|
3549
|
-
p = [];
|
|
3550
|
-
return;
|
|
3551
|
-
}
|
|
4262
|
+
assert(!this.elseTemplates, 'unexpected elsif after else');
|
|
3552
4263
|
this.branches.push({
|
|
3553
|
-
value: new Value(token.
|
|
4264
|
+
value: new Value(token.tokenizer.readFilteredValue(), this.liquid),
|
|
3554
4265
|
templates: (p = [])
|
|
3555
4266
|
});
|
|
3556
4267
|
})
|
|
3557
|
-
.on('tag:else',
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
.on('tag:endif', function () { this.stop(); })
|
|
3562
|
-
.on('template', (tpl) => {
|
|
3563
|
-
if (p !== this.elseTemplates || elseCount === 1) {
|
|
3564
|
-
p.push(tpl);
|
|
3565
|
-
}
|
|
4268
|
+
.on('tag:else', tag => {
|
|
4269
|
+
assertEmpty(tag.args);
|
|
4270
|
+
assert(!this.elseTemplates, 'duplicated else');
|
|
4271
|
+
p = this.elseTemplates = [];
|
|
3566
4272
|
})
|
|
4273
|
+
.on('tag:endif', function (tag) { assertEmpty(tag.args); this.stop(); })
|
|
4274
|
+
.on('template', (tpl) => p.push(tpl))
|
|
3567
4275
|
.on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`); })
|
|
3568
4276
|
.start();
|
|
3569
4277
|
}
|
|
@@ -3576,14 +4284,25 @@ class IfTag extends Tag {
|
|
|
3576
4284
|
return;
|
|
3577
4285
|
}
|
|
3578
4286
|
}
|
|
3579
|
-
yield r.renderTemplates(this.elseTemplates, ctx, emitter);
|
|
4287
|
+
yield r.renderTemplates(this.elseTemplates || [], ctx, emitter);
|
|
4288
|
+
}
|
|
4289
|
+
*children() {
|
|
4290
|
+
const templates = this.branches.flatMap(b => b.templates);
|
|
4291
|
+
if (this.elseTemplates) {
|
|
4292
|
+
templates.push(...this.elseTemplates);
|
|
4293
|
+
}
|
|
4294
|
+
return templates;
|
|
4295
|
+
}
|
|
4296
|
+
arguments() {
|
|
4297
|
+
return this.branches.map(b => b.value);
|
|
3580
4298
|
}
|
|
3581
4299
|
}
|
|
3582
4300
|
|
|
3583
4301
|
class IncrementTag extends Tag {
|
|
3584
4302
|
constructor(token, remainTokens, liquid) {
|
|
3585
4303
|
super(token, remainTokens, liquid);
|
|
3586
|
-
this.
|
|
4304
|
+
this.identifier = this.tokenizer.readIdentifier();
|
|
4305
|
+
this.variable = this.identifier.content;
|
|
3587
4306
|
}
|
|
3588
4307
|
render(context, emitter) {
|
|
3589
4308
|
const scope = context.environments;
|
|
@@ -3594,15 +4313,18 @@ class IncrementTag extends Tag {
|
|
|
3594
4313
|
scope[this.variable]++;
|
|
3595
4314
|
emitter.write(stringify(val));
|
|
3596
4315
|
}
|
|
4316
|
+
*localScope() {
|
|
4317
|
+
yield this.identifier;
|
|
4318
|
+
}
|
|
3597
4319
|
}
|
|
3598
4320
|
|
|
3599
4321
|
class LayoutTag extends Tag {
|
|
3600
|
-
constructor(token, remainTokens, liquid) {
|
|
4322
|
+
constructor(token, remainTokens, liquid, parser) {
|
|
3601
4323
|
super(token, remainTokens, liquid);
|
|
3602
|
-
this.file = parseFilePath(this.tokenizer, this.liquid);
|
|
4324
|
+
this.file = parseFilePath(this.tokenizer, this.liquid, parser);
|
|
3603
4325
|
this['currentFile'] = token.file;
|
|
3604
|
-
this.args = new Hash(this.tokenizer.
|
|
3605
|
-
this.templates =
|
|
4326
|
+
this.args = new Hash(this.tokenizer, liquid.options.keyValueSeparator);
|
|
4327
|
+
this.templates = parser.parseTokens(remainTokens);
|
|
3606
4328
|
}
|
|
3607
4329
|
*render(ctx, emitter) {
|
|
3608
4330
|
const { liquid, args, file } = this;
|
|
@@ -3628,10 +4350,32 @@ class LayoutTag extends Tag {
|
|
|
3628
4350
|
yield renderer.renderTemplates(templates, ctx, emitter);
|
|
3629
4351
|
ctx.pop();
|
|
3630
4352
|
}
|
|
4353
|
+
*children(partials) {
|
|
4354
|
+
const templates = this.templates.slice();
|
|
4355
|
+
if (partials && isString(this.file)) {
|
|
4356
|
+
templates.push(...(yield this.liquid._parsePartialFile(this.file, true, this['currentFile'])));
|
|
4357
|
+
}
|
|
4358
|
+
return templates;
|
|
4359
|
+
}
|
|
4360
|
+
*arguments() {
|
|
4361
|
+
for (const v of Object.values(this.args.hash)) {
|
|
4362
|
+
if (isValueToken(v)) {
|
|
4363
|
+
yield v;
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
if (isValueToken(this.file)) {
|
|
4367
|
+
yield this.file;
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
partialScope() {
|
|
4371
|
+
if (isString(this.file)) {
|
|
4372
|
+
return { name: this.file, isolated: false, scope: Object.keys(this.args.hash) };
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
3631
4375
|
}
|
|
3632
4376
|
|
|
3633
4377
|
class BlockTag extends Tag {
|
|
3634
|
-
constructor(token, remainTokens, liquid) {
|
|
4378
|
+
constructor(token, remainTokens, liquid, parser) {
|
|
3635
4379
|
super(token, remainTokens, liquid);
|
|
3636
4380
|
this.templates = [];
|
|
3637
4381
|
const match = /\w+/.exec(token.args);
|
|
@@ -3640,7 +4384,7 @@ class BlockTag extends Tag {
|
|
|
3640
4384
|
const token = remainTokens.shift();
|
|
3641
4385
|
if (isTagToken(token) && token.name === 'endblock')
|
|
3642
4386
|
return;
|
|
3643
|
-
const template =
|
|
4387
|
+
const template = parser.parseToken(token, remainTokens);
|
|
3644
4388
|
this.templates.push(template);
|
|
3645
4389
|
}
|
|
3646
4390
|
throw new Error(`tag ${token.getText()} not closed`);
|
|
@@ -3667,6 +4411,12 @@ class BlockTag extends Tag {
|
|
|
3667
4411
|
? (superBlock, emitter) => renderChild(new BlockDrop(() => renderCurrent(superBlock, emitter)), emitter)
|
|
3668
4412
|
: renderCurrent;
|
|
3669
4413
|
}
|
|
4414
|
+
*children() {
|
|
4415
|
+
return this.templates;
|
|
4416
|
+
}
|
|
4417
|
+
blockScope() {
|
|
4418
|
+
return ['block'];
|
|
4419
|
+
}
|
|
3670
4420
|
}
|
|
3671
4421
|
|
|
3672
4422
|
class RawTag extends Tag {
|
|
@@ -3710,7 +4460,7 @@ class TablerowloopDrop extends ForloopDrop {
|
|
|
3710
4460
|
}
|
|
3711
4461
|
|
|
3712
4462
|
class TablerowTag extends Tag {
|
|
3713
|
-
constructor(tagToken, remainTokens, liquid) {
|
|
4463
|
+
constructor(tagToken, remainTokens, liquid, parser) {
|
|
3714
4464
|
super(tagToken, remainTokens, liquid);
|
|
3715
4465
|
const variable = this.tokenizer.readIdentifier();
|
|
3716
4466
|
this.tokenizer.skipBlank();
|
|
@@ -3721,10 +4471,10 @@ class TablerowTag extends Tag {
|
|
|
3721
4471
|
}
|
|
3722
4472
|
this.variable = variable.content;
|
|
3723
4473
|
this.collection = collectionToken;
|
|
3724
|
-
this.args = new Hash(this.tokenizer.
|
|
4474
|
+
this.args = new Hash(this.tokenizer, liquid.options.keyValueSeparator);
|
|
3725
4475
|
this.templates = [];
|
|
3726
4476
|
let p;
|
|
3727
|
-
const stream =
|
|
4477
|
+
const stream = parser.parseStream(remainTokens)
|
|
3728
4478
|
.on('start', () => (p = this.templates))
|
|
3729
4479
|
.on('tag:endtablerow', () => stream.stop())
|
|
3730
4480
|
.on('template', (tpl) => p.push(tpl))
|
|
@@ -3759,18 +4509,32 @@ class TablerowTag extends Tag {
|
|
|
3759
4509
|
emitter.write('</tr>');
|
|
3760
4510
|
ctx.pop();
|
|
3761
4511
|
}
|
|
4512
|
+
*children() {
|
|
4513
|
+
return this.templates;
|
|
4514
|
+
}
|
|
4515
|
+
*arguments() {
|
|
4516
|
+
yield this.collection;
|
|
4517
|
+
for (const v of Object.values(this.args.hash)) {
|
|
4518
|
+
if (isValueToken(v)) {
|
|
4519
|
+
yield v;
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
blockScope() {
|
|
4524
|
+
return [this.variable, 'tablerowloop'];
|
|
4525
|
+
}
|
|
3762
4526
|
}
|
|
3763
4527
|
|
|
3764
4528
|
class UnlessTag extends Tag {
|
|
3765
|
-
constructor(tagToken, remainTokens, liquid) {
|
|
4529
|
+
constructor(tagToken, remainTokens, liquid, parser) {
|
|
3766
4530
|
super(tagToken, remainTokens, liquid);
|
|
3767
4531
|
this.branches = [];
|
|
3768
4532
|
this.elseTemplates = [];
|
|
3769
4533
|
let p = [];
|
|
3770
4534
|
let elseCount = 0;
|
|
3771
|
-
|
|
4535
|
+
parser.parseStream(remainTokens)
|
|
3772
4536
|
.on('start', () => this.branches.push({
|
|
3773
|
-
value: new Value(tagToken.
|
|
4537
|
+
value: new Value(tagToken.tokenizer.readFilteredValue(), this.liquid),
|
|
3774
4538
|
test: isFalsy,
|
|
3775
4539
|
templates: (p = [])
|
|
3776
4540
|
}))
|
|
@@ -3780,7 +4544,7 @@ class UnlessTag extends Tag {
|
|
|
3780
4544
|
return;
|
|
3781
4545
|
}
|
|
3782
4546
|
this.branches.push({
|
|
3783
|
-
value: new Value(token.
|
|
4547
|
+
value: new Value(token.tokenizer.readFilteredValue(), this.liquid),
|
|
3784
4548
|
test: isTruthy,
|
|
3785
4549
|
templates: (p = [])
|
|
3786
4550
|
});
|
|
@@ -3809,17 +4573,27 @@ class UnlessTag extends Tag {
|
|
|
3809
4573
|
}
|
|
3810
4574
|
yield r.renderTemplates(this.elseTemplates, ctx, emitter);
|
|
3811
4575
|
}
|
|
4576
|
+
*children() {
|
|
4577
|
+
const children = this.branches.flatMap(b => b.templates);
|
|
4578
|
+
if (this.elseTemplates) {
|
|
4579
|
+
children.push(...this.elseTemplates);
|
|
4580
|
+
}
|
|
4581
|
+
return children;
|
|
4582
|
+
}
|
|
4583
|
+
arguments() {
|
|
4584
|
+
return this.branches.map(b => b.value);
|
|
4585
|
+
}
|
|
3812
4586
|
}
|
|
3813
4587
|
|
|
3814
4588
|
class BreakTag extends Tag {
|
|
3815
|
-
render(ctx,
|
|
3816
|
-
|
|
4589
|
+
render(ctx, _emitter) {
|
|
4590
|
+
ctx.breakCalled = true;
|
|
3817
4591
|
}
|
|
3818
4592
|
}
|
|
3819
4593
|
|
|
3820
4594
|
class ContinueTag extends Tag {
|
|
3821
|
-
render(ctx,
|
|
3822
|
-
|
|
4595
|
+
render(ctx, _emitter) {
|
|
4596
|
+
ctx.continueCalled = true;
|
|
3823
4597
|
}
|
|
3824
4598
|
}
|
|
3825
4599
|
|
|
@@ -3837,17 +4611,25 @@ class EchoTag extends Tag {
|
|
|
3837
4611
|
const val = yield this.value.value(ctx, false);
|
|
3838
4612
|
emitter.write(val);
|
|
3839
4613
|
}
|
|
4614
|
+
*arguments() {
|
|
4615
|
+
if (this.value) {
|
|
4616
|
+
yield this.value;
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
3840
4619
|
}
|
|
3841
4620
|
|
|
3842
4621
|
class LiquidTag extends Tag {
|
|
3843
|
-
constructor(token, remainTokens, liquid) {
|
|
4622
|
+
constructor(token, remainTokens, liquid, parser) {
|
|
3844
4623
|
super(token, remainTokens, liquid);
|
|
3845
4624
|
const tokens = this.tokenizer.readLiquidTagTokens(this.liquid.options);
|
|
3846
|
-
this.templates =
|
|
4625
|
+
this.templates = parser.parseTokens(tokens);
|
|
3847
4626
|
}
|
|
3848
4627
|
*render(ctx, emitter) {
|
|
3849
4628
|
yield this.liquid.renderer.renderTemplates(this.templates, ctx, emitter);
|
|
3850
4629
|
}
|
|
4630
|
+
*children() {
|
|
4631
|
+
return this.templates;
|
|
4632
|
+
}
|
|
3851
4633
|
}
|
|
3852
4634
|
|
|
3853
4635
|
class InlineCommentTag extends Tag {
|
|
@@ -3890,12 +4672,14 @@ class Liquid {
|
|
|
3890
4672
|
this.filters = {};
|
|
3891
4673
|
this.tags = {};
|
|
3892
4674
|
this.options = normalize(opts);
|
|
4675
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
3893
4676
|
this.parser = new Parser(this);
|
|
3894
4677
|
forOwn(tags, (conf, name) => this.registerTag(name, conf));
|
|
3895
4678
|
forOwn(filters, (handler, name) => this.registerFilter(name, handler));
|
|
3896
4679
|
}
|
|
3897
4680
|
parse(html, filepath) {
|
|
3898
|
-
|
|
4681
|
+
const parser = new Parser(this);
|
|
4682
|
+
return parser.parse(html, filepath);
|
|
3899
4683
|
}
|
|
3900
4684
|
_render(tpl, scope, renderOptions) {
|
|
3901
4685
|
const ctx = scope instanceof Context ? scope : new Context(scope, this.options, renderOptions);
|
|
@@ -3922,19 +4706,19 @@ class Liquid {
|
|
|
3922
4706
|
return toValueSync(this._parseAndRender(html, scope, { ...renderOptions, sync: true }));
|
|
3923
4707
|
}
|
|
3924
4708
|
_parsePartialFile(file, sync, currentFile) {
|
|
3925
|
-
return this.
|
|
4709
|
+
return new Parser(this).parseFile(file, sync, LookupType.Partials, currentFile);
|
|
3926
4710
|
}
|
|
3927
4711
|
_parseLayoutFile(file, sync, currentFile) {
|
|
3928
|
-
return this.
|
|
4712
|
+
return new Parser(this).parseFile(file, sync, LookupType.Layouts, currentFile);
|
|
3929
4713
|
}
|
|
3930
4714
|
_parseFile(file, sync, lookupType, currentFile) {
|
|
3931
|
-
return this.
|
|
4715
|
+
return new Parser(this).parseFile(file, sync, lookupType, currentFile);
|
|
3932
4716
|
}
|
|
3933
4717
|
async parseFile(file, lookupType) {
|
|
3934
|
-
return toPromise(this.
|
|
4718
|
+
return toPromise(new Parser(this).parseFile(file, false, lookupType));
|
|
3935
4719
|
}
|
|
3936
4720
|
parseFileSync(file, lookupType) {
|
|
3937
|
-
return toValueSync(this.
|
|
4721
|
+
return toValueSync(new Parser(this).parseFile(file, true, lookupType));
|
|
3938
4722
|
}
|
|
3939
4723
|
*_renderFile(file, ctx, renderFileOptions) {
|
|
3940
4724
|
const templates = (yield this._parseFile(file, renderFileOptions.sync, renderFileOptions.lookupType));
|
|
@@ -3984,10 +4768,82 @@ class Liquid {
|
|
|
3984
4768
|
self.renderFile(filePath, ctx).then(html => callback(null, html), callback);
|
|
3985
4769
|
};
|
|
3986
4770
|
}
|
|
4771
|
+
async analyze(template, options = {}) {
|
|
4772
|
+
return analyze(template, options);
|
|
4773
|
+
}
|
|
4774
|
+
analyzeSync(template, options = {}) {
|
|
4775
|
+
return analyzeSync(template, options);
|
|
4776
|
+
}
|
|
4777
|
+
async parseAndAnalyze(html, filename, options = {}) {
|
|
4778
|
+
return analyze(this.parse(html, filename), options);
|
|
4779
|
+
}
|
|
4780
|
+
parseAndAnalyzeSync(html, filename, options = {}) {
|
|
4781
|
+
return analyzeSync(this.parse(html, filename), options);
|
|
4782
|
+
}
|
|
4783
|
+
/** Return an array of all variables without their properties. */
|
|
4784
|
+
async variables(template, options = {}) {
|
|
4785
|
+
const analysis = await analyze(isString(template) ? this.parse(template) : template, options);
|
|
4786
|
+
return Object.keys(analysis.variables);
|
|
4787
|
+
}
|
|
4788
|
+
/** Return an array of all variables without their properties. */
|
|
4789
|
+
variablesSync(template, options = {}) {
|
|
4790
|
+
const analysis = analyzeSync(isString(template) ? this.parse(template) : template, options);
|
|
4791
|
+
return Object.keys(analysis.variables);
|
|
4792
|
+
}
|
|
4793
|
+
/** Return an array of all variables including their properties/paths. */
|
|
4794
|
+
async fullVariables(template, options = {}) {
|
|
4795
|
+
const analysis = await analyze(isString(template) ? this.parse(template) : template, options);
|
|
4796
|
+
return Array.from(new Set(Object.values(analysis.variables).flatMap((a) => a.map((v) => String(v)))));
|
|
4797
|
+
}
|
|
4798
|
+
/** Return an array of all variables including their properties/paths. */
|
|
4799
|
+
fullVariablesSync(template, options = {}) {
|
|
4800
|
+
const analysis = analyzeSync(isString(template) ? this.parse(template) : template, options);
|
|
4801
|
+
return Array.from(new Set(Object.values(analysis.variables).flatMap((a) => a.map((v) => String(v)))));
|
|
4802
|
+
}
|
|
4803
|
+
/** Return an array of all variables, each as an array of properties/segments. */
|
|
4804
|
+
async variableSegments(template, options = {}) {
|
|
4805
|
+
const analysis = await analyze(isString(template) ? this.parse(template) : template, options);
|
|
4806
|
+
return Array.from(strictUniq(Object.values(analysis.variables).flatMap((a) => a.map((v) => v.toArray()))));
|
|
4807
|
+
}
|
|
4808
|
+
/** Return an array of all variables, each as an array of properties/segments. */
|
|
4809
|
+
variableSegmentsSync(template, options = {}) {
|
|
4810
|
+
const analysis = analyzeSync(isString(template) ? this.parse(template) : template, options);
|
|
4811
|
+
return Array.from(strictUniq(Object.values(analysis.variables).flatMap((a) => a.map((v) => v.toArray()))));
|
|
4812
|
+
}
|
|
4813
|
+
/** Return an array of all expected context variables without their properties. */
|
|
4814
|
+
async globalVariables(template, options = {}) {
|
|
4815
|
+
const analysis = await analyze(isString(template) ? this.parse(template) : template, options);
|
|
4816
|
+
return Object.keys(analysis.globals);
|
|
4817
|
+
}
|
|
4818
|
+
/** Return an array of all expected context variables without their properties. */
|
|
4819
|
+
globalVariablesSync(template, options = {}) {
|
|
4820
|
+
const analysis = analyzeSync(isString(template) ? this.parse(template) : template, options);
|
|
4821
|
+
return Object.keys(analysis.globals);
|
|
4822
|
+
}
|
|
4823
|
+
/** Return an array of all expected context variables including their properties/paths. */
|
|
4824
|
+
async globalFullVariables(template, options = {}) {
|
|
4825
|
+
const analysis = await analyze(isString(template) ? this.parse(template) : template, options);
|
|
4826
|
+
return Array.from(new Set(Object.values(analysis.globals).flatMap((a) => a.map((v) => String(v)))));
|
|
4827
|
+
}
|
|
4828
|
+
/** Return an array of all expected context variables including their properties/paths. */
|
|
4829
|
+
globalFullVariablesSync(template, options = {}) {
|
|
4830
|
+
const analysis = analyzeSync(isString(template) ? this.parse(template) : template, options);
|
|
4831
|
+
return Array.from(new Set(Object.values(analysis.globals).flatMap((a) => a.map((v) => String(v)))));
|
|
4832
|
+
}
|
|
4833
|
+
/** Return an array of all expected context variables, each as an array of properties/segments. */
|
|
4834
|
+
async globalVariableSegments(template, options = {}) {
|
|
4835
|
+
const analysis = await analyze(isString(template) ? this.parse(template) : template, options);
|
|
4836
|
+
return Array.from(strictUniq(Object.values(analysis.globals).flatMap((a) => a.map((v) => v.toArray()))));
|
|
4837
|
+
}
|
|
4838
|
+
/** Return an array of all expected context variables, each as an array of properties/segments. */
|
|
4839
|
+
globalVariableSegmentsSync(template, options = {}) {
|
|
4840
|
+
const analysis = analyzeSync(isString(template) ? this.parse(template) : template, options);
|
|
4841
|
+
return Array.from(strictUniq(Object.values(analysis.globals).flatMap((a) => a.map((v) => v.toArray()))));
|
|
4842
|
+
}
|
|
3987
4843
|
}
|
|
3988
4844
|
|
|
3989
4845
|
/* istanbul ignore file */
|
|
3990
|
-
const version = '10.
|
|
4846
|
+
const version = '10.20.1';
|
|
3991
4847
|
|
|
3992
4848
|
exports.AssertionError = AssertionError;
|
|
3993
4849
|
exports.AssignTag = AssignTag;
|
|
@@ -4017,13 +4873,13 @@ exports.LiquidTag = LiquidTag;
|
|
|
4017
4873
|
exports.Output = Output;
|
|
4018
4874
|
exports.ParseError = ParseError;
|
|
4019
4875
|
exports.ParseStream = ParseStream;
|
|
4876
|
+
exports.Parser = Parser;
|
|
4020
4877
|
exports.RawTag = RawTag;
|
|
4021
4878
|
exports.RenderError = RenderError;
|
|
4022
4879
|
exports.RenderTag = RenderTag;
|
|
4023
4880
|
exports.TablerowTag = TablerowTag;
|
|
4024
4881
|
exports.Tag = Tag;
|
|
4025
4882
|
exports.TagToken = TagToken;
|
|
4026
|
-
exports.TimezoneDate = TimezoneDate;
|
|
4027
4883
|
exports.Token = Token;
|
|
4028
4884
|
exports.TokenizationError = TokenizationError;
|
|
4029
4885
|
exports.Tokenizer = Tokenizer;
|
|
@@ -4031,6 +4887,9 @@ exports.TypeGuards = typeGuards;
|
|
|
4031
4887
|
exports.UndefinedVariableError = UndefinedVariableError;
|
|
4032
4888
|
exports.UnlessTag = UnlessTag;
|
|
4033
4889
|
exports.Value = Value;
|
|
4890
|
+
exports.Variable = Variable;
|
|
4891
|
+
exports.analyze = analyze;
|
|
4892
|
+
exports.analyzeSync = analyzeSync;
|
|
4034
4893
|
exports.assert = assert;
|
|
4035
4894
|
exports.createTrie = createTrie;
|
|
4036
4895
|
exports.defaultOperators = defaultOperators;
|