liquid2 0.1.1 → 0.3.0
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +22 -2
- data/LICENSE_SHOPIFY.txt +8 -0
- data/README.md +420 -13
- data/lib/liquid2/context.rb +29 -18
- data/lib/liquid2/environment.rb +58 -6
- data/lib/liquid2/errors.rb +4 -2
- data/lib/liquid2/expressions/arguments.rb +20 -0
- data/lib/liquid2/expressions/arithmetic.rb +123 -0
- data/lib/liquid2/expressions/boolean.rb +2 -1
- data/lib/liquid2/expressions/filtered.rb +19 -25
- data/lib/liquid2/expressions/lambda.rb +2 -0
- data/lib/liquid2/expressions/loop.rb +7 -5
- data/lib/liquid2/expressions/path.rb +19 -2
- data/lib/liquid2/expressions/relational.rb +1 -1
- data/lib/liquid2/filter.rb +1 -2
- data/lib/liquid2/filters/array.rb +0 -1
- data/lib/liquid2/filters/sort.rb +5 -4
- data/lib/liquid2/loader.rb +1 -0
- data/lib/liquid2/nodes/tags/doc.rb +2 -0
- data/lib/liquid2/nodes/tags/extends.rb +270 -1
- data/lib/liquid2/nodes/tags/include.rb +7 -7
- data/lib/liquid2/nodes/tags/macro.rb +145 -1
- data/lib/liquid2/nodes/tags/raw.rb +2 -1
- data/lib/liquid2/nodes/tags/render.rb +8 -10
- data/lib/liquid2/nodes/tags/with.rb +42 -1
- data/lib/liquid2/parser.rb +170 -22
- data/lib/liquid2/scanner.rb +27 -44
- data/lib/liquid2/static_analysis.rb +1 -1
- data/lib/liquid2/template.rb +52 -5
- data/lib/liquid2/undefined.rb +32 -20
- data/lib/liquid2/version.rb +1 -1
- data/lib/liquid2.rb +2 -0
- data/sig/liquid2.rbs +331 -40
- data.tar.gz.sig +0 -0
- metadata +2 -2
- metadata.gz.sig +0 -0
- data/.vscode/settings.json +0 -32
data/lib/liquid2/parser.rb
CHANGED
@@ -7,6 +7,7 @@ require_relative "node"
|
|
7
7
|
require_relative "nodes/comment"
|
8
8
|
require_relative "nodes/output"
|
9
9
|
require_relative "expressions/arguments"
|
10
|
+
require_relative "expressions/arithmetic"
|
10
11
|
require_relative "expressions/array"
|
11
12
|
require_relative "expressions/blank"
|
12
13
|
require_relative "expressions/boolean"
|
@@ -233,7 +234,6 @@ module Liquid2
|
|
233
234
|
left = parse_primary
|
234
235
|
left = parse_array_literal(left) if current_kind == :token_comma
|
235
236
|
filters = parse_filters if current_kind == :token_pipe
|
236
|
-
filters ||= [] # : Array[Filter]
|
237
237
|
expr = FilteredExpression.new(token, left, filters)
|
238
238
|
|
239
239
|
if current_kind == :token_if
|
@@ -337,14 +337,26 @@ module Liquid2
|
|
337
337
|
|
338
338
|
left = case kind
|
339
339
|
when :token_true
|
340
|
-
|
341
|
-
|
340
|
+
if looks_like_a_path
|
341
|
+
parse_path
|
342
|
+
else
|
343
|
+
self.next
|
344
|
+
true
|
345
|
+
end
|
342
346
|
when :token_false
|
343
|
-
|
344
|
-
|
347
|
+
if looks_like_a_path
|
348
|
+
parse_path
|
349
|
+
else
|
350
|
+
self.next
|
351
|
+
false
|
352
|
+
end
|
345
353
|
when :token_nil
|
346
|
-
|
347
|
-
|
354
|
+
if looks_like_a_path
|
355
|
+
parse_path
|
356
|
+
else
|
357
|
+
self.next
|
358
|
+
nil
|
359
|
+
end
|
348
360
|
when :token_int
|
349
361
|
Liquid2.to_liquid_int(self.next[1])
|
350
362
|
when :token_float
|
@@ -359,7 +371,7 @@ module Liquid2
|
|
359
371
|
parse_path
|
360
372
|
when :token_lparen
|
361
373
|
parse_range_lambda_or_grouped_expression
|
362
|
-
when :token_not
|
374
|
+
when :token_not, :token_plus, :token_minus
|
363
375
|
parse_prefix_expression
|
364
376
|
else
|
365
377
|
unless looks_like_a_path && RESERVED_WORDS.include?(kind)
|
@@ -396,7 +408,7 @@ module Liquid2
|
|
396
408
|
# @raises [LiquidTypeError].
|
397
409
|
def parse_string
|
398
410
|
node = parse_primary
|
399
|
-
raise LiquidTypeError, "expected a string" unless node.is_a?(String)
|
411
|
+
raise LiquidTypeError, "expected a string literal" unless node.is_a?(String)
|
400
412
|
|
401
413
|
node
|
402
414
|
end
|
@@ -411,6 +423,23 @@ module Liquid2
|
|
411
423
|
Identifier.new(token)
|
412
424
|
end
|
413
425
|
|
426
|
+
# Parse a string literals or unquoted word.
|
427
|
+
def parse_name
|
428
|
+
case current_kind
|
429
|
+
when :token_word
|
430
|
+
parse_identifier.name
|
431
|
+
when :token_single_quote_string, :token_double_quote_string
|
432
|
+
node = parse_string_literal
|
433
|
+
unless node.is_a?(String)
|
434
|
+
raise LiquidSyntaxError.new("names can't be template strings", node.token)
|
435
|
+
end
|
436
|
+
|
437
|
+
node
|
438
|
+
else
|
439
|
+
raise LiquidSyntaxError.new("expected a string literal or unquoted word", current)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
414
443
|
# Parse comma separated expression.
|
415
444
|
# Leading commas should be consumed by the caller.
|
416
445
|
# @return [Array<Expression>]
|
@@ -427,7 +456,7 @@ module Liquid2
|
|
427
456
|
args
|
428
457
|
end
|
429
458
|
|
430
|
-
# Parse comma name/value pairs.
|
459
|
+
# Parse comma separated name/value pairs.
|
431
460
|
# Leading commas should be consumed by the caller, if allowed.
|
432
461
|
# @return [Array<KeywordArgument>]
|
433
462
|
def parse_keyword_arguments
|
@@ -438,8 +467,7 @@ module Liquid2
|
|
438
467
|
|
439
468
|
word = eat(:token_word)
|
440
469
|
eat_one_of(:token_assign, :token_colon)
|
441
|
-
|
442
|
-
args << KeywordArgument.new(word, word[1] || raise, val)
|
470
|
+
args << KeywordArgument.new(word, word[1] || raise, parse_primary)
|
443
471
|
|
444
472
|
break unless current_kind == :token_comma
|
445
473
|
|
@@ -449,6 +477,68 @@ module Liquid2
|
|
449
477
|
args
|
450
478
|
end
|
451
479
|
|
480
|
+
# Parse comma separated parameter names with optional default expressions.
|
481
|
+
# Leading commas should be consumed by the caller, if allowed.
|
482
|
+
# @return [Hash[String, Parameter]]
|
483
|
+
def parse_parameters
|
484
|
+
args = {} # : Hash[String, Parameter]
|
485
|
+
|
486
|
+
loop do
|
487
|
+
break if TERMINATE_EXPRESSION.member?(current_kind)
|
488
|
+
|
489
|
+
word = eat(:token_word)
|
490
|
+
name = word[1] || raise
|
491
|
+
|
492
|
+
case current_kind
|
493
|
+
when :token_assign, :token_colon
|
494
|
+
@pos += 1
|
495
|
+
args[name] = Parameter.new(word, name, parse_primary)
|
496
|
+
@pos += 1 if current_kind == :token_comma
|
497
|
+
when :comma
|
498
|
+
args[name] = Parameter.new(word, name, :undefined)
|
499
|
+
@pos += 1
|
500
|
+
else
|
501
|
+
args[name] = Parameter.new(word, name, :undefined)
|
502
|
+
break
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
args
|
507
|
+
end
|
508
|
+
|
509
|
+
# Parse mixed positional and keyword arguments.
|
510
|
+
# Leading commas should be consumed by the caller, if allowed.
|
511
|
+
# @return [[Array[untyped], Array[KeywordArgument]]]
|
512
|
+
def parse_arguments
|
513
|
+
args = [] # : Array[untyped]
|
514
|
+
kwargs = [] # : Array[KeywordArgument]
|
515
|
+
|
516
|
+
loop do
|
517
|
+
break if TERMINATE_EXPRESSION.member?(current_kind)
|
518
|
+
|
519
|
+
case current_kind
|
520
|
+
when :token_word
|
521
|
+
if KEYWORD_ARGUMENT_DELIMITERS.include?(peek_kind)
|
522
|
+
token = self.next
|
523
|
+
@pos += 1 # = or :
|
524
|
+
kwargs << KeywordArgument.new(token, token[1] || raise, parse_primary)
|
525
|
+
else
|
526
|
+
# A positional argument
|
527
|
+
args << parse_primary
|
528
|
+
end
|
529
|
+
else
|
530
|
+
# A positional argument
|
531
|
+
args << parse_primary
|
532
|
+
end
|
533
|
+
|
534
|
+
break unless current_kind == :token_comma
|
535
|
+
|
536
|
+
@pos += 1
|
537
|
+
end
|
538
|
+
|
539
|
+
[args, kwargs]
|
540
|
+
end
|
541
|
+
|
452
542
|
protected
|
453
543
|
|
454
544
|
class Precedence
|
@@ -458,7 +548,10 @@ module Liquid2
|
|
458
548
|
LOGICAL_AND = 4
|
459
549
|
RELATIONAL = 5
|
460
550
|
MEMBERSHIP = 6
|
461
|
-
|
551
|
+
ADD_SUB = 8
|
552
|
+
MUL_DIV = 9
|
553
|
+
POW = 10
|
554
|
+
PREFIX = 11
|
462
555
|
end
|
463
556
|
|
464
557
|
PRECEDENCES = {
|
@@ -474,7 +567,14 @@ module Liquid2
|
|
474
567
|
token_ne: Precedence::RELATIONAL,
|
475
568
|
token_lg: Precedence::RELATIONAL,
|
476
569
|
token_le: Precedence::RELATIONAL,
|
477
|
-
token_ge: Precedence::RELATIONAL
|
570
|
+
token_ge: Precedence::RELATIONAL,
|
571
|
+
token_plus: Precedence::ADD_SUB,
|
572
|
+
token_minus: Precedence::ADD_SUB,
|
573
|
+
token_times: Precedence::MUL_DIV,
|
574
|
+
token_divide: Precedence::MUL_DIV,
|
575
|
+
token_floor_div: Precedence::MUL_DIV,
|
576
|
+
token_mod: Precedence::MUL_DIV,
|
577
|
+
token_pow: Precedence::POW
|
478
578
|
}.freeze
|
479
579
|
|
480
580
|
BINARY_OPERATORS = Set[
|
@@ -488,7 +588,14 @@ module Liquid2
|
|
488
588
|
:token_contains,
|
489
589
|
:token_in,
|
490
590
|
:token_and,
|
491
|
-
:token_or
|
591
|
+
:token_or,
|
592
|
+
:token_plus,
|
593
|
+
:token_minus,
|
594
|
+
:token_times,
|
595
|
+
:token_divide,
|
596
|
+
:token_floor_div,
|
597
|
+
:token_mod,
|
598
|
+
:token_pow
|
492
599
|
]
|
493
600
|
|
494
601
|
TERMINATE_EXPRESSION = Set[
|
@@ -726,14 +833,35 @@ module Liquid2
|
|
726
833
|
end
|
727
834
|
|
728
835
|
eat(:token_rparen)
|
729
|
-
|
836
|
+
expr
|
730
837
|
end
|
731
838
|
|
732
839
|
# @return [Node]
|
733
840
|
def parse_prefix_expression
|
734
|
-
|
735
|
-
|
736
|
-
|
841
|
+
case current_kind
|
842
|
+
when :token_not
|
843
|
+
token = self.next
|
844
|
+
expr = parse_primary(precedence: Precedence::PREFIX)
|
845
|
+
LogicalNot.new(token, expr)
|
846
|
+
when :token_plus
|
847
|
+
token = self.next
|
848
|
+
unless @env.arithmetic_operators
|
849
|
+
raise LiquidSyntaxError.new("unexpected prefix operator +",
|
850
|
+
token)
|
851
|
+
end
|
852
|
+
|
853
|
+
Positive.new(token, parse_primary(precedence: Precedence::PREFIX))
|
854
|
+
when :token_minus
|
855
|
+
token = self.next
|
856
|
+
unless @env.arithmetic_operators
|
857
|
+
raise LiquidSyntaxError.new("unexpected prefix operator -",
|
858
|
+
token)
|
859
|
+
end
|
860
|
+
|
861
|
+
Negative.new(token, parse_primary(precedence: Precedence::PREFIX))
|
862
|
+
else
|
863
|
+
raise LiquidSyntaxError.new("unexpected prefix operator #{current[1]}", current)
|
864
|
+
end
|
737
865
|
end
|
738
866
|
|
739
867
|
# @param left [Expression]
|
@@ -765,7 +893,27 @@ module Liquid2
|
|
765
893
|
when :token_or
|
766
894
|
LogicalOr.new(op_token, left, right)
|
767
895
|
else
|
768
|
-
|
896
|
+
unless @env.arithmetic_operators
|
897
|
+
raise LiquidSyntaxError.new("unexpected infix operator, #{op_token[1]}",
|
898
|
+
op_token)
|
899
|
+
end
|
900
|
+
|
901
|
+
case op_token.first
|
902
|
+
when :token_plus
|
903
|
+
Plus.new(op_token, left, right)
|
904
|
+
when :token_minus
|
905
|
+
Minus.new(op_token, left, right)
|
906
|
+
when :token_times
|
907
|
+
Times.new(op_token, left, right)
|
908
|
+
when :token_divide
|
909
|
+
Divide.new(op_token, left, right)
|
910
|
+
when :token_mod
|
911
|
+
Modulo.new(op_token, left, right)
|
912
|
+
when :token_pow
|
913
|
+
Pow.new(op_token, left, right)
|
914
|
+
else
|
915
|
+
raise LiquidSyntaxError.new("unexpected infix operator, #{op_token[1]}", op_token)
|
916
|
+
end
|
769
917
|
end
|
770
918
|
end
|
771
919
|
|
@@ -783,7 +931,7 @@ module Liquid2
|
|
783
931
|
|
784
932
|
unless current_kind == :token_colon || !TERMINATE_FILTER.member?(current_kind)
|
785
933
|
# No arguments
|
786
|
-
return Filter.new(name, name[1] || raise,
|
934
|
+
return Filter.new(name, name[1] || raise, nil)
|
787
935
|
end
|
788
936
|
|
789
937
|
@pos += 1 # token_colon
|
@@ -804,7 +952,7 @@ module Liquid2
|
|
804
952
|
args << parse_arrow_function
|
805
953
|
else
|
806
954
|
# A positional argument that is a path.
|
807
|
-
args <<
|
955
|
+
args << parse_primary
|
808
956
|
end
|
809
957
|
when :token_lparen
|
810
958
|
# A grouped expression or range or arrow function
|
data/lib/liquid2/scanner.rb
CHANGED
@@ -12,13 +12,11 @@ module Liquid2
|
|
12
12
|
class Scanner
|
13
13
|
attr_reader :tokens
|
14
14
|
|
15
|
-
RE_MARKUP_START = /\{[\{%#]/
|
16
|
-
RE_WHITESPACE = /[ \n\r\t]+/
|
17
15
|
RE_LINE_SPACE = /[ \t]+/
|
18
16
|
RE_WORD = /[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*/
|
19
17
|
RE_INT = /-?\d+(?:[eE]\+?\d+)?/
|
20
18
|
RE_FLOAT = /((?:-?\d+\.\d+(?:[eE][+-]?\d+)?)|(-?\d+[eE]-\d+))/
|
21
|
-
RE_PUNCTUATION =
|
19
|
+
RE_PUNCTUATION = %r{\?|\[|\]|\|{1,2}|\.{1,2}|,|:|\(|\)|[<>=!]+|[+\-%*/]+(?![\}%])}
|
22
20
|
RE_SINGLE_QUOTE_STRING_SPECIAL = /[\\'\$]/
|
23
21
|
RE_DOUBLE_QUOTE_STRING_SPECIAL = /[\\"\$]/
|
24
22
|
|
@@ -60,7 +58,14 @@ module Liquid2
|
|
60
58
|
">=" => :token_ge,
|
61
59
|
"==" => :token_eq,
|
62
60
|
"!=" => :token_ne,
|
63
|
-
"=>" => :token_arrow
|
61
|
+
"=>" => :token_arrow,
|
62
|
+
"+" => :token_plus,
|
63
|
+
"-" => :token_minus,
|
64
|
+
"%" => :token_mod,
|
65
|
+
"*" => :token_times,
|
66
|
+
"/" => :token_divide,
|
67
|
+
"//" => :token_floor_div,
|
68
|
+
"**" => :token_pow
|
64
69
|
}.freeze
|
65
70
|
|
66
71
|
def self.tokenize(source, scanner)
|
@@ -94,31 +99,19 @@ module Liquid2
|
|
94
99
|
# @param value [String?]
|
95
100
|
# @return void
|
96
101
|
def emit(kind, value)
|
97
|
-
# TODO: For debugging. Comment this out when benchmarking.
|
98
|
-
raise "empty span (#{kind}, #{value})" if @scanner.pos == @start
|
99
|
-
|
100
102
|
@tokens << [kind, value, @start]
|
101
103
|
@start = @scanner.pos
|
102
104
|
end
|
103
105
|
|
104
106
|
def skip_trivia
|
105
|
-
|
106
|
-
raise "must emit before skipping trivia" if @scanner.pos != @start
|
107
|
-
|
108
|
-
@start = @scanner.pos if @scanner.skip(RE_WHITESPACE)
|
107
|
+
@start = @scanner.pos if @scanner.skip(/[ \n\r\t]+/)
|
109
108
|
end
|
110
109
|
|
111
110
|
def skip_line_trivia
|
112
|
-
# TODO: For debugging. Comment this out when benchmarking.
|
113
|
-
raise "must emit before skipping line trivia" if @scanner.pos != @start
|
114
|
-
|
115
111
|
@start = @scanner.pos if @scanner.skip(RE_LINE_SPACE)
|
116
112
|
end
|
117
113
|
|
118
114
|
def accept_whitespace_control
|
119
|
-
# TODO: For debugging. Comment this out when benchmarking.
|
120
|
-
raise "must emit before accepting whitespace control" if @scanner.pos != @start
|
121
|
-
|
122
115
|
ch = @scanner.peek(1)
|
123
116
|
|
124
117
|
case ch
|
@@ -133,7 +126,7 @@ module Liquid2
|
|
133
126
|
end
|
134
127
|
|
135
128
|
def lex_markup
|
136
|
-
case @scanner.scan(
|
129
|
+
case @scanner.scan(/\{[\{%#]/)
|
137
130
|
when "{#"
|
138
131
|
:lex_comment
|
139
132
|
when "{{"
|
@@ -197,37 +190,30 @@ module Liquid2
|
|
197
190
|
end
|
198
191
|
|
199
192
|
def lex_expression
|
200
|
-
# TODO: For debugging. Comment this out when benchmarking.
|
201
|
-
raise "must emit before accepting an expression token" if @scanner.pos != @start
|
202
|
-
|
203
193
|
loop do
|
204
194
|
skip_trivia
|
205
|
-
|
206
|
-
|
207
|
-
when "'"
|
195
|
+
if (value = @scanner.scan(RE_FLOAT))
|
196
|
+
@tokens << [:token_float, value, @start]
|
208
197
|
@start = @scanner.pos
|
209
|
-
|
210
|
-
|
198
|
+
elsif (value = @scanner.scan(RE_INT))
|
199
|
+
@tokens << [:token_int, value, @start]
|
200
|
+
@start = @scanner.pos
|
201
|
+
elsif (value = @scanner.scan(RE_PUNCTUATION))
|
202
|
+
@tokens << [TOKEN_MAP[value] || :token_unknown, value, @start]
|
203
|
+
@start = @scanner.pos
|
204
|
+
elsif (value = @scanner.scan(RE_WORD))
|
205
|
+
@tokens << [TOKEN_MAP[value] || :token_word, value, @start]
|
211
206
|
@start = @scanner.pos
|
212
|
-
scan_string("\"", :token_double_quote_string, RE_DOUBLE_QUOTE_STRING_SPECIAL)
|
213
|
-
when nil
|
214
|
-
# End of scanner. Unclosed expression or string literal.
|
215
|
-
break
|
216
207
|
else
|
217
|
-
@scanner.
|
218
|
-
|
219
|
-
@tokens << [:token_float, value, @start]
|
208
|
+
case @scanner.get_byte
|
209
|
+
when "'"
|
220
210
|
@start = @scanner.pos
|
221
|
-
|
222
|
-
|
223
|
-
@start = @scanner.pos
|
224
|
-
elsif (value = @scanner.scan(RE_PUNCTUATION))
|
225
|
-
@tokens << [TOKEN_MAP[value] || :token_unknown, value, @start]
|
226
|
-
@start = @scanner.pos
|
227
|
-
elsif (value = @scanner.scan(RE_WORD))
|
228
|
-
@tokens << [TOKEN_MAP[value] || :token_word, value, @start]
|
211
|
+
scan_string("'", :token_single_quote_string, RE_SINGLE_QUOTE_STRING_SPECIAL)
|
212
|
+
when "\""
|
229
213
|
@start = @scanner.pos
|
214
|
+
scan_string("\"", :token_double_quote_string, RE_DOUBLE_QUOTE_STRING_SPECIAL)
|
230
215
|
else
|
216
|
+
@scanner.pos -= 1
|
231
217
|
break
|
232
218
|
end
|
233
219
|
end
|
@@ -413,9 +399,6 @@ module Liquid2
|
|
413
399
|
end
|
414
400
|
|
415
401
|
def lex_line_statements
|
416
|
-
# TODO: For debugging. Comment this out when benchmarking.
|
417
|
-
raise "must emit before accepting an expression token" if @scanner.pos != @start
|
418
|
-
|
419
402
|
skip_trivia # Leading newlines are OK
|
420
403
|
|
421
404
|
if (tag_name = @scanner.scan(/(?:[a-z][a-z_0-9]*|#)/))
|
@@ -231,7 +231,7 @@ module Liquid2
|
|
231
231
|
def self.extract_filters(expression, template_name)
|
232
232
|
filters = [] # : Array[[String, Span]]
|
233
233
|
|
234
|
-
if expression.is_a?(Liquid2::FilteredExpression)
|
234
|
+
if expression.is_a?(Liquid2::FilteredExpression) && !expression.filters.nil?
|
235
235
|
expression.filters.each do |filter|
|
236
236
|
filters << [filter.name, Span.new(template_name, filter.token.last)]
|
237
237
|
end
|
data/lib/liquid2/template.rb
CHANGED
@@ -43,7 +43,6 @@ module Liquid2
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def render_with_context(context, buffer, partial: false, block_scope: false, namespace: nil)
|
46
|
-
# TODO: don't extend if namespace is nil
|
47
46
|
context.extend(namespace || {}) do
|
48
47
|
index = 0
|
49
48
|
while (node = @ast[index])
|
@@ -59,6 +58,8 @@ module Liquid2
|
|
59
58
|
|
60
59
|
next unless (interrupt = context.interrupts.pop)
|
61
60
|
|
61
|
+
break if interrupt == :stop_render
|
62
|
+
|
62
63
|
if !partial || block_scope
|
63
64
|
raise LiquidSyntaxError.new("unexpected #{interrupt}",
|
64
65
|
node.token) # steep:ignore
|
@@ -69,15 +70,14 @@ module Liquid2
|
|
69
70
|
end
|
70
71
|
end
|
71
72
|
rescue LiquidError => e
|
72
|
-
e.source =
|
73
|
-
e.template_name = @name unless @name.empty?
|
73
|
+
e.source = context.template.source unless e.source
|
74
|
+
e.template_name = @name unless e.template_name || @name.empty?
|
74
75
|
raise
|
75
76
|
end
|
76
77
|
|
77
78
|
# Merge template globals with another namespace.
|
78
79
|
def make_globals(namespace)
|
79
|
-
|
80
|
-
@globals.merge(@overlay || {}, namespace || {})
|
80
|
+
@globals.merge(@overlay, namespace || {})
|
81
81
|
end
|
82
82
|
|
83
83
|
# Return `false` if this template is stale and needs to be loaded again.
|
@@ -123,6 +123,53 @@ module Liquid2
|
|
123
123
|
nodes
|
124
124
|
end
|
125
125
|
|
126
|
+
# Return an array of `{% doc %}` nodes found in this template.
|
127
|
+
#
|
128
|
+
# Each instance of `Liquid2::DocTag` has a `token` and `text` attribute. Use
|
129
|
+
# `Template#docs.map(&:text)` to get an array of doc strings.
|
130
|
+
#
|
131
|
+
# @return [Array[DocTag]]
|
132
|
+
def docs
|
133
|
+
context = RenderContext.new(self)
|
134
|
+
nodes = [] # : Array[DocTag]
|
135
|
+
|
136
|
+
# @type var visit: ^(Node) -> void
|
137
|
+
visit = lambda do |node|
|
138
|
+
nodes << node if node.is_a?(DocTag)
|
139
|
+
|
140
|
+
node.children(context, include_partials: false).each do |child|
|
141
|
+
visit.call(child) if child.is_a?(Node)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
@ast.each { |node| visit.call(node) if node.is_a?(Node) }
|
146
|
+
|
147
|
+
nodes
|
148
|
+
end
|
149
|
+
|
150
|
+
# Return arrays of `{% macro %}` and `{% call %}` tags found in this template.
|
151
|
+
# @param include_partials [bool]
|
152
|
+
# @return [Array[MacroTag], Array[CallTag]]
|
153
|
+
def macros(include_partials: false)
|
154
|
+
context = RenderContext.new(self)
|
155
|
+
macro_nodes = [] # : Array[MacroTag]
|
156
|
+
call_nodes = [] # : Array[CallTag]
|
157
|
+
|
158
|
+
# @type var visit: ^(Node) -> void
|
159
|
+
visit = lambda do |node|
|
160
|
+
macro_nodes << node if node.is_a?(MacroTag)
|
161
|
+
call_nodes << node if node.is_a?(CallTag)
|
162
|
+
|
163
|
+
node.children(context, include_partials: include_partials).each do |child|
|
164
|
+
visit.call(child) if child.is_a?(Node)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
@ast.each { |node| visit.call(node) if node.is_a?(Node) }
|
169
|
+
|
170
|
+
[macro_nodes, call_nodes]
|
171
|
+
end
|
172
|
+
|
126
173
|
# Return an array of variables used in this template, without path segments.
|
127
174
|
# @param include_partials [bool]
|
128
175
|
# @return [Array[String]]
|
data/lib/liquid2/undefined.rb
CHANGED
@@ -5,6 +5,8 @@ require_relative "errors"
|
|
5
5
|
module Liquid2
|
6
6
|
# The default undefined type. Can be iterated over an indexed without error.
|
7
7
|
class Undefined
|
8
|
+
include Enumerable
|
9
|
+
|
8
10
|
attr_reader :force_default
|
9
11
|
|
10
12
|
def initialize(name, node: nil)
|
@@ -26,6 +28,8 @@ module Liquid2
|
|
26
28
|
def to_s = ""
|
27
29
|
def to_i = 0
|
28
30
|
def to_f = 0.0
|
31
|
+
def -@ = self
|
32
|
+
def +@ = self
|
29
33
|
def each(...) = Enumerator.new {} # rubocop:disable Lint/EmptyBlock
|
30
34
|
def each_with_index(...) = Enumerator.new {} # rubocop:disable Lint/EmptyBlock
|
31
35
|
def join(...) = ""
|
@@ -37,87 +41,95 @@ module Liquid2
|
|
37
41
|
class StrictUndefined < Undefined
|
38
42
|
def initialize(name, node: nil)
|
39
43
|
super
|
40
|
-
@message = "#{name.inspect} is undefined"
|
44
|
+
@message = "#{@node.is_a?(Path) ? @node : name.inspect} is undefined"
|
41
45
|
end
|
42
46
|
|
43
47
|
def respond_to_missing? = true
|
44
48
|
|
45
49
|
def method_missing(...)
|
46
|
-
raise UndefinedError.new(@message, @node)
|
50
|
+
raise UndefinedError.new(@message, @node.token)
|
47
51
|
end
|
48
52
|
|
49
53
|
def [](...)
|
50
|
-
raise UndefinedError.new(@message, @node)
|
54
|
+
raise UndefinedError.new(@message, @node.token)
|
51
55
|
end
|
52
56
|
|
53
57
|
def key?(...)
|
54
|
-
raise UndefinedError.new(@message, @node)
|
58
|
+
raise UndefinedError.new(@message, @node.token)
|
55
59
|
end
|
56
60
|
|
57
61
|
def include?(...)
|
58
|
-
raise UndefinedError.new(@message, @node)
|
62
|
+
raise UndefinedError.new(@message, @node.token)
|
59
63
|
end
|
60
64
|
|
61
65
|
def member?(...)
|
62
|
-
raise UndefinedError.new(@message, @node)
|
66
|
+
raise UndefinedError.new(@message, @node.token)
|
63
67
|
end
|
64
68
|
|
65
69
|
def fetch(...)
|
66
|
-
raise UndefinedError.new(@message, @node)
|
70
|
+
raise UndefinedError.new(@message, @node.token)
|
67
71
|
end
|
68
72
|
|
69
73
|
def !
|
70
|
-
raise UndefinedError.new(@message, @node)
|
74
|
+
raise UndefinedError.new(@message, @node.token)
|
71
75
|
end
|
72
76
|
|
73
77
|
def ==(_other)
|
74
|
-
raise UndefinedError.new(@message, @node)
|
78
|
+
raise UndefinedError.new(@message, @node.token)
|
75
79
|
end
|
76
80
|
|
77
81
|
def !=(_other)
|
78
|
-
raise UndefinedError.new(@message, @node)
|
82
|
+
raise UndefinedError.new(@message, @node.token)
|
79
83
|
end
|
80
84
|
|
81
85
|
alias eql? ==
|
82
86
|
|
83
87
|
def size
|
84
|
-
raise UndefinedError.new(@message, @node)
|
88
|
+
raise UndefinedError.new(@message, @node.token)
|
85
89
|
end
|
86
90
|
|
87
91
|
def length
|
88
|
-
raise UndefinedError.new(@message, @node)
|
92
|
+
raise UndefinedError.new(@message, @node.token)
|
89
93
|
end
|
90
94
|
|
91
95
|
def to_s
|
92
|
-
raise UndefinedError.new(@message, @node)
|
96
|
+
raise UndefinedError.new(@message, @node.token)
|
93
97
|
end
|
94
98
|
|
95
99
|
def to_i
|
96
|
-
raise UndefinedError.new(@message, @node)
|
100
|
+
raise UndefinedError.new(@message, @node.token)
|
97
101
|
end
|
98
102
|
|
99
103
|
def to_f
|
100
|
-
raise UndefinedError.new(@message, @node)
|
104
|
+
raise UndefinedError.new(@message, @node.token)
|
105
|
+
end
|
106
|
+
|
107
|
+
def +@
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
def -@
|
112
|
+
self
|
101
113
|
end
|
102
114
|
|
103
115
|
def each(...)
|
104
|
-
raise UndefinedError.new(@message, @node)
|
116
|
+
raise UndefinedError.new(@message, @node.token)
|
105
117
|
end
|
106
118
|
|
107
119
|
def each_with_index(...)
|
108
|
-
raise UndefinedError.new(@message, @node)
|
120
|
+
raise UndefinedError.new(@message, @node.token)
|
109
121
|
end
|
110
122
|
|
111
123
|
def join(...)
|
112
|
-
raise UndefinedError.new(@message, @node)
|
124
|
+
raise UndefinedError.new(@message, @node.token)
|
113
125
|
end
|
114
126
|
|
115
127
|
def to_liquid(_context)
|
116
|
-
|
128
|
+
self
|
117
129
|
end
|
118
130
|
|
119
131
|
def poke
|
120
|
-
raise UndefinedError.new(@message, @node)
|
132
|
+
raise UndefinedError.new(@message, @node.token)
|
121
133
|
end
|
122
134
|
end
|
123
135
|
|
data/lib/liquid2/version.rb
CHANGED
data/lib/liquid2.rb
CHANGED