liquid 4.0.3 → 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.
- checksums.yaml +4 -4
- data/History.md +89 -0
- data/README.md +10 -4
- data/lib/liquid/block.rb +31 -14
- data/lib/liquid/block_body.rb +169 -57
- data/lib/liquid/condition.rb +48 -21
- data/lib/liquid/context.rb +111 -52
- data/lib/liquid/document.rb +47 -9
- data/lib/liquid/drop.rb +4 -2
- data/lib/liquid/errors.rb +20 -18
- data/lib/liquid/expression.rb +28 -32
- data/lib/liquid/extensions.rb +2 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +54 -4
- data/lib/liquid/i18n.rb +5 -3
- data/lib/liquid/interrupts.rb +3 -1
- data/lib/liquid/lexer.rb +30 -23
- data/lib/liquid/locales/en.yml +8 -5
- data/lib/liquid/parse_context.rb +20 -4
- data/lib/liquid/parse_tree_visitor.rb +2 -2
- data/lib/liquid/parser.rb +30 -18
- data/lib/liquid/parser_switching.rb +17 -3
- data/lib/liquid/partial_cache.rb +24 -0
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/profiler.rb +67 -86
- data/lib/liquid/range_lookup.rb +13 -3
- data/lib/liquid/registers.rb +51 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/standardfilters.rb +551 -114
- data/lib/liquid/strainer_factory.rb +41 -0
- 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 +21 -0
- data/lib/liquid/tag.rb +28 -6
- data/lib/liquid/tags/assign.rb +36 -18
- data/lib/liquid/tags/break.rb +16 -3
- data/lib/liquid/tags/capture.rb +24 -18
- data/lib/liquid/tags/case.rb +61 -27
- data/lib/liquid/tags/comment.rb +18 -3
- data/lib/liquid/tags/continue.rb +16 -12
- data/lib/liquid/tags/cycle.rb +37 -25
- data/lib/liquid/tags/decrement.rb +22 -20
- data/lib/liquid/tags/echo.rb +41 -0
- data/lib/liquid/tags/for.rb +90 -87
- data/lib/liquid/tags/if.rb +50 -32
- data/lib/liquid/tags/ifchanged.rb +11 -10
- data/lib/liquid/tags/include.rb +49 -60
- data/lib/liquid/tags/increment.rb +23 -17
- data/lib/liquid/tags/inline_comment.rb +43 -0
- data/lib/liquid/tags/raw.rb +25 -11
- data/lib/liquid/tags/render.rb +109 -0
- data/lib/liquid/tags/table_row.rb +45 -19
- data/lib/liquid/tags/unless.rb +38 -19
- data/lib/liquid/template.rb +52 -72
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +18 -10
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +13 -3
- data/lib/liquid/variable.rb +49 -44
- data/lib/liquid/variable_lookup.rb +18 -10
- data/lib/liquid/version.rb +2 -1
- data/lib/liquid.rb +18 -6
- metadata +20 -108
- data/lib/liquid/strainer.rb +0 -66
- data/lib/liquid/truffle.rb +0 -5
- 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/block_test.rb +0 -12
- 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/parse_tree_visitor_test.rb +0 -247
- data/test/integration/parsing_quirks_test.rb +0 -122
- data/test/integration/render_profiling_test.rb +0 -154
- data/test/integration/security_test.rb +0 -80
- data/test/integration/standard_filter_test.rb +0 -776
- 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 -253
- 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 -332
- data/test/integration/trim_mode_test.rb +0 -529
- data/test/integration/variable_test.rb +0 -96
- data/test/test_helper.rb +0 -116
- data/test/truffle/truffle_test.rb +0 -9
- data/test/unit/block_unit_test.rb +0 -58
- data/test/unit/condition_unit_test.rb +0 -166
- data/test/unit/context_unit_test.rb +0 -489
- 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 -164
- 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,53 @@ 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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
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
|
29
45
|
|
30
46
|
self.exception_renderer = Template.default_exception_renderer
|
31
47
|
if rethrow_errors
|
32
|
-
self.exception_renderer =
|
48
|
+
self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
|
33
49
|
end
|
34
50
|
|
35
|
-
|
36
|
-
|
37
|
-
|
51
|
+
yield self if block_given?
|
52
|
+
|
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
|
38
55
|
end
|
56
|
+
# rubocop:enable Metrics/ParameterLists
|
39
57
|
|
40
58
|
def warnings
|
41
59
|
@warnings ||= []
|
42
60
|
end
|
43
61
|
|
44
62
|
def strainer
|
45
|
-
@strainer ||=
|
63
|
+
@strainer ||= StrainerFactory.create(self, @filters)
|
46
64
|
end
|
47
65
|
|
48
66
|
# Adds filters to this context.
|
@@ -77,7 +95,7 @@ module Liquid
|
|
77
95
|
def handle_error(e, line_number = nil)
|
78
96
|
e = internal_error unless e.is_a?(Liquid::Error)
|
79
97
|
e.template_name ||= template_name
|
80
|
-
e.line_number
|
98
|
+
e.line_number ||= line_number
|
81
99
|
errors.push(e)
|
82
100
|
exception_renderer.call(e).to_s
|
83
101
|
end
|
@@ -89,7 +107,7 @@ module Liquid
|
|
89
107
|
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
90
108
|
def push(new_scope = {})
|
91
109
|
@scopes.unshift(new_scope)
|
92
|
-
|
110
|
+
check_overflow
|
93
111
|
end
|
94
112
|
|
95
113
|
# Merge a hash of variables in the current local scope
|
@@ -110,20 +128,32 @@ module Liquid
|
|
110
128
|
# context['var'] = 'hi'
|
111
129
|
# end
|
112
130
|
#
|
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
|
-
|
131
|
+
# context['var'] #=> nil
|
132
|
+
def stack(new_scope = {})
|
133
|
+
push(new_scope)
|
123
134
|
yield
|
124
135
|
ensure
|
125
|
-
pop
|
126
|
-
|
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
|
127
157
|
end
|
128
158
|
|
129
159
|
def clear_instance_assigns
|
@@ -132,10 +162,6 @@ module Liquid
|
|
132
162
|
|
133
163
|
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
134
164
|
def []=(key, value)
|
135
|
-
unless @this_stack_used
|
136
|
-
@this_stack_used = true
|
137
|
-
push({})
|
138
|
-
end
|
139
165
|
@scopes[0][key] = value
|
140
166
|
end
|
141
167
|
|
@@ -164,26 +190,14 @@ module Liquid
|
|
164
190
|
# This was changed from find() to find_index() because this is a very hot
|
165
191
|
# path and find_index() is optimized in MRI to reduce object allocation
|
166
192
|
index = @scopes.find_index { |s| s.key?(key) }
|
167
|
-
scope = @scopes[index] if index
|
168
|
-
|
169
|
-
variable = nil
|
170
193
|
|
171
|
-
if
|
172
|
-
@
|
173
|
-
|
174
|
-
|
175
|
-
# then it is the value we are looking for.
|
176
|
-
if !variable.nil? || @strict_variables && raise_on_not_found
|
177
|
-
scope = e
|
178
|
-
break
|
179
|
-
end
|
180
|
-
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)
|
181
198
|
end
|
182
199
|
|
183
|
-
|
184
|
-
variable ||= lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found)
|
185
|
-
|
186
|
-
variable = variable.to_liquid
|
200
|
+
variable = variable.to_liquid
|
187
201
|
variable.context = self if variable.respond_to?(:context=)
|
188
202
|
|
189
203
|
variable
|
@@ -197,14 +211,59 @@ module Liquid
|
|
197
211
|
value = obj[key]
|
198
212
|
|
199
213
|
if value.is_a?(Proc) && obj.respond_to?(:[]=)
|
200
|
-
obj[key] =
|
214
|
+
obj[key] = value.arity == 0 ? value.call : value.call(self)
|
201
215
|
else
|
202
216
|
value
|
203
217
|
end
|
204
218
|
end
|
205
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
|
+
|
206
239
|
private
|
207
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
|
+
|
208
267
|
def internal_error
|
209
268
|
# raise and catch to set backtrace and cause on exception
|
210
269
|
raise Liquid::InternalError, 'internal'
|
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
|
@@ -25,7 +27,7 @@ module Liquid
|
|
25
27
|
|
26
28
|
# Catch all for the method
|
27
29
|
def liquid_method_missing(method)
|
28
|
-
return nil unless @context
|
30
|
+
return nil unless @context&.strict_variables
|
29
31
|
raise Liquid::UndefinedDropMethod, "undefined method #{method}"
|
30
32
|
end
|
31
33
|
|
@@ -67,7 +69,7 @@ module Liquid
|
|
67
69
|
|
68
70
|
if include?(Enumerable)
|
69
71
|
blacklist += Enumerable.public_instance_methods
|
70
|
-
blacklist -= [:sort, :count, :first, :min, :max
|
72
|
+
blacklist -= [:sort, :count, :first, :min, :max]
|
71
73
|
end
|
72
74
|
|
73
75
|
whitelist = [:to_liquid] + (public_instance_methods - blacklist)
|
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,19 @@ 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
|
-
UndefinedVariable = Class.new(Error)
|
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)
|
52
53
|
UndefinedDropMethod = Class.new(Error)
|
53
|
-
UndefinedFilter
|
54
|
+
UndefinedFilter = Class.new(Error)
|
54
55
|
MethodOverrideError = Class.new(Error)
|
55
|
-
|
56
|
+
DisabledError = Class.new(Error)
|
57
|
+
InternalError = Class.new(Error)
|
56
58
|
end
|
data/lib/liquid/expression.rb
CHANGED
@@ -1,45 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
class Expression
|
3
|
-
class MethodLiteral
|
4
|
-
attr_reader :method_name, :to_s
|
5
|
-
|
6
|
-
def initialize(method_name, to_s)
|
7
|
-
@method_name = method_name
|
8
|
-
@to_s = to_s
|
9
|
-
end
|
10
|
-
|
11
|
-
def to_liquid
|
12
|
-
to_s
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
5
|
LITERALS = {
|
17
|
-
nil => nil, 'nil'
|
18
|
-
'true'
|
19
|
-
'false'
|
20
|
-
'blank'
|
21
|
-
'empty'
|
6
|
+
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
|
7
|
+
'true' => true,
|
8
|
+
'false' => false,
|
9
|
+
'blank' => '',
|
10
|
+
'empty' => ''
|
22
11
|
}.freeze
|
23
12
|
|
24
|
-
SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
|
25
|
-
DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m
|
26
13
|
INTEGERS_REGEX = /\A(-?\d+)\z/
|
27
14
|
FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
|
28
|
-
|
15
|
+
|
16
|
+
# Use an atomic group (?>...) to avoid pathological backtracing from
|
17
|
+
# malicious input as described in https://github.com/Shopify/liquid/issues/1357
|
18
|
+
RANGES_REGEX = /\A\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\z/
|
29
19
|
|
30
20
|
def self.parse(markup)
|
31
|
-
|
32
|
-
|
21
|
+
return nil unless markup
|
22
|
+
|
23
|
+
markup = markup.strip
|
24
|
+
if (markup.start_with?('"') && markup.end_with?('"')) ||
|
25
|
+
(markup.start_with?("'") && markup.end_with?("'"))
|
26
|
+
return markup[1..-2]
|
27
|
+
end
|
28
|
+
|
29
|
+
case markup
|
30
|
+
when INTEGERS_REGEX
|
31
|
+
Regexp.last_match(1).to_i
|
32
|
+
when RANGES_REGEX
|
33
|
+
RangeLookup.parse(Regexp.last_match(1), Regexp.last_match(2))
|
34
|
+
when FLOATS_REGEX
|
35
|
+
Regexp.last_match(1).to_f
|
33
36
|
else
|
34
|
-
|
35
|
-
|
36
|
-
$1
|
37
|
-
when INTEGERS_REGEX
|
38
|
-
$1.to_i
|
39
|
-
when RANGES_REGEX
|
40
|
-
RangeLookup.parse($1, $2)
|
41
|
-
when FLOATS_REGEX
|
42
|
-
$1.to_f
|
37
|
+
if LITERALS.key?(markup)
|
38
|
+
LITERALS[markup]
|
43
39
|
else
|
44
40
|
VariableLookup.parse(markup)
|
45
41
|
end
|
data/lib/liquid/extensions.rb
CHANGED
data/lib/liquid/file_system.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
# A Liquid file system is a way to let your templates retrieve other templates for use with the include tag.
|
3
5
|
#
|
@@ -44,8 +46,8 @@ module Liquid
|
|
44
46
|
class LocalFileSystem
|
45
47
|
attr_accessor :root
|
46
48
|
|
47
|
-
def initialize(root, pattern = "_%s.liquid"
|
48
|
-
@root
|
49
|
+
def initialize(root, pattern = "_%s.liquid")
|
50
|
+
@root = root
|
49
51
|
@pattern = pattern
|
50
52
|
end
|
51
53
|
|
@@ -57,9 +59,9 @@ module Liquid
|
|
57
59
|
end
|
58
60
|
|
59
61
|
def full_path(template_path)
|
60
|
-
raise FileSystemError, "Illegal template name '#{template_path}'" unless
|
62
|
+
raise FileSystemError, "Illegal template name '#{template_path}'" unless %r{\A[^./][a-zA-Z0-9_/]+\z}.match?(template_path)
|
61
63
|
|
62
|
-
full_path = if template_path.include?('/'
|
64
|
+
full_path = if template_path.include?('/')
|
63
65
|
File.join(root, File.dirname(template_path), @pattern % File.basename(template_path))
|
64
66
|
else
|
65
67
|
File.join(root, @pattern % template_path)
|
data/lib/liquid/forloop_drop.rb
CHANGED
@@ -1,34 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
4
|
+
# @liquid_public_docs
|
5
|
+
# @liquid_type object
|
6
|
+
# @liquid_name forloop
|
7
|
+
# @liquid_summary
|
8
|
+
# Information about a parent [`for` loop](/api/liquid/tags#for).
|
2
9
|
class ForloopDrop < Drop
|
3
10
|
def initialize(name, length, parentloop)
|
4
|
-
@name
|
5
|
-
@length
|
11
|
+
@name = name
|
12
|
+
@length = length
|
6
13
|
@parentloop = parentloop
|
7
|
-
@index
|
14
|
+
@index = 0
|
8
15
|
end
|
9
16
|
|
10
|
-
|
17
|
+
# @liquid_public_docs
|
18
|
+
# @liquid_name length
|
19
|
+
# @liquid_summary
|
20
|
+
# The total number of iterations in the loop.
|
21
|
+
# @liquid_return [number]
|
22
|
+
attr_reader :length
|
23
|
+
|
24
|
+
# @liquid_public_docs
|
25
|
+
# @liquid_name parentloop
|
26
|
+
# @liquid_summary
|
27
|
+
# The parent `forloop` object.
|
28
|
+
# @liquid_description
|
29
|
+
# If the current `for` loop isn't nested inside another `for` loop, then `nil` is returned.
|
30
|
+
# @liquid_return [forloop]
|
31
|
+
attr_reader :parentloop
|
32
|
+
|
33
|
+
def name
|
34
|
+
Usage.increment('forloop_drop_name')
|
35
|
+
@name
|
36
|
+
end
|
11
37
|
|
38
|
+
# @liquid_public_docs
|
39
|
+
# @liquid_summary
|
40
|
+
# The 1-based index of the current iteration.
|
41
|
+
# @liquid_return [number]
|
12
42
|
def index
|
13
43
|
@index + 1
|
14
44
|
end
|
15
45
|
|
46
|
+
# @liquid_public_docs
|
47
|
+
# @liquid_summary
|
48
|
+
# The 0-based index of the current iteration.
|
49
|
+
# @liquid_return [number]
|
16
50
|
def index0
|
17
51
|
@index
|
18
52
|
end
|
19
53
|
|
54
|
+
# @liquid_public_docs
|
55
|
+
# @liquid_summary
|
56
|
+
# The 1-based index of the current iteration, in reverse order.
|
57
|
+
# @liquid_return [number]
|
20
58
|
def rindex
|
21
59
|
@length - @index
|
22
60
|
end
|
23
61
|
|
62
|
+
# @liquid_public_docs
|
63
|
+
# @liquid_summary
|
64
|
+
# The 0-based index of the current iteration, in reverse order.
|
65
|
+
# @liquid_return [number]
|
24
66
|
def rindex0
|
25
67
|
@length - @index - 1
|
26
68
|
end
|
27
69
|
|
70
|
+
# @liquid_public_docs
|
71
|
+
# @liquid_summary
|
72
|
+
# Returns `true` if the current iteration is the first. Returns `false` if not.
|
73
|
+
# @liquid_return [boolean]
|
28
74
|
def first
|
29
75
|
@index == 0
|
30
76
|
end
|
31
77
|
|
78
|
+
# @liquid_public_docs
|
79
|
+
# @liquid_summary
|
80
|
+
# Returns `true` if the current iteration is the last. Returns `false` if not.
|
81
|
+
# @liquid_return [boolean]
|
32
82
|
def last
|
33
83
|
@index == @length - 1
|
34
84
|
end
|
data/lib/liquid/i18n.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'yaml'
|
2
4
|
|
3
5
|
module Liquid
|
@@ -26,13 +28,13 @@ module Liquid
|
|
26
28
|
def interpolate(name, vars)
|
27
29
|
name.gsub(/%\{(\w+)\}/) do
|
28
30
|
# raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
|
29
|
-
(vars[
|
31
|
+
(vars[Regexp.last_match(1).to_sym]).to_s
|
30
32
|
end
|
31
33
|
end
|
32
34
|
|
33
35
|
def deep_fetch_translation(name)
|
34
|
-
name.split('.'
|
35
|
-
level[cur]
|
36
|
+
name.split('.').reduce(locale) do |level, cur|
|
37
|
+
level[cur] || raise(TranslationError, "Translation for #{name} does not exist in locale #{path}")
|
36
38
|
end
|
37
39
|
end
|
38
40
|
end
|
data/lib/liquid/interrupts.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
# An interrupt is any command that breaks processing of a block (ex: a for loop).
|
3
5
|
class Interrupt
|
4
6
|
attr_reader :message
|
5
7
|
|
6
8
|
def initialize(message = nil)
|
7
|
-
@message = message || "interrupt"
|
9
|
+
@message = message || "interrupt"
|
8
10
|
end
|
9
11
|
end
|
10
12
|
|