liquid 3.0.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +130 -62
- data/README.md +31 -0
- data/lib/liquid/block.rb +31 -124
- data/lib/liquid/block_body.rb +75 -59
- data/lib/liquid/condition.rb +23 -22
- data/lib/liquid/context.rb +51 -60
- data/lib/liquid/document.rb +19 -9
- data/lib/liquid/drop.rb +17 -16
- data/lib/liquid/errors.rb +20 -24
- data/lib/liquid/expression.rb +15 -3
- data/lib/liquid/extensions.rb +13 -7
- data/lib/liquid/file_system.rb +11 -11
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +5 -5
- data/lib/liquid/interrupts.rb +1 -2
- data/lib/liquid/lexer.rb +6 -4
- data/lib/liquid/locales/en.yml +5 -1
- data/lib/liquid/parse_context.rb +37 -0
- data/lib/liquid/parser.rb +1 -1
- data/lib/liquid/parser_switching.rb +4 -4
- data/lib/liquid/profiler/hooks.rb +7 -7
- data/lib/liquid/profiler.rb +18 -19
- data/lib/liquid/range_lookup.rb +16 -1
- data/lib/liquid/resource_limits.rb +23 -0
- data/lib/liquid/standardfilters.rb +121 -61
- data/lib/liquid/strainer.rb +32 -29
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +9 -8
- data/lib/liquid/tags/assign.rb +17 -4
- data/lib/liquid/tags/break.rb +0 -3
- data/lib/liquid/tags/capture.rb +2 -2
- data/lib/liquid/tags/case.rb +19 -12
- data/lib/liquid/tags/comment.rb +2 -2
- data/lib/liquid/tags/cycle.rb +6 -6
- data/lib/liquid/tags/decrement.rb +1 -4
- data/lib/liquid/tags/for.rb +95 -75
- data/lib/liquid/tags/if.rb +48 -43
- data/lib/liquid/tags/ifchanged.rb +0 -2
- data/lib/liquid/tags/include.rb +61 -52
- data/lib/liquid/tags/raw.rb +32 -4
- data/lib/liquid/tags/table_row.rb +12 -31
- data/lib/liquid/tags/unless.rb +4 -5
- data/lib/liquid/template.rb +42 -54
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/utils.rb +52 -8
- data/lib/liquid/variable.rb +46 -45
- data/lib/liquid/variable_lookup.rb +9 -5
- data/lib/liquid/version.rb +1 -1
- data/lib/liquid.rb +9 -7
- data/test/integration/assign_test.rb +18 -8
- data/test/integration/blank_test.rb +14 -14
- data/test/integration/capture_test.rb +10 -0
- data/test/integration/context_test.rb +2 -2
- data/test/integration/document_test.rb +19 -0
- data/test/integration/drop_test.rb +42 -40
- data/test/integration/error_handling_test.rb +99 -46
- data/test/integration/filter_test.rb +72 -19
- data/test/integration/hash_ordering_test.rb +9 -9
- data/test/integration/output_test.rb +34 -27
- data/test/integration/parsing_quirks_test.rb +15 -13
- data/test/integration/render_profiling_test.rb +20 -20
- data/test/integration/security_test.rb +9 -7
- data/test/integration/standard_filter_test.rb +198 -42
- data/test/integration/tags/break_tag_test.rb +1 -2
- data/test/integration/tags/continue_tag_test.rb +0 -1
- data/test/integration/tags/for_tag_test.rb +133 -98
- data/test/integration/tags/if_else_tag_test.rb +96 -77
- data/test/integration/tags/include_tag_test.rb +34 -30
- data/test/integration/tags/increment_tag_test.rb +10 -11
- data/test/integration/tags/raw_tag_test.rb +7 -1
- data/test/integration/tags/standard_tag_test.rb +121 -122
- data/test/integration/tags/statements_test.rb +3 -5
- data/test/integration/tags/table_row_test.rb +20 -19
- data/test/integration/tags/unless_else_tag_test.rb +6 -6
- data/test/integration/template_test.rb +190 -49
- data/test/integration/trim_mode_test.rb +525 -0
- data/test/integration/variable_test.rb +23 -13
- data/test/test_helper.rb +44 -9
- data/test/unit/block_unit_test.rb +8 -5
- data/test/unit/condition_unit_test.rb +86 -77
- data/test/unit/context_unit_test.rb +48 -57
- data/test/unit/file_system_unit_test.rb +3 -3
- data/test/unit/i18n_unit_test.rb +2 -2
- data/test/unit/lexer_unit_test.rb +11 -8
- data/test/unit/parser_unit_test.rb +2 -2
- data/test/unit/regexp_unit_test.rb +1 -1
- data/test/unit/strainer_unit_test.rb +85 -8
- data/test/unit/tag_unit_test.rb +7 -2
- data/test/unit/tags/case_tag_unit_test.rb +1 -1
- data/test/unit/tags/for_tag_unit_test.rb +2 -2
- data/test/unit/tags/if_tag_unit_test.rb +1 -1
- data/test/unit/template_unit_test.rb +14 -5
- data/test/unit/tokenizer_unit_test.rb +24 -7
- data/test/unit/variable_unit_test.rb +66 -43
- metadata +55 -50
- data/lib/liquid/module_ex.rb +0 -62
- data/lib/liquid/token.rb +0 -18
- data/test/unit/module_ex_unit_test.rb +0 -87
- /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -2,7 +2,6 @@ require 'cgi'
|
|
2
2
|
require 'bigdecimal'
|
3
3
|
|
4
4
|
module Liquid
|
5
|
-
|
6
5
|
module StandardFilters
|
7
6
|
HTML_ESCAPE = {
|
8
7
|
'&'.freeze => '&'.freeze,
|
@@ -34,7 +33,7 @@ module Liquid
|
|
34
33
|
end
|
35
34
|
|
36
35
|
def escape(input)
|
37
|
-
CGI.escapeHTML(input).untaint
|
36
|
+
CGI.escapeHTML(input).untaint unless input.nil?
|
38
37
|
end
|
39
38
|
alias_method :h, :escape
|
40
39
|
|
@@ -43,12 +42,16 @@ module Liquid
|
|
43
42
|
end
|
44
43
|
|
45
44
|
def url_encode(input)
|
46
|
-
CGI.escape(input)
|
45
|
+
CGI.escape(input) unless input.nil?
|
46
|
+
end
|
47
|
+
|
48
|
+
def url_decode(input)
|
49
|
+
CGI.unescape(input) unless input.nil?
|
47
50
|
end
|
48
51
|
|
49
|
-
def slice(input, offset, length=nil)
|
50
|
-
offset =
|
51
|
-
length = length ?
|
52
|
+
def slice(input, offset, length = nil)
|
53
|
+
offset = Utils.to_integer(offset)
|
54
|
+
length = length ? Utils.to_integer(length) : 1
|
52
55
|
|
53
56
|
if input.is_a?(Array)
|
54
57
|
input.slice(offset, length) || []
|
@@ -59,18 +62,22 @@ module Liquid
|
|
59
62
|
|
60
63
|
# Truncate a string down to x characters
|
61
64
|
def truncate(input, length = 50, truncate_string = "...".freeze)
|
62
|
-
if input.nil?
|
63
|
-
|
65
|
+
return if input.nil?
|
66
|
+
input_str = input.to_s
|
67
|
+
length = Utils.to_integer(length)
|
68
|
+
truncate_string_str = truncate_string.to_s
|
69
|
+
l = length - truncate_string_str.length
|
64
70
|
l = 0 if l < 0
|
65
|
-
|
71
|
+
input_str.length > length ? input_str[0...l] + truncate_string_str : input_str
|
66
72
|
end
|
67
73
|
|
68
74
|
def truncatewords(input, words = 15, truncate_string = "...".freeze)
|
69
|
-
if input.nil?
|
75
|
+
return if input.nil?
|
70
76
|
wordlist = input.to_s.split
|
71
|
-
|
77
|
+
words = Utils.to_integer(words)
|
78
|
+
l = words - 1
|
72
79
|
l = 0 if l < 0
|
73
|
-
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input
|
80
|
+
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string.to_s : input
|
74
81
|
end
|
75
82
|
|
76
83
|
# Split input string into an array of substrings separated by given pattern.
|
@@ -79,7 +86,7 @@ module Liquid
|
|
79
86
|
# <div class="summary">{{ post | split '//' | first }}</div>
|
80
87
|
#
|
81
88
|
def split(input, pattern)
|
82
|
-
input.to_s.split(pattern)
|
89
|
+
input.to_s.split(pattern.to_s)
|
83
90
|
end
|
84
91
|
|
85
92
|
def strip(input)
|
@@ -115,10 +122,32 @@ module Liquid
|
|
115
122
|
ary = InputIterator.new(input)
|
116
123
|
if property.nil?
|
117
124
|
ary.sort
|
125
|
+
elsif ary.empty? # The next two cases assume a non-empty array.
|
126
|
+
[]
|
118
127
|
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
119
|
-
ary.sort
|
120
|
-
|
121
|
-
|
128
|
+
ary.sort do |a, b|
|
129
|
+
a = a[property]
|
130
|
+
b = b[property]
|
131
|
+
if a && b
|
132
|
+
a <=> b
|
133
|
+
else
|
134
|
+
a ? -1 : 1
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Sort elements of an array ignoring case if strings
|
141
|
+
# provide optional property with which to sort an array of hashes or drops
|
142
|
+
def sort_natural(input, property = nil)
|
143
|
+
ary = InputIterator.new(input)
|
144
|
+
|
145
|
+
if property.nil?
|
146
|
+
ary.sort { |a, b| a.casecmp(b) }
|
147
|
+
elsif ary.empty? # The next two cases assume a non-empty array.
|
148
|
+
[]
|
149
|
+
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
150
|
+
ary.sort { |a, b| a[property].casecmp(b[property]) }
|
122
151
|
end
|
123
152
|
end
|
124
153
|
|
@@ -126,10 +155,13 @@ module Liquid
|
|
126
155
|
# provide optional property with which to determine uniqueness
|
127
156
|
def uniq(input, property = nil)
|
128
157
|
ary = InputIterator.new(input)
|
158
|
+
|
129
159
|
if property.nil?
|
130
|
-
|
131
|
-
elsif
|
132
|
-
|
160
|
+
ary.uniq
|
161
|
+
elsif ary.empty? # The next two cases assume a non-empty array.
|
162
|
+
[]
|
163
|
+
elsif ary.first.respond_to?(:[])
|
164
|
+
ary.uniq{ |a| a[property] }
|
133
165
|
end
|
134
166
|
end
|
135
167
|
|
@@ -147,29 +179,44 @@ module Liquid
|
|
147
179
|
if property == "to_liquid".freeze
|
148
180
|
e
|
149
181
|
elsif e.respond_to?(:[])
|
150
|
-
e[property]
|
182
|
+
r = e[property]
|
183
|
+
r.is_a?(Proc) ? r.call : r
|
151
184
|
end
|
152
185
|
end
|
153
186
|
end
|
154
187
|
|
188
|
+
# Remove nils within an array
|
189
|
+
# provide optional property with which to check for nil
|
190
|
+
def compact(input, property = nil)
|
191
|
+
ary = InputIterator.new(input)
|
192
|
+
|
193
|
+
if property.nil?
|
194
|
+
ary.compact
|
195
|
+
elsif ary.empty? # The next two cases assume a non-empty array.
|
196
|
+
[]
|
197
|
+
elsif ary.first.respond_to?(:[])
|
198
|
+
ary.reject{ |a| a[property].nil? }
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
155
202
|
# Replace occurrences of a string with another
|
156
203
|
def replace(input, string, replacement = ''.freeze)
|
157
|
-
input.to_s.gsub(string, replacement.to_s)
|
204
|
+
input.to_s.gsub(string.to_s, replacement.to_s)
|
158
205
|
end
|
159
206
|
|
160
207
|
# Replace the first occurrences of a string with another
|
161
208
|
def replace_first(input, string, replacement = ''.freeze)
|
162
|
-
input.to_s.sub(string, replacement.to_s)
|
209
|
+
input.to_s.sub(string.to_s, replacement.to_s)
|
163
210
|
end
|
164
211
|
|
165
212
|
# remove a substring
|
166
213
|
def remove(input, string)
|
167
|
-
input.to_s.gsub(string, ''.freeze)
|
214
|
+
input.to_s.gsub(string.to_s, ''.freeze)
|
168
215
|
end
|
169
216
|
|
170
217
|
# remove the first occurrences of a substring
|
171
218
|
def remove_first(input, string)
|
172
|
-
input.to_s.sub(string, ''.freeze)
|
219
|
+
input.to_s.sub(string.to_s, ''.freeze)
|
173
220
|
end
|
174
221
|
|
175
222
|
# add one string to another
|
@@ -177,6 +224,13 @@ module Liquid
|
|
177
224
|
input.to_s + string.to_s
|
178
225
|
end
|
179
226
|
|
227
|
+
def concat(input, array)
|
228
|
+
unless array.respond_to?(:to_ary)
|
229
|
+
raise ArgumentError.new("concat filter requires an array argument")
|
230
|
+
end
|
231
|
+
InputIterator.new(input).concat(array)
|
232
|
+
end
|
233
|
+
|
180
234
|
# prepend a string to another
|
181
235
|
def prepend(input, string)
|
182
236
|
string.to_s + input.to_s
|
@@ -221,7 +275,7 @@ module Liquid
|
|
221
275
|
def date(input, format)
|
222
276
|
return input if format.to_s.empty?
|
223
277
|
|
224
|
-
return input unless date = to_date(input)
|
278
|
+
return input unless date = Utils.to_date(input)
|
225
279
|
|
226
280
|
date.strftime(format.to_s)
|
227
281
|
end
|
@@ -244,6 +298,12 @@ module Liquid
|
|
244
298
|
array.last if array.respond_to?(:last)
|
245
299
|
end
|
246
300
|
|
301
|
+
# absolute value
|
302
|
+
def abs(input)
|
303
|
+
result = Utils.to_number(input).abs
|
304
|
+
result.is_a?(BigDecimal) ? result.to_f : result
|
305
|
+
end
|
306
|
+
|
247
307
|
# addition
|
248
308
|
def plus(input, operand)
|
249
309
|
apply_operation(input, operand, :+)
|
@@ -262,66 +322,49 @@ module Liquid
|
|
262
322
|
# division
|
263
323
|
def divided_by(input, operand)
|
264
324
|
apply_operation(input, operand, :/)
|
325
|
+
rescue ::ZeroDivisionError => e
|
326
|
+
raise Liquid::ZeroDivisionError, e.message
|
265
327
|
end
|
266
328
|
|
267
329
|
def modulo(input, operand)
|
268
330
|
apply_operation(input, operand, :%)
|
331
|
+
rescue ::ZeroDivisionError => e
|
332
|
+
raise Liquid::ZeroDivisionError, e.message
|
269
333
|
end
|
270
334
|
|
271
335
|
def round(input, n = 0)
|
272
|
-
result = to_number(input).round(to_number(n))
|
336
|
+
result = Utils.to_number(input).round(Utils.to_number(n))
|
273
337
|
result = result.to_f if result.is_a?(BigDecimal)
|
274
338
|
result = result.to_i if n == 0
|
275
339
|
result
|
340
|
+
rescue ::FloatDomainError => e
|
341
|
+
raise Liquid::FloatDomainError, e.message
|
276
342
|
end
|
277
343
|
|
278
344
|
def ceil(input)
|
279
|
-
to_number(input).ceil.to_i
|
345
|
+
Utils.to_number(input).ceil.to_i
|
346
|
+
rescue ::FloatDomainError => e
|
347
|
+
raise Liquid::FloatDomainError, e.message
|
280
348
|
end
|
281
349
|
|
282
350
|
def floor(input)
|
283
|
-
to_number(input).floor.to_i
|
284
|
-
|
285
|
-
|
286
|
-
def default(input, default_value = "".freeze)
|
287
|
-
is_blank = input.respond_to?(:empty?) ? input.empty? : !input
|
288
|
-
is_blank ? default_value : input
|
351
|
+
Utils.to_number(input).floor.to_i
|
352
|
+
rescue ::FloatDomainError => e
|
353
|
+
raise Liquid::FloatDomainError, e.message
|
289
354
|
end
|
290
355
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
case obj
|
295
|
-
when Float
|
296
|
-
BigDecimal.new(obj.to_s)
|
297
|
-
when Numeric
|
298
|
-
obj
|
299
|
-
when String
|
300
|
-
(obj.strip =~ /\A\d+\.\d+\z/) ? BigDecimal.new(obj) : obj.to_i
|
356
|
+
def default(input, default_value = ''.freeze)
|
357
|
+
if !input || input.respond_to?(:empty?) && input.empty?
|
358
|
+
default_value
|
301
359
|
else
|
302
|
-
|
360
|
+
input
|
303
361
|
end
|
304
362
|
end
|
305
363
|
|
306
|
-
|
307
|
-
return obj if obj.respond_to?(:strftime)
|
308
|
-
|
309
|
-
case obj
|
310
|
-
when 'now'.freeze, 'today'.freeze
|
311
|
-
Time.now
|
312
|
-
when /\A\d+\z/, Integer
|
313
|
-
Time.at(obj.to_i)
|
314
|
-
when String
|
315
|
-
Time.parse(obj)
|
316
|
-
else
|
317
|
-
nil
|
318
|
-
end
|
319
|
-
rescue ArgumentError
|
320
|
-
nil
|
321
|
-
end
|
364
|
+
private
|
322
365
|
|
323
366
|
def apply_operation(input, operand, operation)
|
324
|
-
result = to_number(input).send(operation, to_number(operand))
|
367
|
+
result = Utils.to_number(input).send(operation, Utils.to_number(operand))
|
325
368
|
result.is_a?(BigDecimal) ? result.to_f : result
|
326
369
|
end
|
327
370
|
|
@@ -344,10 +387,27 @@ module Liquid
|
|
344
387
|
to_a.join(glue)
|
345
388
|
end
|
346
389
|
|
390
|
+
def concat(args)
|
391
|
+
to_a.concat(args)
|
392
|
+
end
|
393
|
+
|
347
394
|
def reverse
|
348
395
|
reverse_each.to_a
|
349
396
|
end
|
350
397
|
|
398
|
+
def uniq(&block)
|
399
|
+
to_a.uniq(&block)
|
400
|
+
end
|
401
|
+
|
402
|
+
def compact
|
403
|
+
to_a.compact
|
404
|
+
end
|
405
|
+
|
406
|
+
def empty?
|
407
|
+
@input.each { return false }
|
408
|
+
true
|
409
|
+
end
|
410
|
+
|
351
411
|
def each
|
352
412
|
@input.each do |e|
|
353
413
|
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
data/lib/liquid/strainer.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
require 'set'
|
2
2
|
|
3
3
|
module Liquid
|
4
|
-
|
5
4
|
# Strainer is the parent class for the filters system.
|
6
5
|
# New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
|
7
6
|
#
|
8
7
|
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
|
9
8
|
# Context#add_filters or Template.register_filter
|
10
9
|
class Strainer #:nodoc:
|
11
|
-
@@
|
12
|
-
|
13
|
-
|
10
|
+
@@global_strainer = Class.new(Strainer) do
|
11
|
+
@filter_methods = Set.new
|
12
|
+
end
|
14
13
|
@@strainer_class_cache = Hash.new do |hash, filters|
|
15
|
-
hash[filters] = Class.new(
|
16
|
-
|
14
|
+
hash[filters] = Class.new(@@global_strainer) do
|
15
|
+
@filter_methods = @@global_strainer.filter_methods.dup
|
16
|
+
filters.each { |f| add_filter(f) }
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -21,43 +21,46 @@ module Liquid
|
|
21
21
|
@context = context
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
24
|
+
class << self
|
25
|
+
attr_reader :filter_methods
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.add_filter(filter)
|
29
|
+
raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
|
30
|
+
unless self.class.include?(filter)
|
31
|
+
invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
|
32
|
+
if invokable_non_public_methods.any?
|
33
|
+
raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
|
34
|
+
else
|
35
|
+
send(:include, filter)
|
36
|
+
@filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
37
|
+
end
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
40
|
-
def self.
|
41
|
-
@@strainer_class_cache
|
41
|
+
def self.global_filter(filter)
|
42
|
+
@@strainer_class_cache.clear
|
43
|
+
@@global_strainer.add_filter(filter)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.invokable?(method)
|
47
|
+
@filter_methods.include?(method.to_s)
|
42
48
|
end
|
43
49
|
|
44
50
|
def self.create(context, filters = [])
|
45
|
-
|
46
|
-
strainer_class_cache[filters].new(context)
|
51
|
+
@@strainer_class_cache[filters].new(context)
|
47
52
|
end
|
48
53
|
|
49
54
|
def invoke(method, *args)
|
50
|
-
if invokable?(method)
|
55
|
+
if self.class.invokable?(method)
|
51
56
|
send(method, *args)
|
57
|
+
elsif @context && @context.strict_filters
|
58
|
+
raise Liquid::UndefinedFilter, "undefined filter #{method}"
|
52
59
|
else
|
53
60
|
args.first
|
54
61
|
end
|
55
62
|
rescue ::ArgumentError => e
|
56
|
-
raise Liquid::ArgumentError.
|
57
|
-
end
|
58
|
-
|
59
|
-
def invokable?(method)
|
60
|
-
@@known_methods.include?(method.to_s) && respond_to?(method)
|
63
|
+
raise Liquid::ArgumentError, e.message, e.backtrace
|
61
64
|
end
|
62
65
|
end
|
63
66
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Liquid
|
2
|
+
class TablerowloopDrop < Drop
|
3
|
+
def initialize(length, cols)
|
4
|
+
@length = length
|
5
|
+
@row = 1
|
6
|
+
@col = 1
|
7
|
+
@cols = cols
|
8
|
+
@index = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :length, :col, :row
|
12
|
+
|
13
|
+
def index
|
14
|
+
@index + 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def index0
|
18
|
+
@index
|
19
|
+
end
|
20
|
+
|
21
|
+
def col0
|
22
|
+
@col - 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def rindex
|
26
|
+
@length - @index
|
27
|
+
end
|
28
|
+
|
29
|
+
def rindex0
|
30
|
+
@length - @index - 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def first
|
34
|
+
@index == 0
|
35
|
+
end
|
36
|
+
|
37
|
+
def last
|
38
|
+
@index == @length - 1
|
39
|
+
end
|
40
|
+
|
41
|
+
def col_first
|
42
|
+
@col == 1
|
43
|
+
end
|
44
|
+
|
45
|
+
def col_last
|
46
|
+
@col == @cols
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def increment!
|
52
|
+
@index += 1
|
53
|
+
|
54
|
+
if @col == @cols
|
55
|
+
@col = 1
|
56
|
+
@row += 1
|
57
|
+
else
|
58
|
+
@col += 1
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/liquid/tag.rb
CHANGED
@@ -1,26 +1,27 @@
|
|
1
1
|
module Liquid
|
2
2
|
class Tag
|
3
|
-
|
4
|
-
|
3
|
+
attr_reader :nodelist, :tag_name, :line_number, :parse_context
|
4
|
+
alias_method :options, :parse_context
|
5
5
|
include ParserSwitching
|
6
6
|
|
7
7
|
class << self
|
8
|
-
def parse(tag_name, markup,
|
8
|
+
def parse(tag_name, markup, tokenizer, options)
|
9
9
|
tag = new(tag_name, markup, options)
|
10
|
-
tag.parse(
|
10
|
+
tag.parse(tokenizer)
|
11
11
|
tag
|
12
12
|
end
|
13
13
|
|
14
14
|
private :new
|
15
15
|
end
|
16
16
|
|
17
|
-
def initialize(tag_name, markup,
|
17
|
+
def initialize(tag_name, markup, parse_context)
|
18
18
|
@tag_name = tag_name
|
19
19
|
@markup = markup
|
20
|
-
@
|
20
|
+
@parse_context = parse_context
|
21
|
+
@line_number = parse_context.line_number
|
21
22
|
end
|
22
23
|
|
23
|
-
def parse(
|
24
|
+
def parse(_tokens)
|
24
25
|
end
|
25
26
|
|
26
27
|
def raw
|
@@ -31,7 +32,7 @@ module Liquid
|
|
31
32
|
self.class.name.downcase
|
32
33
|
end
|
33
34
|
|
34
|
-
def render(
|
35
|
+
def render(_context)
|
35
36
|
''.freeze
|
36
37
|
end
|
37
38
|
|
data/lib/liquid/tags/assign.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module Liquid
|
2
|
-
|
3
2
|
# Assign sets a variable in your template.
|
4
3
|
#
|
5
4
|
# {% assign foo = 'monkey' %}
|
@@ -15,8 +14,7 @@ module Liquid
|
|
15
14
|
super
|
16
15
|
if markup =~ Syntax
|
17
16
|
@to = $1
|
18
|
-
@from = Variable.new($2,options)
|
19
|
-
@from.line_number = line_number
|
17
|
+
@from = Variable.new($2, options)
|
20
18
|
else
|
21
19
|
raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
|
22
20
|
end
|
@@ -25,13 +23,28 @@ module Liquid
|
|
25
23
|
def render(context)
|
26
24
|
val = @from.render(context)
|
27
25
|
context.scopes.last[@to] = val
|
28
|
-
context.
|
26
|
+
context.resource_limits.assign_score += assign_score_of(val)
|
29
27
|
''.freeze
|
30
28
|
end
|
31
29
|
|
32
30
|
def blank?
|
33
31
|
true
|
34
32
|
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def assign_score_of(val)
|
37
|
+
if val.instance_of?(String)
|
38
|
+
val.length
|
39
|
+
elsif val.instance_of?(Array) || val.instance_of?(Hash)
|
40
|
+
sum = 1
|
41
|
+
# Uses #each to avoid extra allocations.
|
42
|
+
val.each { |child| sum += assign_score_of(child) }
|
43
|
+
sum
|
44
|
+
else
|
45
|
+
1
|
46
|
+
end
|
47
|
+
end
|
35
48
|
end
|
36
49
|
|
37
50
|
Template.register_tag('assign'.freeze, Assign)
|
data/lib/liquid/tags/break.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module Liquid
|
2
|
-
|
3
2
|
# Break tag to be used to break out of a for loop.
|
4
3
|
#
|
5
4
|
# == Basic Usage:
|
@@ -10,11 +9,9 @@ module Liquid
|
|
10
9
|
# {% endfor %}
|
11
10
|
#
|
12
11
|
class Break < Tag
|
13
|
-
|
14
12
|
def interrupt
|
15
13
|
BreakInterrupt.new
|
16
14
|
end
|
17
|
-
|
18
15
|
end
|
19
16
|
|
20
17
|
Template.register_tag('break'.freeze, Break)
|
data/lib/liquid/tags/capture.rb
CHANGED
@@ -11,7 +11,7 @@ module Liquid
|
|
11
11
|
# in a sidebar or footer.
|
12
12
|
#
|
13
13
|
class Capture < Block
|
14
|
-
Syntax = /(
|
14
|
+
Syntax = /(#{VariableSignature}+)/o
|
15
15
|
|
16
16
|
def initialize(tag_name, markup, options)
|
17
17
|
super
|
@@ -25,7 +25,7 @@ module Liquid
|
|
25
25
|
def render(context)
|
26
26
|
output = super
|
27
27
|
context.scopes.last[@to] = output
|
28
|
-
context.
|
28
|
+
context.resource_limits.assign_score += output.length
|
29
29
|
''.freeze
|
30
30
|
end
|
31
31
|
|
data/lib/liquid/tags/case.rb
CHANGED
@@ -8,18 +8,24 @@ module Liquid
|
|
8
8
|
@blocks = []
|
9
9
|
|
10
10
|
if markup =~ Syntax
|
11
|
-
@left = $1
|
11
|
+
@left = Expression.parse($1)
|
12
12
|
else
|
13
13
|
raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
def parse(tokens)
|
18
|
+
body = BlockBody.new
|
19
|
+
while parse_body(body, tokens)
|
20
|
+
body = @blocks.last.attachment
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
17
24
|
def nodelist
|
18
|
-
@blocks.
|
25
|
+
@blocks.map(&:attachment)
|
19
26
|
end
|
20
27
|
|
21
28
|
def unknown_tag(tag, markup, tokens)
|
22
|
-
@nodelist = []
|
23
29
|
case tag
|
24
30
|
when 'when'.freeze
|
25
31
|
record_when_condition(markup)
|
@@ -37,10 +43,10 @@ module Liquid
|
|
37
43
|
output = ''
|
38
44
|
@blocks.each do |block|
|
39
45
|
if block.else?
|
40
|
-
return
|
46
|
+
return block.attachment.render(context) if execute_else_block
|
41
47
|
elsif block.evaluate(context)
|
42
48
|
execute_else_block = false
|
43
|
-
output <<
|
49
|
+
output << block.attachment.render(context)
|
44
50
|
end
|
45
51
|
end
|
46
52
|
output
|
@@ -50,27 +56,28 @@ module Liquid
|
|
50
56
|
private
|
51
57
|
|
52
58
|
def record_when_condition(markup)
|
59
|
+
body = BlockBody.new
|
60
|
+
|
53
61
|
while markup
|
54
|
-
|
55
|
-
if not markup =~ WhenSyntax
|
62
|
+
unless markup =~ WhenSyntax
|
56
63
|
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
|
57
64
|
end
|
58
65
|
|
59
66
|
markup = $2
|
60
67
|
|
61
|
-
block = Condition.new(@left, '=='.freeze, $1)
|
62
|
-
block.attach(
|
63
|
-
@blocks
|
68
|
+
block = Condition.new(@left, '=='.freeze, Expression.parse($1))
|
69
|
+
block.attach(body)
|
70
|
+
@blocks << block
|
64
71
|
end
|
65
72
|
end
|
66
73
|
|
67
74
|
def record_else_condition(markup)
|
68
|
-
|
75
|
+
unless markup.strip.empty?
|
69
76
|
raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze))
|
70
77
|
end
|
71
78
|
|
72
79
|
block = ElseCondition.new
|
73
|
-
block.attach(
|
80
|
+
block.attach(BlockBody.new)
|
74
81
|
@blocks << block
|
75
82
|
end
|
76
83
|
end
|