liquid-4-0-2 4.0.2

Sign up to get free protection for your applications and to get access to all the features.
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