twig_ruby 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/lib/tasks/twig_parity.rake +278 -0
  3. data/lib/twig/auto_hash.rb +7 -1
  4. data/lib/twig/callable.rb +28 -1
  5. data/lib/twig/compiler.rb +35 -3
  6. data/lib/twig/environment.rb +198 -41
  7. data/lib/twig/error/base.rb +81 -16
  8. data/lib/twig/error/loader.rb +8 -0
  9. data/lib/twig/error/logic.rb +8 -0
  10. data/lib/twig/error/runtime.rb +8 -0
  11. data/lib/twig/expression_parser/base.rb +30 -0
  12. data/lib/twig/expression_parser/expression_parsers.rb +57 -0
  13. data/lib/twig/expression_parser/infix/arrow.rb +31 -0
  14. data/lib/twig/expression_parser/infix/binary.rb +34 -0
  15. data/lib/twig/expression_parser/infix/conditional_ternary.rb +39 -0
  16. data/lib/twig/expression_parser/infix/dot.rb +72 -0
  17. data/lib/twig/expression_parser/infix/filter.rb +43 -0
  18. data/lib/twig/expression_parser/infix/function.rb +67 -0
  19. data/lib/twig/expression_parser/infix/is.rb +53 -0
  20. data/lib/twig/expression_parser/infix/is_not.rb +19 -0
  21. data/lib/twig/expression_parser/infix/parses_arguments.rb +84 -0
  22. data/lib/twig/expression_parser/infix/square_bracket.rb +66 -0
  23. data/lib/twig/expression_parser/infix_expression_parser.rb +34 -0
  24. data/lib/twig/expression_parser/prefix/grouping.rb +60 -0
  25. data/lib/twig/expression_parser/prefix/literal.rb +244 -0
  26. data/lib/twig/expression_parser/prefix/unary.rb +29 -0
  27. data/lib/twig/expression_parser/prefix_expression_parser.rb +18 -0
  28. data/lib/twig/extension/base.rb +26 -4
  29. data/lib/twig/extension/core.rb +1076 -48
  30. data/lib/twig/extension/debug.rb +25 -0
  31. data/lib/twig/extension/escaper.rb +73 -0
  32. data/lib/twig/extension/rails.rb +10 -57
  33. data/lib/twig/extension/string_loader.rb +19 -0
  34. data/lib/twig/extension_set.rb +117 -20
  35. data/lib/twig/file_extension_escaping_strategy.rb +35 -0
  36. data/lib/twig/lexer.rb +225 -81
  37. data/lib/twig/loader/array.rb +25 -8
  38. data/lib/twig/loader/chain.rb +93 -0
  39. data/lib/twig/loader/filesystem.rb +106 -7
  40. data/lib/twig/node/auto_escape.rb +18 -0
  41. data/lib/twig/node/base.rb +58 -2
  42. data/lib/twig/node/block.rb +2 -0
  43. data/lib/twig/node/block_reference.rb +5 -1
  44. data/lib/twig/node/body.rb +7 -0
  45. data/lib/twig/node/cache.rb +50 -0
  46. data/lib/twig/node/capture.rb +22 -0
  47. data/lib/twig/node/deprecated.rb +53 -0
  48. data/lib/twig/node/do.rb +19 -0
  49. data/lib/twig/node/embed.rb +43 -0
  50. data/lib/twig/node/expression/array.rb +29 -20
  51. data/lib/twig/node/expression/arrow_function.rb +55 -0
  52. data/lib/twig/node/expression/assign_name.rb +1 -1
  53. data/lib/twig/node/expression/binary/and.rb +17 -0
  54. data/lib/twig/node/expression/binary/base.rb +6 -4
  55. data/lib/twig/node/expression/binary/boolean.rb +24 -0
  56. data/lib/twig/node/expression/binary/concat.rb +20 -0
  57. data/lib/twig/node/expression/binary/elvis.rb +35 -0
  58. data/lib/twig/node/expression/binary/ends_with.rb +24 -0
  59. data/lib/twig/node/expression/binary/floor_div.rb +21 -0
  60. data/lib/twig/node/expression/binary/has_every.rb +20 -0
  61. data/lib/twig/node/expression/binary/has_some.rb +20 -0
  62. data/lib/twig/node/expression/binary/in.rb +20 -0
  63. data/lib/twig/node/expression/binary/matches.rb +24 -0
  64. data/lib/twig/node/expression/binary/not_in.rb +20 -0
  65. data/lib/twig/node/expression/binary/null_coalesce.rb +49 -0
  66. data/lib/twig/node/expression/binary/or.rb +15 -0
  67. data/lib/twig/node/expression/binary/starts_with.rb +24 -0
  68. data/lib/twig/node/expression/binary/xor.rb +17 -0
  69. data/lib/twig/node/expression/block_reference.rb +62 -0
  70. data/lib/twig/node/expression/call.rb +126 -6
  71. data/lib/twig/node/expression/constant.rb +3 -1
  72. data/lib/twig/node/expression/filter/default.rb +37 -0
  73. data/lib/twig/node/expression/filter/raw.rb +31 -0
  74. data/lib/twig/node/expression/filter.rb +2 -2
  75. data/lib/twig/node/expression/function.rb +37 -0
  76. data/lib/twig/node/expression/get_attribute.rb +51 -7
  77. data/lib/twig/node/expression/hash.rb +75 -0
  78. data/lib/twig/node/expression/helper_method.rb +6 -18
  79. data/lib/twig/node/expression/macro_reference.rb +43 -0
  80. data/lib/twig/node/expression/name.rb +42 -8
  81. data/lib/twig/node/expression/operator_escape.rb +13 -0
  82. data/lib/twig/node/expression/parent.rb +28 -0
  83. data/lib/twig/node/expression/support_defined_test.rb +23 -0
  84. data/lib/twig/node/expression/ternary.rb +7 -1
  85. data/lib/twig/node/expression/test/base.rb +26 -0
  86. data/lib/twig/node/expression/test/constant.rb +35 -0
  87. data/lib/twig/node/expression/test/defined.rb +33 -0
  88. data/lib/twig/node/expression/test/divisible_by.rb +23 -0
  89. data/lib/twig/node/expression/test/even.rb +21 -0
  90. data/lib/twig/node/expression/test/iterable.rb +21 -0
  91. data/lib/twig/node/expression/test/mapping.rb +21 -0
  92. data/lib/twig/node/expression/test/null.rb +21 -0
  93. data/lib/twig/node/expression/test/odd.rb +21 -0
  94. data/lib/twig/node/expression/test/same_as.rb +23 -0
  95. data/lib/twig/node/expression/test/sequence.rb +21 -0
  96. data/lib/twig/node/expression/unary/base.rb +3 -1
  97. data/lib/twig/node/expression/unary/not.rb +18 -0
  98. data/lib/twig/node/expression/unary/spread.rb +18 -0
  99. data/lib/twig/node/expression/unary/string_cast.rb +18 -0
  100. data/lib/twig/node/expression/variable/assign_template.rb +35 -0
  101. data/lib/twig/node/expression/variable/local.rb +35 -0
  102. data/lib/twig/node/expression/variable/template.rb +54 -0
  103. data/lib/twig/node/for.rb +38 -8
  104. data/lib/twig/node/for_loop.rb +0 -22
  105. data/lib/twig/node/if.rb +4 -1
  106. data/lib/twig/node/import.rb +32 -0
  107. data/lib/twig/node/include.rb +38 -8
  108. data/lib/twig/node/macro.rb +79 -0
  109. data/lib/twig/node/module.rb +278 -23
  110. data/lib/twig/node/output.rb +7 -0
  111. data/lib/twig/node/print.rb +4 -1
  112. data/lib/twig/node/set.rb +72 -0
  113. data/lib/twig/node/text.rb +4 -1
  114. data/lib/twig/node/with.rb +50 -0
  115. data/lib/twig/node/yield.rb +6 -1
  116. data/lib/twig/node_traverser.rb +50 -0
  117. data/lib/twig/node_visitor/base.rb +30 -0
  118. data/lib/twig/node_visitor/escaper.rb +165 -0
  119. data/lib/twig/node_visitor/safe_analysis.rb +127 -0
  120. data/lib/twig/node_visitor/spreader.rb +39 -0
  121. data/lib/twig/output_buffer.rb +14 -12
  122. data/lib/twig/parser.rb +281 -8
  123. data/lib/twig/rails/config.rb +33 -0
  124. data/lib/twig/rails/engine.rb +44 -0
  125. data/lib/twig/rails/renderer.rb +41 -0
  126. data/lib/twig/runtime/argument_spreader.rb +46 -0
  127. data/lib/twig/runtime/context.rb +154 -0
  128. data/lib/twig/runtime/enumerable_hash.rb +51 -0
  129. data/lib/twig/runtime/escaper.rb +155 -0
  130. data/lib/twig/runtime/loop_context.rb +81 -0
  131. data/lib/twig/runtime/loop_iterator.rb +60 -0
  132. data/lib/twig/runtime/spread.rb +21 -0
  133. data/lib/twig/runtime_loader/base.rb +12 -0
  134. data/lib/twig/runtime_loader/factory.rb +23 -0
  135. data/lib/twig/template.rb +267 -14
  136. data/lib/twig/template_wrapper.rb +42 -0
  137. data/lib/twig/token.rb +28 -2
  138. data/lib/twig/token_parser/apply.rb +48 -0
  139. data/lib/twig/token_parser/auto_escape.rb +45 -0
  140. data/lib/twig/token_parser/base.rb +26 -0
  141. data/lib/twig/token_parser/block.rb +4 -4
  142. data/lib/twig/token_parser/cache.rb +31 -0
  143. data/lib/twig/token_parser/deprecated.rb +40 -0
  144. data/lib/twig/token_parser/do.rb +19 -0
  145. data/lib/twig/token_parser/embed.rb +62 -0
  146. data/lib/twig/token_parser/extends.rb +4 -3
  147. data/lib/twig/token_parser/for.rb +14 -9
  148. data/lib/twig/token_parser/from.rb +57 -0
  149. data/lib/twig/token_parser/guard.rb +65 -0
  150. data/lib/twig/token_parser/if.rb +9 -9
  151. data/lib/twig/token_parser/import.rb +29 -0
  152. data/lib/twig/token_parser/include.rb +2 -2
  153. data/lib/twig/token_parser/macro.rb +109 -0
  154. data/lib/twig/token_parser/set.rb +76 -0
  155. data/lib/twig/token_parser/use.rb +54 -0
  156. data/lib/twig/token_parser/with.rb +36 -0
  157. data/lib/twig/token_parser/yield.rb +7 -7
  158. data/lib/twig/token_stream.rb +23 -3
  159. data/lib/twig/twig_filter.rb +20 -0
  160. data/lib/twig/twig_function.rb +37 -0
  161. data/lib/twig/twig_test.rb +31 -0
  162. data/lib/twig/util/callable_arguments_extractor.rb +227 -0
  163. data/lib/twig_ruby.rb +21 -2
  164. metadata +145 -6
  165. data/lib/twig/context.rb +0 -64
  166. data/lib/twig/expression_parser.rb +0 -517
  167. data/lib/twig/railtie.rb +0 -60
@@ -3,21 +3,36 @@
3
3
  module Twig
4
4
  module Node
5
5
  class Module < Node::Base
6
- def initialize(body, parent, blocks, source)
6
+ # @param [Node::Body] body
7
+ def initialize(body, parent, blocks, macros, traits, embedded_templates, source)
7
8
  nodes = {
8
9
  body:,
9
10
  blocks:,
11
+ macros:,
12
+ traits:,
10
13
  }
11
14
  nodes[:parent] = parent if parent
12
15
 
13
- super(nodes)
16
+ super(
17
+ nodes,
18
+ {
19
+ index: nil,
20
+ embedded_templates:,
21
+ },
22
+ 1
23
+ )
14
24
 
15
25
  self.source_context = source
16
26
  end
17
27
 
28
+ def index=(index)
29
+ attributes[:index] = index
30
+ end
31
+
18
32
  def compile(compiler)
33
+ class_name = compiler.environment.template_class(source_context.name, attributes[:index])
19
34
  class_begin = <<~CLASS
20
- class Twig::#{compiler.environment.template_class(source_context.name)} < ::Twig::Template
35
+ class Twig::#{class_name} < ::Twig::Template
21
36
  CLASS
22
37
 
23
38
  class_end = <<~CLASS
@@ -26,48 +41,288 @@ module Twig
26
41
 
27
42
  compiler.
28
43
  raw(class_begin).
29
- indent.
30
- write("def call(context = {}, blocks = {})\n").
31
44
  indent
32
45
 
46
+ compile_constructor(compiler)
47
+ compile_get_parent(compiler)
48
+
49
+ compiler.
50
+ write("private def call(context = {}, blocks = {})\n").
51
+ indent.
52
+ write("unless context.is_a?(::Twig::Runtime::Context)\n").
53
+ indent.
54
+ write("context = ::Twig::Runtime::Context.new(context)\n").
55
+ outdent.
56
+ write("end\n").
57
+ write("macros = @macros.dup\n")
58
+
59
+ compiler.
60
+ subcompile(nodes[:body])
61
+
33
62
  if nodes.key?(:parent)
63
+ parent = nodes[:parent]
64
+ compiler.add_debug_info(parent)
65
+
66
+ if parent.is_a?(Expression::Constant)
67
+ compiler.
68
+ write('@parent = load(').
69
+ subcompile(parent).
70
+ raw(', ').
71
+ repr(parent.lineno).
72
+ raw(")\n").
73
+ write('@parent')
74
+ else
75
+ compiler.write('get_parent(context)')
76
+ end
77
+
34
78
  compiler.
35
- write('load_template(').
36
- subcompile(nodes[:parent]).
37
- raw(").render(context, block_list.merge(blocks));\n")
79
+ raw(".unwrap.render_with_blocks(context, self.blocks.merge(blocks));\n")
80
+ end
81
+
82
+ compiler.
83
+ write("context.output_buffer\n").
84
+ outdent.
85
+ write("end\n\n")
86
+
87
+ # Blocks
88
+ compiler.
89
+ subcompile(nodes[:blocks])
90
+
91
+ # Macros
92
+ compiler.
93
+ subcompile(nodes[:macros])
94
+
95
+ compile_get_template_name(compiler)
96
+ compile_traitable(compiler)
97
+ compile_debug_info(compiler)
98
+ compile_get_source_context(compiler)
99
+
100
+ compiler.
101
+ outdent.
102
+ raw(class_end)
103
+
104
+ attributes[:embedded_templates].nodes.each_value do |template|
105
+ compiler.subcompile(template)
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ # @param [Compiler] compiler
112
+ def compile_constructor(compiler)
113
+ compiler.
114
+ write("def initialize(*, **)\n").
115
+ indent.
116
+ write("super\n\n").
117
+ write("@source = source_context\n")
118
+
119
+ unless nodes.key?(:parent)
120
+ compiler.write("@parent = false\n")
121
+ end
122
+
123
+ traits_count = nodes[:traits].length
124
+
125
+ if traits_count.positive?
126
+ nodes[:traits].nodes.each do |i, trait|
127
+ node = trait.nodes[:template]
128
+
129
+ compiler.
130
+ add_debug_info(node).
131
+ write("_trait_#{i} = load(").
132
+ subcompile(node).
133
+ raw(', ').
134
+ repr(node.lineno).
135
+ raw(")\n").
136
+ write("unless _trait_#{i}.unwrap.traitable?\n").
137
+ indent.
138
+ write(%q[raise ::Twig::Error::Runtime.new('Template "' + ]).
139
+ subcompile(node).
140
+ raw(%q(+ '" cannot be used as a trait.', )).
141
+ repr(node.lineno).
142
+ raw(", @source)\n").
143
+ outdent.
144
+ write("end\n").
145
+ write("_trait_#{i}_blocks = _trait_#{i}.unwrap.blocks.dup\n\n")
146
+
147
+ trait.nodes[:targets].nodes.each do |key, value|
148
+ compiler.
149
+ write("unless _trait_#{i}_blocks.key?(").
150
+ string(key).
151
+ raw(".to_sym)\n").
152
+ indent.
153
+ write('raise ::Twig::Error::Runtime.new("Block \"#{').
154
+ string(key).
155
+ raw('}\" is not defined in trait \"#{').
156
+ subcompile(node).
157
+ raw('}\".", ').
158
+ repr(node.lineno).
159
+ raw(", @source)\n").
160
+ outdent.
161
+ write("end\n\n").
162
+
163
+ write("_trait_#{i}_blocks[").
164
+ subcompile(value).
165
+ raw(".to_sym] = _trait_#{i}_blocks[").
166
+ symbol(key).
167
+ raw("]\n").
168
+ write("_trait_#{i}_blocks.delete(").
169
+ symbol(key).
170
+ raw(")\n").
171
+ write('@trait_aliases[').
172
+ subcompile(value).
173
+ raw('.to_sym] = ').
174
+ symbol(key).
175
+ raw("\n\n")
176
+ end
177
+ end
178
+
179
+ if traits_count > 1
180
+ trait_names = (0...traits_count).map { |i| "_trait_#{i}_blocks" }
181
+
182
+ compiler.
183
+ write("@traits = {}.merge(#{trait_names.join(', ')})\n\n")
184
+ else
185
+ compiler.
186
+ write("@traits = _trait_0_blocks\n\n")
187
+ end
188
+
189
+ compiler.
190
+ write("@blocks = @traits.merge({\n")
38
191
  else
39
192
  compiler.
40
- subcompile(nodes[:body])
193
+ write("@blocks = {\n")
194
+ end
195
+
196
+ compiler.indent
197
+
198
+ nodes[:blocks].nodes.each_key do |name|
199
+ compiler.
200
+ write("#{name}: [self, 'block_#{name}'],\n")
201
+ end
202
+
203
+ compiler.outdent
204
+
205
+ if traits_count.positive?
206
+ compiler.
207
+ write("})\n")
208
+ else
209
+ compiler.
210
+ write("}\n")
41
211
  end
42
212
 
43
213
  compiler.
44
- write("@output_buffer\n").
45
214
  outdent.
46
- write("end\n\n").
47
- subcompile(nodes[:blocks]).
48
- outdent
215
+ write("end\n\n")
216
+ end
217
+
218
+ # @param [Compiler] compiler
219
+ def compile_get_parent(compiler)
220
+ unless nodes.key?(:parent)
221
+ return
222
+ end
223
+
224
+ parent = nodes[:parent]
49
225
 
50
226
  compiler.
227
+ write("private def do_get_parent(context)\n").
51
228
  indent.
52
- write("def block_list\n").
53
- indent.
54
- write("{\n").
55
- indent
229
+ add_debug_info(parent).
230
+ write('')
56
231
 
57
- nodes[:blocks].nodes.each_value do |block|
232
+ if parent.is_a?(Node::Expression::Constant)
233
+ compiler.subcompile(parent)
234
+ else
58
235
  compiler.
59
- write("#{block.attributes[:name]}: self,\n")
236
+ raw('load(').
237
+ subcompile(parent).
238
+ raw(', ').
239
+ repr(parent.lineno).
240
+ raw(')')
60
241
  end
61
242
 
62
243
  compiler.
244
+ raw("\n").
63
245
  outdent.
64
- write("}\n").
246
+ write("end\n\n")
247
+ end
248
+
249
+ def compile_get_template_name(compiler)
250
+ compiler.
251
+ write("def template_name\n").
252
+ indent.
253
+ write('').
254
+ repr(source_context.name).
255
+ raw("\n").
65
256
  outdent.
66
- write("end\n").
67
- outdent
257
+ write("end\n\n")
258
+ end
259
+
260
+ def compile_traitable(compiler)
261
+ # A template can be used as a trait if:
262
+ # * it has no parent
263
+ # * it has no macros
264
+ # * it has no body
265
+ #
266
+ # Put another way, a template can be used as a trait if it
267
+ # only contains blocks and use statements.
268
+ traitable = !nodes.key?(:parent) && nodes[:macros].empty?
269
+
270
+ if traitable
271
+ body_node = nodes[:body].nodes[0]
272
+
273
+ if body_node.empty?
274
+ body_node = Node::Nodes.new({ 0 => body_node })
275
+ end
276
+
277
+ body_node.nodes.each_value do |node|
278
+ if node.length.positive?
279
+ traitable = false
280
+ break
281
+ end
282
+ end
283
+ end
284
+
285
+ if traitable
286
+ return
287
+ end
68
288
 
69
289
  compiler.
70
- raw(class_end)
290
+ write("def traitable?\n").
291
+ indent.
292
+ write("false\n").
293
+ outdent.
294
+ write("end\n\n")
295
+ end
296
+
297
+ def compile_debug_info(compiler)
298
+ compiler.
299
+ write("def self.debug_info\n").
300
+ indent.
301
+ write('').
302
+ repr(compiler.debug_info.to_a.reverse.to_h).
303
+ raw("\n").
304
+ outdent.
305
+ write("end\n\n")
306
+ end
307
+
308
+ def compile_get_source_context(compiler)
309
+ compiler.
310
+ write("def source_context\n").
311
+ indent.
312
+ write("self.class.source_context\n").
313
+ outdent.
314
+ write("end\n\n").
315
+ write("def self.source_context\n").
316
+ indent.
317
+ write('::Twig::Source.new(').
318
+ string(compiler.environment.debug? ? source_context.code : '').
319
+ raw(', ').
320
+ string(source_context.name).
321
+ raw(', ').
322
+ string(source_context.path).
323
+ raw(")\n").
324
+ outdent.
325
+ write("end\n")
71
326
  end
72
327
  end
73
328
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Output; end
6
+ end
7
+ end
@@ -3,13 +3,16 @@
3
3
  module Twig
4
4
  module Node
5
5
  class Print < Node::Base
6
+ include Output
7
+
6
8
  def initialize(expr, lineno)
7
9
  super({ expr: }, {}, lineno)
8
10
  end
9
11
 
10
12
  def compile(compiler)
11
13
  compiler.
12
- write('@output_buffer.append = ').
14
+ add_debug_info(self).
15
+ write('context.output_buffer.append = ').
13
16
  subcompile(nodes[:expr]).
14
17
  raw(";\n")
15
18
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ class Set < Node::Base
6
+ # @param [Boolean] capture
7
+ # @param [Node::Base] names
8
+ # @param [Node::Base] values
9
+ # @param [Integer] lineno
10
+ def initialize(capture, names, values, lineno)
11
+ safe = false
12
+
13
+ if capture
14
+ safe = true
15
+ capture = false
16
+
17
+ if values.is_a?(Nodes) && values.nodes.empty?
18
+ values = Expression::Constant.new('', values.lineno)
19
+ elsif values.is_a?(Text)
20
+ values = Expression::Constant.new(values.attributes[:data], values.lineno)
21
+ elsif values.is_a?(Print) && values.nodes[:expr].is_a?(Expression::Constant)
22
+ values = values.nodes[:expr]
23
+ else
24
+ values = Capture.new(values, values.lineno)
25
+ capture = true
26
+ end
27
+ end
28
+
29
+ super({ names:, values: }, { capture:, safe: }, lineno)
30
+ end
31
+
32
+ def compile(compiler)
33
+ compiler.add_debug_info(self)
34
+
35
+ if nodes[:names].length > 1
36
+ compiler.write('')
37
+
38
+ nodes[:names].nodes.each do |i, node|
39
+ compiler.raw(', ') if i.positive?
40
+ compiler.subcompile(node)
41
+ end
42
+
43
+ compiler.raw(' = ')
44
+
45
+ nodes[:values].nodes.each do |i, node|
46
+ compiler.raw(', ') if i.positive?
47
+ compiler.
48
+ raw('(').
49
+ subcompile(node).
50
+ raw(')')
51
+
52
+ if attributes[:safe]
53
+ compiler.raw('.html_safe')
54
+ end
55
+ end
56
+ else
57
+ compiler.
58
+ subcompile(nodes[:names], raw: false).
59
+ raw(' = (').
60
+ subcompile(nodes[:values]).
61
+ raw(')')
62
+
63
+ if attributes[:safe]
64
+ compiler.raw('.html_safe')
65
+ end
66
+ end
67
+
68
+ compiler.raw("\n")
69
+ end
70
+ end
71
+ end
72
+ end
@@ -3,6 +3,8 @@
3
3
  module Twig
4
4
  module Node
5
5
  class Text < Node::Base
6
+ include Output
7
+
6
8
  # @param [String] data
7
9
  # @param [Integer] lineno
8
10
  def initialize(data, lineno = 0)
@@ -11,7 +13,8 @@ module Twig
11
13
 
12
14
  def compile(compiler)
13
15
  compiler.
14
- write('@output_buffer.safe_append = ').
16
+ add_debug_info(self).
17
+ write('context.output_buffer.safe_append = ').
15
18
  string(attributes[:data]).
16
19
  raw(";\n")
17
20
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ class With < Node::Base
6
+ # @param [Node::Base] body
7
+ # @param [Node::Base|nil] variables
8
+ # @param [Boolean] only
9
+ # @param [Integer] lineno
10
+ def initialize(body, variables, only, lineno)
11
+ nodes = { body: }
12
+ nodes[:variables] = variables unless variables.nil?
13
+
14
+ super(nodes, { only: }, lineno)
15
+ end
16
+
17
+ def compile(compiler)
18
+ compiler.
19
+ add_debug_info(self).
20
+ write("context.push_stack\n")
21
+
22
+ if nodes.key?(:variables)
23
+ if attributes[:only]
24
+ compiler.write("context.clear\n")
25
+ end
26
+
27
+ var_name = compiler.var_name
28
+
29
+ compiler.
30
+ write("#{var_name} = ").
31
+ subcompile(nodes[:variables]).
32
+ write("\n").
33
+ write("unless #{var_name}.is_a?(::Hash) || #{var_name} == []\n").
34
+ indent.
35
+ write("raise ::Twig::Error::Syntax.new('Variables passed to the \"with\" tag must be a mapping.',").
36
+ repr(nodes[:variables].lineno).
37
+ raw(", source_context)\n").
38
+ outdent.
39
+ write("end\n").
40
+ write("context.merge!(env.globals)\n").
41
+ write("context.merge!(#{var_name})\n")
42
+ end
43
+
44
+ compiler.
45
+ subcompile(nodes[:body]).
46
+ write("context.pop_stack\n")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -10,7 +10,8 @@ module Twig
10
10
 
11
11
  def compile(compiler)
12
12
  compiler.
13
- write('@output_buffer.append = (').
13
+ add_debug_info(self).
14
+ write('context.output_buffer.append = (').
14
15
  subcompile(nodes[:expr]).
15
16
  raw(' do')
16
17
 
@@ -21,6 +22,8 @@ module Twig
21
22
 
22
23
  compiler.
23
24
  raw("\n").
25
+ indent.
26
+ write("context.buffer_and_return do\n").
24
27
  indent
25
28
 
26
29
  if attributes.key?(:arguments)
@@ -46,6 +49,8 @@ module Twig
46
49
  end
47
50
 
48
51
  compiler.
52
+ outdent.
53
+ write("end.to_s.html_safe\n").
49
54
  outdent.
50
55
  write("end);\n")
51
56
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ class NodeTraverser
5
+ def initialize(env, visitors = [])
6
+ @env = env
7
+ @visitors = {}
8
+
9
+ visitors.each { |visitor| add_visitor(visitor) }
10
+ end
11
+
12
+ # @param [NodeVisitor::Base]
13
+ def add_visitor(visitor)
14
+ @visitors[visitor.priority] ||= []
15
+ @visitors[visitor.priority] << visitor
16
+ end
17
+
18
+ # Traverses a node and calls the registered visitors.
19
+ # @param [Node::Base] node
20
+ def traverse(node)
21
+ @visitors = @visitors.sort.to_h
22
+
23
+ @visitors.each_value do |visitors|
24
+ visitors.each do |visitor|
25
+ node = traverse_for_visitor(visitor, node)
26
+ end
27
+ end
28
+
29
+ node
30
+ end
31
+
32
+ private
33
+
34
+ # @param [NodeVisitor::Base] visitor
35
+ # @param [Node::Base] node
36
+ def traverse_for_visitor(visitor, node)
37
+ node = visitor.enter_node(node, @env)
38
+
39
+ node.nodes.each do |k, n|
40
+ if (m = traverse_for_visitor(visitor, n)).nil?
41
+ node.nodes.delete(k)
42
+ elsif m != n
43
+ node.nodes[k] = m
44
+ end
45
+ end
46
+
47
+ visitor.leave_node(node, @env)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module NodeVisitor
5
+ class Base
6
+ # Called before child nodes are visited.
7
+ #
8
+ # @param [Node::Base] node
9
+ # @param [Environment] env
10
+ # @return [Node::Base] The modified node
11
+ def enter_node(node, env)
12
+ raise NotImplementedError, "Method #{__method__} must be implemented"
13
+ end
14
+
15
+ # Called after child nodes are visited.
16
+ #
17
+ # @param [Node::Base] node
18
+ # @param [Environment] env
19
+ # @return [Node::Base, nil] The modified node or nil if the node must be removed
20
+ def leave_node(node, env)
21
+ raise NotImplementedError, "Method #{__method__} must be implemented"
22
+ end
23
+
24
+ # @return [Integer] The priority level
25
+ def priority
26
+ 0
27
+ end
28
+ end
29
+ end
30
+ end