liquid 3.0.6 → 4.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +89 -58
- data/{MIT-LICENSE → LICENSE} +0 -0
- data/lib/liquid.rb +7 -6
- data/lib/liquid/block.rb +31 -124
- data/lib/liquid/block_body.rb +54 -57
- data/lib/liquid/condition.rb +23 -22
- data/lib/liquid/context.rb +50 -42
- data/lib/liquid/document.rb +19 -9
- data/lib/liquid/drop.rb +12 -13
- data/lib/liquid/errors.rb +16 -17
- data/lib/liquid/expression.rb +15 -3
- data/lib/liquid/extensions.rb +7 -7
- data/lib/liquid/file_system.rb +3 -3
- 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 +3 -1
- data/lib/liquid/parse_context.rb +37 -0
- data/lib/liquid/parser_switching.rb +4 -4
- data/lib/liquid/profiler.rb +18 -19
- data/lib/liquid/profiler/hooks.rb +7 -7
- data/lib/liquid/range_lookup.rb +16 -1
- data/lib/liquid/resource_limits.rb +23 -0
- data/lib/liquid/standardfilters.rb +101 -56
- data/lib/liquid/strainer.rb +4 -5
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +9 -8
- data/lib/liquid/tags/assign.rb +5 -4
- data/lib/liquid/tags/break.rb +0 -3
- data/lib/liquid/tags/capture.rb +1 -1
- 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 +93 -75
- data/lib/liquid/tags/if.rb +49 -44
- data/lib/liquid/tags/ifchanged.rb +0 -2
- data/lib/liquid/tags/include.rb +60 -52
- data/lib/liquid/tags/raw.rb +26 -4
- data/lib/liquid/tags/table_row.rb +12 -30
- data/lib/liquid/tags/unless.rb +3 -4
- data/lib/liquid/template.rb +23 -50
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/utils.rb +48 -8
- data/lib/liquid/variable.rb +46 -45
- data/lib/liquid/variable_lookup.rb +3 -3
- data/lib/liquid/version.rb +1 -1
- data/test/integration/assign_test.rb +8 -8
- data/test/integration/blank_test.rb +14 -14
- 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 +64 -45
- data/test/integration/filter_test.rb +60 -20
- data/test/integration/output_test.rb +26 -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 +5 -7
- data/test/integration/standard_filter_test.rb +119 -37
- 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 +75 -77
- data/test/integration/tags/include_tag_test.rb +23 -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 +91 -45
- data/test/integration/variable_test.rb +23 -13
- data/test/test_helper.rb +33 -5
- data/test/unit/block_unit_test.rb +6 -5
- data/test/unit/condition_unit_test.rb +82 -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 +13 -2
- 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 +6 -5
- data/test/unit/tokenizer_unit_test.rb +24 -7
- data/test/unit/variable_unit_test.rb +60 -43
- metadata +44 -41
- 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
@@ -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,16 +62,19 @@ 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
|
+
l = length - truncate_string.length
|
64
69
|
l = 0 if l < 0
|
65
|
-
|
70
|
+
input_str.length > length ? input_str[0...l] + truncate_string : input_str
|
66
71
|
end
|
67
72
|
|
68
73
|
def truncatewords(input, words = 15, truncate_string = "...".freeze)
|
69
|
-
if input.nil?
|
74
|
+
return if input.nil?
|
70
75
|
wordlist = input.to_s.split
|
71
|
-
|
76
|
+
words = Utils.to_integer(words)
|
77
|
+
l = words - 1
|
72
78
|
l = 0 if l < 0
|
73
79
|
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input
|
74
80
|
end
|
@@ -115,10 +121,28 @@ module Liquid
|
|
115
121
|
ary = InputIterator.new(input)
|
116
122
|
if property.nil?
|
117
123
|
ary.sort
|
124
|
+
elsif ary.empty? # The next two cases assume a non-empty array.
|
125
|
+
[]
|
126
|
+
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
127
|
+
ary.sort { |a, b| a[property] <=> b[property] }
|
128
|
+
elsif ary.first.respond_to?(property)
|
129
|
+
ary.sort { |a, b| a.send(property) <=> b.send(property) }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Sort elements of an array ignoring case if strings
|
134
|
+
# provide optional property with which to sort an array of hashes or drops
|
135
|
+
def sort_natural(input, property = nil)
|
136
|
+
ary = InputIterator.new(input)
|
137
|
+
|
138
|
+
if property.nil?
|
139
|
+
ary.sort { |a, b| a.casecmp(b) }
|
140
|
+
elsif ary.empty? # The next two cases assume a non-empty array.
|
141
|
+
[]
|
118
142
|
elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
|
119
|
-
ary.sort {|a,b| a[property]
|
143
|
+
ary.sort { |a, b| a[property].casecmp(b[property]) }
|
120
144
|
elsif ary.first.respond_to?(property)
|
121
|
-
ary.sort {|a,b| a.send(property)
|
145
|
+
ary.sort { |a, b| a.send(property).casecmp(b.send(property)) }
|
122
146
|
end
|
123
147
|
end
|
124
148
|
|
@@ -126,10 +150,13 @@ module Liquid
|
|
126
150
|
# provide optional property with which to determine uniqueness
|
127
151
|
def uniq(input, property = nil)
|
128
152
|
ary = InputIterator.new(input)
|
153
|
+
|
129
154
|
if property.nil?
|
130
|
-
|
131
|
-
elsif
|
132
|
-
|
155
|
+
ary.uniq
|
156
|
+
elsif ary.empty? # The next two cases assume a non-empty array.
|
157
|
+
[]
|
158
|
+
elsif ary.first.respond_to?(:[])
|
159
|
+
ary.uniq{ |a| a[property] }
|
133
160
|
end
|
134
161
|
end
|
135
162
|
|
@@ -147,29 +174,46 @@ module Liquid
|
|
147
174
|
if property == "to_liquid".freeze
|
148
175
|
e
|
149
176
|
elsif e.respond_to?(:[])
|
150
|
-
e[property]
|
177
|
+
r = e[property]
|
178
|
+
r.is_a?(Proc) ? r.call : r
|
151
179
|
end
|
152
180
|
end
|
153
181
|
end
|
154
182
|
|
183
|
+
# Remove nils within an array
|
184
|
+
# provide optional property with which to check for nil
|
185
|
+
def compact(input, property = nil)
|
186
|
+
ary = InputIterator.new(input)
|
187
|
+
|
188
|
+
if property.nil?
|
189
|
+
ary.compact
|
190
|
+
elsif ary.empty? # The next two cases assume a non-empty array.
|
191
|
+
[]
|
192
|
+
elsif ary.first.respond_to?(:[])
|
193
|
+
ary.reject{ |a| a[property].nil? }
|
194
|
+
elsif ary.first.respond_to?(property)
|
195
|
+
ary.reject { |a| a.send(property).nil? }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
155
199
|
# Replace occurrences of a string with another
|
156
200
|
def replace(input, string, replacement = ''.freeze)
|
157
|
-
input.to_s.gsub(string, replacement.to_s)
|
201
|
+
input.to_s.gsub(string.to_s, replacement.to_s)
|
158
202
|
end
|
159
203
|
|
160
204
|
# Replace the first occurrences of a string with another
|
161
205
|
def replace_first(input, string, replacement = ''.freeze)
|
162
|
-
input.to_s.sub(string, replacement.to_s)
|
206
|
+
input.to_s.sub(string.to_s, replacement.to_s)
|
163
207
|
end
|
164
208
|
|
165
209
|
# remove a substring
|
166
210
|
def remove(input, string)
|
167
|
-
input.to_s.gsub(string, ''.freeze)
|
211
|
+
input.to_s.gsub(string.to_s, ''.freeze)
|
168
212
|
end
|
169
213
|
|
170
214
|
# remove the first occurrences of a substring
|
171
215
|
def remove_first(input, string)
|
172
|
-
input.to_s.sub(string, ''.freeze)
|
216
|
+
input.to_s.sub(string.to_s, ''.freeze)
|
173
217
|
end
|
174
218
|
|
175
219
|
# add one string to another
|
@@ -177,6 +221,10 @@ module Liquid
|
|
177
221
|
input.to_s + string.to_s
|
178
222
|
end
|
179
223
|
|
224
|
+
def concat(input, array)
|
225
|
+
InputIterator.new(input).concat(array)
|
226
|
+
end
|
227
|
+
|
180
228
|
# prepend a string to another
|
181
229
|
def prepend(input, string)
|
182
230
|
string.to_s + input.to_s
|
@@ -221,7 +269,7 @@ module Liquid
|
|
221
269
|
def date(input, format)
|
222
270
|
return input if format.to_s.empty?
|
223
271
|
|
224
|
-
return input unless date = to_date(input)
|
272
|
+
return input unless date = Utils.to_date(input)
|
225
273
|
|
226
274
|
date.strftime(format.to_s)
|
227
275
|
end
|
@@ -262,25 +310,35 @@ module Liquid
|
|
262
310
|
# division
|
263
311
|
def divided_by(input, operand)
|
264
312
|
apply_operation(input, operand, :/)
|
313
|
+
rescue ::ZeroDivisionError => e
|
314
|
+
raise Liquid::ZeroDivisionError, e.message
|
265
315
|
end
|
266
316
|
|
267
317
|
def modulo(input, operand)
|
268
318
|
apply_operation(input, operand, :%)
|
319
|
+
rescue ::ZeroDivisionError => e
|
320
|
+
raise Liquid::ZeroDivisionError, e.message
|
269
321
|
end
|
270
322
|
|
271
323
|
def round(input, n = 0)
|
272
|
-
result = to_number(input).round(to_number(n))
|
324
|
+
result = Utils.to_number(input).round(Utils.to_number(n))
|
273
325
|
result = result.to_f if result.is_a?(BigDecimal)
|
274
326
|
result = result.to_i if n == 0
|
275
327
|
result
|
328
|
+
rescue ::FloatDomainError => e
|
329
|
+
raise Liquid::FloatDomainError, e.message
|
276
330
|
end
|
277
331
|
|
278
332
|
def ceil(input)
|
279
|
-
to_number(input).ceil.to_i
|
333
|
+
Utils.to_number(input).ceil.to_i
|
334
|
+
rescue ::FloatDomainError => e
|
335
|
+
raise Liquid::FloatDomainError, e.message
|
280
336
|
end
|
281
337
|
|
282
338
|
def floor(input)
|
283
|
-
to_number(input).floor.to_i
|
339
|
+
Utils.to_number(input).floor.to_i
|
340
|
+
rescue ::FloatDomainError => e
|
341
|
+
raise Liquid::FloatDomainError, e.message
|
284
342
|
end
|
285
343
|
|
286
344
|
def default(input, default_value = "".freeze)
|
@@ -290,38 +348,8 @@ module Liquid
|
|
290
348
|
|
291
349
|
private
|
292
350
|
|
293
|
-
def to_number(obj)
|
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
|
301
|
-
else
|
302
|
-
0
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
def to_date(obj)
|
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
|
322
|
-
|
323
351
|
def apply_operation(input, operand, operation)
|
324
|
-
result = to_number(input).send(operation, to_number(operand))
|
352
|
+
result = Utils.to_number(input).send(operation, Utils.to_number(operand))
|
325
353
|
result.is_a?(BigDecimal) ? result.to_f : result
|
326
354
|
end
|
327
355
|
|
@@ -344,10 +372,27 @@ module Liquid
|
|
344
372
|
to_a.join(glue)
|
345
373
|
end
|
346
374
|
|
375
|
+
def concat(args)
|
376
|
+
to_a.concat args
|
377
|
+
end
|
378
|
+
|
347
379
|
def reverse
|
348
380
|
reverse_each.to_a
|
349
381
|
end
|
350
382
|
|
383
|
+
def uniq(&block)
|
384
|
+
to_a.uniq(&block)
|
385
|
+
end
|
386
|
+
|
387
|
+
def compact
|
388
|
+
to_a.compact
|
389
|
+
end
|
390
|
+
|
391
|
+
def empty?
|
392
|
+
@input.each { return false }
|
393
|
+
true
|
394
|
+
end
|
395
|
+
|
351
396
|
def each
|
352
397
|
@input.each do |e|
|
353
398
|
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
data/lib/liquid/strainer.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
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
|
#
|
@@ -22,14 +21,14 @@ module Liquid
|
|
22
21
|
@context = context
|
23
22
|
end
|
24
23
|
|
25
|
-
|
26
|
-
|
24
|
+
class << self
|
25
|
+
attr_reader :filter_methods
|
27
26
|
end
|
28
27
|
|
29
28
|
def self.add_filter(filter)
|
30
29
|
raise ArgumentError, "Expected module but got: #{f.class}" unless filter.is_a?(Module)
|
31
30
|
unless self.class.include?(filter)
|
32
|
-
|
31
|
+
send(:include, filter)
|
33
32
|
@filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
34
33
|
end
|
35
34
|
end
|
@@ -53,7 +52,7 @@ module Liquid
|
|
53
52
|
args.first
|
54
53
|
end
|
55
54
|
rescue ::ArgumentError => e
|
56
|
-
raise Liquid::ArgumentError.
|
55
|
+
raise Liquid::ArgumentError, e.message, e.backtrace
|
57
56
|
end
|
58
57
|
end
|
59
58
|
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,7 +23,10 @@ module Liquid
|
|
25
23
|
def render(context)
|
26
24
|
val = @from.render(context)
|
27
25
|
context.scopes.last[@to] = val
|
28
|
-
|
26
|
+
|
27
|
+
inc = val.instance_of?(String) || val.instance_of?(Array) || val.instance_of?(Hash) ? val.length : 1
|
28
|
+
context.resource_limits.assign_score += inc
|
29
|
+
|
29
30
|
''.freeze
|
30
31
|
end
|
31
32
|
|