liquid 3.0.6 → 4.0.0.rc1
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 -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
|
|