liquid 4.0.0 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +101 -2
  3. data/README.md +8 -0
  4. data/lib/liquid.rb +18 -5
  5. data/lib/liquid/block.rb +47 -20
  6. data/lib/liquid/block_body.rb +192 -76
  7. data/lib/liquid/condition.rb +69 -29
  8. data/lib/liquid/context.rb +110 -53
  9. data/lib/liquid/document.rb +47 -9
  10. data/lib/liquid/drop.rb +4 -2
  11. data/lib/liquid/errors.rb +20 -18
  12. data/lib/liquid/expression.rb +30 -31
  13. data/lib/liquid/extensions.rb +8 -0
  14. data/lib/liquid/file_system.rb +6 -4
  15. data/lib/liquid/forloop_drop.rb +11 -4
  16. data/lib/liquid/i18n.rb +5 -3
  17. data/lib/liquid/interrupts.rb +3 -1
  18. data/lib/liquid/lexer.rb +35 -26
  19. data/lib/liquid/locales/en.yml +4 -2
  20. data/lib/liquid/parse_context.rb +21 -4
  21. data/lib/liquid/parse_tree_visitor.rb +42 -0
  22. data/lib/liquid/parser.rb +30 -18
  23. data/lib/liquid/parser_switching.rb +17 -3
  24. data/lib/liquid/partial_cache.rb +24 -0
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/profiler/hooks.rb +26 -14
  27. data/lib/liquid/range_lookup.rb +5 -3
  28. data/lib/liquid/register.rb +6 -0
  29. data/lib/liquid/resource_limits.rb +47 -8
  30. data/lib/liquid/standardfilters.rb +170 -63
  31. data/lib/liquid/static_registers.rb +44 -0
  32. data/lib/liquid/strainer_factory.rb +36 -0
  33. data/lib/liquid/strainer_template.rb +53 -0
  34. data/lib/liquid/tablerowloop_drop.rb +6 -4
  35. data/lib/liquid/tag.rb +28 -6
  36. data/lib/liquid/tag/disableable.rb +22 -0
  37. data/lib/liquid/tag/disabler.rb +21 -0
  38. data/lib/liquid/tags/assign.rb +32 -10
  39. data/lib/liquid/tags/break.rb +8 -3
  40. data/lib/liquid/tags/capture.rb +11 -8
  41. data/lib/liquid/tags/case.rb +41 -27
  42. data/lib/liquid/tags/comment.rb +5 -3
  43. data/lib/liquid/tags/continue.rb +8 -3
  44. data/lib/liquid/tags/cycle.rb +35 -16
  45. data/lib/liquid/tags/decrement.rb +6 -3
  46. data/lib/liquid/tags/echo.rb +34 -0
  47. data/lib/liquid/tags/for.rb +79 -47
  48. data/lib/liquid/tags/if.rb +53 -30
  49. data/lib/liquid/tags/ifchanged.rb +11 -10
  50. data/lib/liquid/tags/include.rb +42 -44
  51. data/lib/liquid/tags/increment.rb +7 -3
  52. data/lib/liquid/tags/raw.rb +14 -11
  53. data/lib/liquid/tags/render.rb +84 -0
  54. data/lib/liquid/tags/table_row.rb +32 -20
  55. data/lib/liquid/tags/unless.rb +15 -15
  56. data/lib/liquid/template.rb +53 -72
  57. data/lib/liquid/template_factory.rb +9 -0
  58. data/lib/liquid/tokenizer.rb +17 -9
  59. data/lib/liquid/usage.rb +8 -0
  60. data/lib/liquid/utils.rb +6 -4
  61. data/lib/liquid/variable.rb +55 -38
  62. data/lib/liquid/variable_lookup.rb +14 -6
  63. data/lib/liquid/version.rb +3 -1
  64. data/test/integration/assign_test.rb +74 -5
  65. data/test/integration/blank_test.rb +11 -8
  66. data/test/integration/block_test.rb +58 -0
  67. data/test/integration/capture_test.rb +18 -10
  68. data/test/integration/context_test.rb +609 -5
  69. data/test/integration/document_test.rb +4 -2
  70. data/test/integration/drop_test.rb +67 -83
  71. data/test/integration/error_handling_test.rb +73 -61
  72. data/test/integration/expression_test.rb +46 -0
  73. data/test/integration/filter_test.rb +53 -42
  74. data/test/integration/hash_ordering_test.rb +5 -3
  75. data/test/integration/output_test.rb +26 -24
  76. data/test/integration/parsing_quirks_test.rb +24 -8
  77. data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
  78. data/test/integration/security_test.rb +41 -18
  79. data/test/integration/standard_filter_test.rb +513 -210
  80. data/test/integration/tag/disableable_test.rb +59 -0
  81. data/test/integration/tag_test.rb +45 -0
  82. data/test/integration/tags/break_tag_test.rb +4 -2
  83. data/test/integration/tags/continue_tag_test.rb +4 -2
  84. data/test/integration/tags/echo_test.rb +13 -0
  85. data/test/integration/tags/for_tag_test.rb +109 -53
  86. data/test/integration/tags/if_else_tag_test.rb +5 -3
  87. data/test/integration/tags/include_tag_test.rb +83 -52
  88. data/test/integration/tags/increment_tag_test.rb +4 -2
  89. data/test/integration/tags/liquid_tag_test.rb +116 -0
  90. data/test/integration/tags/raw_tag_test.rb +14 -11
  91. data/test/integration/tags/render_tag_test.rb +213 -0
  92. data/test/integration/tags/standard_tag_test.rb +38 -31
  93. data/test/integration/tags/statements_test.rb +23 -21
  94. data/test/integration/tags/table_row_test.rb +2 -0
  95. data/test/integration/tags/unless_else_tag_test.rb +4 -2
  96. data/test/integration/template_test.rb +123 -120
  97. data/test/integration/trim_mode_test.rb +82 -44
  98. data/test/integration/variable_test.rb +46 -31
  99. data/test/test_helper.rb +75 -23
  100. data/test/unit/block_unit_test.rb +19 -24
  101. data/test/unit/condition_unit_test.rb +82 -72
  102. data/test/unit/file_system_unit_test.rb +6 -4
  103. data/test/unit/i18n_unit_test.rb +7 -5
  104. data/test/unit/lexer_unit_test.rb +12 -10
  105. data/test/unit/parse_tree_visitor_test.rb +254 -0
  106. data/test/unit/parser_unit_test.rb +37 -35
  107. data/test/unit/partial_cache_unit_test.rb +128 -0
  108. data/test/unit/regexp_unit_test.rb +17 -15
  109. data/test/unit/static_registers_unit_test.rb +156 -0
  110. data/test/unit/strainer_factory_unit_test.rb +100 -0
  111. data/test/unit/strainer_template_unit_test.rb +82 -0
  112. data/test/unit/tag_unit_test.rb +5 -3
  113. data/test/unit/tags/case_tag_unit_test.rb +3 -1
  114. data/test/unit/tags/for_tag_unit_test.rb +4 -2
  115. data/test/unit/tags/if_tag_unit_test.rb +3 -1
  116. data/test/unit/template_factory_unit_test.rb +12 -0
  117. data/test/unit/template_unit_test.rb +19 -10
  118. data/test/unit/tokenizer_unit_test.rb +26 -19
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +79 -46
  121. data/lib/liquid/strainer.rb +0 -66
  122. data/test/unit/context_unit_test.rb +0 -483
  123. data/test/unit/strainer_unit_test.rb +0 -148
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Container for liquid nodes which conveniently wraps decision making logic
3
5
  #
@@ -8,58 +10,83 @@ module Liquid
8
10
  #
9
11
  class Condition #:nodoc:
10
12
  @@operators = {
11
- '=='.freeze => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
12
- '!='.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
13
- '<>'.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
14
- '<'.freeze => :<,
15
- '>'.freeze => :>,
16
- '>='.freeze => :>=,
17
- '<='.freeze => :<=,
18
- 'contains'.freeze => lambda do |cond, left, right|
13
+ '==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
14
+ '!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
15
+ '<>' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
16
+ '<' => :<,
17
+ '>' => :>,
18
+ '>=' => :>=,
19
+ '<=' => :<=,
20
+ 'contains' => lambda do |_cond, left, right|
19
21
  if left && right && left.respond_to?(:include?)
20
22
  right = right.to_s if left.is_a?(String)
21
23
  left.include?(right)
22
24
  else
23
25
  false
24
26
  end
27
+ end,
28
+ }
29
+
30
+ class MethodLiteral
31
+ attr_reader :method_name, :to_s
32
+
33
+ def initialize(method_name, to_s)
34
+ @method_name = method_name
35
+ @to_s = to_s
25
36
  end
37
+ end
38
+
39
+ @@method_literals = {
40
+ 'blank' => MethodLiteral.new(:blank?, '').freeze,
41
+ 'empty' => MethodLiteral.new(:empty?, '').freeze,
26
42
  }
27
43
 
28
44
  def self.operators
29
45
  @@operators
30
46
  end
31
47
 
32
- attr_reader :attachment
48
+ def self.parse_expression(parse_context, markup)
49
+ @@method_literals[markup] || parse_context.parse_expression(markup)
50
+ end
51
+
52
+ attr_reader :attachment, :child_condition
33
53
  attr_accessor :left, :operator, :right
34
54
 
35
55
  def initialize(left = nil, operator = nil, right = nil)
36
- @left = left
56
+ @left = left
37
57
  @operator = operator
38
- @right = right
58
+ @right = right
59
+
39
60
  @child_relation = nil
40
61
  @child_condition = nil
41
62
  end
42
63
 
43
64
  def evaluate(context = Context.new)
44
- result = interpret_condition(left, right, operator, context)
45
-
46
- case @child_relation
47
- when :or
48
- result || @child_condition.evaluate(context)
49
- when :and
50
- result && @child_condition.evaluate(context)
51
- else
52
- result
65
+ condition = self
66
+ result = nil
67
+ loop do
68
+ result = interpret_condition(condition.left, condition.right, condition.operator, context)
69
+
70
+ case condition.child_relation
71
+ when :or
72
+ break if result
73
+ when :and
74
+ break unless result
75
+ else
76
+ break
77
+ end
78
+ condition = condition.child_condition
53
79
  end
80
+ result
54
81
  end
55
82
 
56
83
  def or(condition)
57
- @child_relation = :or
84
+ @child_relation = :or
58
85
  @child_condition = condition
59
86
  end
60
87
 
61
88
  def and(condition)
62
- @child_relation = :and
89
+ @child_relation = :and
63
90
  @child_condition = condition
64
91
  end
65
92
 
@@ -72,13 +99,17 @@ module Liquid
72
99
  end
73
100
 
74
101
  def inspect
75
- "#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
102
+ "#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
76
103
  end
77
104
 
105
+ protected
106
+
107
+ attr_reader :child_relation
108
+
78
109
  private
79
110
 
80
111
  def equal_variables(left, right)
81
- if left.is_a?(Liquid::Expression::MethodLiteral)
112
+ if left.is_a?(MethodLiteral)
82
113
  if right.respond_to?(left.method_name)
83
114
  return right.send(left.method_name)
84
115
  else
@@ -86,7 +117,7 @@ module Liquid
86
117
  end
87
118
  end
88
119
 
89
- if right.is_a?(Liquid::Expression::MethodLiteral)
120
+ if right.is_a?(MethodLiteral)
90
121
  if left.respond_to?(right.method_name)
91
122
  return left.send(right.method_name)
92
123
  else
@@ -103,21 +134,30 @@ module Liquid
103
134
  # return this as the result.
104
135
  return context.evaluate(left) if op.nil?
105
136
 
106
- left = context.evaluate(left)
137
+ left = context.evaluate(left)
107
138
  right = context.evaluate(right)
108
139
 
109
- operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))
140
+ operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")
110
141
 
111
142
  if operation.respond_to?(:call)
112
143
  operation.call(self, left, right)
113
- elsif left.respond_to?(operation) && right.respond_to?(operation)
144
+ elsif left.respond_to?(operation) && right.respond_to?(operation) && !left.is_a?(Hash) && !right.is_a?(Hash)
114
145
  begin
115
146
  left.send(operation, right)
116
147
  rescue ::ArgumentError => e
117
- raise Liquid::ArgumentError.new(e.message)
148
+ raise Liquid::ArgumentError, e.message
118
149
  end
119
150
  end
120
151
  end
152
+
153
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
154
+ def children
155
+ [
156
+ @node.left, @node.right,
157
+ @node.child_condition, @node.attachment
158
+ ].compact
159
+ end
160
+ end
121
161
  end
122
162
 
123
163
  class ElseCondition < Condition
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Context keeps the variable stack and resolves variables, as well as keywords
3
5
  #
@@ -12,37 +14,49 @@ module Liquid
12
14
  #
13
15
  # context['bob'] #=> nil class Context
14
16
  class Context
15
- attr_reader :scopes, :errors, :registers, :environments, :resource_limits
17
+ attr_reader :scopes, :errors, :registers, :environments, :resource_limits, :static_registers, :static_environments
16
18
  attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
17
19
 
18
- def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
19
- @environments = [environments].flatten
20
- @scopes = [(outer_scope || {})]
21
- @registers = registers
22
- @errors = []
23
- @partial = false
24
- @strict_variables = false
25
- @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
26
- squash_instance_assigns_with_environments
20
+ # rubocop:disable Metrics/ParameterLists
21
+ def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
22
+ new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, &block)
23
+ end
27
24
 
28
- @this_stack_used = false
25
+ def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
26
+ @environments = [environments]
27
+ @environments.flatten!
28
+
29
+ @static_environments = [static_environments].flat_map(&:freeze).freeze
30
+ @scopes = [(outer_scope || {})]
31
+ @registers = registers
32
+ @errors = []
33
+ @partial = false
34
+ @strict_variables = false
35
+ @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
36
+ @base_scope_depth = 0
37
+ @interrupts = []
38
+ @filters = []
39
+ @global_filter = nil
40
+ @disabled_tags = {}
29
41
 
30
42
  self.exception_renderer = Template.default_exception_renderer
31
43
  if rethrow_errors
32
- self.exception_renderer = ->(e) { raise }
44
+ self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
33
45
  end
34
46
 
35
- @interrupts = []
36
- @filters = []
37
- @global_filter = nil
47
+ yield self if block_given?
48
+
49
+ # Do this last, since it could result in this object being passed to a Proc in the environment
50
+ squash_instance_assigns_with_environments
38
51
  end
52
+ # rubocop:enable Metrics/ParameterLists
39
53
 
40
54
  def warnings
41
55
  @warnings ||= []
42
56
  end
43
57
 
44
58
  def strainer
45
- @strainer ||= Strainer.create(self, @filters)
59
+ @strainer ||= StrainerFactory.create(self, @filters)
46
60
  end
47
61
 
48
62
  # Adds filters to this context.
@@ -74,10 +88,10 @@ module Liquid
74
88
  @interrupts.pop
75
89
  end
76
90
 
77
- def handle_error(e, line_number = nil, raw_token = nil)
91
+ def handle_error(e, line_number = nil)
78
92
  e = internal_error unless e.is_a?(Liquid::Error)
79
93
  e.template_name ||= template_name
80
- e.line_number ||= line_number
94
+ e.line_number ||= line_number
81
95
  errors.push(e)
82
96
  exception_renderer.call(e).to_s
83
97
  end
@@ -89,7 +103,7 @@ module Liquid
89
103
  # Push new local scope on the stack. use <tt>Context#stack</tt> instead
90
104
  def push(new_scope = {})
91
105
  @scopes.unshift(new_scope)
92
- raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100
106
+ check_overflow
93
107
  end
94
108
 
95
109
  # Merge a hash of variables in the current local scope
@@ -111,19 +125,31 @@ module Liquid
111
125
  # end
112
126
  #
113
127
  # context['var] #=> nil
114
- def stack(new_scope = nil)
115
- old_stack_used = @this_stack_used
116
- if new_scope
117
- push(new_scope)
118
- @this_stack_used = true
119
- else
120
- @this_stack_used = false
121
- end
122
-
128
+ def stack(new_scope = {})
129
+ push(new_scope)
123
130
  yield
124
131
  ensure
125
- pop if @this_stack_used
126
- @this_stack_used = old_stack_used
132
+ pop
133
+ end
134
+
135
+ # Creates a new context inheriting resource limits, filters, environment etc.,
136
+ # but with an isolated scope.
137
+ def new_isolated_subcontext
138
+ check_overflow
139
+
140
+ self.class.build(
141
+ resource_limits: resource_limits,
142
+ static_environments: static_environments,
143
+ registers: StaticRegisters.new(registers)
144
+ ).tap do |subcontext|
145
+ subcontext.base_scope_depth = base_scope_depth + 1
146
+ subcontext.exception_renderer = exception_renderer
147
+ subcontext.filters = @filters
148
+ subcontext.strainer = nil
149
+ subcontext.errors = errors
150
+ subcontext.warnings = warnings
151
+ subcontext.disabled_tags = @disabled_tags
152
+ end
127
153
  end
128
154
 
129
155
  def clear_instance_assigns
@@ -132,10 +158,6 @@ module Liquid
132
158
 
133
159
  # Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
134
160
  def []=(key, value)
135
- unless @this_stack_used
136
- @this_stack_used = true
137
- push({})
138
- end
139
161
  @scopes[0][key] = value
140
162
  end
141
163
 
@@ -160,49 +182,84 @@ module Liquid
160
182
  end
161
183
 
162
184
  # Fetches an object starting at the local scope and then moving up the hierachy
163
- def find_variable(key)
185
+ def find_variable(key, raise_on_not_found: true)
164
186
  # This was changed from find() to find_index() because this is a very hot
165
187
  # path and find_index() is optimized in MRI to reduce object allocation
166
188
  index = @scopes.find_index { |s| s.key?(key) }
167
- scope = @scopes[index] if index
168
-
169
- variable = nil
170
189
 
171
- if scope.nil?
172
- @environments.each do |e|
173
- variable = lookup_and_evaluate(e, key)
174
- unless variable.nil?
175
- scope = e
176
- break
177
- end
178
- end
190
+ variable = if index
191
+ lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
192
+ else
193
+ try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
179
194
  end
180
195
 
181
- scope ||= @environments.last || @scopes.last
182
- variable ||= lookup_and_evaluate(scope, key)
183
-
184
- variable = variable.to_liquid
196
+ variable = variable.to_liquid
185
197
  variable.context = self if variable.respond_to?(:context=)
186
198
 
187
199
  variable
188
200
  end
189
201
 
190
- def lookup_and_evaluate(obj, key)
191
- if @strict_variables && obj.respond_to?(:key?) && !obj.key?(key)
202
+ def lookup_and_evaluate(obj, key, raise_on_not_found: true)
203
+ if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
192
204
  raise Liquid::UndefinedVariable, "undefined variable #{key}"
193
205
  end
194
206
 
195
207
  value = obj[key]
196
208
 
197
209
  if value.is_a?(Proc) && obj.respond_to?(:[]=)
198
- obj[key] = (value.arity == 0) ? value.call : value.call(self)
210
+ obj[key] = value.arity == 0 ? value.call : value.call(self)
199
211
  else
200
212
  value
201
213
  end
202
214
  end
203
215
 
216
+ def with_disabled_tags(tag_names)
217
+ tag_names.each do |name|
218
+ @disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
219
+ end
220
+ yield
221
+ ensure
222
+ tag_names.each do |name|
223
+ @disabled_tags[name] -= 1
224
+ end
225
+ end
226
+
227
+ def tag_disabled?(tag_name)
228
+ @disabled_tags.fetch(tag_name, 0) > 0
229
+ end
230
+
231
+ protected
232
+
233
+ attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags
234
+
204
235
  private
205
236
 
237
+ attr_reader :base_scope_depth
238
+
239
+ def try_variable_find_in_environments(key, raise_on_not_found:)
240
+ @environments.each do |environment|
241
+ found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
242
+ if !found_variable.nil? || @strict_variables && raise_on_not_found
243
+ return found_variable
244
+ end
245
+ end
246
+ @static_environments.each do |environment|
247
+ found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
248
+ if !found_variable.nil? || @strict_variables && raise_on_not_found
249
+ return found_variable
250
+ end
251
+ end
252
+ nil
253
+ end
254
+
255
+ def check_overflow
256
+ raise StackLevelError, "Nesting too deep" if overflow?
257
+ end
258
+
259
+ def overflow?
260
+ base_scope_depth + @scopes.length > Block::MAX_DEPTH
261
+ end
262
+
206
263
  def internal_error
207
264
  # raise and catch to set backtrace and cause on exception
208
265
  raise Liquid::InternalError, 'internal'
@@ -1,26 +1,64 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
- class Document < BlockBody
4
+ class Document
3
5
  def self.parse(tokens, parse_context)
4
- doc = new
6
+ doc = new(parse_context)
5
7
  doc.parse(tokens, parse_context)
6
8
  doc
7
9
  end
8
10
 
9
- def parse(tokens, parse_context)
10
- super do |end_tag_name, end_tag_params|
11
- unknown_tag(end_tag_name, parse_context) if end_tag_name
11
+ attr_reader :parse_context, :body
12
+
13
+ def initialize(parse_context)
14
+ @parse_context = parse_context
15
+ @body = new_body
16
+ end
17
+
18
+ def nodelist
19
+ @body.nodelist
20
+ end
21
+
22
+ def parse(tokenizer, parse_context)
23
+ while parse_body(tokenizer)
12
24
  end
25
+ @body.freeze
13
26
  rescue SyntaxError => e
14
27
  e.line_number ||= parse_context.line_number
15
28
  raise
16
29
  end
17
30
 
18
- def unknown_tag(tag, parse_context)
31
+ def unknown_tag(tag, _markup, _tokenizer)
19
32
  case tag
20
- when 'else'.freeze, 'end'.freeze
21
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_outer_tag".freeze, tag: tag))
33
+ when 'else', 'end'
34
+ raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag", tag: tag)
22
35
  else
23
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag))
36
+ raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
37
+ end
38
+ end
39
+
40
+ def render_to_output_buffer(context, output)
41
+ @body.render_to_output_buffer(context, output)
42
+ end
43
+
44
+ def render(context)
45
+ render_to_output_buffer(context, +'')
46
+ end
47
+
48
+ private
49
+
50
+ def new_body
51
+ parse_context.new_block_body
52
+ end
53
+
54
+ def parse_body(tokenizer)
55
+ @body.parse(tokenizer, parse_context) do |unknown_tag_name, unknown_tag_markup|
56
+ if unknown_tag_name
57
+ unknown_tag(unknown_tag_name, unknown_tag_markup, tokenizer)
58
+ true
59
+ else
60
+ false
61
+ end
24
62
  end
25
63
  end
26
64
  end