liquid 4.0.0 → 5.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/History.md +235 -2
- data/README.md +58 -8
- data/lib/liquid/block.rb +51 -20
- data/lib/liquid/block_body.rb +216 -82
- data/lib/liquid/condition.rb +83 -32
- data/lib/liquid/const.rb +8 -0
- data/lib/liquid/context.rb +130 -59
- data/lib/liquid/deprecations.rb +22 -0
- data/lib/liquid/document.rb +47 -9
- data/lib/liquid/drop.rb +8 -2
- data/lib/liquid/environment.rb +159 -0
- data/lib/liquid/errors.rb +23 -20
- data/lib/liquid/expression.rb +114 -31
- data/lib/liquid/extensions.rb +8 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +51 -4
- data/lib/liquid/i18n.rb +5 -3
- data/lib/liquid/interrupts.rb +3 -1
- data/lib/liquid/lexer.rb +165 -39
- data/lib/liquid/locales/en.yml +16 -6
- data/lib/liquid/parse_context.rb +62 -7
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser.rb +31 -19
- data/lib/liquid/parser_switching.rb +42 -3
- data/lib/liquid/partial_cache.rb +33 -0
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/profiler.rb +67 -86
- data/lib/liquid/range_lookup.rb +26 -6
- data/lib/liquid/registers.rb +51 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/snippet_drop.rb +22 -0
- data/lib/liquid/standardfilters.rb +813 -137
- data/lib/liquid/strainer_template.rb +62 -0
- data/lib/liquid/tablerowloop_drop.rb +64 -5
- data/lib/liquid/tag/disableable.rb +22 -0
- data/lib/liquid/tag/disabler.rb +13 -0
- data/lib/liquid/tag.rb +42 -6
- data/lib/liquid/tags/assign.rb +46 -18
- data/lib/liquid/tags/break.rb +15 -4
- data/lib/liquid/tags/capture.rb +26 -18
- data/lib/liquid/tags/case.rb +108 -32
- data/lib/liquid/tags/comment.rb +76 -4
- data/lib/liquid/tags/continue.rb +15 -13
- data/lib/liquid/tags/cycle.rb +117 -34
- data/lib/liquid/tags/decrement.rb +30 -23
- data/lib/liquid/tags/doc.rb +81 -0
- data/lib/liquid/tags/echo.rb +39 -0
- data/lib/liquid/tags/for.rb +109 -96
- data/lib/liquid/tags/if.rb +72 -41
- data/lib/liquid/tags/ifchanged.rb +10 -11
- data/lib/liquid/tags/include.rb +89 -63
- data/lib/liquid/tags/increment.rb +31 -20
- data/lib/liquid/tags/inline_comment.rb +28 -0
- data/lib/liquid/tags/raw.rb +25 -13
- data/lib/liquid/tags/render.rb +151 -0
- data/lib/liquid/tags/snippet.rb +45 -0
- data/lib/liquid/tags/table_row.rb +104 -21
- data/lib/liquid/tags/unless.rb +37 -20
- data/lib/liquid/tags.rb +51 -0
- data/lib/liquid/template.rb +90 -106
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +143 -13
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +114 -5
- data/lib/liquid/variable.rb +119 -45
- data/lib/liquid/variable_lookup.rb +35 -13
- data/lib/liquid/version.rb +3 -1
- data/lib/liquid.rb +31 -18
- metadata +56 -107
- data/lib/liquid/strainer.rb +0 -66
- data/test/fixtures/en_locale.yml +0 -9
- data/test/integration/assign_test.rb +0 -48
- data/test/integration/blank_test.rb +0 -106
- data/test/integration/capture_test.rb +0 -50
- data/test/integration/context_test.rb +0 -32
- data/test/integration/document_test.rb +0 -19
- data/test/integration/drop_test.rb +0 -273
- data/test/integration/error_handling_test.rb +0 -260
- data/test/integration/filter_test.rb +0 -178
- data/test/integration/hash_ordering_test.rb +0 -23
- data/test/integration/output_test.rb +0 -123
- data/test/integration/parsing_quirks_test.rb +0 -118
- data/test/integration/render_profiling_test.rb +0 -154
- data/test/integration/security_test.rb +0 -66
- data/test/integration/standard_filter_test.rb +0 -535
- data/test/integration/tags/break_tag_test.rb +0 -15
- data/test/integration/tags/continue_tag_test.rb +0 -15
- data/test/integration/tags/for_tag_test.rb +0 -410
- data/test/integration/tags/if_else_tag_test.rb +0 -188
- data/test/integration/tags/include_tag_test.rb +0 -238
- data/test/integration/tags/increment_tag_test.rb +0 -23
- data/test/integration/tags/raw_tag_test.rb +0 -31
- data/test/integration/tags/standard_tag_test.rb +0 -296
- data/test/integration/tags/statements_test.rb +0 -111
- data/test/integration/tags/table_row_test.rb +0 -64
- data/test/integration/tags/unless_else_tag_test.rb +0 -26
- data/test/integration/template_test.rb +0 -323
- data/test/integration/trim_mode_test.rb +0 -525
- data/test/integration/variable_test.rb +0 -92
- data/test/test_helper.rb +0 -117
- data/test/unit/block_unit_test.rb +0 -58
- data/test/unit/condition_unit_test.rb +0 -158
- data/test/unit/context_unit_test.rb +0 -483
- data/test/unit/file_system_unit_test.rb +0 -35
- data/test/unit/i18n_unit_test.rb +0 -37
- data/test/unit/lexer_unit_test.rb +0 -51
- data/test/unit/parser_unit_test.rb +0 -82
- data/test/unit/regexp_unit_test.rb +0 -44
- data/test/unit/strainer_unit_test.rb +0 -148
- data/test/unit/tag_unit_test.rb +0 -21
- data/test/unit/tags/case_tag_unit_test.rb +0 -10
- data/test/unit/tags/for_tag_unit_test.rb +0 -13
- data/test/unit/tags/if_tag_unit_test.rb +0 -8
- data/test/unit/template_unit_test.rb +0 -78
- data/test/unit/tokenizer_unit_test.rb +0 -55
- data/test/unit/variable_unit_test.rb +0 -162
data/lib/liquid/context.rb
CHANGED
|
@@ -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,58 @@ module Liquid
|
|
|
12
14
|
#
|
|
13
15
|
# context['bob'] #=> nil class Context
|
|
14
16
|
class Context
|
|
15
|
-
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
|
16
|
-
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
|
|
17
|
-
|
|
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
|
|
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, :environment
|
|
27
19
|
|
|
28
|
-
|
|
20
|
+
# rubocop:disable Metrics/ParameterLists
|
|
21
|
+
def self.build(environment: Environment.default, 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, environment, &block)
|
|
23
|
+
end
|
|
29
24
|
|
|
30
|
-
|
|
25
|
+
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {}, environment = Environment.default)
|
|
26
|
+
@environment = environment
|
|
27
|
+
@environments = [environments]
|
|
28
|
+
@environments.flatten!
|
|
29
|
+
|
|
30
|
+
@static_environments = [static_environments].flatten(1).freeze
|
|
31
|
+
@scopes = [outer_scope || {}]
|
|
32
|
+
@registers = registers.is_a?(Registers) ? registers : Registers.new(registers)
|
|
33
|
+
@errors = []
|
|
34
|
+
@partial = false
|
|
35
|
+
@strict_variables = false
|
|
36
|
+
@resource_limits = resource_limits || ResourceLimits.new(environment.default_resource_limits)
|
|
37
|
+
@base_scope_depth = 0
|
|
38
|
+
@interrupts = []
|
|
39
|
+
@filters = []
|
|
40
|
+
@global_filter = nil
|
|
41
|
+
@disabled_tags = {}
|
|
42
|
+
|
|
43
|
+
# Instead of constructing new StringScanner objects for each Expression parse,
|
|
44
|
+
# we recycle the same one.
|
|
45
|
+
@string_scanner = StringScanner.new("")
|
|
46
|
+
|
|
47
|
+
@registers.static[:cached_partials] ||= {}
|
|
48
|
+
@registers.static[:file_system] ||= environment.file_system
|
|
49
|
+
@registers.static[:template_factory] ||= Liquid::TemplateFactory.new
|
|
50
|
+
|
|
51
|
+
self.exception_renderer = environment.exception_renderer
|
|
31
52
|
if rethrow_errors
|
|
32
|
-
self.exception_renderer =
|
|
53
|
+
self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
|
|
33
54
|
end
|
|
34
55
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
yield self if block_given?
|
|
57
|
+
|
|
58
|
+
# Do this last, since it could result in this object being passed to a Proc in the environment
|
|
59
|
+
squash_instance_assigns_with_environments
|
|
38
60
|
end
|
|
61
|
+
# rubocop:enable Metrics/ParameterLists
|
|
39
62
|
|
|
40
63
|
def warnings
|
|
41
64
|
@warnings ||= []
|
|
42
65
|
end
|
|
43
66
|
|
|
44
67
|
def strainer
|
|
45
|
-
@strainer ||=
|
|
68
|
+
@strainer ||= @environment.create_strainer(self, @filters)
|
|
46
69
|
end
|
|
47
70
|
|
|
48
71
|
# Adds filters to this context.
|
|
@@ -74,10 +97,10 @@ module Liquid
|
|
|
74
97
|
@interrupts.pop
|
|
75
98
|
end
|
|
76
99
|
|
|
77
|
-
def handle_error(e, line_number = nil
|
|
100
|
+
def handle_error(e, line_number = nil)
|
|
78
101
|
e = internal_error unless e.is_a?(Liquid::Error)
|
|
79
102
|
e.template_name ||= template_name
|
|
80
|
-
e.line_number
|
|
103
|
+
e.line_number ||= line_number
|
|
81
104
|
errors.push(e)
|
|
82
105
|
exception_renderer.call(e).to_s
|
|
83
106
|
end
|
|
@@ -89,7 +112,7 @@ module Liquid
|
|
|
89
112
|
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
|
90
113
|
def push(new_scope = {})
|
|
91
114
|
@scopes.unshift(new_scope)
|
|
92
|
-
|
|
115
|
+
check_overflow
|
|
93
116
|
end
|
|
94
117
|
|
|
95
118
|
# Merge a hash of variables in the current local scope
|
|
@@ -110,20 +133,33 @@ module Liquid
|
|
|
110
133
|
# context['var'] = 'hi'
|
|
111
134
|
# end
|
|
112
135
|
#
|
|
113
|
-
# context['var] #=> nil
|
|
114
|
-
def stack(new_scope =
|
|
115
|
-
|
|
116
|
-
if new_scope
|
|
117
|
-
push(new_scope)
|
|
118
|
-
@this_stack_used = true
|
|
119
|
-
else
|
|
120
|
-
@this_stack_used = false
|
|
121
|
-
end
|
|
122
|
-
|
|
136
|
+
# context['var'] #=> nil
|
|
137
|
+
def stack(new_scope = {})
|
|
138
|
+
push(new_scope)
|
|
123
139
|
yield
|
|
124
140
|
ensure
|
|
125
|
-
pop
|
|
126
|
-
|
|
141
|
+
pop
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Creates a new context inheriting resource limits, filters, environment etc.,
|
|
145
|
+
# but with an isolated scope.
|
|
146
|
+
def new_isolated_subcontext
|
|
147
|
+
check_overflow
|
|
148
|
+
|
|
149
|
+
self.class.build(
|
|
150
|
+
environment: @environment,
|
|
151
|
+
resource_limits: resource_limits,
|
|
152
|
+
static_environments: static_environments,
|
|
153
|
+
registers: Registers.new(registers),
|
|
154
|
+
).tap do |subcontext|
|
|
155
|
+
subcontext.base_scope_depth = base_scope_depth + 1
|
|
156
|
+
subcontext.exception_renderer = exception_renderer
|
|
157
|
+
subcontext.filters = @filters
|
|
158
|
+
subcontext.strainer = nil
|
|
159
|
+
subcontext.errors = errors
|
|
160
|
+
subcontext.warnings = warnings
|
|
161
|
+
subcontext.disabled_tags = @disabled_tags
|
|
162
|
+
end
|
|
127
163
|
end
|
|
128
164
|
|
|
129
165
|
def clear_instance_assigns
|
|
@@ -132,10 +168,6 @@ module Liquid
|
|
|
132
168
|
|
|
133
169
|
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
|
134
170
|
def []=(key, value)
|
|
135
|
-
unless @this_stack_used
|
|
136
|
-
@this_stack_used = true
|
|
137
|
-
push({})
|
|
138
|
-
end
|
|
139
171
|
@scopes[0][key] = value
|
|
140
172
|
end
|
|
141
173
|
|
|
@@ -148,7 +180,7 @@ module Liquid
|
|
|
148
180
|
# Example:
|
|
149
181
|
# products == empty #=> products.empty?
|
|
150
182
|
def [](expression)
|
|
151
|
-
evaluate(Expression.parse(expression))
|
|
183
|
+
evaluate(Expression.parse(expression, @string_scanner))
|
|
152
184
|
end
|
|
153
185
|
|
|
154
186
|
def key?(key)
|
|
@@ -160,49 +192,88 @@ module Liquid
|
|
|
160
192
|
end
|
|
161
193
|
|
|
162
194
|
# Fetches an object starting at the local scope and then moving up the hierachy
|
|
163
|
-
def find_variable(key)
|
|
195
|
+
def find_variable(key, raise_on_not_found: true)
|
|
164
196
|
# This was changed from find() to find_index() because this is a very hot
|
|
165
197
|
# path and find_index() is optimized in MRI to reduce object allocation
|
|
166
198
|
index = @scopes.find_index { |s| s.key?(key) }
|
|
167
|
-
scope = @scopes[index] if index
|
|
168
|
-
|
|
169
|
-
variable = nil
|
|
170
199
|
|
|
171
|
-
if
|
|
172
|
-
@
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
scope = e
|
|
176
|
-
break
|
|
177
|
-
end
|
|
178
|
-
end
|
|
200
|
+
variable = if index
|
|
201
|
+
lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
|
|
202
|
+
else
|
|
203
|
+
try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
|
|
179
204
|
end
|
|
180
205
|
|
|
181
|
-
|
|
182
|
-
variable ||= lookup_and_evaluate(scope, key)
|
|
183
|
-
|
|
184
|
-
variable = variable.to_liquid
|
|
206
|
+
# update variable's context before invoking #to_liquid
|
|
185
207
|
variable.context = self if variable.respond_to?(:context=)
|
|
186
208
|
|
|
187
|
-
variable
|
|
209
|
+
liquid_variable = variable.to_liquid
|
|
210
|
+
|
|
211
|
+
liquid_variable.context = self if variable != liquid_variable && liquid_variable.respond_to?(:context=)
|
|
212
|
+
|
|
213
|
+
liquid_variable
|
|
188
214
|
end
|
|
189
215
|
|
|
190
|
-
def lookup_and_evaluate(obj, key)
|
|
191
|
-
if @strict_variables && obj.respond_to?(:key?) && !obj.key?(key)
|
|
216
|
+
def lookup_and_evaluate(obj, key, raise_on_not_found: true)
|
|
217
|
+
if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
|
|
192
218
|
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
|
193
219
|
end
|
|
194
220
|
|
|
195
221
|
value = obj[key]
|
|
196
222
|
|
|
197
223
|
if value.is_a?(Proc) && obj.respond_to?(:[]=)
|
|
198
|
-
obj[key] =
|
|
224
|
+
obj[key] = value.arity == 0 ? value.call : value.call(self)
|
|
199
225
|
else
|
|
200
226
|
value
|
|
201
227
|
end
|
|
202
228
|
end
|
|
203
229
|
|
|
230
|
+
def with_disabled_tags(tag_names)
|
|
231
|
+
tag_names.each do |name|
|
|
232
|
+
@disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
|
|
233
|
+
end
|
|
234
|
+
yield
|
|
235
|
+
ensure
|
|
236
|
+
tag_names.each do |name|
|
|
237
|
+
@disabled_tags[name] -= 1
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def tag_disabled?(tag_name)
|
|
242
|
+
@disabled_tags.fetch(tag_name, 0) > 0
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
protected
|
|
246
|
+
|
|
247
|
+
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags
|
|
248
|
+
|
|
204
249
|
private
|
|
205
250
|
|
|
251
|
+
attr_reader :base_scope_depth
|
|
252
|
+
|
|
253
|
+
def try_variable_find_in_environments(key, raise_on_not_found:)
|
|
254
|
+
@environments.each do |environment|
|
|
255
|
+
found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
|
|
256
|
+
if !found_variable.nil? || @strict_variables && raise_on_not_found
|
|
257
|
+
return found_variable
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
@static_environments.each do |environment|
|
|
261
|
+
found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
|
|
262
|
+
if !found_variable.nil? || @strict_variables && raise_on_not_found
|
|
263
|
+
return found_variable
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
nil
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def check_overflow
|
|
270
|
+
raise StackLevelError, "Nesting too deep" if overflow?
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def overflow?
|
|
274
|
+
base_scope_depth + @scopes.length > Block::MAX_DEPTH
|
|
275
|
+
end
|
|
276
|
+
|
|
206
277
|
def internal_error
|
|
207
278
|
# raise and catch to set backtrace and cause on exception
|
|
208
279
|
raise Liquid::InternalError, 'internal'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
module Liquid
|
|
6
|
+
class Deprecations
|
|
7
|
+
class << self
|
|
8
|
+
attr_accessor :warned
|
|
9
|
+
|
|
10
|
+
Deprecations.warned = Set.new
|
|
11
|
+
|
|
12
|
+
def warn(name, alternative)
|
|
13
|
+
return if warned.include?(name)
|
|
14
|
+
|
|
15
|
+
warned << name
|
|
16
|
+
|
|
17
|
+
caller_location = caller_locations(2, 1).first
|
|
18
|
+
Warning.warn("[DEPRECATION] #{name} is deprecated. Use #{alternative} instead. Called from #{caller_location}\n")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/liquid/document.rb
CHANGED
|
@@ -1,26 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Liquid
|
|
2
|
-
class Document
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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,
|
|
31
|
+
def unknown_tag(tag, _markup, _tokenizer)
|
|
19
32
|
case tag
|
|
20
|
-
when 'else'
|
|
21
|
-
raise SyntaxError
|
|
33
|
+
when 'else', 'end'
|
|
34
|
+
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag", tag: tag)
|
|
22
35
|
else
|
|
23
|
-
raise SyntaxError
|
|
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
|
data/lib/liquid/drop.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'set'
|
|
2
4
|
|
|
3
5
|
module Liquid
|
|
@@ -23,9 +25,13 @@ module Liquid
|
|
|
23
25
|
class Drop
|
|
24
26
|
attr_writer :context
|
|
25
27
|
|
|
28
|
+
def initialize
|
|
29
|
+
@context = nil
|
|
30
|
+
end
|
|
31
|
+
|
|
26
32
|
# Catch all for the method
|
|
27
33
|
def liquid_method_missing(method)
|
|
28
|
-
return nil unless @context
|
|
34
|
+
return nil unless @context&.strict_variables
|
|
29
35
|
raise Liquid::UndefinedDropMethod, "undefined method #{method}"
|
|
30
36
|
end
|
|
31
37
|
|
|
@@ -67,7 +73,7 @@ module Liquid
|
|
|
67
73
|
|
|
68
74
|
if include?(Enumerable)
|
|
69
75
|
blacklist += Enumerable.public_instance_methods
|
|
70
|
-
blacklist -= [:sort, :count, :first, :min, :max
|
|
76
|
+
blacklist -= [:sort, :count, :first, :min, :max]
|
|
71
77
|
end
|
|
72
78
|
|
|
73
79
|
whitelist = [:to_liquid] + (public_instance_methods - blacklist)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Liquid
|
|
4
|
+
# The Environment is the container for all configuration options of Liquid, such as
|
|
5
|
+
# the registered tags, filters, and the default error mode.
|
|
6
|
+
class Environment
|
|
7
|
+
# The default error mode for all templates. This can be overridden on a
|
|
8
|
+
# per-template basis.
|
|
9
|
+
attr_accessor :error_mode
|
|
10
|
+
|
|
11
|
+
# The tags that are available to use in the template.
|
|
12
|
+
attr_accessor :tags
|
|
13
|
+
|
|
14
|
+
# The strainer template which is used to store filters that are available to
|
|
15
|
+
# use in templates.
|
|
16
|
+
attr_accessor :strainer_template
|
|
17
|
+
|
|
18
|
+
# The exception renderer that is used to render exceptions that are raised
|
|
19
|
+
# when rendering a template
|
|
20
|
+
attr_accessor :exception_renderer
|
|
21
|
+
|
|
22
|
+
# The default file system that is used to load templates from.
|
|
23
|
+
attr_accessor :file_system
|
|
24
|
+
|
|
25
|
+
# The default resource limits that are used to limit the resources that a
|
|
26
|
+
# template can consume.
|
|
27
|
+
attr_accessor :default_resource_limits
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
# Creates a new environment instance.
|
|
31
|
+
#
|
|
32
|
+
# @param tags [Hash] The tags that are available to use in
|
|
33
|
+
# the template.
|
|
34
|
+
# @param file_system The default file system that is used
|
|
35
|
+
# to load templates from.
|
|
36
|
+
# @param error_mode [Symbol] The default error mode for all templates
|
|
37
|
+
# (either :rigid, :strict, :warn, or :lax).
|
|
38
|
+
# @param exception_renderer [Proc] The exception renderer that is used to
|
|
39
|
+
# render exceptions.
|
|
40
|
+
# @yieldparam environment [Environment] The environment instance that is being built.
|
|
41
|
+
# @return [Environment] The new environment instance.
|
|
42
|
+
def build(tags: nil, file_system: nil, error_mode: nil, exception_renderer: nil)
|
|
43
|
+
ret = new
|
|
44
|
+
ret.tags = tags if tags
|
|
45
|
+
ret.file_system = file_system if file_system
|
|
46
|
+
ret.error_mode = error_mode if error_mode
|
|
47
|
+
ret.exception_renderer = exception_renderer if exception_renderer
|
|
48
|
+
yield ret if block_given?
|
|
49
|
+
ret.freeze
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns the default environment instance.
|
|
53
|
+
#
|
|
54
|
+
# @return [Environment] The default environment instance.
|
|
55
|
+
def default
|
|
56
|
+
@default ||= new
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Sets the default environment instance for the duration of the block
|
|
60
|
+
#
|
|
61
|
+
# @param environment [Environment] The environment instance to use as the default for the
|
|
62
|
+
# duration of the block.
|
|
63
|
+
# @yield
|
|
64
|
+
# @return [Object] The return value of the block.
|
|
65
|
+
def dangerously_override(environment)
|
|
66
|
+
original_default = @default
|
|
67
|
+
@default = environment
|
|
68
|
+
yield
|
|
69
|
+
ensure
|
|
70
|
+
@default = original_default
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Initializes a new environment instance.
|
|
75
|
+
# @api private
|
|
76
|
+
def initialize
|
|
77
|
+
@tags = Tags::STANDARD_TAGS.dup
|
|
78
|
+
@error_mode = :lax
|
|
79
|
+
@strainer_template = Class.new(StrainerTemplate).tap do |klass|
|
|
80
|
+
klass.add_filter(StandardFilters)
|
|
81
|
+
end
|
|
82
|
+
@exception_renderer = ->(exception) { exception }
|
|
83
|
+
@file_system = BlankFileSystem.new
|
|
84
|
+
@default_resource_limits = Const::EMPTY_HASH
|
|
85
|
+
@strainer_template_class_cache = {}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Registers a new tag with the environment.
|
|
89
|
+
#
|
|
90
|
+
# @param name [String] The name of the tag.
|
|
91
|
+
# @param klass [Liquid::Tag] The class that implements the tag.
|
|
92
|
+
# @return [void]
|
|
93
|
+
def register_tag(name, klass)
|
|
94
|
+
@tags[name] = klass
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Registers a new filter with the environment.
|
|
98
|
+
#
|
|
99
|
+
# @param filter [Module] The module that contains the filter methods.
|
|
100
|
+
# @return [void]
|
|
101
|
+
def register_filter(filter)
|
|
102
|
+
@strainer_template_class_cache.clear
|
|
103
|
+
@strainer_template.add_filter(filter)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Registers multiple filters with this environment.
|
|
107
|
+
#
|
|
108
|
+
# @param filters [Array<Module>] The modules that contain the filter methods.
|
|
109
|
+
# @return [self]
|
|
110
|
+
def register_filters(filters)
|
|
111
|
+
@strainer_template_class_cache.clear
|
|
112
|
+
filters.each { |f| @strainer_template.add_filter(f) }
|
|
113
|
+
self
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Creates a new strainer instance with the given filters, caching the result
|
|
117
|
+
# for faster lookup.
|
|
118
|
+
#
|
|
119
|
+
# @param context [Liquid::Context] The context that the strainer will be
|
|
120
|
+
# used in.
|
|
121
|
+
# @param filters [Array<Module>] The filters that the strainer will have
|
|
122
|
+
# access to.
|
|
123
|
+
# @return [Liquid::Strainer] The new strainer instance.
|
|
124
|
+
def create_strainer(context, filters = Const::EMPTY_ARRAY)
|
|
125
|
+
return @strainer_template.new(context) if filters.empty?
|
|
126
|
+
|
|
127
|
+
strainer_template = @strainer_template_class_cache[filters] ||= begin
|
|
128
|
+
klass = Class.new(@strainer_template)
|
|
129
|
+
filters.each { |f| klass.add_filter(f) }
|
|
130
|
+
klass
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
strainer_template.new(context)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Returns the names of all the filter methods that are available to use in
|
|
137
|
+
# the strainer template.
|
|
138
|
+
#
|
|
139
|
+
# @return [Array<String>] The names of all the filter methods.
|
|
140
|
+
def filter_method_names
|
|
141
|
+
@strainer_template.filter_method_names
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Returns the tag class for the given tag name.
|
|
145
|
+
#
|
|
146
|
+
# @param name [String] The name of the tag.
|
|
147
|
+
# @return [Liquid::Tag] The tag class.
|
|
148
|
+
def tag_for_name(name)
|
|
149
|
+
@tags[name]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def freeze
|
|
153
|
+
@tags.freeze
|
|
154
|
+
# TODO: freeze the tags, currently this is not possible because of liquid-c
|
|
155
|
+
# @strainer_template.freeze
|
|
156
|
+
super
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
data/lib/liquid/errors.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Liquid
|
|
2
4
|
class Error < ::StandardError
|
|
3
5
|
attr_accessor :line_number
|
|
@@ -5,7 +7,7 @@ module Liquid
|
|
|
5
7
|
attr_accessor :markup_context
|
|
6
8
|
|
|
7
9
|
def to_s(with_prefix = true)
|
|
8
|
-
str = ""
|
|
10
|
+
str = +""
|
|
9
11
|
str << message_prefix if with_prefix
|
|
10
12
|
str << super()
|
|
11
13
|
|
|
@@ -20,11 +22,11 @@ module Liquid
|
|
|
20
22
|
private
|
|
21
23
|
|
|
22
24
|
def message_prefix
|
|
23
|
-
str = ""
|
|
24
|
-
if is_a?(SyntaxError)
|
|
25
|
-
|
|
25
|
+
str = +""
|
|
26
|
+
str << if is_a?(SyntaxError)
|
|
27
|
+
"Liquid syntax error"
|
|
26
28
|
else
|
|
27
|
-
|
|
29
|
+
"Liquid error"
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
if line_number
|
|
@@ -38,19 +40,20 @@ module Liquid
|
|
|
38
40
|
end
|
|
39
41
|
end
|
|
40
42
|
|
|
41
|
-
ArgumentError
|
|
42
|
-
ContextError
|
|
43
|
-
FileSystemError
|
|
44
|
-
StandardError
|
|
45
|
-
SyntaxError
|
|
46
|
-
StackLevelError
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
InternalError
|
|
43
|
+
ArgumentError = Class.new(Error)
|
|
44
|
+
ContextError = Class.new(Error)
|
|
45
|
+
FileSystemError = Class.new(Error)
|
|
46
|
+
StandardError = Class.new(Error)
|
|
47
|
+
SyntaxError = Class.new(Error)
|
|
48
|
+
StackLevelError = Class.new(Error)
|
|
49
|
+
MemoryError = Class.new(Error)
|
|
50
|
+
ZeroDivisionError = Class.new(Error)
|
|
51
|
+
FloatDomainError = Class.new(Error)
|
|
52
|
+
UndefinedVariable = Class.new(Error)
|
|
53
|
+
UndefinedDropMethod = Class.new(Error)
|
|
54
|
+
UndefinedFilter = Class.new(Error)
|
|
55
|
+
MethodOverrideError = Class.new(Error)
|
|
56
|
+
DisabledError = Class.new(Error)
|
|
57
|
+
InternalError = Class.new(Error)
|
|
58
|
+
TemplateEncodingError = Class.new(Error)
|
|
56
59
|
end
|