liquid 3.0.6 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
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