liquid 4.0.0.rc3 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/History.md +93 -2
- data/README.md +8 -0
- data/lib/liquid.rb +18 -5
- data/lib/liquid/block.rb +47 -20
- data/lib/liquid/block_body.rb +190 -76
- data/lib/liquid/condition.rb +69 -29
- data/lib/liquid/context.rb +122 -76
- data/lib/liquid/document.rb +47 -9
- data/lib/liquid/drop.rb +4 -2
- data/lib/liquid/errors.rb +20 -25
- data/lib/liquid/expression.rb +30 -31
- data/lib/liquid/extensions.rb +8 -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 +35 -26
- data/lib/liquid/locales/en.yml +4 -2
- data/lib/liquid/parse_context.rb +17 -4
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- 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.rb +67 -86
- data/lib/liquid/profiler/hooks.rb +26 -14
- 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 +171 -57
- 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.rb +28 -6
- data/lib/liquid/tag/disableable.rb +22 -0
- data/lib/liquid/tag/disabler.rb +21 -0
- data/lib/liquid/tags/assign.rb +32 -10
- data/lib/liquid/tags/break.rb +8 -3
- data/lib/liquid/tags/capture.rb +11 -8
- data/lib/liquid/tags/case.rb +41 -27
- data/lib/liquid/tags/comment.rb +5 -3
- data/lib/liquid/tags/continue.rb +8 -3
- data/lib/liquid/tags/cycle.rb +35 -16
- data/lib/liquid/tags/decrement.rb +6 -3
- data/lib/liquid/tags/echo.rb +26 -0
- data/lib/liquid/tags/for.rb +79 -47
- data/lib/liquid/tags/if.rb +53 -30
- data/lib/liquid/tags/ifchanged.rb +11 -10
- data/lib/liquid/tags/include.rb +42 -44
- 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 +32 -20
- data/lib/liquid/tags/unless.rb +15 -15
- data/lib/liquid/template.rb +60 -71
- 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 +6 -4
- data/lib/liquid/variable.rb +55 -38
- data/lib/liquid/variable_lookup.rb +14 -6
- data/lib/liquid/version.rb +3 -1
- data/test/integration/assign_test.rb +74 -5
- data/test/integration/blank_test.rb +11 -8
- data/test/integration/block_test.rb +58 -0
- 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 -83
- data/test/integration/error_handling_test.rb +90 -60
- 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 +24 -8
- data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
- data/test/integration/security_test.rb +41 -18
- data/test/integration/standard_filter_test.rb +523 -205
- 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 +109 -53
- data/test/integration/tags/if_else_tag_test.rb +5 -3
- data/test/integration/tags/include_tag_test.rb +83 -52
- 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 +128 -121
- data/test/integration/trim_mode_test.rb +82 -44
- data/test/integration/variable_test.rb +46 -31
- data/test/test_helper.rb +75 -23
- data/test/unit/block_unit_test.rb +19 -24
- data/test/unit/condition_unit_test.rb +82 -72
- 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 +12 -10
- data/test/unit/parse_tree_visitor_test.rb +247 -0
- 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 +83 -50
- data/lib/liquid/strainer.rb +0 -65
- data/test/unit/context_unit_test.rb +0 -483
- data/test/unit/strainer_unit_test.rb +0 -136
data/lib/liquid/range_lookup.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
class RangeLookup
|
3
5
|
def self.parse(start_markup, end_markup)
|
4
6
|
start_obj = Expression.parse(start_markup)
|
5
|
-
end_obj
|
7
|
+
end_obj = Expression.parse(end_markup)
|
6
8
|
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
|
7
9
|
new(start_obj, end_obj)
|
8
10
|
else
|
@@ -12,12 +14,12 @@ module Liquid
|
|
12
14
|
|
13
15
|
def initialize(start_obj, end_obj)
|
14
16
|
@start_obj = start_obj
|
15
|
-
@end_obj
|
17
|
+
@end_obj = end_obj
|
16
18
|
end
|
17
19
|
|
18
20
|
def evaluate(context)
|
19
21
|
start_int = to_integer(context.evaluate(@start_obj))
|
20
|
-
end_int
|
22
|
+
end_int = to_integer(context.evaluate(@end_obj))
|
21
23
|
start_int..end_int
|
22
24
|
end
|
23
25
|
|
@@ -1,23 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
class ResourceLimits
|
3
|
-
attr_accessor :
|
4
|
-
|
5
|
+
attr_accessor :render_length_limit, :render_score_limit, :assign_score_limit
|
6
|
+
attr_reader :render_score, :assign_score
|
5
7
|
|
6
8
|
def initialize(limits)
|
7
9
|
@render_length_limit = limits[:render_length_limit]
|
8
|
-
@render_score_limit
|
9
|
-
@assign_score_limit
|
10
|
+
@render_score_limit = limits[:render_score_limit]
|
11
|
+
@assign_score_limit = limits[:assign_score_limit]
|
10
12
|
reset
|
11
13
|
end
|
12
14
|
|
15
|
+
def increment_render_score(amount)
|
16
|
+
@render_score += amount
|
17
|
+
raise_limits_reached if @render_score_limit && @render_score > @render_score_limit
|
18
|
+
end
|
19
|
+
|
20
|
+
def increment_assign_score(amount)
|
21
|
+
@assign_score += amount
|
22
|
+
raise_limits_reached if @assign_score_limit && @assign_score > @assign_score_limit
|
23
|
+
end
|
24
|
+
|
25
|
+
# update either render_length or assign_score based on whether or not the writes are captured
|
26
|
+
def increment_write_score(output)
|
27
|
+
if (last_captured = @last_capture_length)
|
28
|
+
captured = output.bytesize
|
29
|
+
increment = captured - last_captured
|
30
|
+
@last_capture_length = captured
|
31
|
+
increment_assign_score(increment)
|
32
|
+
elsif @render_length_limit && output.bytesize > @render_length_limit
|
33
|
+
raise_limits_reached
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def raise_limits_reached
|
38
|
+
@reached_limit = true
|
39
|
+
raise MemoryError, "Memory limits exceeded"
|
40
|
+
end
|
41
|
+
|
13
42
|
def reached?
|
14
|
-
|
15
|
-
(@render_score_limit && @render_score > @render_score_limit) ||
|
16
|
-
(@assign_score_limit && @assign_score > @assign_score_limit)
|
43
|
+
@reached_limit
|
17
44
|
end
|
18
45
|
|
19
46
|
def reset
|
20
|
-
@
|
47
|
+
@reached_limit = false
|
48
|
+
@last_capture_length = nil
|
49
|
+
@render_score = @assign_score = 0
|
50
|
+
end
|
51
|
+
|
52
|
+
def with_capture
|
53
|
+
old_capture_length = @last_capture_length
|
54
|
+
begin
|
55
|
+
@last_capture_length = 0
|
56
|
+
yield
|
57
|
+
ensure
|
58
|
+
@last_capture_length = old_capture_length
|
59
|
+
end
|
21
60
|
end
|
22
61
|
end
|
23
62
|
end
|
@@ -1,16 +1,24 @@
|
|
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
|
-
"'"
|
12
|
-
}
|
9
|
+
'&' => '&',
|
10
|
+
'>' => '>',
|
11
|
+
'<' => '<',
|
12
|
+
'"' => '"',
|
13
|
+
"'" => ''',
|
14
|
+
}.freeze
|
13
15
|
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
|
16
|
+
STRIP_HTML_BLOCKS = Regexp.union(
|
17
|
+
%r{<script.*?</script>}m,
|
18
|
+
/<!--.*?-->/m,
|
19
|
+
%r{<style.*?</style>}m
|
20
|
+
)
|
21
|
+
STRIP_HTML_TAGS = /<.*?>/m
|
14
22
|
|
15
23
|
# Return the size of an array or of an string
|
16
24
|
def size(input)
|
@@ -33,7 +41,7 @@ module Liquid
|
|
33
41
|
end
|
34
42
|
|
35
43
|
def escape(input)
|
36
|
-
CGI.escapeHTML(input)
|
44
|
+
CGI.escapeHTML(input.to_s) unless input.nil?
|
37
45
|
end
|
38
46
|
alias_method :h, :escape
|
39
47
|
|
@@ -42,11 +50,16 @@ module Liquid
|
|
42
50
|
end
|
43
51
|
|
44
52
|
def url_encode(input)
|
45
|
-
CGI.escape(input) unless input.nil?
|
53
|
+
CGI.escape(input.to_s) unless input.nil?
|
46
54
|
end
|
47
55
|
|
48
56
|
def url_decode(input)
|
49
|
-
|
57
|
+
return if input.nil?
|
58
|
+
|
59
|
+
result = CGI.unescape(input.to_s)
|
60
|
+
raise Liquid::ArgumentError, "invalid byte sequence in #{result.encoding}" unless result.valid_encoding?
|
61
|
+
|
62
|
+
result
|
50
63
|
end
|
51
64
|
|
52
65
|
def slice(input, offset, length = nil)
|
@@ -61,22 +74,28 @@ module Liquid
|
|
61
74
|
end
|
62
75
|
|
63
76
|
# Truncate a string down to x characters
|
64
|
-
def truncate(input, length = 50, truncate_string = "..."
|
77
|
+
def truncate(input, length = 50, truncate_string = "...")
|
65
78
|
return if input.nil?
|
66
79
|
input_str = input.to_s
|
67
|
-
length
|
68
|
-
|
80
|
+
length = Utils.to_integer(length)
|
81
|
+
|
82
|
+
truncate_string_str = truncate_string.to_s
|
83
|
+
|
84
|
+
l = length - truncate_string_str.length
|
69
85
|
l = 0 if l < 0
|
70
|
-
|
86
|
+
|
87
|
+
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
|
71
88
|
end
|
72
89
|
|
73
|
-
def truncatewords(input, words = 15, truncate_string = "..."
|
90
|
+
def truncatewords(input, words = 15, truncate_string = "...")
|
74
91
|
return if input.nil?
|
75
92
|
wordlist = input.to_s.split
|
76
|
-
words
|
93
|
+
words = Utils.to_integer(words)
|
94
|
+
|
77
95
|
l = words - 1
|
78
96
|
l = 0 if l < 0
|
79
|
-
|
97
|
+
|
98
|
+
wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input
|
80
99
|
end
|
81
100
|
|
82
101
|
# Split input string into an array of substrings separated by given pattern.
|
@@ -85,7 +104,7 @@ module Liquid
|
|
85
104
|
# <div class="summary">{{ post | split '//' | first }}</div>
|
86
105
|
#
|
87
106
|
def split(input, pattern)
|
88
|
-
input.to_s.split(pattern)
|
107
|
+
input.to_s.split(pattern.to_s)
|
89
108
|
end
|
90
109
|
|
91
110
|
def strip(input)
|
@@ -101,113 +120,160 @@ module Liquid
|
|
101
120
|
end
|
102
121
|
|
103
122
|
def strip_html(input)
|
104
|
-
empty
|
105
|
-
input.to_s.gsub(
|
123
|
+
empty = ''
|
124
|
+
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
|
125
|
+
result.gsub!(STRIP_HTML_TAGS, empty)
|
126
|
+
result
|
106
127
|
end
|
107
128
|
|
108
129
|
# Remove all newlines from the string
|
109
130
|
def strip_newlines(input)
|
110
|
-
input.to_s.gsub(/\r?\n/, ''
|
131
|
+
input.to_s.gsub(/\r?\n/, '')
|
111
132
|
end
|
112
133
|
|
113
134
|
# Join elements of the array with certain character between them
|
114
|
-
def join(input, glue = ' '
|
115
|
-
InputIterator.new(input).join(glue)
|
135
|
+
def join(input, glue = ' ')
|
136
|
+
InputIterator.new(input, context).join(glue)
|
116
137
|
end
|
117
138
|
|
118
139
|
# Sort elements of the array
|
119
140
|
# provide optional property with which to sort an array of hashes or drops
|
120
141
|
def sort(input, property = nil)
|
121
|
-
ary = InputIterator.new(input)
|
142
|
+
ary = InputIterator.new(input, context)
|
143
|
+
|
144
|
+
return [] if ary.empty?
|
145
|
+
|
122
146
|
if property.nil?
|
123
|
-
ary.sort
|
124
|
-
|
125
|
-
|
126
|
-
elsif ary.
|
127
|
-
|
147
|
+
ary.sort do |a, b|
|
148
|
+
nil_safe_compare(a, b)
|
149
|
+
end
|
150
|
+
elsif ary.all? { |el| el.respond_to?(:[]) }
|
151
|
+
begin
|
152
|
+
ary.sort { |a, b| nil_safe_compare(a[property], b[property]) }
|
153
|
+
rescue TypeError
|
154
|
+
raise_property_error(property)
|
155
|
+
end
|
128
156
|
end
|
129
157
|
end
|
130
158
|
|
131
159
|
# Sort elements of an array ignoring case if strings
|
132
160
|
# provide optional property with which to sort an array of hashes or drops
|
133
161
|
def sort_natural(input, property = nil)
|
134
|
-
ary = InputIterator.new(input)
|
162
|
+
ary = InputIterator.new(input, context)
|
163
|
+
|
164
|
+
return [] if ary.empty?
|
135
165
|
|
136
166
|
if property.nil?
|
137
|
-
ary.sort
|
138
|
-
|
167
|
+
ary.sort do |a, b|
|
168
|
+
nil_safe_casecmp(a, b)
|
169
|
+
end
|
170
|
+
elsif ary.all? { |el| el.respond_to?(:[]) }
|
171
|
+
begin
|
172
|
+
ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) }
|
173
|
+
rescue TypeError
|
174
|
+
raise_property_error(property)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Filter the elements of an array to those with a certain property value.
|
180
|
+
# By default the target is any truthy value.
|
181
|
+
def where(input, property, target_value = nil)
|
182
|
+
ary = InputIterator.new(input, context)
|
183
|
+
|
184
|
+
if ary.empty?
|
139
185
|
[]
|
140
|
-
elsif ary.first.respond_to?(:[]) &&
|
141
|
-
|
186
|
+
elsif ary.first.respond_to?(:[]) && target_value.nil?
|
187
|
+
begin
|
188
|
+
ary.select { |item| item[property] }
|
189
|
+
rescue TypeError
|
190
|
+
raise_property_error(property)
|
191
|
+
end
|
192
|
+
elsif ary.first.respond_to?(:[])
|
193
|
+
begin
|
194
|
+
ary.select { |item| item[property] == target_value }
|
195
|
+
rescue TypeError
|
196
|
+
raise_property_error(property)
|
197
|
+
end
|
142
198
|
end
|
143
199
|
end
|
144
200
|
|
145
201
|
# Remove duplicate elements from an array
|
146
202
|
# provide optional property with which to determine uniqueness
|
147
203
|
def uniq(input, property = nil)
|
148
|
-
ary = InputIterator.new(input)
|
204
|
+
ary = InputIterator.new(input, context)
|
149
205
|
|
150
206
|
if property.nil?
|
151
207
|
ary.uniq
|
152
208
|
elsif ary.empty? # The next two cases assume a non-empty array.
|
153
209
|
[]
|
154
210
|
elsif ary.first.respond_to?(:[])
|
155
|
-
|
211
|
+
begin
|
212
|
+
ary.uniq { |a| a[property] }
|
213
|
+
rescue TypeError
|
214
|
+
raise_property_error(property)
|
215
|
+
end
|
156
216
|
end
|
157
217
|
end
|
158
218
|
|
159
219
|
# Reverse the elements of an array
|
160
220
|
def reverse(input)
|
161
|
-
ary = InputIterator.new(input)
|
221
|
+
ary = InputIterator.new(input, context)
|
162
222
|
ary.reverse
|
163
223
|
end
|
164
224
|
|
165
225
|
# map/collect on a given property
|
166
226
|
def map(input, property)
|
167
|
-
InputIterator.new(input).map do |e|
|
227
|
+
InputIterator.new(input, context).map do |e|
|
168
228
|
e = e.call if e.is_a?(Proc)
|
169
229
|
|
170
|
-
if property == "to_liquid"
|
230
|
+
if property == "to_liquid"
|
171
231
|
e
|
172
232
|
elsif e.respond_to?(:[])
|
173
233
|
r = e[property]
|
174
234
|
r.is_a?(Proc) ? r.call : r
|
175
235
|
end
|
176
236
|
end
|
237
|
+
rescue TypeError
|
238
|
+
raise_property_error(property)
|
177
239
|
end
|
178
240
|
|
179
241
|
# Remove nils within an array
|
180
242
|
# provide optional property with which to check for nil
|
181
243
|
def compact(input, property = nil)
|
182
|
-
ary = InputIterator.new(input)
|
244
|
+
ary = InputIterator.new(input, context)
|
183
245
|
|
184
246
|
if property.nil?
|
185
247
|
ary.compact
|
186
248
|
elsif ary.empty? # The next two cases assume a non-empty array.
|
187
249
|
[]
|
188
250
|
elsif ary.first.respond_to?(:[])
|
189
|
-
|
251
|
+
begin
|
252
|
+
ary.reject { |a| a[property].nil? }
|
253
|
+
rescue TypeError
|
254
|
+
raise_property_error(property)
|
255
|
+
end
|
190
256
|
end
|
191
257
|
end
|
192
258
|
|
193
259
|
# Replace occurrences of a string with another
|
194
|
-
def replace(input, string, replacement = ''
|
260
|
+
def replace(input, string, replacement = '')
|
195
261
|
input.to_s.gsub(string.to_s, replacement.to_s)
|
196
262
|
end
|
197
263
|
|
198
264
|
# Replace the first occurrences of a string with another
|
199
|
-
def replace_first(input, string, replacement = ''
|
265
|
+
def replace_first(input, string, replacement = '')
|
200
266
|
input.to_s.sub(string.to_s, replacement.to_s)
|
201
267
|
end
|
202
268
|
|
203
269
|
# remove a substring
|
204
270
|
def remove(input, string)
|
205
|
-
input.to_s.gsub(string.to_s, ''
|
271
|
+
input.to_s.gsub(string.to_s, '')
|
206
272
|
end
|
207
273
|
|
208
274
|
# remove the first occurrences of a substring
|
209
275
|
def remove_first(input, string)
|
210
|
-
input.to_s.sub(string.to_s, ''
|
276
|
+
input.to_s.sub(string.to_s, '')
|
211
277
|
end
|
212
278
|
|
213
279
|
# add one string to another
|
@@ -217,9 +283,9 @@ module Liquid
|
|
217
283
|
|
218
284
|
def concat(input, array)
|
219
285
|
unless array.respond_to?(:to_ary)
|
220
|
-
raise ArgumentError
|
286
|
+
raise ArgumentError, "concat filter requires an array argument"
|
221
287
|
end
|
222
|
-
InputIterator.new(input).concat(array)
|
288
|
+
InputIterator.new(input, context).concat(array)
|
223
289
|
end
|
224
290
|
|
225
291
|
# prepend a string to another
|
@@ -229,7 +295,7 @@ module Liquid
|
|
229
295
|
|
230
296
|
# Add <br /> tags in front of all newlines in input string
|
231
297
|
def newline_to_br(input)
|
232
|
-
input.to_s.gsub(/\n/, "<br />\n"
|
298
|
+
input.to_s.gsub(/\n/, "<br />\n")
|
233
299
|
end
|
234
300
|
|
235
301
|
# Reformat a date using Ruby's core Time#strftime( string ) -> string
|
@@ -266,7 +332,7 @@ module Liquid
|
|
266
332
|
def date(input, format)
|
267
333
|
return input if format.to_s.empty?
|
268
334
|
|
269
|
-
return input unless date = Utils.to_date(input)
|
335
|
+
return input unless (date = Utils.to_date(input))
|
270
336
|
|
271
337
|
date.strftime(format.to_s)
|
272
338
|
end
|
@@ -344,26 +410,73 @@ module Liquid
|
|
344
410
|
raise Liquid::FloatDomainError, e.message
|
345
411
|
end
|
346
412
|
|
347
|
-
def
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
413
|
+
def at_least(input, n)
|
414
|
+
min_value = Utils.to_number(n)
|
415
|
+
|
416
|
+
result = Utils.to_number(input)
|
417
|
+
result = min_value if min_value > result
|
418
|
+
result.is_a?(BigDecimal) ? result.to_f : result
|
419
|
+
end
|
420
|
+
|
421
|
+
def at_most(input, n)
|
422
|
+
max_value = Utils.to_number(n)
|
423
|
+
|
424
|
+
result = Utils.to_number(input)
|
425
|
+
result = max_value if max_value < result
|
426
|
+
result.is_a?(BigDecimal) ? result.to_f : result
|
427
|
+
end
|
428
|
+
|
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
|
353
443
|
end
|
354
444
|
|
355
445
|
private
|
356
446
|
|
447
|
+
attr_reader :context
|
448
|
+
|
449
|
+
def raise_property_error(property)
|
450
|
+
raise Liquid::ArgumentError, "cannot select the property '#{property}'"
|
451
|
+
end
|
452
|
+
|
357
453
|
def apply_operation(input, operand, operation)
|
358
454
|
result = Utils.to_number(input).send(operation, Utils.to_number(operand))
|
359
455
|
result.is_a?(BigDecimal) ? result.to_f : result
|
360
456
|
end
|
361
457
|
|
458
|
+
def nil_safe_compare(a, b)
|
459
|
+
if !a.nil? && !b.nil?
|
460
|
+
a <=> b
|
461
|
+
else
|
462
|
+
a.nil? ? 1 : -1
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
def nil_safe_casecmp(a, b)
|
467
|
+
if !a.nil? && !b.nil?
|
468
|
+
a.to_s.casecmp(b.to_s)
|
469
|
+
else
|
470
|
+
a.nil? ? 1 : -1
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
362
474
|
class InputIterator
|
363
475
|
include Enumerable
|
364
476
|
|
365
|
-
def initialize(input)
|
366
|
-
@
|
477
|
+
def initialize(input, context)
|
478
|
+
@context = context
|
479
|
+
@input = if input.is_a?(Array)
|
367
480
|
input.flatten
|
368
481
|
elsif input.is_a?(Hash)
|
369
482
|
[input]
|
@@ -375,7 +488,7 @@ module Liquid
|
|
375
488
|
end
|
376
489
|
|
377
490
|
def join(glue)
|
378
|
-
to_a.join(glue)
|
491
|
+
to_a.join(glue.to_s)
|
379
492
|
end
|
380
493
|
|
381
494
|
def concat(args)
|
@@ -401,6 +514,7 @@ module Liquid
|
|
401
514
|
|
402
515
|
def each
|
403
516
|
@input.each do |e|
|
517
|
+
e.context = @context if e.respond_to?(:context=)
|
404
518
|
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
405
519
|
end
|
406
520
|
end
|