liquid2 0.1.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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data/.rubocop.yml +46 -0
- data/.ruby-version +1 -0
- data/.vscode/settings.json +32 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/LICENSE_SHOPIFY.txt +20 -0
- data/README.md +219 -0
- data/Rakefile +23 -0
- data/Steepfile +26 -0
- data/lib/liquid2/context.rb +297 -0
- data/lib/liquid2/environment.rb +287 -0
- data/lib/liquid2/errors.rb +79 -0
- data/lib/liquid2/expression.rb +20 -0
- data/lib/liquid2/expressions/arguments.rb +25 -0
- data/lib/liquid2/expressions/array.rb +20 -0
- data/lib/liquid2/expressions/blank.rb +41 -0
- data/lib/liquid2/expressions/boolean.rb +20 -0
- data/lib/liquid2/expressions/filtered.rb +136 -0
- data/lib/liquid2/expressions/identifier.rb +43 -0
- data/lib/liquid2/expressions/lambda.rb +53 -0
- data/lib/liquid2/expressions/logical.rb +71 -0
- data/lib/liquid2/expressions/loop.rb +79 -0
- data/lib/liquid2/expressions/path.rb +33 -0
- data/lib/liquid2/expressions/range.rb +28 -0
- data/lib/liquid2/expressions/relational.rb +119 -0
- data/lib/liquid2/expressions/template_string.rb +20 -0
- data/lib/liquid2/filter.rb +95 -0
- data/lib/liquid2/filters/array.rb +202 -0
- data/lib/liquid2/filters/date.rb +20 -0
- data/lib/liquid2/filters/default.rb +16 -0
- data/lib/liquid2/filters/json.rb +15 -0
- data/lib/liquid2/filters/math.rb +87 -0
- data/lib/liquid2/filters/size.rb +11 -0
- data/lib/liquid2/filters/slice.rb +17 -0
- data/lib/liquid2/filters/sort.rb +96 -0
- data/lib/liquid2/filters/string.rb +204 -0
- data/lib/liquid2/loader.rb +59 -0
- data/lib/liquid2/loaders/file_system_loader.rb +76 -0
- data/lib/liquid2/loaders/mixins.rb +52 -0
- data/lib/liquid2/node.rb +113 -0
- data/lib/liquid2/nodes/comment.rb +18 -0
- data/lib/liquid2/nodes/output.rb +24 -0
- data/lib/liquid2/nodes/tags/assign.rb +35 -0
- data/lib/liquid2/nodes/tags/block_comment.rb +26 -0
- data/lib/liquid2/nodes/tags/capture.rb +40 -0
- data/lib/liquid2/nodes/tags/case.rb +111 -0
- data/lib/liquid2/nodes/tags/cycle.rb +63 -0
- data/lib/liquid2/nodes/tags/decrement.rb +29 -0
- data/lib/liquid2/nodes/tags/doc.rb +24 -0
- data/lib/liquid2/nodes/tags/echo.rb +31 -0
- data/lib/liquid2/nodes/tags/extends.rb +3 -0
- data/lib/liquid2/nodes/tags/for.rb +155 -0
- data/lib/liquid2/nodes/tags/if.rb +84 -0
- data/lib/liquid2/nodes/tags/include.rb +123 -0
- data/lib/liquid2/nodes/tags/increment.rb +29 -0
- data/lib/liquid2/nodes/tags/inline_comment.rb +28 -0
- data/lib/liquid2/nodes/tags/liquid.rb +29 -0
- data/lib/liquid2/nodes/tags/macro.rb +3 -0
- data/lib/liquid2/nodes/tags/raw.rb +30 -0
- data/lib/liquid2/nodes/tags/render.rb +137 -0
- data/lib/liquid2/nodes/tags/tablerow.rb +143 -0
- data/lib/liquid2/nodes/tags/translate.rb +3 -0
- data/lib/liquid2/nodes/tags/unless.rb +23 -0
- data/lib/liquid2/nodes/tags/with.rb +3 -0
- data/lib/liquid2/parser.rb +917 -0
- data/lib/liquid2/scanner.rb +595 -0
- data/lib/liquid2/static_analysis.rb +301 -0
- data/lib/liquid2/tag.rb +22 -0
- data/lib/liquid2/template.rb +182 -0
- data/lib/liquid2/undefined.rb +131 -0
- data/lib/liquid2/utils/cache.rb +80 -0
- data/lib/liquid2/utils/chain_hash.rb +40 -0
- data/lib/liquid2/utils/unescape.rb +119 -0
- data/lib/liquid2/version.rb +5 -0
- data/lib/liquid2.rb +90 -0
- data/performance/benchmark.rb +73 -0
- data/performance/memory_profile.rb +62 -0
- data/performance/profile.rb +71 -0
- data/sig/liquid2.rbs +2348 -0
- data.tar.gz.sig +0 -0
- metadata +164 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../tag"
|
4
|
+
|
5
|
+
module Liquid2
|
6
|
+
# The standard _case_ tag.
|
7
|
+
class CaseTag < Tag
|
8
|
+
END_BLOCK = Set["endcase", "when", "else"]
|
9
|
+
WHEN_DELIM = Set[:token_comma, :token_or]
|
10
|
+
|
11
|
+
def self.parse(token, parser)
|
12
|
+
expression = parser.parse_primary
|
13
|
+
parser.carry_whitespace_control
|
14
|
+
parser.eat(:token_tag_end)
|
15
|
+
parser.eat(:token_other) if parser.current_kind == :token_other
|
16
|
+
|
17
|
+
whens = [] # : Array[MultiEqualBlock]
|
18
|
+
default = nil # : Block?
|
19
|
+
|
20
|
+
whens << parse_when(parser, expression) while parser.tag?("when")
|
21
|
+
|
22
|
+
if parser.tag?("else")
|
23
|
+
parser.eat_empty_tag("else")
|
24
|
+
default = parser.parse_block(END_BLOCK)
|
25
|
+
end
|
26
|
+
|
27
|
+
parser.eat_empty_tag("endcase")
|
28
|
+
new(token, expression, whens, default)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [MultiEqualBlock]
|
32
|
+
def self.parse_when(parser, expr)
|
33
|
+
parser.eat(:token_tag_start)
|
34
|
+
parser.skip_whitespace_control
|
35
|
+
token = parser.eat(:token_tag_name)
|
36
|
+
|
37
|
+
parser.next if parser.current_kind == :token_comma
|
38
|
+
|
39
|
+
args = [] # : Array[Expression]
|
40
|
+
|
41
|
+
loop do
|
42
|
+
args << parser.parse_primary(infix: false)
|
43
|
+
break unless WHEN_DELIM.member?(parser.current_kind)
|
44
|
+
|
45
|
+
parser.next
|
46
|
+
end
|
47
|
+
|
48
|
+
parser.carry_whitespace_control
|
49
|
+
parser.eat(:token_tag_end)
|
50
|
+
|
51
|
+
block = parser.parse_block(END_BLOCK)
|
52
|
+
MultiEqualBlock.new(token, expr, args, block)
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize(token, expression, whens, default)
|
56
|
+
super(token)
|
57
|
+
@expression = expression
|
58
|
+
@whens = whens
|
59
|
+
@default = default
|
60
|
+
@blank = whens.map(&:blank).all? && (!default || default.blank)
|
61
|
+
end
|
62
|
+
|
63
|
+
def render(context, buffer)
|
64
|
+
rendered = false
|
65
|
+
index = 0
|
66
|
+
while (node = @whens[index])
|
67
|
+
rendered_ = node.render(context, buffer)
|
68
|
+
rendered ||= rendered_
|
69
|
+
index += 1
|
70
|
+
end
|
71
|
+
|
72
|
+
(@default || raise).render(context, buffer) if @default && !rendered
|
73
|
+
end
|
74
|
+
|
75
|
+
def children(_static_context, include_partials: true)
|
76
|
+
# @type var nodes: Array[Node]
|
77
|
+
nodes = @whens.clone
|
78
|
+
nodes << (@default || raise) if @default
|
79
|
+
nodes
|
80
|
+
end
|
81
|
+
|
82
|
+
def expressions = [@expression]
|
83
|
+
end
|
84
|
+
|
85
|
+
# A Liquid block guarded by any one of multiple expressions.
|
86
|
+
class MultiEqualBlock < Node
|
87
|
+
# @param left [Expression]
|
88
|
+
# @param conditions [Array<Expression>]
|
89
|
+
# @param block [Block]
|
90
|
+
def initialize(token, left, conditions, block)
|
91
|
+
super(token)
|
92
|
+
@left = left
|
93
|
+
@conditions = conditions
|
94
|
+
@block = block
|
95
|
+
@blank = block.blank
|
96
|
+
end
|
97
|
+
|
98
|
+
def render(context, buffer)
|
99
|
+
left = context.evaluate(@left)
|
100
|
+
if @conditions.map { |right| Liquid2.eq?(left, context.evaluate(right)) }.any?
|
101
|
+
@block.render(context, buffer)
|
102
|
+
true
|
103
|
+
else
|
104
|
+
false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def children(_static_context, include_partials: true) = [@block]
|
109
|
+
def expressions = @conditions
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../tag"
|
4
|
+
|
5
|
+
module Liquid2
|
6
|
+
# The standard _cycle_ tag.
|
7
|
+
class CycleTag < Tag
|
8
|
+
# @param parser [Parser]
|
9
|
+
# @return [CycleTag]
|
10
|
+
def self.parse(token, parser)
|
11
|
+
items = [] # : Array[untyped]
|
12
|
+
group_name = nil # : untyped?
|
13
|
+
named = false
|
14
|
+
first = parser.parse_primary
|
15
|
+
|
16
|
+
# Is the first expression followed by a colon? If so, it is a group name
|
17
|
+
# followed by items to cycle.
|
18
|
+
if parser.current_kind == :token_colon
|
19
|
+
group_name = first
|
20
|
+
named = true
|
21
|
+
parser.next
|
22
|
+
else
|
23
|
+
items << first
|
24
|
+
end
|
25
|
+
|
26
|
+
parser.next if parser.current_kind == :token_comma
|
27
|
+
|
28
|
+
items.push(*parser.parse_positional_arguments)
|
29
|
+
parser.carry_whitespace_control
|
30
|
+
parser.eat(:token_tag_end)
|
31
|
+
new(token, group_name, items, named)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param name [Expression?]
|
35
|
+
# @param items [Array<Expression>]
|
36
|
+
def initialize(token, name, items, named)
|
37
|
+
super(token)
|
38
|
+
@name = name
|
39
|
+
@items = items
|
40
|
+
@named = named
|
41
|
+
@blank = false
|
42
|
+
end
|
43
|
+
|
44
|
+
def render(context, buffer)
|
45
|
+
args = @items.map { |expr| context.evaluate(expr) }
|
46
|
+
|
47
|
+
key = if @named
|
48
|
+
context.evaluate(@name).to_s
|
49
|
+
else
|
50
|
+
@items.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
index = context.tag_namespace[:cycles][key]
|
54
|
+
buffer << Liquid2.to_output_s(args[index])
|
55
|
+
|
56
|
+
index += 1
|
57
|
+
index = 0 if index >= @items.length
|
58
|
+
context.tag_namespace[:cycles][key] = index
|
59
|
+
end
|
60
|
+
|
61
|
+
def expressions = @items
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../tag"
|
4
|
+
|
5
|
+
module Liquid2
|
6
|
+
# The standard _decrement_ tag.
|
7
|
+
class DecrementTag < Tag
|
8
|
+
# @param parser [Parser]
|
9
|
+
# @return [DecrementTag]
|
10
|
+
def self.parse(token, parser)
|
11
|
+
name = parser.parse_identifier(trailing_question: false)
|
12
|
+
parser.carry_whitespace_control
|
13
|
+
parser.eat(:token_tag_end)
|
14
|
+
new(token, name)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param name [Identifier]
|
18
|
+
def initialize(token, name)
|
19
|
+
super(token)
|
20
|
+
@name = name
|
21
|
+
end
|
22
|
+
|
23
|
+
def render(context, buffer)
|
24
|
+
buffer << context.decrement(@name.name).to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def template_scope = [@name]
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../tag"
|
4
|
+
|
5
|
+
module Liquid2
|
6
|
+
# The standard _doc_ tag.
|
7
|
+
class DocTag < Tag
|
8
|
+
def self.parse(token, parser)
|
9
|
+
parser.carry_whitespace_control
|
10
|
+
parser.eat(:token_tag_end)
|
11
|
+
text_token = parser.eat(:token_doc)
|
12
|
+
parser.eat_empty_tag("enddoc")
|
13
|
+
new(token, text_token[1] || raise)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param text [String]
|
17
|
+
def initialize(token, text)
|
18
|
+
super(token)
|
19
|
+
@text = text
|
20
|
+
end
|
21
|
+
|
22
|
+
def render(_context, _buffer) = 0
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../tag"
|
4
|
+
|
5
|
+
module Liquid2
|
6
|
+
# The standard _echo_ tag.
|
7
|
+
class EchoTag < Tag
|
8
|
+
# @param parser [Parser]
|
9
|
+
# @return [EchoTag]
|
10
|
+
def self.parse(token, parser)
|
11
|
+
expression = unless %i[token_whitespace_control token_tag_end].include?(parser.current_kind)
|
12
|
+
parser.parse_filtered_expression
|
13
|
+
end
|
14
|
+
parser.carry_whitespace_control
|
15
|
+
parser.eat(:token_tag_end)
|
16
|
+
new(token, expression)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(token, expression)
|
20
|
+
super(token)
|
21
|
+
@expression = expression
|
22
|
+
@blank = false
|
23
|
+
end
|
24
|
+
|
25
|
+
def render(context, buffer)
|
26
|
+
buffer << Liquid2.to_output_s(context.evaluate(@expression))
|
27
|
+
end
|
28
|
+
|
29
|
+
def expressions = [@expression]
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../tag"
|
4
|
+
|
5
|
+
module Liquid2
|
6
|
+
# The standard _for_ tag.
|
7
|
+
class ForTag < Tag
|
8
|
+
END_BLOCK = Set["endfor", "else"]
|
9
|
+
|
10
|
+
def self.parse(token, parser)
|
11
|
+
parser.expect_expression
|
12
|
+
expression = parser.parse_loop_expression
|
13
|
+
parser.carry_whitespace_control
|
14
|
+
parser.eat(:token_tag_end)
|
15
|
+
block = parser.parse_block(END_BLOCK)
|
16
|
+
|
17
|
+
if parser.tag?("else")
|
18
|
+
parser.eat_empty_tag("else")
|
19
|
+
default = parser.parse_block(END_BLOCK)
|
20
|
+
else
|
21
|
+
default = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
parser.eat_empty_tag("endfor")
|
25
|
+
new(token, expression, block, default)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param token [[Symbol, String?, Integer]]
|
29
|
+
# @param expression [LoopExpression]
|
30
|
+
# @param block [Block]
|
31
|
+
# @param default [Block?]
|
32
|
+
def initialize(token, expression, block, default)
|
33
|
+
super(token)
|
34
|
+
@expression = expression
|
35
|
+
@block = block
|
36
|
+
@default = default
|
37
|
+
@blank = block.blank && (!default || default.blank)
|
38
|
+
end
|
39
|
+
|
40
|
+
def render(context, buffer)
|
41
|
+
array = @expression.evaluate(context)
|
42
|
+
|
43
|
+
if array.empty?
|
44
|
+
return @default ? (@default || raise).render(context, buffer) : 0
|
45
|
+
end
|
46
|
+
|
47
|
+
name = @expression.identifier.name
|
48
|
+
forloop = ForLoop.new(@expression.name, array.length, context.parent_loop(self))
|
49
|
+
namespace = { "forloop" => forloop }
|
50
|
+
|
51
|
+
context.loop(namespace, forloop) do
|
52
|
+
index = 0
|
53
|
+
while index < array.length
|
54
|
+
namespace[name] = array[index]
|
55
|
+
index += 1
|
56
|
+
forloop.next
|
57
|
+
@block.render(context, buffer)
|
58
|
+
case context.interrupts.pop
|
59
|
+
when :continue
|
60
|
+
next
|
61
|
+
when :break
|
62
|
+
break
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def children(_static_context, include_partials: true)
|
69
|
+
# @type var nodes: Array[Node]
|
70
|
+
nodes = [@block]
|
71
|
+
nodes << (@default || raise) if @default
|
72
|
+
nodes
|
73
|
+
end
|
74
|
+
|
75
|
+
def expressions = [@expression]
|
76
|
+
|
77
|
+
def block_scope
|
78
|
+
[@expression.identifier, Identifier.new([:token_word, "forloop", @expression.token.last])]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# The standard _break_ tag.
|
83
|
+
class BreakTag < Tag
|
84
|
+
def self.parse(token, parser)
|
85
|
+
parser.carry_whitespace_control
|
86
|
+
parser.eat(:token_tag_end)
|
87
|
+
new(token)
|
88
|
+
end
|
89
|
+
|
90
|
+
def render(context, _buffer)
|
91
|
+
context.interrupts << :break
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# The standard _continue_ tag.
|
96
|
+
class ContinueTag < Tag
|
97
|
+
def self.parse(token, parser)
|
98
|
+
parser.carry_whitespace_control
|
99
|
+
parser.eat(:token_tag_end)
|
100
|
+
new(token)
|
101
|
+
end
|
102
|
+
|
103
|
+
def render(context, _buffer)
|
104
|
+
context.interrupts << :continue
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# `for` loop helper variables.
|
109
|
+
class ForLoop
|
110
|
+
attr_reader :name, :length, :parentloop
|
111
|
+
|
112
|
+
KEYS = Set[
|
113
|
+
"name",
|
114
|
+
"length",
|
115
|
+
"index",
|
116
|
+
"index0",
|
117
|
+
"rindex",
|
118
|
+
"rindex0",
|
119
|
+
"first",
|
120
|
+
"last",
|
121
|
+
"parentloop"
|
122
|
+
]
|
123
|
+
|
124
|
+
def initialize(name, length, parent_loop)
|
125
|
+
@name = name
|
126
|
+
@length = length
|
127
|
+
@parentloop = parent_loop
|
128
|
+
@index = -1
|
129
|
+
end
|
130
|
+
|
131
|
+
def key?(key)
|
132
|
+
KEYS.member?(key)
|
133
|
+
end
|
134
|
+
|
135
|
+
def [](key)
|
136
|
+
send(key) if KEYS.member?(key)
|
137
|
+
end
|
138
|
+
|
139
|
+
def fetch(key, default = :undefined)
|
140
|
+
if KEYS.member?(key)
|
141
|
+
send(key)
|
142
|
+
else
|
143
|
+
default
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def next = @index += 1
|
148
|
+
def index = @index + 1
|
149
|
+
def index0 = @index
|
150
|
+
def rindex = @length - @index
|
151
|
+
def rindex0 = @length - @index - 1
|
152
|
+
def first = @index.zero?
|
153
|
+
def last = @index == @length - 1
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require_relative "../../tag"
|
5
|
+
|
6
|
+
module Liquid2
|
7
|
+
# The standard _if_ tag
|
8
|
+
class IfTag < Tag
|
9
|
+
END_TAG = "endif"
|
10
|
+
END_BLOCK = Set["else", "elsif", "endif"].freeze
|
11
|
+
|
12
|
+
# @param parser [Parser]
|
13
|
+
# @return [IfTag]
|
14
|
+
def self.parse(token, parser)
|
15
|
+
parser.expect_expression
|
16
|
+
expression = BooleanExpression.new(parser.current, parser.parse_primary)
|
17
|
+
parser.carry_whitespace_control
|
18
|
+
parser.eat(:token_tag_end)
|
19
|
+
|
20
|
+
block = parser.parse_block(self::END_BLOCK)
|
21
|
+
alternatives = [] # : Array[ConditionalBlock]
|
22
|
+
alternatives << parse_elsif(parser) while parser.tag?("elsif")
|
23
|
+
|
24
|
+
if parser.tag?("else")
|
25
|
+
parser.eat_empty_tag("else")
|
26
|
+
default = parser.parse_block(self::END_BLOCK)
|
27
|
+
else
|
28
|
+
default = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
parser.eat_empty_tag(self::END_TAG)
|
32
|
+
new(token, expression, block, alternatives, default)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [ConditionalBlock]
|
36
|
+
def self.parse_elsif(parser)
|
37
|
+
parser.eat(:token_tag_start)
|
38
|
+
parser.skip_whitespace_control
|
39
|
+
token = parser.eat(:token_tag_name)
|
40
|
+
parser.expect_expression
|
41
|
+
expression = BooleanExpression.new(parser.current, parser.parse_primary)
|
42
|
+
parser.carry_whitespace_control
|
43
|
+
parser.eat(:token_tag_end)
|
44
|
+
|
45
|
+
block = parser.parse_block(self::END_BLOCK)
|
46
|
+
ConditionalBlock.new(token, expression, block)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @param token [[Symbol, String?, Integer]]
|
50
|
+
# @param expression [Expression]
|
51
|
+
# @param block [Block]
|
52
|
+
# @param alternatives [Array<[ConditionalBlock]>]
|
53
|
+
# @param default [Block?]
|
54
|
+
def initialize(token, expression, block, alternatives, default)
|
55
|
+
super(token)
|
56
|
+
@expression = expression
|
57
|
+
@block = block
|
58
|
+
@alternatives = alternatives
|
59
|
+
@default = default
|
60
|
+
@blank = block.blank && alternatives.all?(&:blank) && (!default || default.blank)
|
61
|
+
end
|
62
|
+
|
63
|
+
def render(context, buffer)
|
64
|
+
return @block.render(context, buffer) if context.evaluate(@expression)
|
65
|
+
|
66
|
+
index = 0
|
67
|
+
while (alt = @alternatives[index])
|
68
|
+
index += 1
|
69
|
+
return alt.block.render(context, buffer) if context.evaluate(alt.expression)
|
70
|
+
end
|
71
|
+
|
72
|
+
(@default || raise).render(context, buffer) if @default
|
73
|
+
end
|
74
|
+
|
75
|
+
def children(_static_context, include_partials: true)
|
76
|
+
# @type var nodes: Array[Node]
|
77
|
+
nodes = [@block, *@alternatives]
|
78
|
+
nodes << (@default || raise) if @default
|
79
|
+
nodes
|
80
|
+
end
|
81
|
+
|
82
|
+
def expressions = [@expression]
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../tag"
|
4
|
+
|
5
|
+
module Liquid2
|
6
|
+
# The standard _include_ tag.
|
7
|
+
class IncludeTag < Tag
|
8
|
+
# @param parser [Parser]
|
9
|
+
# @return [IncludeTag]
|
10
|
+
def self.parse(token, parser)
|
11
|
+
name = parser.parse_primary
|
12
|
+
|
13
|
+
repeat = false
|
14
|
+
var = nil # : Expression?
|
15
|
+
as = nil # : Identifier?
|
16
|
+
|
17
|
+
if parser.current_kind == :token_for && !%i[token_comma
|
18
|
+
token_colon].include?(parser.peek_kind)
|
19
|
+
parser.next
|
20
|
+
repeat = true
|
21
|
+
var = parser.parse_primary
|
22
|
+
if parser.current_kind == :token_as
|
23
|
+
parser.next
|
24
|
+
as = parser.parse_identifier
|
25
|
+
end
|
26
|
+
elsif parser.current_kind == :token_with && !%i[token_comma
|
27
|
+
token_colon].include?(parser.peek_kind)
|
28
|
+
parser.next
|
29
|
+
var = parser.parse_primary
|
30
|
+
if parser.current_kind == :token_as
|
31
|
+
parser.next
|
32
|
+
as = parser.parse_identifier
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
parser.next if parser.current_kind == :token_comma
|
37
|
+
args = parser.parse_keyword_arguments
|
38
|
+
parser.carry_whitespace_control
|
39
|
+
parser.eat(:token_tag_end)
|
40
|
+
new(token, name, repeat, var, as, args)
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param name [Expression]
|
44
|
+
# @param repeat [bool]
|
45
|
+
# @param var [Expression?]
|
46
|
+
# @param as [Identifier?]
|
47
|
+
# @param args [Array<KeywordArgument> | nil]
|
48
|
+
def initialize(token, name, repeat, var, as, args)
|
49
|
+
super(token)
|
50
|
+
@name = name
|
51
|
+
@repeat = repeat
|
52
|
+
@var = var
|
53
|
+
@as = as
|
54
|
+
@args = args
|
55
|
+
@blank = false
|
56
|
+
end
|
57
|
+
|
58
|
+
def render(context, buffer)
|
59
|
+
name = context.evaluate(@name)
|
60
|
+
template = context.env.get_template(name.to_s, context: context, tag: :include)
|
61
|
+
namespace = @args.to_h { |arg| [arg.name, context.evaluate(arg.value)] }
|
62
|
+
|
63
|
+
context.extend(namespace, template: template) do
|
64
|
+
if @var
|
65
|
+
val = context.evaluate(@var || raise)
|
66
|
+
key = @as&.name || template.name.split(".").first
|
67
|
+
|
68
|
+
if val.is_a?(Array)
|
69
|
+
context.raise_for_loop_limit(length: val.size)
|
70
|
+
index = 0
|
71
|
+
while index < val.length
|
72
|
+
namespace[key] = val[index]
|
73
|
+
template.render_with_context(context, buffer, partial: true, block_scope: false)
|
74
|
+
index += 1
|
75
|
+
end
|
76
|
+
else
|
77
|
+
namespace[key] = val
|
78
|
+
template.render_with_context(context, buffer, partial: true, block_scope: false)
|
79
|
+
end
|
80
|
+
else
|
81
|
+
template.render_with_context(context, buffer, partial: true, block_scope: false)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
rescue LiquidTemplateNotFoundError => e
|
85
|
+
e.token = @token
|
86
|
+
e.template_name = context.template.full_name unless context.template.full_name.empty?
|
87
|
+
raise e
|
88
|
+
end
|
89
|
+
|
90
|
+
def children(static_context, include_partials: true)
|
91
|
+
return [] unless include_partials
|
92
|
+
|
93
|
+
name = static_context.evaluate(@name)
|
94
|
+
template = static_context.env.get_template(name.to_s, context: static_context, tag: :include)
|
95
|
+
template.ast
|
96
|
+
rescue LiquidTemplateNotFoundError => e
|
97
|
+
e.token = @token
|
98
|
+
e.template_name = static_context.template.full_name
|
99
|
+
raise e
|
100
|
+
end
|
101
|
+
|
102
|
+
def expressions
|
103
|
+
exprs = [@name]
|
104
|
+
exprs << @var if @var
|
105
|
+
exprs.concat(@args.map(&:value))
|
106
|
+
exprs
|
107
|
+
end
|
108
|
+
|
109
|
+
def partial_scope
|
110
|
+
scope = @args.map { |arg| Identifier.new([:token_word, arg.name, arg.token.last]) }
|
111
|
+
|
112
|
+
if @var
|
113
|
+
if @as
|
114
|
+
scope << @as # steep:ignore
|
115
|
+
elsif @name.is_a?(String)
|
116
|
+
scope << Identifier.new([:token_word, @name.split(".").first, @token.last])
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
Partial.new(@name, :shared, scope)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../tag"
|
4
|
+
|
5
|
+
module Liquid2
|
6
|
+
# The standard _increment_ tag.
|
7
|
+
class IncrementTag < Tag
|
8
|
+
# @param parser [Parser]
|
9
|
+
# @return [DecrementTag]
|
10
|
+
def self.parse(token, parser)
|
11
|
+
name = parser.parse_identifier(trailing_question: false)
|
12
|
+
parser.carry_whitespace_control
|
13
|
+
parser.eat(:token_tag_end)
|
14
|
+
new(token, name)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param name [Identifier]
|
18
|
+
def initialize(token, name)
|
19
|
+
super(token)
|
20
|
+
@name = name
|
21
|
+
end
|
22
|
+
|
23
|
+
def render(context, buffer)
|
24
|
+
buffer << context.increment(@name.name).to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def template_scope = [@name]
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../tag"
|
4
|
+
|
5
|
+
module Liquid2
|
6
|
+
# `{% # comment %}` style comments.
|
7
|
+
class InlineComment < Tag
|
8
|
+
attr_reader :text
|
9
|
+
|
10
|
+
def self.parse(token, parser)
|
11
|
+
comment = parser.eat(:token_comment)
|
12
|
+
parser.carry_whitespace_control
|
13
|
+
parser.eat(:token_tag_end)
|
14
|
+
new(token, comment[1] || "")
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param text [String]
|
18
|
+
def initialize(token, text)
|
19
|
+
super(token)
|
20
|
+
@text = text
|
21
|
+
return unless /\n\s*[^#\s]/.match?(text)
|
22
|
+
|
23
|
+
raise LiquidSyntaxError.new("every line must start with a '#'", token)
|
24
|
+
end
|
25
|
+
|
26
|
+
def render(_context, _buffer) = 0
|
27
|
+
end
|
28
|
+
end
|