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
@@ -1,3 +1,272 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative "../../tag"
|
4
|
+
|
5
|
+
module Liquid2
|
6
|
+
# The _extends_ tag.
|
7
|
+
class ExtendsTag < Tag
|
8
|
+
attr_reader :template_name
|
9
|
+
|
10
|
+
# @param token [[Symbol, String?, Integer]]
|
11
|
+
# @param parser [Parser]
|
12
|
+
# @return [ExtendsTag]
|
13
|
+
def self.parse(token, parser)
|
14
|
+
name = parser.parse_name
|
15
|
+
parser.carry_whitespace_control
|
16
|
+
parser.eat(:token_tag_end)
|
17
|
+
new(token, name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(token, name)
|
21
|
+
super(token)
|
22
|
+
@template_name = name
|
23
|
+
@blank = false
|
24
|
+
end
|
25
|
+
|
26
|
+
def render(context, buffer)
|
27
|
+
base_template = stack_blocks(context, context.template)
|
28
|
+
context.extend({}, template: base_template) do
|
29
|
+
base_template&.render_with_context(context, buffer)
|
30
|
+
end
|
31
|
+
context.tag_namespace[:extends].clear
|
32
|
+
context.interrupts << :stop_render
|
33
|
+
end
|
34
|
+
|
35
|
+
def children(static_context, include_partials: true)
|
36
|
+
return [] unless include_partials
|
37
|
+
|
38
|
+
begin
|
39
|
+
parent = static_context.env.get_template(
|
40
|
+
@template_name,
|
41
|
+
context: static_context,
|
42
|
+
tag: "extends"
|
43
|
+
)
|
44
|
+
parent.ast
|
45
|
+
rescue LiquidTemplateNotFoundError => e
|
46
|
+
e.token = @token
|
47
|
+
e.template_name = static_context.template.full_name
|
48
|
+
raise e
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def partial_scope
|
53
|
+
Partial.new(@template_name, :inherited, [])
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
# Visit all templates in the inheritance chain and build a stack for each `block` tag.
|
59
|
+
def stack_blocks(context, template)
|
60
|
+
# @type var stacks: Hash[String, Array[untyped]]
|
61
|
+
stacks = context.tag_namespace[:extends]
|
62
|
+
|
63
|
+
# Guard against recursive `extends`.
|
64
|
+
seen_extends = Set[] # : Set[String]
|
65
|
+
|
66
|
+
# @type var stack_blocks_: ^(Template) -> Template?
|
67
|
+
stack_blocks_ = lambda do |template_|
|
68
|
+
extends_nodes, block_nodes = inheritance_nodes(context, template_)
|
69
|
+
template_name = template_.path || template_.name
|
70
|
+
|
71
|
+
if extends_nodes.length > 1
|
72
|
+
raise TemplateInheritanceError.new("too many 'extends' tags", extends_nodes[1].token,
|
73
|
+
template_name: template_name)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Identify duplicate blocks.
|
77
|
+
seen_blocks = Set[] # : Set[String]
|
78
|
+
|
79
|
+
block_nodes.each do |block|
|
80
|
+
if seen_blocks.include?(block.block_name)
|
81
|
+
raise TemplateInheritanceError.new("duplicate block #{block.block_name.inspect}",
|
82
|
+
block.token, template_name: template_name)
|
83
|
+
end
|
84
|
+
|
85
|
+
seen_blocks.add(block.block_name)
|
86
|
+
|
87
|
+
stack = stacks[block.block_name]
|
88
|
+
required = !stack.empty? && !block.required ? false : block.required
|
89
|
+
# [block, required, template, parent]
|
90
|
+
# [BlockTag, bool, Template, untyped?]
|
91
|
+
stack << [block, required, template_, nil]
|
92
|
+
# Populate parent block.
|
93
|
+
stack[-2][-1] = stack.last if stack.length > 1
|
94
|
+
end
|
95
|
+
|
96
|
+
return nil if extends_nodes.empty? # steep:ignore
|
97
|
+
|
98
|
+
extends_node = extends_nodes.first
|
99
|
+
|
100
|
+
if seen_extends.include?(extends_node.template_name)
|
101
|
+
raise TemplateInheritanceError.new(
|
102
|
+
"circular extends #{extends_node.template_name.inspect}",
|
103
|
+
extends_node.token,
|
104
|
+
template_name: template_name
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
seen_extends.add(extends_node.template_name)
|
109
|
+
|
110
|
+
begin
|
111
|
+
context.env.get_template(extends_node.template_name, context: context, tag: "extends")
|
112
|
+
rescue LiquidTemplateNotFoundError => e
|
113
|
+
e.token = extends_node.token
|
114
|
+
e.template_name = template_.full_name
|
115
|
+
raise e
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# @type var next_template: Template?
|
120
|
+
base = next_template = stack_blocks_.call(template)
|
121
|
+
|
122
|
+
while next_template
|
123
|
+
next_template = stack_blocks_.call(next_template)
|
124
|
+
base = next_template if next_template
|
125
|
+
end
|
126
|
+
|
127
|
+
base
|
128
|
+
end
|
129
|
+
|
130
|
+
# Traverse the template's syntax tree looking for `{% extends %}` and `{% block %}`.
|
131
|
+
# @return [[Array[ExtendsTag], Array[BlockTag]]]
|
132
|
+
def inheritance_nodes(context, template)
|
133
|
+
extends_nodes = [] # : Array[ExtendsTag]
|
134
|
+
block_nodes = [] # : Array[BlockTag]
|
135
|
+
|
136
|
+
# @type var visit: ^(Node) -> void
|
137
|
+
visit = lambda do |node|
|
138
|
+
extends_nodes << node if node.is_a?(ExtendsTag)
|
139
|
+
block_nodes << node if node.is_a?(BlockTag)
|
140
|
+
|
141
|
+
node.children(context, include_partials: false).each do |child|
|
142
|
+
visit.call(child) if child.is_a?(Node)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
template.ast.each { |node| visit.call(node) if node.is_a?(Node) }
|
147
|
+
|
148
|
+
[extends_nodes, block_nodes]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# The _block_ tag.
|
153
|
+
class BlockTag < Tag
|
154
|
+
attr_reader :block_name, :required, :block
|
155
|
+
|
156
|
+
END_BLOCK = Set["endblock"]
|
157
|
+
|
158
|
+
# @param token [[Symbol, String?, Integer]]
|
159
|
+
# @param parser [Parser]
|
160
|
+
# @return [BlockTag]
|
161
|
+
def self.parse(token, parser)
|
162
|
+
block_name = parser.parse_name
|
163
|
+
required = if parser.current_kind == :token_required
|
164
|
+
parser.next
|
165
|
+
true
|
166
|
+
else
|
167
|
+
false
|
168
|
+
end
|
169
|
+
|
170
|
+
parser.carry_whitespace_control
|
171
|
+
parser.eat(:token_tag_end)
|
172
|
+
block = parser.parse_block(END_BLOCK)
|
173
|
+
parser.eat_empty_tag("endblock")
|
174
|
+
new(token, block_name, block, required: required)
|
175
|
+
end
|
176
|
+
|
177
|
+
def initialize(token, name, block, required:)
|
178
|
+
super(token)
|
179
|
+
@block_name = name
|
180
|
+
@block = block
|
181
|
+
@required = required
|
182
|
+
@blank = false
|
183
|
+
end
|
184
|
+
|
185
|
+
def render(context, buffer)
|
186
|
+
# @type var stack: Array[[BlockTag, bool, Template, untyped?]]
|
187
|
+
stack = context.tag_namespace[:extends][@block_name]
|
188
|
+
|
189
|
+
if stack.empty?
|
190
|
+
# This base block is being rendered directly.
|
191
|
+
if @required
|
192
|
+
raise RequiredBlockError.new("block #{@block_name.inspect} is required",
|
193
|
+
@token)
|
194
|
+
end
|
195
|
+
|
196
|
+
context.extend({ "block" => BlockDrop.new(token, context, @block_name, nil) }) do
|
197
|
+
@block.render(context, buffer)
|
198
|
+
end
|
199
|
+
|
200
|
+
return
|
201
|
+
end
|
202
|
+
|
203
|
+
block_tag, required, template, parent = stack.first
|
204
|
+
|
205
|
+
if required
|
206
|
+
raise RequiredBlockError.new("block #{@block_name.inspect} is required", @token,
|
207
|
+
template_name: template.path || template.name)
|
208
|
+
end
|
209
|
+
|
210
|
+
namespace = { "block" => BlockDrop.new(token, context, @block_name, parent) }
|
211
|
+
|
212
|
+
block_context = context.copy(namespace,
|
213
|
+
carry_loop_iterations: true,
|
214
|
+
block_scope: true,
|
215
|
+
template: template)
|
216
|
+
|
217
|
+
begin
|
218
|
+
block_tag.block.render(block_context, buffer)
|
219
|
+
rescue LiquidError => e
|
220
|
+
e.template_name = template.path || template.name
|
221
|
+
e.source = template.source
|
222
|
+
raise
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def children(_static_context, include_partials: true)
|
227
|
+
[@block]
|
228
|
+
end
|
229
|
+
|
230
|
+
def block_scope
|
231
|
+
[Identifier.new([:token_word, "block", @token.last])]
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# A `block` object available within `{% block %}` tags.
|
236
|
+
class BlockDrop
|
237
|
+
attr_reader :token
|
238
|
+
|
239
|
+
# @param token [[Symbol, String?, Integer]]
|
240
|
+
# @param context [RenderContext]
|
241
|
+
# @param name [String]
|
242
|
+
# @param parent [[BlockTag, bool, Template, Block?]?]
|
243
|
+
def initialize(token, context, name, parent)
|
244
|
+
@token = token
|
245
|
+
@context = context
|
246
|
+
@name = name
|
247
|
+
@parent = parent
|
248
|
+
end
|
249
|
+
|
250
|
+
def to_s = "BlockDrop(#{@name})"
|
251
|
+
|
252
|
+
def key?(key)
|
253
|
+
key == "super" && @parent
|
254
|
+
end
|
255
|
+
|
256
|
+
def [](key)
|
257
|
+
return nil if key != "super" || @parent.nil?
|
258
|
+
|
259
|
+
parent = @parent || raise
|
260
|
+
buf = +""
|
261
|
+
namespace = { "block" => BlockDrop.new(parent.first.token,
|
262
|
+
@context,
|
263
|
+
parent[2].path || parent[2].name,
|
264
|
+
parent.last) }
|
265
|
+
@context.extend(namespace) do
|
266
|
+
parent.first.block.render(@context, buf)
|
267
|
+
end
|
268
|
+
|
269
|
+
buf.freeze
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
@@ -47,7 +47,7 @@ module Liquid2
|
|
47
47
|
# @param args [Array<KeywordArgument> | nil]
|
48
48
|
def initialize(token, name, repeat, var, as, args)
|
49
49
|
super(token)
|
50
|
-
@
|
50
|
+
@template_name = name
|
51
51
|
@repeat = repeat
|
52
52
|
@var = var
|
53
53
|
@as = as
|
@@ -56,7 +56,7 @@ module Liquid2
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def render(context, buffer)
|
59
|
-
name = context.evaluate(@
|
59
|
+
name = context.evaluate(@template_name)
|
60
60
|
template = context.env.get_template(name.to_s, context: context, tag: :include)
|
61
61
|
namespace = @args.to_h { |arg| [arg.name, context.evaluate(arg.value)] }
|
62
62
|
|
@@ -90,7 +90,7 @@ module Liquid2
|
|
90
90
|
def children(static_context, include_partials: true)
|
91
91
|
return [] unless include_partials
|
92
92
|
|
93
|
-
name = static_context.evaluate(@
|
93
|
+
name = static_context.evaluate(@template_name)
|
94
94
|
template = static_context.env.get_template(name.to_s, context: static_context, tag: :include)
|
95
95
|
template.ast
|
96
96
|
rescue LiquidTemplateNotFoundError => e
|
@@ -100,7 +100,7 @@ module Liquid2
|
|
100
100
|
end
|
101
101
|
|
102
102
|
def expressions
|
103
|
-
exprs = [@
|
103
|
+
exprs = [@template_name]
|
104
104
|
exprs << @var if @var
|
105
105
|
exprs.concat(@args.map(&:value))
|
106
106
|
exprs
|
@@ -112,12 +112,12 @@ module Liquid2
|
|
112
112
|
if @var
|
113
113
|
if @as
|
114
114
|
scope << @as # steep:ignore
|
115
|
-
elsif @
|
116
|
-
scope << Identifier.new([:token_word, @
|
115
|
+
elsif @template_name.is_a?(String)
|
116
|
+
scope << Identifier.new([:token_word, @template_name.split(".").first, @token.last])
|
117
117
|
end
|
118
118
|
end
|
119
119
|
|
120
|
-
Partial.new(@
|
120
|
+
Partial.new(@template_name, :shared, scope)
|
121
121
|
end
|
122
122
|
end
|
123
123
|
end
|
@@ -1,3 +1,147 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative "../../tag"
|
4
|
+
|
5
|
+
module Liquid2
|
6
|
+
# The _macro_ tag.
|
7
|
+
class MacroTag < Tag
|
8
|
+
attr_reader :macro_name, :params, :block
|
9
|
+
|
10
|
+
END_BLOCK = Set["endmacro"]
|
11
|
+
|
12
|
+
# @param token [[Symbol, String?, Integer]]
|
13
|
+
# @param parser [Parser]
|
14
|
+
# @return [MacroTag]
|
15
|
+
def self.parse(token, parser)
|
16
|
+
name = parser.parse_name
|
17
|
+
parser.next if parser.current_kind == :token_comma
|
18
|
+
params = parser.parse_parameters
|
19
|
+
parser.next if parser.current_kind == :token_comma
|
20
|
+
parser.carry_whitespace_control
|
21
|
+
parser.eat(:token_tag_end)
|
22
|
+
block = parser.parse_block(END_BLOCK)
|
23
|
+
parser.eat_empty_tag("endmacro")
|
24
|
+
new(token, name, params, block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(token, name, params, block)
|
28
|
+
super(token)
|
29
|
+
@macro_name = name
|
30
|
+
@params = params
|
31
|
+
@block = block
|
32
|
+
@blank = true
|
33
|
+
end
|
34
|
+
|
35
|
+
def render(context, _buffer)
|
36
|
+
# Macro tags don't render or evaluate anything, just store their parameter list
|
37
|
+
# and block on the render context so it can be called later by a `call` tag.
|
38
|
+
context.tag_namespace[:macros][@macro_name] = [@params, @block]
|
39
|
+
end
|
40
|
+
|
41
|
+
def children(_static_context, include_partials: true) = [@block]
|
42
|
+
def expressions = @params.values.filter_map(&:value)
|
43
|
+
|
44
|
+
def block_scope
|
45
|
+
[
|
46
|
+
Identifier.new([:token_word, "args", @token.last]),
|
47
|
+
Identifier.new([:token_word, "kwargs", @token.last]),
|
48
|
+
*@params.values.map { |param| Identifier.new([:token_word, param.name, param.token.last]) }
|
49
|
+
]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# The _call_ tag.
|
54
|
+
class CallTag < Tag
|
55
|
+
attr_reader :macro_name, :args, :kwargs
|
56
|
+
|
57
|
+
DISABLED_TAGS = Set["include", "block"]
|
58
|
+
|
59
|
+
# @param token [[Symbol, String?, Integer]]
|
60
|
+
# @param parser [Parser]
|
61
|
+
# @return [CallTag]
|
62
|
+
def self.parse(token, parser)
|
63
|
+
name = parser.parse_name
|
64
|
+
parser.next if parser.current_kind == :token_comma
|
65
|
+
args, kwargs = parser.parse_arguments
|
66
|
+
parser.carry_whitespace_control
|
67
|
+
parser.eat(:token_tag_end)
|
68
|
+
new(token, name, args, kwargs)
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize(token, name, args, kwargs)
|
72
|
+
super(token)
|
73
|
+
@macro_name = name
|
74
|
+
@args = args
|
75
|
+
@kwargs = kwargs
|
76
|
+
@blank = false
|
77
|
+
end
|
78
|
+
|
79
|
+
def render(context, buffer)
|
80
|
+
# @type var params: Hash[String, Parameter]?
|
81
|
+
# @type var block: Block
|
82
|
+
params, block = context.tag_namespace[:macros][@macro_name]
|
83
|
+
|
84
|
+
unless params
|
85
|
+
buffer << Liquid2.to_output_string(context.env.undefined(@macro_name, node: self))
|
86
|
+
return
|
87
|
+
end
|
88
|
+
|
89
|
+
# Parameter names mapped to default values. :undefined is used if there is no default.
|
90
|
+
args = params.values.to_h { |p| [p.name, p.value] }
|
91
|
+
excess_args = [] # : Array[untyped]
|
92
|
+
excess_kwargs = {} # : Hash[String, untyped]
|
93
|
+
|
94
|
+
# Update args with positional arguments.
|
95
|
+
# Keyword arguments are pushed to the end if they appear before positional arguments.
|
96
|
+
names = args.keys
|
97
|
+
length = @args.length
|
98
|
+
index = 0
|
99
|
+
while index < length
|
100
|
+
name = names[index]
|
101
|
+
expr = @args[index]
|
102
|
+
if name.nil?
|
103
|
+
excess_args << expr
|
104
|
+
else
|
105
|
+
args[name] = expr
|
106
|
+
end
|
107
|
+
index += 1
|
108
|
+
end
|
109
|
+
|
110
|
+
# Update args with keyword arguments.
|
111
|
+
@kwargs.each do |arg|
|
112
|
+
if params.include?(arg.name)
|
113
|
+
# This has the potential to override a positional argument.
|
114
|
+
args[arg.name] = arg.value
|
115
|
+
else
|
116
|
+
excess_kwargs[arg.name] = arg.value
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# @type var namespace: Hash[String, untyped]
|
121
|
+
namespace = {
|
122
|
+
"args" => excess_args.map { |arg| context.evaluate(arg) },
|
123
|
+
"kwargs" => excess_kwargs.transform_values! { |val| context.evaluate(val) }
|
124
|
+
}
|
125
|
+
|
126
|
+
args.each do |k, v|
|
127
|
+
namespace[k] = if v == :undefined
|
128
|
+
context.env.undefined(k, node: params[k])
|
129
|
+
else
|
130
|
+
context.evaluate(v)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
macro_context = context.copy(
|
135
|
+
namespace,
|
136
|
+
disabled_tags: DISABLED_TAGS,
|
137
|
+
carry_loop_iterations: true
|
138
|
+
)
|
139
|
+
|
140
|
+
block.render(macro_context, buffer)
|
141
|
+
end
|
142
|
+
|
143
|
+
def expressions
|
144
|
+
[*@args, *@kwargs.map(&:value)]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -10,7 +10,8 @@ module Liquid2
|
|
10
10
|
def self.parse(token, parser)
|
11
11
|
parser.carry_whitespace_control
|
12
12
|
parser.eat(:token_tag_end)
|
13
|
-
# TODO: apply whitespace control to raw text
|
13
|
+
# TODO: apply whitespace control to raw text?
|
14
|
+
# Shopify/liquid does not apply whitespace control to raw content.
|
14
15
|
raw = parser.eat(:token_raw)
|
15
16
|
parser.eat_empty_tag("endraw")
|
16
17
|
new(token, raw[1] || raise)
|
@@ -12,8 +12,6 @@ module Liquid2
|
|
12
12
|
# @return [RenderTag]
|
13
13
|
def self.parse(token, parser)
|
14
14
|
name = parser.parse_string
|
15
|
-
raise LiquidTypeError, "expected a string literal" unless name.is_a?(String)
|
16
|
-
|
17
15
|
repeat = false
|
18
16
|
var = nil # : Expression?
|
19
17
|
as = nil # : Identifier?
|
@@ -51,7 +49,7 @@ module Liquid2
|
|
51
49
|
# @param args [Array<KeywordArgument> | nil]
|
52
50
|
def initialize(token, name, repeat, var, as, args)
|
53
51
|
super(token)
|
54
|
-
@
|
52
|
+
@template_name = name
|
55
53
|
@repeat = repeat
|
56
54
|
@var = var
|
57
55
|
@as = as&.name
|
@@ -60,7 +58,7 @@ module Liquid2
|
|
60
58
|
end
|
61
59
|
|
62
60
|
def render(context, buffer)
|
63
|
-
template = context.env.get_template(@
|
61
|
+
template = context.env.get_template(@template_name, context: context, tag: :render)
|
64
62
|
namespace = @args.to_h { |arg| [arg.name, context.evaluate(arg.value)] }
|
65
63
|
|
66
64
|
ctx = context.copy(namespace,
|
@@ -96,7 +94,7 @@ module Liquid2
|
|
96
94
|
template.render_with_context(ctx, buffer, partial: true, block_scope: true)
|
97
95
|
end
|
98
96
|
rescue LiquidTemplateNotFoundError => e
|
99
|
-
e.token = @
|
97
|
+
e.token = @template_name
|
100
98
|
e.template_name = context.template.full_name
|
101
99
|
raise e
|
102
100
|
end
|
@@ -104,7 +102,7 @@ module Liquid2
|
|
104
102
|
def children(static_context, include_partials: true)
|
105
103
|
return [] unless include_partials
|
106
104
|
|
107
|
-
name = static_context.evaluate(@
|
105
|
+
name = static_context.evaluate(@template_name)
|
108
106
|
template = static_context.env.get_template(name.to_s, context: static_context, tag: :include)
|
109
107
|
template.ast
|
110
108
|
rescue LiquidTemplateNotFoundError => e
|
@@ -114,7 +112,7 @@ module Liquid2
|
|
114
112
|
end
|
115
113
|
|
116
114
|
def expressions
|
117
|
-
exprs = [@
|
115
|
+
exprs = [@template_name]
|
118
116
|
exprs << @var if @var
|
119
117
|
exprs.concat(@args.map(&:value))
|
120
118
|
exprs
|
@@ -126,12 +124,12 @@ module Liquid2
|
|
126
124
|
if @var
|
127
125
|
if @as
|
128
126
|
scope << @as # steep:ignore
|
129
|
-
elsif @
|
130
|
-
scope << Identifier.new([:token_word, @
|
127
|
+
elsif @template_name.is_a?(String)
|
128
|
+
scope << Identifier.new([:token_word, @template_name.split(".").first, @token.last])
|
131
129
|
end
|
132
130
|
end
|
133
131
|
|
134
|
-
Partial.new(@
|
132
|
+
Partial.new(@template_name, :isolated, scope)
|
135
133
|
end
|
136
134
|
end
|
137
135
|
end
|
@@ -1,3 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative "../../tag"
|
4
|
+
|
5
|
+
module Liquid2
|
6
|
+
# The _with_ tag.
|
7
|
+
class WithTag < Tag
|
8
|
+
END_BLOCK = Set["endwith"]
|
9
|
+
|
10
|
+
# @param token [[Symbol, String?, Integer]]
|
11
|
+
# @param parser [Parser]
|
12
|
+
# @return [WithTag]
|
13
|
+
def self.parse(token, parser)
|
14
|
+
parser.next if parser.current_kind == :token_comma
|
15
|
+
args = parser.parse_keyword_arguments
|
16
|
+
parser.next if parser.current_kind == :token_comma
|
17
|
+
parser.carry_whitespace_control
|
18
|
+
parser.eat(:token_tag_end)
|
19
|
+
block = parser.parse_block(END_BLOCK)
|
20
|
+
parser.eat_empty_tag("endwith")
|
21
|
+
new(token, args, block)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param token [[Symbol, String?, Integer]]
|
25
|
+
# @param args [Array[KeywordArgument]]
|
26
|
+
# @param block [Block]
|
27
|
+
def initialize(token, args, block)
|
28
|
+
super(token)
|
29
|
+
@args = args
|
30
|
+
@block = block
|
31
|
+
@blank = block.blank
|
32
|
+
end
|
33
|
+
|
34
|
+
def render(context, buffer)
|
35
|
+
namespace = @args.to_h { |arg| [arg.name, context.evaluate(arg.value)] }
|
36
|
+
context.extend(namespace) do
|
37
|
+
@block.render(context, buffer)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def children(_static_context, include_partials: true) = [@block]
|
42
|
+
def block_scope = @args.map { |arg| Identifier.new(arg.token) }
|
43
|
+
end
|
44
|
+
end
|