liquid 3.0.6 → 4.0.3
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 +5 -5
- data/History.md +154 -58
- data/{MIT-LICENSE → LICENSE} +0 -0
- data/README.md +33 -0
- data/lib/liquid/block.rb +42 -125
- data/lib/liquid/block_body.rb +99 -79
- data/lib/liquid/condition.rb +52 -32
- data/lib/liquid/context.rb +57 -51
- 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 +26 -10
- data/lib/liquid/extensions.rb +19 -7
- data/lib/liquid/file_system.rb +11 -11
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +6 -6
- data/lib/liquid/interrupts.rb +1 -2
- data/lib/liquid/lexer.rb +12 -8
- data/lib/liquid/locales/en.yml +6 -2
- data/lib/liquid/parse_context.rb +38 -0
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- 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 +207 -61
- data/lib/liquid/strainer.rb +15 -8
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +9 -8
- data/lib/liquid/tags/assign.rb +25 -4
- data/lib/liquid/tags/break.rb +0 -3
- data/lib/liquid/tags/capture.rb +1 -1
- data/lib/liquid/tags/case.rb +27 -12
- data/lib/liquid/tags/comment.rb +2 -2
- data/lib/liquid/tags/cycle.rb +16 -8
- data/lib/liquid/tags/decrement.rb +1 -4
- data/lib/liquid/tags/for.rb +103 -75
- data/lib/liquid/tags/if.rb +60 -44
- data/lib/liquid/tags/ifchanged.rb +0 -2
- data/lib/liquid/tags/include.rb +71 -51
- data/lib/liquid/tags/raw.rb +32 -4
- data/lib/liquid/tags/table_row.rb +21 -31
- data/lib/liquid/tags/unless.rb +3 -4
- data/lib/liquid/template.rb +42 -54
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/truffle.rb +5 -0
- data/lib/liquid/utils.rb +52 -8
- data/lib/liquid/variable.rb +59 -46
- data/lib/liquid/variable_lookup.rb +14 -6
- data/lib/liquid/version.rb +2 -1
- data/lib/liquid.rb +10 -7
- data/test/integration/assign_test.rb +8 -8
- data/test/integration/blank_test.rb +14 -14
- data/test/integration/block_test.rb +12 -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 +96 -43
- data/test/integration/filter_test.rb +60 -20
- data/test/integration/hash_ordering_test.rb +9 -9
- data/test/integration/output_test.rb +26 -27
- data/test/integration/parse_tree_visitor_test.rb +247 -0
- data/test/integration/parsing_quirks_test.rb +19 -13
- data/test/integration/render_profiling_test.rb +20 -20
- data/test/integration/security_test.rb +23 -7
- data/test/integration/standard_filter_test.rb +426 -46
- 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 +135 -100
- data/test/integration/tags/if_else_tag_test.rb +75 -77
- data/test/integration/tags/include_tag_test.rb +50 -31
- 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 +199 -49
- data/test/integration/trim_mode_test.rb +529 -0
- data/test/integration/variable_test.rb +27 -13
- data/test/test_helper.rb +33 -6
- data/test/truffle/truffle_test.rb +9 -0
- data/test/unit/block_unit_test.rb +8 -5
- data/test/unit/condition_unit_test.rb +94 -77
- data/test/unit/context_unit_test.rb +69 -72
- 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 +12 -9
- 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 +96 -1
- 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 +60 -43
- metadata +62 -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/lib/liquid/profiler.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
|
1
|
+
require 'liquid/profiler/hooks'
|
2
2
|
|
3
|
+
module Liquid
|
3
4
|
# Profiler enables support for profiling template rendering to help track down performance issues.
|
4
5
|
#
|
5
|
-
# To enable profiling,
|
6
|
-
#
|
6
|
+
# To enable profiling, first require 'liquid/profiler'.
|
7
|
+
# Then, to profile a parse/render cycle, pass the <tt>profile: true</tt> option to <tt>Liquid::Template.parse</tt>.
|
8
|
+
# After <tt>Liquid::Template#render</tt> is called, the template object makes available an instance of this
|
7
9
|
# class via the <tt>Liquid::Template#profiler</tt> method.
|
8
10
|
#
|
9
11
|
# template = Liquid::Template.parse(template_content, profile: true)
|
@@ -17,7 +19,7 @@ module Liquid
|
|
17
19
|
# inside of <tt>{% include %}</tt> tags.
|
18
20
|
#
|
19
21
|
# profile.each do |node|
|
20
|
-
# # Access to the
|
22
|
+
# # Access to the node itself
|
21
23
|
# node.code
|
22
24
|
#
|
23
25
|
# # Which template and line number of this node.
|
@@ -44,17 +46,15 @@ module Liquid
|
|
44
46
|
class Timing
|
45
47
|
attr_reader :code, :partial, :line_number, :children
|
46
48
|
|
47
|
-
def initialize(
|
48
|
-
@code =
|
49
|
+
def initialize(node, partial)
|
50
|
+
@code = node.respond_to?(:raw) ? node.raw : node
|
49
51
|
@partial = partial
|
50
|
-
@line_number =
|
52
|
+
@line_number = node.respond_to?(:line_number) ? node.line_number : nil
|
51
53
|
@children = []
|
52
54
|
end
|
53
55
|
|
54
|
-
def self.start(
|
55
|
-
new(
|
56
|
-
t.start
|
57
|
-
end
|
56
|
+
def self.start(node, partial)
|
57
|
+
new(node, partial).tap(&:start)
|
58
58
|
end
|
59
59
|
|
60
60
|
def start
|
@@ -70,11 +70,11 @@ module Liquid
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
def self.
|
74
|
-
if Profiler.current_profile &&
|
75
|
-
Profiler.current_profile.
|
73
|
+
def self.profile_node_render(node)
|
74
|
+
if Profiler.current_profile && node.respond_to?(:render)
|
75
|
+
Profiler.current_profile.start_node(node)
|
76
76
|
output = yield
|
77
|
-
Profiler.current_profile.
|
77
|
+
Profiler.current_profile.end_node(node)
|
78
78
|
output
|
79
79
|
else
|
80
80
|
yield
|
@@ -132,11 +132,11 @@ module Liquid
|
|
132
132
|
@root_timing.children.length
|
133
133
|
end
|
134
134
|
|
135
|
-
def
|
136
|
-
@timing_stack.push(Timing.start(
|
135
|
+
def start_node(node)
|
136
|
+
@timing_stack.push(Timing.start(node, current_partial))
|
137
137
|
end
|
138
138
|
|
139
|
-
def
|
139
|
+
def end_node(_node)
|
140
140
|
timing = @timing_stack.pop
|
141
141
|
timing.finish
|
142
142
|
|
@@ -154,6 +154,5 @@ module Liquid
|
|
154
154
|
def pop_partial
|
155
155
|
@partial_stack.pop
|
156
156
|
end
|
157
|
-
|
158
157
|
end
|
159
158
|
end
|
data/lib/liquid/range_lookup.rb
CHANGED
@@ -16,7 +16,22 @@ module Liquid
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def evaluate(context)
|
19
|
-
context.evaluate(@start_obj)
|
19
|
+
start_int = to_integer(context.evaluate(@start_obj))
|
20
|
+
end_int = to_integer(context.evaluate(@end_obj))
|
21
|
+
start_int..end_int
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def to_integer(input)
|
27
|
+
case input
|
28
|
+
when Integer
|
29
|
+
input
|
30
|
+
when NilClass, String
|
31
|
+
input.to_i
|
32
|
+
else
|
33
|
+
Utils.to_integer(input)
|
34
|
+
end
|
20
35
|
end
|
21
36
|
end
|
22
37
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Liquid
|
2
|
+
class ResourceLimits
|
3
|
+
attr_accessor :render_length, :render_score, :assign_score,
|
4
|
+
:render_length_limit, :render_score_limit, :assign_score_limit
|
5
|
+
|
6
|
+
def initialize(limits)
|
7
|
+
@render_length_limit = limits[:render_length_limit]
|
8
|
+
@render_score_limit = limits[:render_score_limit]
|
9
|
+
@assign_score_limit = limits[:assign_score_limit]
|
10
|
+
reset
|
11
|
+
end
|
12
|
+
|
13
|
+
def reached?
|
14
|
+
(@render_length_limit && @render_length > @render_length_limit) ||
|
15
|
+
(@render_score_limit && @render_score > @render_score_limit) ||
|
16
|
+
(@assign_score_limit && @assign_score > @assign_score_limit)
|
17
|
+
end
|
18
|
+
|
19
|
+
def reset
|
20
|
+
@render_length = @render_score = @assign_score = 0
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -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,
|
@@ -10,8 +9,14 @@ module Liquid
|
|
10
9
|
'<'.freeze => '<'.freeze,
|
11
10
|
'"'.freeze => '"'.freeze,
|
12
11
|
"'".freeze => '''.freeze
|
13
|
-
}
|
12
|
+
}.freeze
|
14
13
|
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
|
14
|
+
STRIP_HTML_BLOCKS = Regexp.union(
|
15
|
+
/<script.*?<\/script>/m,
|
16
|
+
/<!--.*?-->/m,
|
17
|
+
/<style.*?<\/style>/m
|
18
|
+
)
|
19
|
+
STRIP_HTML_TAGS = /<.*?>/m
|
15
20
|
|
16
21
|
# Return the size of an array or of an string
|
17
22
|
def size(input)
|
@@ -34,7 +39,7 @@ module Liquid
|
|
34
39
|
end
|
35
40
|
|
36
41
|
def escape(input)
|
37
|
-
CGI.escapeHTML(input).untaint
|
42
|
+
CGI.escapeHTML(input.to_s).untaint unless input.nil?
|
38
43
|
end
|
39
44
|
alias_method :h, :escape
|
40
45
|
|
@@ -43,12 +48,21 @@ module Liquid
|
|
43
48
|
end
|
44
49
|
|
45
50
|
def url_encode(input)
|
46
|
-
CGI.escape(input)
|
51
|
+
CGI.escape(input.to_s) unless input.nil?
|
47
52
|
end
|
48
53
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
54
|
+
def url_decode(input)
|
55
|
+
return if input.nil?
|
56
|
+
|
57
|
+
result = CGI.unescape(input.to_s)
|
58
|
+
raise Liquid::ArgumentError, "invalid byte sequence in #{result.encoding}" unless result.valid_encoding?
|
59
|
+
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
def slice(input, offset, length = nil)
|
64
|
+
offset = Utils.to_integer(offset)
|
65
|
+
length = length ? Utils.to_integer(length) : 1
|
52
66
|
|
53
67
|
if input.is_a?(Array)
|
54
68
|
input.slice(offset, length) || []
|
@@ -59,18 +73,22 @@ module Liquid
|
|
59
73
|
|
60
74
|
# Truncate a string down to x characters
|
61
75
|
def truncate(input, length = 50, truncate_string = "...".freeze)
|
62
|
-
if input.nil?
|
63
|
-
|
76
|
+
return if input.nil?
|
77
|
+
input_str = input.to_s
|
78
|
+
length = Utils.to_integer(length)
|
79
|
+
truncate_string_str = truncate_string.to_s
|
80
|
+
l = length - truncate_string_str.length
|
64
81
|
l = 0 if l < 0
|
65
|
-
|
82
|
+
input_str.length > length ? input_str[0...l] + truncate_string_str : input_str
|
66
83
|
end
|
67
84
|
|
68
85
|
def truncatewords(input, words = 15, truncate_string = "...".freeze)
|
69
|
-
if input.nil?
|
86
|
+
return if input.nil?
|
70
87
|
wordlist = input.to_s.split
|
71
|
-
|
88
|
+
words = Utils.to_integer(words)
|
89
|
+
l = words - 1
|
72
90
|
l = 0 if l < 0
|
73
|
-
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input
|
91
|
+
wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string.to_s : input
|
74
92
|
end
|
75
93
|
|
76
94
|
# Split input string into an array of substrings separated by given pattern.
|
@@ -79,7 +97,7 @@ module Liquid
|
|
79
97
|
# <div class="summary">{{ post | split '//' | first }}</div>
|
80
98
|
#
|
81
99
|
def split(input, pattern)
|
82
|
-
input.to_s.split(pattern)
|
100
|
+
input.to_s.split(pattern.to_s)
|
83
101
|
end
|
84
102
|
|
85
103
|
def strip(input)
|
@@ -96,7 +114,9 @@ module Liquid
|
|
96
114
|
|
97
115
|
def strip_html(input)
|
98
116
|
empty = ''.freeze
|
99
|
-
input.to_s.gsub(
|
117
|
+
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
|
118
|
+
result.gsub!(STRIP_HTML_TAGS, empty)
|
119
|
+
result
|
100
120
|
end
|
101
121
|
|
102
122
|
# Remove all newlines from the string
|
@@ -113,12 +133,61 @@ module Liquid
|
|
113
133
|
# provide optional property with which to sort an array of hashes or drops
|
114
134
|
def sort(input, property = nil)
|
115
135
|
ary = InputIterator.new(input)
|
136
|
+
|
137
|
+
return [] if ary.empty?
|
138
|
+
|
139
|
+
if property.nil?
|
140
|
+
ary.sort do |a, b|
|
141
|
+
nil_safe_compare(a, b)
|
142
|
+
end
|
143
|
+
elsif ary.all? { |el| el.respond_to?(:[]) }
|
144
|
+
begin
|
145
|
+
ary.sort { |a, b| nil_safe_compare(a[property], b[property]) }
|
146
|
+
rescue TypeError
|
147
|
+
raise_property_error(property)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Sort elements of an array ignoring case if strings
|
153
|
+
# provide optional property with which to sort an array of hashes or drops
|
154
|
+
def sort_natural(input, property = nil)
|
155
|
+
ary = InputIterator.new(input)
|
156
|
+
|
157
|
+
return [] if ary.empty?
|
158
|
+
|
116
159
|
if property.nil?
|
117
|
-
ary.sort
|
118
|
-
|
119
|
-
|
120
|
-
elsif ary.
|
121
|
-
|
160
|
+
ary.sort do |a, b|
|
161
|
+
nil_safe_casecmp(a, b)
|
162
|
+
end
|
163
|
+
elsif ary.all? { |el| el.respond_to?(:[]) }
|
164
|
+
begin
|
165
|
+
ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) }
|
166
|
+
rescue TypeError
|
167
|
+
raise_property_error(property)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Filter the elements of an array to those with a certain property value.
|
173
|
+
# By default the target is any truthy value.
|
174
|
+
def where(input, property, target_value = nil)
|
175
|
+
ary = InputIterator.new(input)
|
176
|
+
|
177
|
+
if ary.empty?
|
178
|
+
[]
|
179
|
+
elsif ary.first.respond_to?(:[]) && target_value.nil?
|
180
|
+
begin
|
181
|
+
ary.select { |item| item[property] }
|
182
|
+
rescue TypeError
|
183
|
+
raise_property_error(property)
|
184
|
+
end
|
185
|
+
elsif ary.first.respond_to?(:[])
|
186
|
+
begin
|
187
|
+
ary.select { |item| item[property] == target_value }
|
188
|
+
rescue TypeError
|
189
|
+
raise_property_error(property)
|
190
|
+
end
|
122
191
|
end
|
123
192
|
end
|
124
193
|
|
@@ -126,10 +195,17 @@ module Liquid
|
|
126
195
|
# provide optional property with which to determine uniqueness
|
127
196
|
def uniq(input, property = nil)
|
128
197
|
ary = InputIterator.new(input)
|
198
|
+
|
129
199
|
if property.nil?
|
130
|
-
|
131
|
-
elsif
|
132
|
-
|
200
|
+
ary.uniq
|
201
|
+
elsif ary.empty? # The next two cases assume a non-empty array.
|
202
|
+
[]
|
203
|
+
elsif ary.first.respond_to?(:[])
|
204
|
+
begin
|
205
|
+
ary.uniq { |a| a[property] }
|
206
|
+
rescue TypeError
|
207
|
+
raise_property_error(property)
|
208
|
+
end
|
133
209
|
end
|
134
210
|
end
|
135
211
|
|
@@ -147,29 +223,50 @@ module Liquid
|
|
147
223
|
if property == "to_liquid".freeze
|
148
224
|
e
|
149
225
|
elsif e.respond_to?(:[])
|
150
|
-
e[property]
|
226
|
+
r = e[property]
|
227
|
+
r.is_a?(Proc) ? r.call : r
|
228
|
+
end
|
229
|
+
end
|
230
|
+
rescue TypeError
|
231
|
+
raise_property_error(property)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Remove nils within an array
|
235
|
+
# provide optional property with which to check for nil
|
236
|
+
def compact(input, property = nil)
|
237
|
+
ary = InputIterator.new(input)
|
238
|
+
|
239
|
+
if property.nil?
|
240
|
+
ary.compact
|
241
|
+
elsif ary.empty? # The next two cases assume a non-empty array.
|
242
|
+
[]
|
243
|
+
elsif ary.first.respond_to?(:[])
|
244
|
+
begin
|
245
|
+
ary.reject { |a| a[property].nil? }
|
246
|
+
rescue TypeError
|
247
|
+
raise_property_error(property)
|
151
248
|
end
|
152
249
|
end
|
153
250
|
end
|
154
251
|
|
155
252
|
# Replace occurrences of a string with another
|
156
253
|
def replace(input, string, replacement = ''.freeze)
|
157
|
-
input.to_s.gsub(string, replacement.to_s)
|
254
|
+
input.to_s.gsub(string.to_s, replacement.to_s)
|
158
255
|
end
|
159
256
|
|
160
257
|
# Replace the first occurrences of a string with another
|
161
258
|
def replace_first(input, string, replacement = ''.freeze)
|
162
|
-
input.to_s.sub(string, replacement.to_s)
|
259
|
+
input.to_s.sub(string.to_s, replacement.to_s)
|
163
260
|
end
|
164
261
|
|
165
262
|
# remove a substring
|
166
263
|
def remove(input, string)
|
167
|
-
input.to_s.gsub(string, ''.freeze)
|
264
|
+
input.to_s.gsub(string.to_s, ''.freeze)
|
168
265
|
end
|
169
266
|
|
170
267
|
# remove the first occurrences of a substring
|
171
268
|
def remove_first(input, string)
|
172
|
-
input.to_s.sub(string, ''.freeze)
|
269
|
+
input.to_s.sub(string.to_s, ''.freeze)
|
173
270
|
end
|
174
271
|
|
175
272
|
# add one string to another
|
@@ -177,6 +274,13 @@ module Liquid
|
|
177
274
|
input.to_s + string.to_s
|
178
275
|
end
|
179
276
|
|
277
|
+
def concat(input, array)
|
278
|
+
unless array.respond_to?(:to_ary)
|
279
|
+
raise ArgumentError.new("concat filter requires an array argument")
|
280
|
+
end
|
281
|
+
InputIterator.new(input).concat(array)
|
282
|
+
end
|
283
|
+
|
180
284
|
# prepend a string to another
|
181
285
|
def prepend(input, string)
|
182
286
|
string.to_s + input.to_s
|
@@ -221,7 +325,7 @@ module Liquid
|
|
221
325
|
def date(input, format)
|
222
326
|
return input if format.to_s.empty?
|
223
327
|
|
224
|
-
return input unless date = to_date(input)
|
328
|
+
return input unless date = Utils.to_date(input)
|
225
329
|
|
226
330
|
date.strftime(format.to_s)
|
227
331
|
end
|
@@ -244,6 +348,12 @@ module Liquid
|
|
244
348
|
array.last if array.respond_to?(:last)
|
245
349
|
end
|
246
350
|
|
351
|
+
# absolute value
|
352
|
+
def abs(input)
|
353
|
+
result = Utils.to_number(input).abs
|
354
|
+
result.is_a?(BigDecimal) ? result.to_f : result
|
355
|
+
end
|
356
|
+
|
247
357
|
# addition
|
248
358
|
def plus(input, operand)
|
249
359
|
apply_operation(input, operand, :+)
|
@@ -262,69 +372,88 @@ module Liquid
|
|
262
372
|
# division
|
263
373
|
def divided_by(input, operand)
|
264
374
|
apply_operation(input, operand, :/)
|
375
|
+
rescue ::ZeroDivisionError => e
|
376
|
+
raise Liquid::ZeroDivisionError, e.message
|
265
377
|
end
|
266
378
|
|
267
379
|
def modulo(input, operand)
|
268
380
|
apply_operation(input, operand, :%)
|
381
|
+
rescue ::ZeroDivisionError => e
|
382
|
+
raise Liquid::ZeroDivisionError, e.message
|
269
383
|
end
|
270
384
|
|
271
385
|
def round(input, n = 0)
|
272
|
-
result = to_number(input).round(to_number(n))
|
386
|
+
result = Utils.to_number(input).round(Utils.to_number(n))
|
273
387
|
result = result.to_f if result.is_a?(BigDecimal)
|
274
388
|
result = result.to_i if n == 0
|
275
389
|
result
|
390
|
+
rescue ::FloatDomainError => e
|
391
|
+
raise Liquid::FloatDomainError, e.message
|
276
392
|
end
|
277
393
|
|
278
394
|
def ceil(input)
|
279
|
-
to_number(input).ceil.to_i
|
395
|
+
Utils.to_number(input).ceil.to_i
|
396
|
+
rescue ::FloatDomainError => e
|
397
|
+
raise Liquid::FloatDomainError, e.message
|
280
398
|
end
|
281
399
|
|
282
400
|
def floor(input)
|
283
|
-
to_number(input).floor.to_i
|
401
|
+
Utils.to_number(input).floor.to_i
|
402
|
+
rescue ::FloatDomainError => e
|
403
|
+
raise Liquid::FloatDomainError, e.message
|
284
404
|
end
|
285
405
|
|
286
|
-
def
|
287
|
-
|
288
|
-
|
406
|
+
def at_least(input, n)
|
407
|
+
min_value = Utils.to_number(n)
|
408
|
+
|
409
|
+
result = Utils.to_number(input)
|
410
|
+
result = min_value if min_value > result
|
411
|
+
result.is_a?(BigDecimal) ? result.to_f : result
|
289
412
|
end
|
290
413
|
|
291
|
-
|
414
|
+
def at_most(input, n)
|
415
|
+
max_value = Utils.to_number(n)
|
416
|
+
|
417
|
+
result = Utils.to_number(input)
|
418
|
+
result = max_value if max_value < result
|
419
|
+
result.is_a?(BigDecimal) ? result.to_f : result
|
420
|
+
end
|
292
421
|
|
293
|
-
def
|
294
|
-
|
295
|
-
|
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
|
422
|
+
def default(input, default_value = ''.freeze)
|
423
|
+
if !input || input.respond_to?(:empty?) && input.empty?
|
424
|
+
default_value
|
301
425
|
else
|
302
|
-
|
426
|
+
input
|
303
427
|
end
|
304
428
|
end
|
305
429
|
|
306
|
-
|
307
|
-
return obj if obj.respond_to?(:strftime)
|
430
|
+
private
|
308
431
|
|
309
|
-
|
310
|
-
|
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
|
432
|
+
def raise_property_error(property)
|
433
|
+
raise Liquid::ArgumentError.new("cannot select the property '#{property}'")
|
321
434
|
end
|
322
435
|
|
323
436
|
def apply_operation(input, operand, operation)
|
324
|
-
result = to_number(input).send(operation, to_number(operand))
|
437
|
+
result = Utils.to_number(input).send(operation, Utils.to_number(operand))
|
325
438
|
result.is_a?(BigDecimal) ? result.to_f : result
|
326
439
|
end
|
327
440
|
|
441
|
+
def nil_safe_compare(a, b)
|
442
|
+
if !a.nil? && !b.nil?
|
443
|
+
a <=> b
|
444
|
+
else
|
445
|
+
a.nil? ? 1 : -1
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
def nil_safe_casecmp(a, b)
|
450
|
+
if !a.nil? && !b.nil?
|
451
|
+
a.to_s.casecmp(b.to_s)
|
452
|
+
else
|
453
|
+
a.nil? ? 1 : -1
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
328
457
|
class InputIterator
|
329
458
|
include Enumerable
|
330
459
|
|
@@ -341,13 +470,30 @@ module Liquid
|
|
341
470
|
end
|
342
471
|
|
343
472
|
def join(glue)
|
344
|
-
to_a.join(glue)
|
473
|
+
to_a.join(glue.to_s)
|
474
|
+
end
|
475
|
+
|
476
|
+
def concat(args)
|
477
|
+
to_a.concat(args)
|
345
478
|
end
|
346
479
|
|
347
480
|
def reverse
|
348
481
|
reverse_each.to_a
|
349
482
|
end
|
350
483
|
|
484
|
+
def uniq(&block)
|
485
|
+
to_a.uniq(&block)
|
486
|
+
end
|
487
|
+
|
488
|
+
def compact
|
489
|
+
to_a.compact
|
490
|
+
end
|
491
|
+
|
492
|
+
def empty?
|
493
|
+
@input.each { return false }
|
494
|
+
true
|
495
|
+
end
|
496
|
+
|
351
497
|
def each
|
352
498
|
@input.each do |e|
|
353
499
|
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,19 +21,25 @@ 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
|
-
raise ArgumentError, "Expected module but got: #{
|
31
|
-
unless self.
|
32
|
-
|
33
|
-
|
29
|
+
raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
|
30
|
+
unless self.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
|
34
38
|
end
|
35
39
|
end
|
36
40
|
|
37
41
|
def self.global_filter(filter)
|
42
|
+
@@strainer_class_cache.clear
|
38
43
|
@@global_strainer.add_filter(filter)
|
39
44
|
end
|
40
45
|
|
@@ -49,11 +54,13 @@ module Liquid
|
|
49
54
|
def invoke(method, *args)
|
50
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.
|
63
|
+
raise Liquid::ArgumentError, e.message, e.backtrace
|
57
64
|
end
|
58
65
|
end
|
59
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
|