liquid 4.0.4 → 5.0.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 +32 -4
- data/README.md +6 -0
- data/lib/liquid/block.rb +31 -14
- data/lib/liquid/block_body.rb +164 -54
- data/lib/liquid/condition.rb +39 -18
- data/lib/liquid/context.rb +106 -51
- 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 +29 -34
- data/lib/liquid/extensions.rb +2 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +11 -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 +3 -1
- data/lib/liquid/parse_context.rb +16 -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 +5 -3
- data/lib/liquid/register.rb +6 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/standardfilters.rb +62 -43
- data/lib/liquid/static_registers.rb +44 -0
- data/lib/liquid/strainer_factory.rb +36 -0
- data/lib/liquid/strainer_template.rb +53 -0
- data/lib/liquid/tablerowloop_drop.rb +6 -4
- 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 +24 -10
- data/lib/liquid/tags/break.rb +8 -3
- data/lib/liquid/tags/capture.rb +11 -8
- data/lib/liquid/tags/case.rb +33 -27
- data/lib/liquid/tags/comment.rb +5 -3
- data/lib/liquid/tags/continue.rb +8 -3
- data/lib/liquid/tags/cycle.rb +25 -14
- data/lib/liquid/tags/decrement.rb +6 -3
- data/lib/liquid/tags/echo.rb +26 -0
- data/lib/liquid/tags/for.rb +68 -44
- data/lib/liquid/tags/if.rb +35 -23
- data/lib/liquid/tags/ifchanged.rb +11 -10
- data/lib/liquid/tags/include.rb +34 -47
- data/lib/liquid/tags/increment.rb +7 -3
- data/lib/liquid/tags/raw.rb +14 -11
- data/lib/liquid/tags/render.rb +84 -0
- data/lib/liquid/tags/table_row.rb +23 -19
- data/lib/liquid/tags/unless.rb +15 -15
- data/lib/liquid/template.rb +55 -69
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +17 -9
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +5 -3
- data/lib/liquid/variable.rb +47 -19
- data/lib/liquid/variable_lookup.rb +8 -6
- data/lib/liquid/version.rb +2 -1
- data/lib/liquid.rb +17 -5
- data/test/integration/assign_test.rb +74 -5
- data/test/integration/blank_test.rb +11 -8
- data/test/integration/block_test.rb +47 -1
- data/test/integration/capture_test.rb +18 -10
- data/test/integration/context_test.rb +608 -5
- data/test/integration/document_test.rb +4 -2
- data/test/integration/drop_test.rb +67 -57
- data/test/integration/error_handling_test.rb +73 -61
- data/test/integration/expression_test.rb +46 -0
- data/test/integration/filter_test.rb +53 -42
- data/test/integration/hash_ordering_test.rb +5 -3
- data/test/integration/output_test.rb +26 -24
- data/test/integration/parsing_quirks_test.rb +19 -7
- data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
- data/test/integration/security_test.rb +30 -21
- data/test/integration/standard_filter_test.rb +339 -281
- data/test/integration/tag/disableable_test.rb +59 -0
- data/test/integration/tag_test.rb +45 -0
- data/test/integration/tags/break_tag_test.rb +4 -2
- data/test/integration/tags/continue_tag_test.rb +4 -2
- data/test/integration/tags/echo_test.rb +13 -0
- data/test/integration/tags/for_tag_test.rb +107 -51
- data/test/integration/tags/if_else_tag_test.rb +5 -3
- data/test/integration/tags/include_tag_test.rb +70 -54
- data/test/integration/tags/increment_tag_test.rb +4 -2
- data/test/integration/tags/liquid_tag_test.rb +116 -0
- data/test/integration/tags/raw_tag_test.rb +14 -11
- data/test/integration/tags/render_tag_test.rb +213 -0
- data/test/integration/tags/standard_tag_test.rb +38 -31
- data/test/integration/tags/statements_test.rb +23 -21
- data/test/integration/tags/table_row_test.rb +2 -0
- data/test/integration/tags/unless_else_tag_test.rb +4 -2
- data/test/integration/template_test.rb +118 -124
- data/test/integration/trim_mode_test.rb +78 -44
- data/test/integration/variable_test.rb +43 -32
- data/test/test_helper.rb +75 -14
- data/test/unit/block_unit_test.rb +19 -24
- data/test/unit/condition_unit_test.rb +79 -77
- data/test/unit/file_system_unit_test.rb +6 -4
- data/test/unit/i18n_unit_test.rb +7 -5
- data/test/unit/lexer_unit_test.rb +11 -9
- data/test/{integration → unit}/parse_tree_visitor_test.rb +1 -1
- data/test/unit/parser_unit_test.rb +37 -35
- data/test/unit/partial_cache_unit_test.rb +128 -0
- data/test/unit/regexp_unit_test.rb +17 -15
- data/test/unit/static_registers_unit_test.rb +156 -0
- data/test/unit/strainer_factory_unit_test.rb +100 -0
- data/test/unit/strainer_template_unit_test.rb +82 -0
- data/test/unit/tag_unit_test.rb +5 -3
- data/test/unit/tags/case_tag_unit_test.rb +3 -1
- data/test/unit/tags/for_tag_unit_test.rb +4 -2
- data/test/unit/tags/if_tag_unit_test.rb +3 -1
- data/test/unit/template_factory_unit_test.rb +12 -0
- data/test/unit/template_unit_test.rb +19 -10
- data/test/unit/tokenizer_unit_test.rb +19 -17
- data/test/unit/variable_unit_test.rb +51 -49
- metadata +75 -47
- data/lib/liquid/strainer.rb +0 -66
- data/test/unit/context_unit_test.rb +0 -490
- data/test/unit/strainer_unit_test.rb +0 -164
@@ -1,20 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cgi'
|
2
4
|
require 'bigdecimal'
|
3
5
|
|
4
6
|
module Liquid
|
5
7
|
module StandardFilters
|
6
8
|
HTML_ESCAPE = {
|
7
|
-
'&'
|
8
|
-
'>'
|
9
|
-
'<'
|
10
|
-
'"'
|
11
|
-
"'"
|
9
|
+
'&' => '&',
|
10
|
+
'>' => '>',
|
11
|
+
'<' => '<',
|
12
|
+
'"' => '"',
|
13
|
+
"'" => ''',
|
12
14
|
}.freeze
|
13
15
|
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
|
14
|
-
STRIP_HTML_BLOCKS
|
15
|
-
|
16
|
+
STRIP_HTML_BLOCKS = Regexp.union(
|
17
|
+
%r{<script.*?</script>}m,
|
16
18
|
/<!--.*?-->/m,
|
17
|
-
|
19
|
+
%r{<style.*?</style>}m
|
18
20
|
)
|
19
21
|
STRIP_HTML_TAGS = /<.*?>/m
|
20
22
|
|
@@ -72,23 +74,28 @@ module Liquid
|
|
72
74
|
end
|
73
75
|
|
74
76
|
# Truncate a string down to x characters
|
75
|
-
def truncate(input, length = 50, truncate_string = "..."
|
77
|
+
def truncate(input, length = 50, truncate_string = "...")
|
76
78
|
return if input.nil?
|
77
79
|
input_str = input.to_s
|
78
|
-
length
|
80
|
+
length = Utils.to_integer(length)
|
81
|
+
|
79
82
|
truncate_string_str = truncate_string.to_s
|
83
|
+
|
80
84
|
l = length - truncate_string_str.length
|
81
85
|
l = 0 if l < 0
|
82
|
-
|
86
|
+
|
87
|
+
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
|
83
88
|
end
|
84
89
|
|
85
|
-
def truncatewords(input, words = 15, truncate_string = "..."
|
90
|
+
def truncatewords(input, words = 15, truncate_string = "...")
|
86
91
|
return if input.nil?
|
87
92
|
wordlist = input.to_s.split
|
88
|
-
words
|
93
|
+
words = Utils.to_integer(words)
|
94
|
+
|
89
95
|
l = words - 1
|
90
96
|
l = 0 if l < 0
|
91
|
-
|
97
|
+
|
98
|
+
wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input
|
92
99
|
end
|
93
100
|
|
94
101
|
# Split input string into an array of substrings separated by given pattern.
|
@@ -113,7 +120,7 @@ module Liquid
|
|
113
120
|
end
|
114
121
|
|
115
122
|
def strip_html(input)
|
116
|
-
empty
|
123
|
+
empty = ''
|
117
124
|
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
|
118
125
|
result.gsub!(STRIP_HTML_TAGS, empty)
|
119
126
|
result
|
@@ -121,18 +128,18 @@ module Liquid
|
|
121
128
|
|
122
129
|
# Remove all newlines from the string
|
123
130
|
def strip_newlines(input)
|
124
|
-
input.to_s.gsub(/\r?\n/, ''
|
131
|
+
input.to_s.gsub(/\r?\n/, '')
|
125
132
|
end
|
126
133
|
|
127
134
|
# Join elements of the array with certain character between them
|
128
|
-
def join(input, glue = ' '
|
129
|
-
InputIterator.new(input).join(glue)
|
135
|
+
def join(input, glue = ' ')
|
136
|
+
InputIterator.new(input, context).join(glue)
|
130
137
|
end
|
131
138
|
|
132
139
|
# Sort elements of the array
|
133
140
|
# provide optional property with which to sort an array of hashes or drops
|
134
141
|
def sort(input, property = nil)
|
135
|
-
ary = InputIterator.new(input)
|
142
|
+
ary = InputIterator.new(input, context)
|
136
143
|
|
137
144
|
return [] if ary.empty?
|
138
145
|
|
@@ -152,7 +159,7 @@ module Liquid
|
|
152
159
|
# Sort elements of an array ignoring case if strings
|
153
160
|
# provide optional property with which to sort an array of hashes or drops
|
154
161
|
def sort_natural(input, property = nil)
|
155
|
-
ary = InputIterator.new(input)
|
162
|
+
ary = InputIterator.new(input, context)
|
156
163
|
|
157
164
|
return [] if ary.empty?
|
158
165
|
|
@@ -172,7 +179,7 @@ module Liquid
|
|
172
179
|
# Filter the elements of an array to those with a certain property value.
|
173
180
|
# By default the target is any truthy value.
|
174
181
|
def where(input, property, target_value = nil)
|
175
|
-
ary = InputIterator.new(input)
|
182
|
+
ary = InputIterator.new(input, context)
|
176
183
|
|
177
184
|
if ary.empty?
|
178
185
|
[]
|
@@ -194,7 +201,7 @@ module Liquid
|
|
194
201
|
# Remove duplicate elements from an array
|
195
202
|
# provide optional property with which to determine uniqueness
|
196
203
|
def uniq(input, property = nil)
|
197
|
-
ary = InputIterator.new(input)
|
204
|
+
ary = InputIterator.new(input, context)
|
198
205
|
|
199
206
|
if property.nil?
|
200
207
|
ary.uniq
|
@@ -211,16 +218,16 @@ module Liquid
|
|
211
218
|
|
212
219
|
# Reverse the elements of an array
|
213
220
|
def reverse(input)
|
214
|
-
ary = InputIterator.new(input)
|
221
|
+
ary = InputIterator.new(input, context)
|
215
222
|
ary.reverse
|
216
223
|
end
|
217
224
|
|
218
225
|
# map/collect on a given property
|
219
226
|
def map(input, property)
|
220
|
-
InputIterator.new(input).map do |e|
|
227
|
+
InputIterator.new(input, context).map do |e|
|
221
228
|
e = e.call if e.is_a?(Proc)
|
222
229
|
|
223
|
-
if property == "to_liquid"
|
230
|
+
if property == "to_liquid"
|
224
231
|
e
|
225
232
|
elsif e.respond_to?(:[])
|
226
233
|
r = e[property]
|
@@ -234,7 +241,7 @@ module Liquid
|
|
234
241
|
# Remove nils within an array
|
235
242
|
# provide optional property with which to check for nil
|
236
243
|
def compact(input, property = nil)
|
237
|
-
ary = InputIterator.new(input)
|
244
|
+
ary = InputIterator.new(input, context)
|
238
245
|
|
239
246
|
if property.nil?
|
240
247
|
ary.compact
|
@@ -250,23 +257,23 @@ module Liquid
|
|
250
257
|
end
|
251
258
|
|
252
259
|
# Replace occurrences of a string with another
|
253
|
-
def replace(input, string, replacement = ''
|
260
|
+
def replace(input, string, replacement = '')
|
254
261
|
input.to_s.gsub(string.to_s, replacement.to_s)
|
255
262
|
end
|
256
263
|
|
257
264
|
# Replace the first occurrences of a string with another
|
258
|
-
def replace_first(input, string, replacement = ''
|
265
|
+
def replace_first(input, string, replacement = '')
|
259
266
|
input.to_s.sub(string.to_s, replacement.to_s)
|
260
267
|
end
|
261
268
|
|
262
269
|
# remove a substring
|
263
270
|
def remove(input, string)
|
264
|
-
input.to_s.gsub(string.to_s, ''
|
271
|
+
input.to_s.gsub(string.to_s, '')
|
265
272
|
end
|
266
273
|
|
267
274
|
# remove the first occurrences of a substring
|
268
275
|
def remove_first(input, string)
|
269
|
-
input.to_s.sub(string.to_s, ''
|
276
|
+
input.to_s.sub(string.to_s, '')
|
270
277
|
end
|
271
278
|
|
272
279
|
# add one string to another
|
@@ -276,9 +283,9 @@ module Liquid
|
|
276
283
|
|
277
284
|
def concat(input, array)
|
278
285
|
unless array.respond_to?(:to_ary)
|
279
|
-
raise ArgumentError
|
286
|
+
raise ArgumentError, "concat filter requires an array argument"
|
280
287
|
end
|
281
|
-
InputIterator.new(input).concat(array)
|
288
|
+
InputIterator.new(input, context).concat(array)
|
282
289
|
end
|
283
290
|
|
284
291
|
# prepend a string to another
|
@@ -288,7 +295,7 @@ module Liquid
|
|
288
295
|
|
289
296
|
# Add <br /> tags in front of all newlines in input string
|
290
297
|
def newline_to_br(input)
|
291
|
-
input.to_s.gsub(/\n/, "<br />\n"
|
298
|
+
input.to_s.gsub(/\n/, "<br />\n")
|
292
299
|
end
|
293
300
|
|
294
301
|
# Reformat a date using Ruby's core Time#strftime( string ) -> string
|
@@ -325,7 +332,7 @@ module Liquid
|
|
325
332
|
def date(input, format)
|
326
333
|
return input if format.to_s.empty?
|
327
334
|
|
328
|
-
return input unless date = Utils.to_date(input)
|
335
|
+
return input unless (date = Utils.to_date(input))
|
329
336
|
|
330
337
|
date.strftime(format.to_s)
|
331
338
|
end
|
@@ -419,18 +426,28 @@ module Liquid
|
|
419
426
|
result.is_a?(BigDecimal) ? result.to_f : result
|
420
427
|
end
|
421
428
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
429
|
+
# Set a default value when the input is nil, false or empty
|
430
|
+
#
|
431
|
+
# Example:
|
432
|
+
# {{ product.title | default: "No Title" }}
|
433
|
+
#
|
434
|
+
# Use `allow_false` when an input should only be tested against nil or empty and not false.
|
435
|
+
#
|
436
|
+
# Example:
|
437
|
+
# {{ product.title | default: "No Title", allow_false: true }}
|
438
|
+
#
|
439
|
+
def default(input, default_value = '', options = {})
|
440
|
+
options = {} unless options.is_a?(Hash)
|
441
|
+
false_check = options['allow_false'] ? input.nil? : !input
|
442
|
+
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
|
428
443
|
end
|
429
444
|
|
430
445
|
private
|
431
446
|
|
447
|
+
attr_reader :context
|
448
|
+
|
432
449
|
def raise_property_error(property)
|
433
|
-
raise Liquid::ArgumentError
|
450
|
+
raise Liquid::ArgumentError, "cannot select the property '#{property}'"
|
434
451
|
end
|
435
452
|
|
436
453
|
def apply_operation(input, operand, operation)
|
@@ -457,8 +474,9 @@ module Liquid
|
|
457
474
|
class InputIterator
|
458
475
|
include Enumerable
|
459
476
|
|
460
|
-
def initialize(input)
|
461
|
-
@
|
477
|
+
def initialize(input, context)
|
478
|
+
@context = context
|
479
|
+
@input = if input.is_a?(Array)
|
462
480
|
input.flatten
|
463
481
|
elsif input.is_a?(Hash)
|
464
482
|
[input]
|
@@ -496,6 +514,7 @@ module Liquid
|
|
496
514
|
|
497
515
|
def each
|
498
516
|
@input.each do |e|
|
517
|
+
e.context = @context if e.respond_to?(:context=)
|
499
518
|
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
500
519
|
end
|
501
520
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class StaticRegisters
|
5
|
+
attr_reader :static
|
6
|
+
|
7
|
+
def initialize(registers = {})
|
8
|
+
@static = registers.is_a?(StaticRegisters) ? registers.static : registers
|
9
|
+
@registers = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def []=(key, value)
|
13
|
+
@registers[key] = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
if @registers.key?(key)
|
18
|
+
@registers[key]
|
19
|
+
else
|
20
|
+
@static[key]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(key)
|
25
|
+
@registers.delete(key)
|
26
|
+
end
|
27
|
+
|
28
|
+
UNDEFINED = Object.new
|
29
|
+
|
30
|
+
def fetch(key, default = UNDEFINED, &block)
|
31
|
+
if @registers.key?(key)
|
32
|
+
@registers.fetch(key)
|
33
|
+
elsif default != UNDEFINED
|
34
|
+
@static.fetch(key, default, &block)
|
35
|
+
else
|
36
|
+
@static.fetch(key, &block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def key?(key)
|
41
|
+
@registers.key?(key) || @static.key?(key)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
# StrainerFactory is the factory for the filters system.
|
5
|
+
module StrainerFactory
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def add_global_filter(filter)
|
9
|
+
strainer_class_cache.clear
|
10
|
+
global_filters << filter
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(context, filters = [])
|
14
|
+
strainer_from_cache(filters).new(context)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def global_filters
|
20
|
+
@global_filters ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def strainer_from_cache(filters)
|
24
|
+
strainer_class_cache[filters] ||= begin
|
25
|
+
klass = Class.new(StrainerTemplate)
|
26
|
+
global_filters.each { |f| klass.add_filter(f) }
|
27
|
+
filters.each { |f| klass.add_filter(f) }
|
28
|
+
klass
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def strainer_class_cache
|
33
|
+
@strainer_class_cache ||= {}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Liquid
|
6
|
+
# StrainerTemplate is the computed class for the filters system.
|
7
|
+
# New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
|
8
|
+
#
|
9
|
+
# The Strainer only allows method calls defined in filters given to it via StrainerFactory.add_global_filter,
|
10
|
+
# Context#add_filters or Template.register_filter
|
11
|
+
class StrainerTemplate
|
12
|
+
def initialize(context)
|
13
|
+
@context = context
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def add_filter(filter)
|
18
|
+
return if include?(filter)
|
19
|
+
|
20
|
+
invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
|
21
|
+
if invokable_non_public_methods.any?
|
22
|
+
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
|
23
|
+
end
|
24
|
+
|
25
|
+
include(filter)
|
26
|
+
|
27
|
+
filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
28
|
+
end
|
29
|
+
|
30
|
+
def invokable?(method)
|
31
|
+
filter_methods.include?(method.to_s)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def filter_methods
|
37
|
+
@filter_methods ||= Set.new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def invoke(method, *args)
|
42
|
+
if self.class.invokable?(method)
|
43
|
+
send(method, *args)
|
44
|
+
elsif @context.strict_filters
|
45
|
+
raise Liquid::UndefinedFilter, "undefined filter #{method}"
|
46
|
+
else
|
47
|
+
args.first
|
48
|
+
end
|
49
|
+
rescue ::ArgumentError => e
|
50
|
+
raise Liquid::ArgumentError, e.message, e.backtrace
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
class TablerowloopDrop < Drop
|
3
5
|
def initialize(length, cols)
|
4
6
|
@length = length
|
5
|
-
@row
|
6
|
-
@col
|
7
|
-
@cols
|
8
|
-
@index
|
7
|
+
@row = 1
|
8
|
+
@col = 1
|
9
|
+
@cols = cols
|
10
|
+
@index = 0
|
9
11
|
end
|
10
12
|
|
11
13
|
attr_reader :length, :col, :row
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class Tag
|
5
|
+
module Disableable
|
6
|
+
def render_to_output_buffer(context, output)
|
7
|
+
if context.tag_disabled?(tag_name)
|
8
|
+
output << disabled_error(context)
|
9
|
+
return
|
10
|
+
end
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def disabled_error(context)
|
15
|
+
# raise then rescue the exception so that the Context#exception_renderer can re-raise it
|
16
|
+
raise DisabledError, "#{tag_name} #{parse_context[:locale].t('errors.disabled.tag')}"
|
17
|
+
rescue DisabledError => exc
|
18
|
+
context.handle_error(exc, line_number)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class Tag
|
5
|
+
module Disabler
|
6
|
+
module ClassMethods
|
7
|
+
attr_reader :disabled_tags
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.prepended(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
end
|
13
|
+
|
14
|
+
def render_to_output_buffer(context, output)
|
15
|
+
context.with_disabled_tags(self.class.disabled_tags) do
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/liquid/tag.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
class Tag
|
3
5
|
attr_reader :nodelist, :tag_name, :line_number, :parse_context
|
@@ -5,20 +7,26 @@ module Liquid
|
|
5
7
|
include ParserSwitching
|
6
8
|
|
7
9
|
class << self
|
8
|
-
def parse(tag_name, markup, tokenizer,
|
9
|
-
tag = new(tag_name, markup,
|
10
|
+
def parse(tag_name, markup, tokenizer, parse_context)
|
11
|
+
tag = new(tag_name, markup, parse_context)
|
10
12
|
tag.parse(tokenizer)
|
11
13
|
tag
|
12
14
|
end
|
13
15
|
|
16
|
+
def disable_tags(*tag_names)
|
17
|
+
@disabled_tags ||= []
|
18
|
+
@disabled_tags.concat(tag_names)
|
19
|
+
prepend(Disabler)
|
20
|
+
end
|
21
|
+
|
14
22
|
private :new
|
15
23
|
end
|
16
24
|
|
17
25
|
def initialize(tag_name, markup, parse_context)
|
18
|
-
@tag_name
|
19
|
-
@markup
|
26
|
+
@tag_name = tag_name
|
27
|
+
@markup = markup
|
20
28
|
@parse_context = parse_context
|
21
|
-
@line_number
|
29
|
+
@line_number = parse_context.line_number
|
22
30
|
end
|
23
31
|
|
24
32
|
def parse(_tokens)
|
@@ -33,11 +41,25 @@ module Liquid
|
|
33
41
|
end
|
34
42
|
|
35
43
|
def render(_context)
|
36
|
-
''
|
44
|
+
''
|
45
|
+
end
|
46
|
+
|
47
|
+
# For backwards compatibility with custom tags. In a future release, the semantics
|
48
|
+
# of the `render_to_output_buffer` method will become the default and the `render`
|
49
|
+
# method will be removed.
|
50
|
+
def render_to_output_buffer(context, output)
|
51
|
+
output << render(context)
|
52
|
+
output
|
37
53
|
end
|
38
54
|
|
39
55
|
def blank?
|
40
56
|
false
|
41
57
|
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def parse_expression(markup)
|
62
|
+
parse_context.parse_expression(markup)
|
63
|
+
end
|
42
64
|
end
|
43
65
|
end
|
data/lib/liquid/tags/assign.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
# Assign sets a variable in your template.
|
3
5
|
#
|
@@ -10,23 +12,28 @@ module Liquid
|
|
10
12
|
class Assign < Tag
|
11
13
|
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
|
12
14
|
|
15
|
+
# @api private
|
16
|
+
def self.raise_syntax_error(parse_context)
|
17
|
+
raise Liquid::SyntaxError, parse_context.locale.t('errors.syntax.assign')
|
18
|
+
end
|
19
|
+
|
13
20
|
attr_reader :to, :from
|
14
21
|
|
15
|
-
def initialize(tag_name, markup,
|
22
|
+
def initialize(tag_name, markup, parse_context)
|
16
23
|
super
|
17
24
|
if markup =~ Syntax
|
18
|
-
@to
|
19
|
-
@from = Variable.new(
|
25
|
+
@to = Regexp.last_match(1)
|
26
|
+
@from = Variable.new(Regexp.last_match(2), parse_context)
|
20
27
|
else
|
21
|
-
|
28
|
+
self.class.raise_syntax_error(parse_context)
|
22
29
|
end
|
23
30
|
end
|
24
31
|
|
25
|
-
def
|
32
|
+
def render_to_output_buffer(context, output)
|
26
33
|
val = @from.render(context)
|
27
34
|
context.scopes.last[@to] = val
|
28
|
-
context.resource_limits.
|
29
|
-
|
35
|
+
context.resource_limits.increment_assign_score(assign_score_of(val))
|
36
|
+
output
|
30
37
|
end
|
31
38
|
|
32
39
|
def blank?
|
@@ -37,12 +44,19 @@ module Liquid
|
|
37
44
|
|
38
45
|
def assign_score_of(val)
|
39
46
|
if val.instance_of?(String)
|
40
|
-
val.
|
41
|
-
elsif val.instance_of?(Array)
|
47
|
+
val.bytesize
|
48
|
+
elsif val.instance_of?(Array)
|
42
49
|
sum = 1
|
43
50
|
# Uses #each to avoid extra allocations.
|
44
51
|
val.each { |child| sum += assign_score_of(child) }
|
45
52
|
sum
|
53
|
+
elsif val.instance_of?(Hash)
|
54
|
+
sum = 1
|
55
|
+
val.each do |key, entry_value|
|
56
|
+
sum += assign_score_of(key)
|
57
|
+
sum += assign_score_of(entry_value)
|
58
|
+
end
|
59
|
+
sum
|
46
60
|
else
|
47
61
|
1
|
48
62
|
end
|
@@ -55,5 +69,5 @@ module Liquid
|
|
55
69
|
end
|
56
70
|
end
|
57
71
|
|
58
|
-
Template.register_tag('assign'
|
72
|
+
Template.register_tag('assign', Assign)
|
59
73
|
end
|
data/lib/liquid/tags/break.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
# Break tag to be used to break out of a for loop.
|
3
5
|
#
|
@@ -9,10 +11,13 @@ module Liquid
|
|
9
11
|
# {% endfor %}
|
10
12
|
#
|
11
13
|
class Break < Tag
|
12
|
-
|
13
|
-
|
14
|
+
INTERRUPT = BreakInterrupt.new.freeze
|
15
|
+
|
16
|
+
def render_to_output_buffer(context, output)
|
17
|
+
context.push_interrupt(INTERRUPT)
|
18
|
+
output
|
14
19
|
end
|
15
20
|
end
|
16
21
|
|
17
|
-
Template.register_tag('break'
|
22
|
+
Template.register_tag('break', Break)
|
18
23
|
end
|
data/lib/liquid/tags/capture.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
# Capture stores the result of a block into a variable without rendering it inplace.
|
3
5
|
#
|
@@ -16,17 +18,18 @@ module Liquid
|
|
16
18
|
def initialize(tag_name, markup, options)
|
17
19
|
super
|
18
20
|
if markup =~ Syntax
|
19
|
-
@to =
|
21
|
+
@to = Regexp.last_match(1)
|
20
22
|
else
|
21
|
-
raise SyntaxError
|
23
|
+
raise SyntaxError, options[:locale].t("errors.syntax.capture")
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
def render_to_output_buffer(context, output)
|
28
|
+
context.resource_limits.with_capture do
|
29
|
+
capture_output = render(context)
|
30
|
+
context.scopes.last[@to] = capture_output
|
31
|
+
end
|
32
|
+
output
|
30
33
|
end
|
31
34
|
|
32
35
|
def blank?
|
@@ -34,5 +37,5 @@ module Liquid
|
|
34
37
|
end
|
35
38
|
end
|
36
39
|
|
37
|
-
Template.register_tag('capture'
|
40
|
+
Template.register_tag('capture', Capture)
|
38
41
|
end
|