liquid 3.0.6 → 5.4.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.
Files changed (113) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +243 -58
  3. data/README.md +43 -4
  4. data/lib/liquid/block.rb +57 -123
  5. data/lib/liquid/block_body.rb +217 -85
  6. data/lib/liquid/condition.rb +92 -45
  7. data/lib/liquid/context.rb +154 -89
  8. data/lib/liquid/document.rb +57 -9
  9. data/lib/liquid/drop.rb +20 -17
  10. data/lib/liquid/errors.rb +27 -29
  11. data/lib/liquid/expression.rb +32 -20
  12. data/lib/liquid/extensions.rb +21 -7
  13. data/lib/liquid/file_system.rb +17 -15
  14. data/lib/liquid/forloop_drop.rb +92 -0
  15. data/lib/liquid/i18n.rb +10 -8
  16. data/lib/liquid/interrupts.rb +4 -3
  17. data/lib/liquid/lexer.rb +37 -26
  18. data/lib/liquid/locales/en.yml +13 -6
  19. data/lib/liquid/parse_context.rb +54 -0
  20. data/lib/liquid/parse_tree_visitor.rb +42 -0
  21. data/lib/liquid/parser.rb +30 -18
  22. data/lib/liquid/parser_switching.rb +20 -6
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +26 -14
  25. data/lib/liquid/profiler.rb +72 -92
  26. data/lib/liquid/range_lookup.rb +28 -3
  27. data/lib/liquid/registers.rb +51 -0
  28. data/lib/liquid/resource_limits.rb +62 -0
  29. data/lib/liquid/standardfilters.rb +715 -132
  30. data/lib/liquid/strainer_factory.rb +41 -0
  31. data/lib/liquid/strainer_template.rb +62 -0
  32. data/lib/liquid/tablerowloop_drop.rb +121 -0
  33. data/lib/liquid/tag/disableable.rb +22 -0
  34. data/lib/liquid/tag/disabler.rb +21 -0
  35. data/lib/liquid/tag.rb +35 -12
  36. data/lib/liquid/tags/assign.rb +57 -18
  37. data/lib/liquid/tags/break.rb +15 -5
  38. data/lib/liquid/tags/capture.rb +24 -18
  39. data/lib/liquid/tags/case.rb +79 -30
  40. data/lib/liquid/tags/comment.rb +19 -4
  41. data/lib/liquid/tags/continue.rb +16 -12
  42. data/lib/liquid/tags/cycle.rb +47 -27
  43. data/lib/liquid/tags/decrement.rb +23 -24
  44. data/lib/liquid/tags/echo.rb +41 -0
  45. data/lib/liquid/tags/for.rb +155 -124
  46. data/lib/liquid/tags/if.rb +97 -63
  47. data/lib/liquid/tags/ifchanged.rb +11 -12
  48. data/lib/liquid/tags/include.rb +82 -73
  49. data/lib/liquid/tags/increment.rb +23 -17
  50. data/lib/liquid/tags/inline_comment.rb +43 -0
  51. data/lib/liquid/tags/raw.rb +50 -8
  52. data/lib/liquid/tags/render.rb +109 -0
  53. data/lib/liquid/tags/table_row.rb +57 -41
  54. data/lib/liquid/tags/unless.rb +38 -20
  55. data/lib/liquid/template.rb +71 -103
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +39 -0
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +63 -9
  60. data/lib/liquid/variable.rb +74 -56
  61. data/lib/liquid/variable_lookup.rb +31 -15
  62. data/lib/liquid/version.rb +3 -1
  63. data/lib/liquid.rb +27 -12
  64. metadata +30 -106
  65. data/lib/liquid/module_ex.rb +0 -62
  66. data/lib/liquid/strainer.rb +0 -59
  67. data/lib/liquid/token.rb +0 -18
  68. data/test/fixtures/en_locale.yml +0 -9
  69. data/test/integration/assign_test.rb +0 -48
  70. data/test/integration/blank_test.rb +0 -106
  71. data/test/integration/capture_test.rb +0 -50
  72. data/test/integration/context_test.rb +0 -32
  73. data/test/integration/drop_test.rb +0 -271
  74. data/test/integration/error_handling_test.rb +0 -207
  75. data/test/integration/filter_test.rb +0 -138
  76. data/test/integration/hash_ordering_test.rb +0 -23
  77. data/test/integration/output_test.rb +0 -124
  78. data/test/integration/parsing_quirks_test.rb +0 -116
  79. data/test/integration/render_profiling_test.rb +0 -154
  80. data/test/integration/security_test.rb +0 -64
  81. data/test/integration/standard_filter_test.rb +0 -396
  82. data/test/integration/tags/break_tag_test.rb +0 -16
  83. data/test/integration/tags/continue_tag_test.rb +0 -16
  84. data/test/integration/tags/for_tag_test.rb +0 -375
  85. data/test/integration/tags/if_else_tag_test.rb +0 -190
  86. data/test/integration/tags/include_tag_test.rb +0 -234
  87. data/test/integration/tags/increment_tag_test.rb +0 -24
  88. data/test/integration/tags/raw_tag_test.rb +0 -25
  89. data/test/integration/tags/standard_tag_test.rb +0 -297
  90. data/test/integration/tags/statements_test.rb +0 -113
  91. data/test/integration/tags/table_row_test.rb +0 -63
  92. data/test/integration/tags/unless_else_tag_test.rb +0 -26
  93. data/test/integration/template_test.rb +0 -182
  94. data/test/integration/variable_test.rb +0 -82
  95. data/test/test_helper.rb +0 -89
  96. data/test/unit/block_unit_test.rb +0 -55
  97. data/test/unit/condition_unit_test.rb +0 -149
  98. data/test/unit/context_unit_test.rb +0 -492
  99. data/test/unit/file_system_unit_test.rb +0 -35
  100. data/test/unit/i18n_unit_test.rb +0 -37
  101. data/test/unit/lexer_unit_test.rb +0 -48
  102. data/test/unit/module_ex_unit_test.rb +0 -87
  103. data/test/unit/parser_unit_test.rb +0 -82
  104. data/test/unit/regexp_unit_test.rb +0 -44
  105. data/test/unit/strainer_unit_test.rb +0 -69
  106. data/test/unit/tag_unit_test.rb +0 -16
  107. data/test/unit/tags/case_tag_unit_test.rb +0 -10
  108. data/test/unit/tags/for_tag_unit_test.rb +0 -13
  109. data/test/unit/tags/if_tag_unit_test.rb +0 -8
  110. data/test/unit/template_unit_test.rb +0 -69
  111. data/test/unit/tokenizer_unit_test.rb +0 -38
  112. data/test/unit/variable_unit_test.rb +0 -145
  113. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -1,60 +1,92 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Container for liquid nodes which conveniently wraps decision making logic
3
5
  #
4
6
  # Example:
5
7
  #
6
- # c = Condition.new('1', '==', '1')
8
+ # c = Condition.new(1, '==', 1)
7
9
  # c.evaluate #=> true
8
10
  #
9
- class Condition #:nodoc:
11
+ class Condition # :nodoc:
10
12
  @@operators = {
11
- '=='.freeze => lambda { |cond, left, right| cond.send(:equal_variables, left, right) },
12
- '!='.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
13
- '<>'.freeze => lambda { |cond, left, right| !cond.send(:equal_variables, left, right) },
14
- '<'.freeze => :<,
15
- '>'.freeze => :>,
16
- '>='.freeze => :>=,
17
- '<='.freeze => :<=,
18
- 'contains'.freeze => lambda { |cond, left, right|
19
- left && right && left.respond_to?(:include?) ? left.include?(right) : false
20
- }
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|
21
+ if left && right && left.respond_to?(:include?)
22
+ right = right.to_s if left.is_a?(String)
23
+ left.include?(right)
24
+ else
25
+ false
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
36
+ end
37
+ end
38
+
39
+ @@method_literals = {
40
+ 'blank' => MethodLiteral.new(:blank?, '').freeze,
41
+ 'empty' => MethodLiteral.new(:empty?, '').freeze,
21
42
  }
22
43
 
23
44
  def self.operators
24
45
  @@operators
25
46
  end
26
47
 
27
- 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
28
53
  attr_accessor :left, :operator, :right
29
54
 
30
55
  def initialize(left = nil, operator = nil, right = nil)
31
- @left = left
56
+ @left = left
32
57
  @operator = operator
33
- @right = right
58
+ @right = right
59
+
34
60
  @child_relation = nil
35
61
  @child_condition = nil
36
62
  end
37
63
 
38
- def evaluate(context = Context.new)
39
- result = interpret_condition(left, right, operator, context)
40
-
41
- case @child_relation
42
- when :or
43
- result || @child_condition.evaluate(context)
44
- when :and
45
- result && @child_condition.evaluate(context)
46
- else
47
- result
64
+ def evaluate(context = deprecated_default_context)
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
48
79
  end
80
+ result
49
81
  end
50
82
 
51
83
  def or(condition)
52
- @child_relation = :or
84
+ @child_relation = :or
53
85
  @child_condition = condition
54
86
  end
55
87
 
56
88
  def and(condition)
57
- @child_relation = :and
89
+ @child_relation = :and
58
90
  @child_condition = condition
59
91
  end
60
92
 
@@ -67,23 +99,27 @@ module Liquid
67
99
  end
68
100
 
69
101
  def inspect
70
- "#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
102
+ "#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
71
103
  end
72
104
 
105
+ protected
106
+
107
+ attr_reader :child_relation
108
+
73
109
  private
74
110
 
75
111
  def equal_variables(left, right)
76
- if left.is_a?(Symbol)
77
- if right.respond_to?(left)
78
- return right.send(left.to_s)
112
+ if left.is_a?(MethodLiteral)
113
+ if right.respond_to?(left.method_name)
114
+ return right.send(left.method_name)
79
115
  else
80
116
  return nil
81
117
  end
82
118
  end
83
119
 
84
- if right.is_a?(Symbol)
85
- if left.respond_to?(right)
86
- return left.send(right.to_s)
120
+ if right.is_a?(MethodLiteral)
121
+ if left.respond_to?(right.method_name)
122
+ return left.send(right.method_name)
87
123
  else
88
124
  return nil
89
125
  end
@@ -96,36 +132,47 @@ module Liquid
96
132
  # If the operator is empty this means that the decision statement is just
97
133
  # a single variable. We can just poll this variable from the context and
98
134
  # return this as the result.
99
- return context[left] if op == nil
135
+ return context.evaluate(left) if op.nil?
100
136
 
101
- left = context[left]
102
- right = context[right]
137
+ left = Liquid::Utils.to_liquid_value(context.evaluate(left))
138
+ right = Liquid::Utils.to_liquid_value(context.evaluate(right))
103
139
 
104
- operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))
140
+ operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")
105
141
 
106
142
  if operation.respond_to?(:call)
107
143
  operation.call(self, left, right)
108
- elsif left.respond_to?(operation) and right.respond_to?(operation)
144
+ elsif left.respond_to?(operation) && right.respond_to?(operation) && !left.is_a?(Hash) && !right.is_a?(Hash)
109
145
  begin
110
146
  left.send(operation, right)
111
147
  rescue ::ArgumentError => e
112
- raise Liquid::ArgumentError.new(e.message)
148
+ raise Liquid::ArgumentError, e.message
113
149
  end
114
- else
115
- nil
116
150
  end
117
151
  end
118
- end
119
152
 
153
+ def deprecated_default_context
154
+ warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated" \
155
+ " and will be removed from Liquid 6.0.0.")
156
+ Context.new
157
+ end
158
+
159
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
160
+ def children
161
+ [
162
+ @node.left, @node.right,
163
+ @node.child_condition, @node.attachment
164
+ ].compact
165
+ end
166
+ end
167
+ end
120
168
 
121
169
  class ElseCondition < Condition
122
170
  def else?
123
171
  true
124
172
  end
125
173
 
126
- def evaluate(context)
174
+ def evaluate(_context)
127
175
  true
128
176
  end
129
177
  end
130
-
131
178
  end
@@ -1,5 +1,6 @@
1
- module Liquid
1
+ # frozen_string_literal: true
2
2
 
3
+ module Liquid
3
4
  # Context keeps the variable stack and resolves variables, as well as keywords
4
5
  #
5
6
  # context['variable'] = 'testing'
@@ -13,46 +14,53 @@ module Liquid
13
14
  #
14
15
  # context['bob'] #=> nil class Context
15
16
  class Context
16
- attr_reader :scopes, :errors, :registers, :environments, :resource_limits
17
- attr_accessor :exception_handler
18
-
19
- def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
20
- @environments = [environments].flatten
21
- @scopes = [(outer_scope || {})]
22
- @registers = registers
23
- @errors = []
24
- @resource_limits = resource_limits || Template.default_resource_limits.dup
25
- @resource_limits[:render_score_current] = 0
26
- @resource_limits[:assign_score_current] = 0
27
- @parsed_expression = Hash.new{ |cache, markup| cache[markup] = Expression.parse(markup) }
28
- squash_instance_assigns_with_environments
17
+ attr_reader :scopes, :errors, :registers, :environments, :resource_limits, :static_registers, :static_environments
18
+ attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
29
19
 
30
- @this_stack_used = false
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
31
24
 
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.is_a?(Registers) ? registers : Registers.new(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 = {}
41
+
42
+ @registers.static[:cached_partials] ||= {}
43
+ @registers.static[:file_system] ||= Liquid::Template.file_system
44
+ @registers.static[:template_factory] ||= Liquid::TemplateFactory.new
45
+
46
+ self.exception_renderer = Template.default_exception_renderer
32
47
  if rethrow_errors
33
- self.exception_handler = ->(e) { true }
48
+ self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
34
49
  end
35
50
 
36
- @interrupts = []
37
- @filters = []
38
- end
51
+ yield self if block_given?
39
52
 
40
- def increment_used_resources(key, obj)
41
- @resource_limits[key] += if obj.kind_of?(String) || obj.kind_of?(Array) || obj.kind_of?(Hash)
42
- obj.length
43
- else
44
- 1
45
- end
53
+ # Do this last, since it could result in this object being passed to a Proc in the environment
54
+ squash_instance_assigns_with_environments
46
55
  end
56
+ # rubocop:enable Metrics/ParameterLists
47
57
 
48
- def resource_limits_reached?
49
- (@resource_limits[:render_length_limit] && @resource_limits[:render_length_current] > @resource_limits[:render_length_limit]) ||
50
- (@resource_limits[:render_score_limit] && @resource_limits[:render_score_current] > @resource_limits[:render_score_limit] ) ||
51
- (@resource_limits[:assign_score_limit] && @resource_limits[:assign_score_current] > @resource_limits[:assign_score_limit] )
58
+ def warnings
59
+ @warnings ||= []
52
60
  end
53
61
 
54
62
  def strainer
55
- @strainer ||= Strainer.create(self, @filters)
63
+ @strainer ||= StrainerFactory.create(self, @filters)
56
64
  end
57
65
 
58
66
  # Adds filters to this context.
@@ -65,8 +73,12 @@ module Liquid
65
73
  @strainer = nil
66
74
  end
67
75
 
76
+ def apply_global_filter(obj)
77
+ global_filter.nil? ? obj : global_filter.call(obj)
78
+ end
79
+
68
80
  # are there any not handled interrupts?
69
- def has_interrupt?
81
+ def interrupt?
70
82
  !@interrupts.empty?
71
83
  end
72
84
 
@@ -80,15 +92,12 @@ module Liquid
80
92
  @interrupts.pop
81
93
  end
82
94
 
83
-
84
- def handle_error(e, token=nil)
85
- if e.is_a?(Liquid::Error)
86
- e.set_line_number_from_token(token)
87
- end
88
-
95
+ def handle_error(e, line_number = nil)
96
+ e = internal_error unless e.is_a?(Liquid::Error)
97
+ e.template_name ||= template_name
98
+ e.line_number ||= line_number
89
99
  errors.push(e)
90
- raise if exception_handler && exception_handler.call(e)
91
- Liquid::Error.render(e)
100
+ exception_renderer.call(e).to_s
92
101
  end
93
102
 
94
103
  def invoke(method, *args)
@@ -96,9 +105,9 @@ module Liquid
96
105
  end
97
106
 
98
107
  # Push new local scope on the stack. use <tt>Context#stack</tt> instead
99
- def push(new_scope={})
108
+ def push(new_scope = {})
100
109
  @scopes.unshift(new_scope)
101
- raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100
110
+ check_overflow
102
111
  end
103
112
 
104
113
  # Merge a hash of variables in the current local scope
@@ -119,20 +128,32 @@ module Liquid
119
128
  # context['var'] = 'hi'
120
129
  # end
121
130
  #
122
- # context['var] #=> nil
123
- def stack(new_scope=nil)
124
- old_stack_used = @this_stack_used
125
- if new_scope
126
- push(new_scope)
127
- @this_stack_used = true
128
- else
129
- @this_stack_used = false
130
- end
131
-
131
+ # context['var'] #=> nil
132
+ def stack(new_scope = {})
133
+ push(new_scope)
132
134
  yield
133
135
  ensure
134
- pop if @this_stack_used
135
- @this_stack_used = old_stack_used
136
+ pop
137
+ end
138
+
139
+ # Creates a new context inheriting resource limits, filters, environment etc.,
140
+ # but with an isolated scope.
141
+ def new_isolated_subcontext
142
+ check_overflow
143
+
144
+ self.class.build(
145
+ resource_limits: resource_limits,
146
+ static_environments: static_environments,
147
+ registers: Registers.new(registers)
148
+ ).tap do |subcontext|
149
+ subcontext.base_scope_depth = base_scope_depth + 1
150
+ subcontext.exception_renderer = exception_renderer
151
+ subcontext.filters = @filters
152
+ subcontext.strainer = nil
153
+ subcontext.errors = errors
154
+ subcontext.warnings = warnings
155
+ subcontext.disabled_tags = @disabled_tags
156
+ end
136
157
  end
137
158
 
138
159
  def clear_instance_assigns
@@ -141,10 +162,6 @@ module Liquid
141
162
 
142
163
  # Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
143
164
  def []=(key, value)
144
- unless @this_stack_used
145
- @this_stack_used = true
146
- push({})
147
- end
148
165
  @scopes[0][key] = value
149
166
  end
150
167
 
@@ -157,10 +174,10 @@ module Liquid
157
174
  # Example:
158
175
  # products == empty #=> products.empty?
159
176
  def [](expression)
160
- evaluate(@parsed_expression[expression])
177
+ evaluate(Expression.parse(expression))
161
178
  end
162
179
 
163
- def has_key?(key)
180
+ def key?(key)
164
181
  self[key] != nil
165
182
  end
166
183
 
@@ -169,52 +186,100 @@ module Liquid
169
186
  end
170
187
 
171
188
  # Fetches an object starting at the local scope and then moving up the hierachy
172
- def find_variable(key)
173
-
189
+ def find_variable(key, raise_on_not_found: true)
174
190
  # This was changed from find() to find_index() because this is a very hot
175
191
  # path and find_index() is optimized in MRI to reduce object allocation
176
- index = @scopes.find_index { |s| s.has_key?(key) }
177
- scope = @scopes[index] if index
192
+ index = @scopes.find_index { |s| s.key?(key) }
178
193
 
179
- variable = nil
180
-
181
- if scope.nil?
182
- @environments.each do |e|
183
- variable = lookup_and_evaluate(e, key)
184
- unless variable.nil?
185
- scope = e
186
- break
187
- end
188
- end
194
+ variable = if index
195
+ lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
196
+ else
197
+ try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
189
198
  end
190
199
 
191
- scope ||= @environments.last || @scopes.last
192
- variable ||= lookup_and_evaluate(scope, key)
193
-
194
- variable = variable.to_liquid
200
+ variable = variable.to_liquid
195
201
  variable.context = self if variable.respond_to?(:context=)
196
202
 
197
- return variable
203
+ variable
198
204
  end
199
205
 
200
- def lookup_and_evaluate(obj, key)
201
- if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
202
- obj[key] = (value.arity == 0) ? value.call : value.call(self)
206
+ def lookup_and_evaluate(obj, key, raise_on_not_found: true)
207
+ if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
208
+ raise Liquid::UndefinedVariable, "undefined variable #{key}"
209
+ end
210
+
211
+ value = obj[key]
212
+
213
+ if value.is_a?(Proc) && obj.respond_to?(:[]=)
214
+ obj[key] = value.arity == 0 ? value.call : value.call(self)
203
215
  else
204
216
  value
205
217
  end
206
218
  end
207
219
 
220
+ def with_disabled_tags(tag_names)
221
+ tag_names.each do |name|
222
+ @disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
223
+ end
224
+ yield
225
+ ensure
226
+ tag_names.each do |name|
227
+ @disabled_tags[name] -= 1
228
+ end
229
+ end
230
+
231
+ def tag_disabled?(tag_name)
232
+ @disabled_tags.fetch(tag_name, 0) > 0
233
+ end
234
+
235
+ protected
236
+
237
+ attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags
238
+
208
239
  private
209
- def squash_instance_assigns_with_environments
210
- @scopes.last.each_key do |k|
211
- @environments.each do |env|
212
- if env.has_key?(k)
213
- scopes.last[k] = lookup_and_evaluate(env, k)
214
- break
215
- end
240
+
241
+ attr_reader :base_scope_depth
242
+
243
+ def try_variable_find_in_environments(key, raise_on_not_found:)
244
+ @environments.each do |environment|
245
+ found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
246
+ if !found_variable.nil? || @strict_variables && raise_on_not_found
247
+ return found_variable
248
+ end
249
+ end
250
+ @static_environments.each do |environment|
251
+ found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
252
+ if !found_variable.nil? || @strict_variables && raise_on_not_found
253
+ return found_variable
254
+ end
255
+ end
256
+ nil
257
+ end
258
+
259
+ def check_overflow
260
+ raise StackLevelError, "Nesting too deep" if overflow?
261
+ end
262
+
263
+ def overflow?
264
+ base_scope_depth + @scopes.length > Block::MAX_DEPTH
265
+ end
266
+
267
+ def internal_error
268
+ # raise and catch to set backtrace and cause on exception
269
+ raise Liquid::InternalError, 'internal'
270
+ rescue Liquid::InternalError => exc
271
+ exc
272
+ end
273
+
274
+ def squash_instance_assigns_with_environments
275
+ @scopes.last.each_key do |k|
276
+ @environments.each do |env|
277
+ if env.key?(k)
278
+ scopes.last[k] = lookup_and_evaluate(env, k)
279
+ break
216
280
  end
217
281
  end
218
- end # squash_instance_assigns_with_environments
282
+ end
283
+ end # squash_instance_assigns_with_environments
219
284
  end # Context
220
285
  end # Liquid
@@ -1,17 +1,65 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
- class Document < Block
3
- def self.parse(tokens, options={})
4
- # we don't need markup to open this block
5
- super(nil, nil, tokens, options)
4
+ class Document
5
+ def self.parse(tokens, parse_context)
6
+ doc = new(parse_context)
7
+ doc.parse(tokens, parse_context)
8
+ doc
9
+ end
10
+
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)
24
+ end
25
+ @body.freeze
26
+ rescue SyntaxError => e
27
+ e.line_number ||= parse_context.line_number
28
+ raise
6
29
  end
7
30
 
8
- # There isn't a real delimiter
9
- def block_delimiter
10
- []
31
+ def unknown_tag(tag, _markup, _tokenizer)
32
+ case tag
33
+ when 'else', 'end'
34
+ raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag", tag: tag)
35
+ else
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
11
52
  end
12
53
 
13
- # Document blocks don't need to be terminated since they are not actually opened
14
- def assert_missing_delimitation!
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
62
+ end
15
63
  end
16
64
  end
17
65
  end