liquid-4-0-2 4.0.2

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.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +235 -0
  3. data/LICENSE +20 -0
  4. data/README.md +108 -0
  5. data/lib/liquid.rb +80 -0
  6. data/lib/liquid/block.rb +77 -0
  7. data/lib/liquid/block_body.rb +142 -0
  8. data/lib/liquid/condition.rb +151 -0
  9. data/lib/liquid/context.rb +226 -0
  10. data/lib/liquid/document.rb +27 -0
  11. data/lib/liquid/drop.rb +78 -0
  12. data/lib/liquid/errors.rb +56 -0
  13. data/lib/liquid/expression.rb +49 -0
  14. data/lib/liquid/extensions.rb +74 -0
  15. data/lib/liquid/file_system.rb +73 -0
  16. data/lib/liquid/forloop_drop.rb +42 -0
  17. data/lib/liquid/i18n.rb +39 -0
  18. data/lib/liquid/interrupts.rb +16 -0
  19. data/lib/liquid/lexer.rb +55 -0
  20. data/lib/liquid/locales/en.yml +26 -0
  21. data/lib/liquid/parse_context.rb +38 -0
  22. data/lib/liquid/parse_tree_visitor.rb +42 -0
  23. data/lib/liquid/parser.rb +90 -0
  24. data/lib/liquid/parser_switching.rb +31 -0
  25. data/lib/liquid/profiler.rb +158 -0
  26. data/lib/liquid/profiler/hooks.rb +23 -0
  27. data/lib/liquid/range_lookup.rb +37 -0
  28. data/lib/liquid/resource_limits.rb +23 -0
  29. data/lib/liquid/standardfilters.rb +485 -0
  30. data/lib/liquid/strainer.rb +66 -0
  31. data/lib/liquid/tablerowloop_drop.rb +62 -0
  32. data/lib/liquid/tag.rb +43 -0
  33. data/lib/liquid/tags/assign.rb +59 -0
  34. data/lib/liquid/tags/break.rb +18 -0
  35. data/lib/liquid/tags/capture.rb +38 -0
  36. data/lib/liquid/tags/case.rb +94 -0
  37. data/lib/liquid/tags/comment.rb +16 -0
  38. data/lib/liquid/tags/continue.rb +18 -0
  39. data/lib/liquid/tags/cycle.rb +65 -0
  40. data/lib/liquid/tags/decrement.rb +35 -0
  41. data/lib/liquid/tags/for.rb +203 -0
  42. data/lib/liquid/tags/if.rb +122 -0
  43. data/lib/liquid/tags/ifchanged.rb +18 -0
  44. data/lib/liquid/tags/include.rb +124 -0
  45. data/lib/liquid/tags/increment.rb +31 -0
  46. data/lib/liquid/tags/raw.rb +47 -0
  47. data/lib/liquid/tags/table_row.rb +62 -0
  48. data/lib/liquid/tags/unless.rb +30 -0
  49. data/lib/liquid/template.rb +254 -0
  50. data/lib/liquid/tokenizer.rb +31 -0
  51. data/lib/liquid/utils.rb +83 -0
  52. data/lib/liquid/variable.rb +148 -0
  53. data/lib/liquid/variable_lookup.rb +88 -0
  54. data/lib/liquid/version.rb +4 -0
  55. data/test/fixtures/en_locale.yml +9 -0
  56. data/test/integration/assign_test.rb +48 -0
  57. data/test/integration/blank_test.rb +106 -0
  58. data/test/integration/block_test.rb +12 -0
  59. data/test/integration/capture_test.rb +50 -0
  60. data/test/integration/context_test.rb +32 -0
  61. data/test/integration/document_test.rb +19 -0
  62. data/test/integration/drop_test.rb +273 -0
  63. data/test/integration/error_handling_test.rb +260 -0
  64. data/test/integration/filter_test.rb +178 -0
  65. data/test/integration/hash_ordering_test.rb +23 -0
  66. data/test/integration/output_test.rb +123 -0
  67. data/test/integration/parse_tree_visitor_test.rb +247 -0
  68. data/test/integration/parsing_quirks_test.rb +122 -0
  69. data/test/integration/render_profiling_test.rb +154 -0
  70. data/test/integration/security_test.rb +80 -0
  71. data/test/integration/standard_filter_test.rb +698 -0
  72. data/test/integration/tags/break_tag_test.rb +15 -0
  73. data/test/integration/tags/continue_tag_test.rb +15 -0
  74. data/test/integration/tags/for_tag_test.rb +410 -0
  75. data/test/integration/tags/if_else_tag_test.rb +188 -0
  76. data/test/integration/tags/include_tag_test.rb +245 -0
  77. data/test/integration/tags/increment_tag_test.rb +23 -0
  78. data/test/integration/tags/raw_tag_test.rb +31 -0
  79. data/test/integration/tags/standard_tag_test.rb +296 -0
  80. data/test/integration/tags/statements_test.rb +111 -0
  81. data/test/integration/tags/table_row_test.rb +64 -0
  82. data/test/integration/tags/unless_else_tag_test.rb +26 -0
  83. data/test/integration/template_test.rb +332 -0
  84. data/test/integration/trim_mode_test.rb +529 -0
  85. data/test/integration/variable_test.rb +96 -0
  86. data/test/test_helper.rb +116 -0
  87. data/test/unit/block_unit_test.rb +58 -0
  88. data/test/unit/condition_unit_test.rb +166 -0
  89. data/test/unit/context_unit_test.rb +489 -0
  90. data/test/unit/file_system_unit_test.rb +35 -0
  91. data/test/unit/i18n_unit_test.rb +37 -0
  92. data/test/unit/lexer_unit_test.rb +51 -0
  93. data/test/unit/parser_unit_test.rb +82 -0
  94. data/test/unit/regexp_unit_test.rb +44 -0
  95. data/test/unit/strainer_unit_test.rb +164 -0
  96. data/test/unit/tag_unit_test.rb +21 -0
  97. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  98. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  99. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  100. data/test/unit/template_unit_test.rb +78 -0
  101. data/test/unit/tokenizer_unit_test.rb +55 -0
  102. data/test/unit/variable_unit_test.rb +162 -0
  103. metadata +224 -0
@@ -0,0 +1,23 @@
1
+ module Liquid
2
+ class BlockBody
3
+ def render_node_with_profiling(node, output, context, skip_output = false)
4
+ Profiler.profile_node_render(node) do
5
+ render_node_without_profiling(node, output, context, skip_output)
6
+ end
7
+ end
8
+
9
+ alias_method :render_node_without_profiling, :render_node_to_output
10
+ alias_method :render_node_to_output, :render_node_with_profiling
11
+ end
12
+
13
+ class Include < Tag
14
+ def render_with_profiling(context)
15
+ Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
16
+ render_without_profiling(context)
17
+ end
18
+ end
19
+
20
+ alias_method :render_without_profiling, :render
21
+ alias_method :render, :render_with_profiling
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ module Liquid
2
+ class RangeLookup
3
+ def self.parse(start_markup, end_markup)
4
+ start_obj = Expression.parse(start_markup)
5
+ end_obj = Expression.parse(end_markup)
6
+ if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
7
+ new(start_obj, end_obj)
8
+ else
9
+ start_obj.to_i..end_obj.to_i
10
+ end
11
+ end
12
+
13
+ def initialize(start_obj, end_obj)
14
+ @start_obj = start_obj
15
+ @end_obj = end_obj
16
+ end
17
+
18
+ def evaluate(context)
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
35
+ end
36
+ end
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
@@ -0,0 +1,485 @@
1
+ require 'cgi'
2
+ require 'bigdecimal'
3
+
4
+ module Liquid
5
+ module StandardFilters
6
+ HTML_ESCAPE = {
7
+ '&'.freeze => '&amp;'.freeze,
8
+ '>'.freeze => '&gt;'.freeze,
9
+ '<'.freeze => '&lt;'.freeze,
10
+ '"'.freeze => '&quot;'.freeze,
11
+ "'".freeze => '&#39;'.freeze
12
+ }
13
+ HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
14
+
15
+ # Return the size of an array or of an string
16
+ def size(input)
17
+ input.respond_to?(:size) ? input.size : 0
18
+ end
19
+
20
+ # convert an input string to DOWNCASE
21
+ def downcase(input)
22
+ input.to_s.downcase
23
+ end
24
+
25
+ # convert an input string to UPCASE
26
+ def upcase(input)
27
+ input.to_s.upcase
28
+ end
29
+
30
+ # capitalize words in the input centence
31
+ def capitalize(input)
32
+ input.to_s.capitalize
33
+ end
34
+
35
+ def escape(input)
36
+ CGI.escapeHTML(input.to_s).untaint unless input.nil?
37
+ end
38
+ alias_method :h, :escape
39
+
40
+ def escape_once(input)
41
+ input.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
42
+ end
43
+
44
+ def url_encode(input)
45
+ CGI.escape(input.to_s) unless input.nil?
46
+ end
47
+
48
+ def url_decode(input)
49
+ CGI.unescape(input.to_s) unless input.nil?
50
+ end
51
+
52
+ def slice(input, offset, length = nil)
53
+ offset = Utils.to_integer(offset)
54
+ length = length ? Utils.to_integer(length) : 1
55
+
56
+ if input.is_a?(Array)
57
+ input.slice(offset, length) || []
58
+ else
59
+ input.to_s.slice(offset, length) || ''
60
+ end
61
+ end
62
+
63
+ # Truncate a string down to x characters
64
+ def truncate(input, length = 50, truncate_string = "...".freeze)
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
70
+ l = 0 if l < 0
71
+ input_str.length > length ? input_str[0...l] + truncate_string_str : input_str
72
+ end
73
+
74
+ def truncatewords(input, words = 15, truncate_string = "...".freeze)
75
+ return if input.nil?
76
+ wordlist = input.to_s.split
77
+ words = Utils.to_integer(words)
78
+ l = words - 1
79
+ l = 0 if l < 0
80
+ wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string.to_s : input
81
+ end
82
+
83
+ # Split input string into an array of substrings separated by given pattern.
84
+ #
85
+ # Example:
86
+ # <div class="summary">{{ post | split '//' | first }}</div>
87
+ #
88
+ def split(input, pattern)
89
+ input.to_s.split(pattern.to_s)
90
+ end
91
+
92
+ def strip(input)
93
+ input.to_s.strip
94
+ end
95
+
96
+ def lstrip(input)
97
+ input.to_s.lstrip
98
+ end
99
+
100
+ def rstrip(input)
101
+ input.to_s.rstrip
102
+ end
103
+
104
+ def strip_html(input)
105
+ empty = ''.freeze
106
+ input.to_s.gsub(/<script.*?<\/script>/m, empty).gsub(/<!--.*?-->/m, empty).gsub(/<style.*?<\/style>/m, empty).gsub(/<.*?>/m, empty)
107
+ end
108
+
109
+ # Remove all newlines from the string
110
+ def strip_newlines(input)
111
+ input.to_s.gsub(/\r?\n/, ''.freeze)
112
+ end
113
+
114
+ # Join elements of the array with certain character between them
115
+ def join(input, glue = ' '.freeze)
116
+ InputIterator.new(input).join(glue)
117
+ end
118
+
119
+ # Sort elements of the array
120
+ # provide optional property with which to sort an array of hashes or drops
121
+ def sort(input, property = nil)
122
+ ary = InputIterator.new(input)
123
+
124
+ return [] if ary.empty?
125
+
126
+ if property.nil?
127
+ ary.sort do |a, b|
128
+ nil_safe_compare(a, b)
129
+ end
130
+ elsif ary.all? { |el| el.respond_to?(:[]) }
131
+ ary.sort do |a, b|
132
+ nil_safe_compare(a[property], b[property])
133
+ end
134
+ end
135
+ end
136
+
137
+ # Sort elements of an array ignoring case if strings
138
+ # provide optional property with which to sort an array of hashes or drops
139
+ def sort_natural(input, property = nil)
140
+ ary = InputIterator.new(input)
141
+
142
+ return [] if ary.empty?
143
+
144
+ if property.nil?
145
+ ary.sort do |a, b|
146
+ nil_safe_casecmp(a, b)
147
+ end
148
+ elsif ary.all? { |el| el.respond_to?(:[]) }
149
+ ary.sort do |a, b|
150
+ nil_safe_casecmp(a[property], b[property])
151
+ end
152
+ end
153
+ end
154
+
155
+ # Filter the elements of an array to those with a certain property value.
156
+ # By default the target is any truthy value.
157
+ def where(input, property, target_value = nil)
158
+ ary = InputIterator.new(input)
159
+
160
+ if ary.empty?
161
+ []
162
+ elsif ary.first.respond_to?(:[]) && target_value.nil?
163
+ ary.where_present(property)
164
+ elsif ary.first.respond_to?(:[])
165
+ ary.where(property, target_value)
166
+ end
167
+ end
168
+
169
+ # Remove duplicate elements from an array
170
+ # provide optional property with which to determine uniqueness
171
+ def uniq(input, property = nil)
172
+ ary = InputIterator.new(input)
173
+
174
+ if property.nil?
175
+ ary.uniq
176
+ elsif ary.empty? # The next two cases assume a non-empty array.
177
+ []
178
+ elsif ary.first.respond_to?(:[])
179
+ ary.uniq{ |a| a[property] }
180
+ end
181
+ end
182
+
183
+ # Reverse the elements of an array
184
+ def reverse(input)
185
+ ary = InputIterator.new(input)
186
+ ary.reverse
187
+ end
188
+
189
+ # map/collect on a given property
190
+ def map(input, property)
191
+ InputIterator.new(input).map do |e|
192
+ e = e.call if e.is_a?(Proc)
193
+
194
+ if property == "to_liquid".freeze
195
+ e
196
+ elsif e.respond_to?(:[])
197
+ r = e[property]
198
+ r.is_a?(Proc) ? r.call : r
199
+ end
200
+ end
201
+ end
202
+
203
+ # Remove nils within an array
204
+ # provide optional property with which to check for nil
205
+ def compact(input, property = nil)
206
+ ary = InputIterator.new(input)
207
+
208
+ if property.nil?
209
+ ary.compact
210
+ elsif ary.empty? # The next two cases assume a non-empty array.
211
+ []
212
+ elsif ary.first.respond_to?(:[])
213
+ ary.reject{ |a| a[property].nil? }
214
+ end
215
+ end
216
+
217
+ # Replace occurrences of a string with another
218
+ def replace(input, string, replacement = ''.freeze)
219
+ input.to_s.gsub(string.to_s, replacement.to_s)
220
+ end
221
+
222
+ # Replace the first occurrences of a string with another
223
+ def replace_first(input, string, replacement = ''.freeze)
224
+ input.to_s.sub(string.to_s, replacement.to_s)
225
+ end
226
+
227
+ # remove a substring
228
+ def remove(input, string)
229
+ input.to_s.gsub(string.to_s, ''.freeze)
230
+ end
231
+
232
+ # remove the first occurrences of a substring
233
+ def remove_first(input, string)
234
+ input.to_s.sub(string.to_s, ''.freeze)
235
+ end
236
+
237
+ # add one string to another
238
+ def append(input, string)
239
+ input.to_s + string.to_s
240
+ end
241
+
242
+ def concat(input, array)
243
+ unless array.respond_to?(:to_ary)
244
+ raise ArgumentError.new("concat filter requires an array argument")
245
+ end
246
+ InputIterator.new(input).concat(array)
247
+ end
248
+
249
+ # prepend a string to another
250
+ def prepend(input, string)
251
+ string.to_s + input.to_s
252
+ end
253
+
254
+ # Add <br /> tags in front of all newlines in input string
255
+ def newline_to_br(input)
256
+ input.to_s.gsub(/\n/, "<br />\n".freeze)
257
+ end
258
+
259
+ # Reformat a date using Ruby's core Time#strftime( string ) -> string
260
+ #
261
+ # %a - The abbreviated weekday name (``Sun'')
262
+ # %A - The full weekday name (``Sunday'')
263
+ # %b - The abbreviated month name (``Jan'')
264
+ # %B - The full month name (``January'')
265
+ # %c - The preferred local date and time representation
266
+ # %d - Day of the month (01..31)
267
+ # %H - Hour of the day, 24-hour clock (00..23)
268
+ # %I - Hour of the day, 12-hour clock (01..12)
269
+ # %j - Day of the year (001..366)
270
+ # %m - Month of the year (01..12)
271
+ # %M - Minute of the hour (00..59)
272
+ # %p - Meridian indicator (``AM'' or ``PM'')
273
+ # %s - Number of seconds since 1970-01-01 00:00:00 UTC.
274
+ # %S - Second of the minute (00..60)
275
+ # %U - Week number of the current year,
276
+ # starting with the first Sunday as the first
277
+ # day of the first week (00..53)
278
+ # %W - Week number of the current year,
279
+ # starting with the first Monday as the first
280
+ # day of the first week (00..53)
281
+ # %w - Day of the week (Sunday is 0, 0..6)
282
+ # %x - Preferred representation for the date alone, no time
283
+ # %X - Preferred representation for the time alone, no date
284
+ # %y - Year without a century (00..99)
285
+ # %Y - Year with century
286
+ # %Z - Time zone name
287
+ # %% - Literal ``%'' character
288
+ #
289
+ # See also: http://www.ruby-doc.org/core/Time.html#method-i-strftime
290
+ def date(input, format)
291
+ return input if format.to_s.empty?
292
+
293
+ return input unless date = Utils.to_date(input)
294
+
295
+ date.strftime(format.to_s)
296
+ end
297
+
298
+ # Get the first element of the passed in array
299
+ #
300
+ # Example:
301
+ # {{ product.images | first | to_img }}
302
+ #
303
+ def first(array)
304
+ array.first if array.respond_to?(:first)
305
+ end
306
+
307
+ # Get the last element of the passed in array
308
+ #
309
+ # Example:
310
+ # {{ product.images | last | to_img }}
311
+ #
312
+ def last(array)
313
+ array.last if array.respond_to?(:last)
314
+ end
315
+
316
+ # absolute value
317
+ def abs(input)
318
+ result = Utils.to_number(input).abs
319
+ result.is_a?(BigDecimal) ? result.to_f : result
320
+ end
321
+
322
+ # addition
323
+ def plus(input, operand)
324
+ apply_operation(input, operand, :+)
325
+ end
326
+
327
+ # subtraction
328
+ def minus(input, operand)
329
+ apply_operation(input, operand, :-)
330
+ end
331
+
332
+ # multiplication
333
+ def times(input, operand)
334
+ apply_operation(input, operand, :*)
335
+ end
336
+
337
+ # division
338
+ def divided_by(input, operand)
339
+ apply_operation(input, operand, :/)
340
+ rescue ::ZeroDivisionError => e
341
+ raise Liquid::ZeroDivisionError, e.message
342
+ end
343
+
344
+ def modulo(input, operand)
345
+ apply_operation(input, operand, :%)
346
+ rescue ::ZeroDivisionError => e
347
+ raise Liquid::ZeroDivisionError, e.message
348
+ end
349
+
350
+ def round(input, n = 0)
351
+ result = Utils.to_number(input).round(Utils.to_number(n))
352
+ result = result.to_f if result.is_a?(BigDecimal)
353
+ result = result.to_i if n == 0
354
+ result
355
+ rescue ::FloatDomainError => e
356
+ raise Liquid::FloatDomainError, e.message
357
+ end
358
+
359
+ def ceil(input)
360
+ Utils.to_number(input).ceil.to_i
361
+ rescue ::FloatDomainError => e
362
+ raise Liquid::FloatDomainError, e.message
363
+ end
364
+
365
+ def floor(input)
366
+ Utils.to_number(input).floor.to_i
367
+ rescue ::FloatDomainError => e
368
+ raise Liquid::FloatDomainError, e.message
369
+ end
370
+
371
+ def at_least(input, n)
372
+ min_value = Utils.to_number(n)
373
+
374
+ result = Utils.to_number(input)
375
+ result = min_value if min_value > result
376
+ result.is_a?(BigDecimal) ? result.to_f : result
377
+ end
378
+
379
+ def at_most(input, n)
380
+ max_value = Utils.to_number(n)
381
+
382
+ result = Utils.to_number(input)
383
+ result = max_value if max_value < result
384
+ result.is_a?(BigDecimal) ? result.to_f : result
385
+ end
386
+
387
+ def default(input, default_value = ''.freeze)
388
+ if !input || input.respond_to?(:empty?) && input.empty?
389
+ default_value
390
+ else
391
+ input
392
+ end
393
+ end
394
+
395
+ private
396
+
397
+ def apply_operation(input, operand, operation)
398
+ result = Utils.to_number(input).send(operation, Utils.to_number(operand))
399
+ result.is_a?(BigDecimal) ? result.to_f : result
400
+ end
401
+
402
+ def nil_safe_compare(a, b)
403
+ if !a.nil? && !b.nil?
404
+ a <=> b
405
+ else
406
+ a.nil? ? 1 : -1
407
+ end
408
+ end
409
+
410
+ def nil_safe_casecmp(a, b)
411
+ if !a.nil? && !b.nil?
412
+ a.to_s.casecmp(b.to_s)
413
+ else
414
+ a.nil? ? 1 : -1
415
+ end
416
+ end
417
+
418
+ class InputIterator
419
+ include Enumerable
420
+
421
+ def initialize(input)
422
+ @input = if input.is_a?(Array)
423
+ input.flatten
424
+ elsif input.is_a?(Hash)
425
+ [input]
426
+ elsif input.is_a?(Enumerable)
427
+ input
428
+ else
429
+ Array(input)
430
+ end
431
+ end
432
+
433
+ def join(glue)
434
+ to_a.join(glue.to_s)
435
+ end
436
+
437
+ def concat(args)
438
+ to_a.concat(args)
439
+ end
440
+
441
+ def reverse
442
+ reverse_each.to_a
443
+ end
444
+
445
+ def uniq(&block)
446
+ to_a.uniq(&block)
447
+ end
448
+
449
+ def compact
450
+ to_a.compact
451
+ end
452
+
453
+ def empty?
454
+ @input.each { return false }
455
+ true
456
+ end
457
+
458
+ def each
459
+ @input.each do |e|
460
+ yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
461
+ end
462
+ end
463
+
464
+ def where(property, target_value)
465
+ select do |item|
466
+ item[property] == target_value
467
+ end
468
+ rescue TypeError
469
+ # Cannot index with the given property type (eg. indexing integers with strings
470
+ # which are only allowed to be indexed by other integers).
471
+ raise ArgumentError.new("cannot select the property `#{property}`")
472
+ end
473
+
474
+ def where_present(property)
475
+ select { |item| item[property] }
476
+ rescue TypeError
477
+ # Cannot index with the given property type (eg. indexing integers with strings
478
+ # which are only allowed to be indexed by other integers).
479
+ raise ArgumentError.new("cannot select the property `#{property}`")
480
+ end
481
+ end
482
+ end
483
+
484
+ Template.register_filter(StandardFilters)
485
+ end