liquid 3.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +130 -62
  3. data/README.md +31 -0
  4. data/lib/liquid/block.rb +31 -124
  5. data/lib/liquid/block_body.rb +75 -59
  6. data/lib/liquid/condition.rb +23 -22
  7. data/lib/liquid/context.rb +51 -60
  8. data/lib/liquid/document.rb +19 -9
  9. data/lib/liquid/drop.rb +17 -16
  10. data/lib/liquid/errors.rb +20 -24
  11. data/lib/liquid/expression.rb +15 -3
  12. data/lib/liquid/extensions.rb +13 -7
  13. data/lib/liquid/file_system.rb +11 -11
  14. data/lib/liquid/forloop_drop.rb +42 -0
  15. data/lib/liquid/i18n.rb +5 -5
  16. data/lib/liquid/interrupts.rb +1 -2
  17. data/lib/liquid/lexer.rb +6 -4
  18. data/lib/liquid/locales/en.yml +5 -1
  19. data/lib/liquid/parse_context.rb +37 -0
  20. data/lib/liquid/parser.rb +1 -1
  21. data/lib/liquid/parser_switching.rb +4 -4
  22. data/lib/liquid/profiler/hooks.rb +7 -7
  23. data/lib/liquid/profiler.rb +18 -19
  24. data/lib/liquid/range_lookup.rb +16 -1
  25. data/lib/liquid/resource_limits.rb +23 -0
  26. data/lib/liquid/standardfilters.rb +121 -61
  27. data/lib/liquid/strainer.rb +32 -29
  28. data/lib/liquid/tablerowloop_drop.rb +62 -0
  29. data/lib/liquid/tag.rb +9 -8
  30. data/lib/liquid/tags/assign.rb +17 -4
  31. data/lib/liquid/tags/break.rb +0 -3
  32. data/lib/liquid/tags/capture.rb +2 -2
  33. data/lib/liquid/tags/case.rb +19 -12
  34. data/lib/liquid/tags/comment.rb +2 -2
  35. data/lib/liquid/tags/cycle.rb +6 -6
  36. data/lib/liquid/tags/decrement.rb +1 -4
  37. data/lib/liquid/tags/for.rb +95 -75
  38. data/lib/liquid/tags/if.rb +48 -43
  39. data/lib/liquid/tags/ifchanged.rb +0 -2
  40. data/lib/liquid/tags/include.rb +61 -52
  41. data/lib/liquid/tags/raw.rb +32 -4
  42. data/lib/liquid/tags/table_row.rb +12 -31
  43. data/lib/liquid/tags/unless.rb +4 -5
  44. data/lib/liquid/template.rb +42 -54
  45. data/lib/liquid/tokenizer.rb +31 -0
  46. data/lib/liquid/utils.rb +52 -8
  47. data/lib/liquid/variable.rb +46 -45
  48. data/lib/liquid/variable_lookup.rb +9 -5
  49. data/lib/liquid/version.rb +1 -1
  50. data/lib/liquid.rb +9 -7
  51. data/test/integration/assign_test.rb +18 -8
  52. data/test/integration/blank_test.rb +14 -14
  53. data/test/integration/capture_test.rb +10 -0
  54. data/test/integration/context_test.rb +2 -2
  55. data/test/integration/document_test.rb +19 -0
  56. data/test/integration/drop_test.rb +42 -40
  57. data/test/integration/error_handling_test.rb +99 -46
  58. data/test/integration/filter_test.rb +72 -19
  59. data/test/integration/hash_ordering_test.rb +9 -9
  60. data/test/integration/output_test.rb +34 -27
  61. data/test/integration/parsing_quirks_test.rb +15 -13
  62. data/test/integration/render_profiling_test.rb +20 -20
  63. data/test/integration/security_test.rb +9 -7
  64. data/test/integration/standard_filter_test.rb +198 -42
  65. data/test/integration/tags/break_tag_test.rb +1 -2
  66. data/test/integration/tags/continue_tag_test.rb +0 -1
  67. data/test/integration/tags/for_tag_test.rb +133 -98
  68. data/test/integration/tags/if_else_tag_test.rb +96 -77
  69. data/test/integration/tags/include_tag_test.rb +34 -30
  70. data/test/integration/tags/increment_tag_test.rb +10 -11
  71. data/test/integration/tags/raw_tag_test.rb +7 -1
  72. data/test/integration/tags/standard_tag_test.rb +121 -122
  73. data/test/integration/tags/statements_test.rb +3 -5
  74. data/test/integration/tags/table_row_test.rb +20 -19
  75. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  76. data/test/integration/template_test.rb +190 -49
  77. data/test/integration/trim_mode_test.rb +525 -0
  78. data/test/integration/variable_test.rb +23 -13
  79. data/test/test_helper.rb +44 -9
  80. data/test/unit/block_unit_test.rb +8 -5
  81. data/test/unit/condition_unit_test.rb +86 -77
  82. data/test/unit/context_unit_test.rb +48 -57
  83. data/test/unit/file_system_unit_test.rb +3 -3
  84. data/test/unit/i18n_unit_test.rb +2 -2
  85. data/test/unit/lexer_unit_test.rb +11 -8
  86. data/test/unit/parser_unit_test.rb +2 -2
  87. data/test/unit/regexp_unit_test.rb +1 -1
  88. data/test/unit/strainer_unit_test.rb +85 -8
  89. data/test/unit/tag_unit_test.rb +7 -2
  90. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  91. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  92. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  93. data/test/unit/template_unit_test.rb +14 -5
  94. data/test/unit/tokenizer_unit_test.rb +24 -7
  95. data/test/unit/variable_unit_test.rb +66 -43
  96. metadata +55 -50
  97. data/lib/liquid/module_ex.rb +0 -62
  98. data/lib/liquid/token.rb +0 -18
  99. data/test/unit/module_ex_unit_test.rb +0 -87
  100. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -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 rescue input
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) rescue 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 = Integer(offset)
51
- length = length ? Integer(length) : 1
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,18 +62,22 @@ 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? then return end
63
- l = length.to_i - truncate_string.length
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
64
70
  l = 0 if l < 0
65
- input.length > length.to_i ? input[0...l] + truncate_string : input
71
+ input_str.length > length ? input_str[0...l] + truncate_string_str : input_str
66
72
  end
67
73
 
68
74
  def truncatewords(input, words = 15, truncate_string = "...".freeze)
69
- if input.nil? then return end
75
+ return if input.nil?
70
76
  wordlist = input.to_s.split
71
- l = words.to_i - 1
77
+ words = Utils.to_integer(words)
78
+ l = words - 1
72
79
  l = 0 if l < 0
73
- wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string : input
80
+ wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string.to_s : input
74
81
  end
75
82
 
76
83
  # Split input string into an array of substrings separated by given pattern.
@@ -79,7 +86,7 @@ module Liquid
79
86
  # <div class="summary">{{ post | split '//' | first }}</div>
80
87
  #
81
88
  def split(input, pattern)
82
- input.to_s.split(pattern)
89
+ input.to_s.split(pattern.to_s)
83
90
  end
84
91
 
85
92
  def strip(input)
@@ -115,10 +122,32 @@ module Liquid
115
122
  ary = InputIterator.new(input)
116
123
  if property.nil?
117
124
  ary.sort
125
+ elsif ary.empty? # The next two cases assume a non-empty array.
126
+ []
118
127
  elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
119
- ary.sort {|a,b| a[property] <=> b[property] }
120
- elsif ary.first.respond_to?(property)
121
- ary.sort {|a,b| a.send(property) <=> b.send(property) }
128
+ ary.sort do |a, b|
129
+ a = a[property]
130
+ b = b[property]
131
+ if a && b
132
+ a <=> b
133
+ else
134
+ a ? -1 : 1
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ # Sort elements of an array ignoring case if strings
141
+ # provide optional property with which to sort an array of hashes or drops
142
+ def sort_natural(input, property = nil)
143
+ ary = InputIterator.new(input)
144
+
145
+ if property.nil?
146
+ ary.sort { |a, b| a.casecmp(b) }
147
+ elsif ary.empty? # The next two cases assume a non-empty array.
148
+ []
149
+ elsif ary.first.respond_to?(:[]) && !ary.first[property].nil?
150
+ ary.sort { |a, b| a[property].casecmp(b[property]) }
122
151
  end
123
152
  end
124
153
 
@@ -126,10 +155,13 @@ module Liquid
126
155
  # provide optional property with which to determine uniqueness
127
156
  def uniq(input, property = nil)
128
157
  ary = InputIterator.new(input)
158
+
129
159
  if property.nil?
130
- input.uniq
131
- elsif input.first.respond_to?(:[])
132
- input.uniq{ |a| a[property] }
160
+ ary.uniq
161
+ elsif ary.empty? # The next two cases assume a non-empty array.
162
+ []
163
+ elsif ary.first.respond_to?(:[])
164
+ ary.uniq{ |a| a[property] }
133
165
  end
134
166
  end
135
167
 
@@ -147,29 +179,44 @@ module Liquid
147
179
  if property == "to_liquid".freeze
148
180
  e
149
181
  elsif e.respond_to?(:[])
150
- e[property]
182
+ r = e[property]
183
+ r.is_a?(Proc) ? r.call : r
151
184
  end
152
185
  end
153
186
  end
154
187
 
188
+ # Remove nils within an array
189
+ # provide optional property with which to check for nil
190
+ def compact(input, property = nil)
191
+ ary = InputIterator.new(input)
192
+
193
+ if property.nil?
194
+ ary.compact
195
+ elsif ary.empty? # The next two cases assume a non-empty array.
196
+ []
197
+ elsif ary.first.respond_to?(:[])
198
+ ary.reject{ |a| a[property].nil? }
199
+ end
200
+ end
201
+
155
202
  # Replace occurrences of a string with another
156
203
  def replace(input, string, replacement = ''.freeze)
157
- input.to_s.gsub(string, replacement.to_s)
204
+ input.to_s.gsub(string.to_s, replacement.to_s)
158
205
  end
159
206
 
160
207
  # Replace the first occurrences of a string with another
161
208
  def replace_first(input, string, replacement = ''.freeze)
162
- input.to_s.sub(string, replacement.to_s)
209
+ input.to_s.sub(string.to_s, replacement.to_s)
163
210
  end
164
211
 
165
212
  # remove a substring
166
213
  def remove(input, string)
167
- input.to_s.gsub(string, ''.freeze)
214
+ input.to_s.gsub(string.to_s, ''.freeze)
168
215
  end
169
216
 
170
217
  # remove the first occurrences of a substring
171
218
  def remove_first(input, string)
172
- input.to_s.sub(string, ''.freeze)
219
+ input.to_s.sub(string.to_s, ''.freeze)
173
220
  end
174
221
 
175
222
  # add one string to another
@@ -177,6 +224,13 @@ module Liquid
177
224
  input.to_s + string.to_s
178
225
  end
179
226
 
227
+ def concat(input, array)
228
+ unless array.respond_to?(:to_ary)
229
+ raise ArgumentError.new("concat filter requires an array argument")
230
+ end
231
+ InputIterator.new(input).concat(array)
232
+ end
233
+
180
234
  # prepend a string to another
181
235
  def prepend(input, string)
182
236
  string.to_s + input.to_s
@@ -221,7 +275,7 @@ module Liquid
221
275
  def date(input, format)
222
276
  return input if format.to_s.empty?
223
277
 
224
- return input unless date = to_date(input)
278
+ return input unless date = Utils.to_date(input)
225
279
 
226
280
  date.strftime(format.to_s)
227
281
  end
@@ -244,6 +298,12 @@ module Liquid
244
298
  array.last if array.respond_to?(:last)
245
299
  end
246
300
 
301
+ # absolute value
302
+ def abs(input)
303
+ result = Utils.to_number(input).abs
304
+ result.is_a?(BigDecimal) ? result.to_f : result
305
+ end
306
+
247
307
  # addition
248
308
  def plus(input, operand)
249
309
  apply_operation(input, operand, :+)
@@ -262,66 +322,49 @@ module Liquid
262
322
  # division
263
323
  def divided_by(input, operand)
264
324
  apply_operation(input, operand, :/)
325
+ rescue ::ZeroDivisionError => e
326
+ raise Liquid::ZeroDivisionError, e.message
265
327
  end
266
328
 
267
329
  def modulo(input, operand)
268
330
  apply_operation(input, operand, :%)
331
+ rescue ::ZeroDivisionError => e
332
+ raise Liquid::ZeroDivisionError, e.message
269
333
  end
270
334
 
271
335
  def round(input, n = 0)
272
- result = to_number(input).round(to_number(n))
336
+ result = Utils.to_number(input).round(Utils.to_number(n))
273
337
  result = result.to_f if result.is_a?(BigDecimal)
274
338
  result = result.to_i if n == 0
275
339
  result
340
+ rescue ::FloatDomainError => e
341
+ raise Liquid::FloatDomainError, e.message
276
342
  end
277
343
 
278
344
  def ceil(input)
279
- to_number(input).ceil.to_i
345
+ Utils.to_number(input).ceil.to_i
346
+ rescue ::FloatDomainError => e
347
+ raise Liquid::FloatDomainError, e.message
280
348
  end
281
349
 
282
350
  def floor(input)
283
- to_number(input).floor.to_i
284
- end
285
-
286
- def default(input, default_value = "".freeze)
287
- is_blank = input.respond_to?(:empty?) ? input.empty? : !input
288
- is_blank ? default_value : input
351
+ Utils.to_number(input).floor.to_i
352
+ rescue ::FloatDomainError => e
353
+ raise Liquid::FloatDomainError, e.message
289
354
  end
290
355
 
291
- private
292
-
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
356
+ def default(input, default_value = ''.freeze)
357
+ if !input || input.respond_to?(:empty?) && input.empty?
358
+ default_value
301
359
  else
302
- 0
360
+ input
303
361
  end
304
362
  end
305
363
 
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
364
+ private
322
365
 
323
366
  def apply_operation(input, operand, operation)
324
- result = to_number(input).send(operation, to_number(operand))
367
+ result = Utils.to_number(input).send(operation, Utils.to_number(operand))
325
368
  result.is_a?(BigDecimal) ? result.to_f : result
326
369
  end
327
370
 
@@ -344,10 +387,27 @@ module Liquid
344
387
  to_a.join(glue)
345
388
  end
346
389
 
390
+ def concat(args)
391
+ to_a.concat(args)
392
+ end
393
+
347
394
  def reverse
348
395
  reverse_each.to_a
349
396
  end
350
397
 
398
+ def uniq(&block)
399
+ to_a.uniq(&block)
400
+ end
401
+
402
+ def compact
403
+ to_a.compact
404
+ end
405
+
406
+ def empty?
407
+ @input.each { return false }
408
+ true
409
+ end
410
+
351
411
  def each
352
412
  @input.each do |e|
353
413
  yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
@@ -1,19 +1,19 @@
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
  #
8
7
  # The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
9
8
  # Context#add_filters or Template.register_filter
10
9
  class Strainer #:nodoc:
11
- @@filters = []
12
- @@known_filters = Set.new
13
- @@known_methods = Set.new
10
+ @@global_strainer = Class.new(Strainer) do
11
+ @filter_methods = Set.new
12
+ end
14
13
  @@strainer_class_cache = Hash.new do |hash, filters|
15
- hash[filters] = Class.new(Strainer) do
16
- filters.each { |f| include f }
14
+ hash[filters] = Class.new(@@global_strainer) do
15
+ @filter_methods = @@global_strainer.filter_methods.dup
16
+ filters.each { |f| add_filter(f) }
17
17
  end
18
18
  end
19
19
 
@@ -21,43 +21,46 @@ module Liquid
21
21
  @context = context
22
22
  end
23
23
 
24
- def self.global_filter(filter)
25
- raise ArgumentError, "Passed filter is not a module" unless filter.is_a?(Module)
26
- add_known_filter(filter)
27
- @@filters << filter unless @@filters.include?(filter)
28
- end
29
-
30
- def self.add_known_filter(filter)
31
- unless @@known_filters.include?(filter)
32
- @@method_blacklist ||= Set.new(Strainer.instance_methods.map(&:to_s))
33
- new_methods = filter.instance_methods.map(&:to_s)
34
- new_methods.reject!{ |m| @@method_blacklist.include?(m) }
35
- @@known_methods.merge(new_methods)
36
- @@known_filters.add(filter)
24
+ class << self
25
+ attr_reader :filter_methods
26
+ end
27
+
28
+ def self.add_filter(filter)
29
+ raise ArgumentError, "Expected module but got: #{filter.class}" unless filter.is_a?(Module)
30
+ unless self.class.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
37
38
  end
38
39
  end
39
40
 
40
- def self.strainer_class_cache
41
- @@strainer_class_cache
41
+ def self.global_filter(filter)
42
+ @@strainer_class_cache.clear
43
+ @@global_strainer.add_filter(filter)
44
+ end
45
+
46
+ def self.invokable?(method)
47
+ @filter_methods.include?(method.to_s)
42
48
  end
43
49
 
44
50
  def self.create(context, filters = [])
45
- filters = @@filters + filters
46
- strainer_class_cache[filters].new(context)
51
+ @@strainer_class_cache[filters].new(context)
47
52
  end
48
53
 
49
54
  def invoke(method, *args)
50
- if invokable?(method)
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.new(e.message)
57
- end
58
-
59
- def invokable?(method)
60
- @@known_methods.include?(method.to_s) && respond_to?(method)
63
+ raise Liquid::ArgumentError, e.message, e.backtrace
61
64
  end
62
65
  end
63
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
data/lib/liquid/tag.rb CHANGED
@@ -1,26 +1,27 @@
1
1
  module Liquid
2
2
  class Tag
3
- attr_accessor :options, :line_number
4
- attr_reader :nodelist, :warnings
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, tokens, options)
8
+ def parse(tag_name, markup, tokenizer, options)
9
9
  tag = new(tag_name, markup, options)
10
- tag.parse(tokens)
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, options)
17
+ def initialize(tag_name, markup, parse_context)
18
18
  @tag_name = tag_name
19
19
  @markup = markup
20
- @options = options
20
+ @parse_context = parse_context
21
+ @line_number = parse_context.line_number
21
22
  end
22
23
 
23
- def parse(tokens)
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(context)
35
+ def render(_context)
35
36
  ''.freeze
36
37
  end
37
38
 
@@ -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,13 +23,28 @@ module Liquid
25
23
  def render(context)
26
24
  val = @from.render(context)
27
25
  context.scopes.last[@to] = val
28
- context.increment_used_resources(:assign_score_current, val)
26
+ context.resource_limits.assign_score += assign_score_of(val)
29
27
  ''.freeze
30
28
  end
31
29
 
32
30
  def blank?
33
31
  true
34
32
  end
33
+
34
+ private
35
+
36
+ def assign_score_of(val)
37
+ if val.instance_of?(String)
38
+ val.length
39
+ elsif val.instance_of?(Array) || val.instance_of?(Hash)
40
+ sum = 1
41
+ # Uses #each to avoid extra allocations.
42
+ val.each { |child| sum += assign_score_of(child) }
43
+ sum
44
+ else
45
+ 1
46
+ end
47
+ end
35
48
  end
36
49
 
37
50
  Template.register_tag('assign'.freeze, Assign)
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Break tag to be used to break out of a for loop.
4
3
  #
5
4
  # == Basic Usage:
@@ -10,11 +9,9 @@ module Liquid
10
9
  # {% endfor %}
11
10
  #
12
11
  class Break < Tag
13
-
14
12
  def interrupt
15
13
  BreakInterrupt.new
16
14
  end
17
-
18
15
  end
19
16
 
20
17
  Template.register_tag('break'.freeze, Break)
@@ -11,7 +11,7 @@ module Liquid
11
11
  # in a sidebar or footer.
12
12
  #
13
13
  class Capture < Block
14
- Syntax = /(\w+)/
14
+ Syntax = /(#{VariableSignature}+)/o
15
15
 
16
16
  def initialize(tag_name, markup, options)
17
17
  super
@@ -25,7 +25,7 @@ module Liquid
25
25
  def render(context)
26
26
  output = super
27
27
  context.scopes.last[@to] = output
28
- context.increment_used_resources(:assign_score_current, output)
28
+ context.resource_limits.assign_score += output.length
29
29
  ''.freeze
30
30
  end
31
31
 
@@ -8,18 +8,24 @@ module Liquid
8
8
  @blocks = []
9
9
 
10
10
  if markup =~ Syntax
11
- @left = $1
11
+ @left = Expression.parse($1)
12
12
  else
13
13
  raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
14
14
  end
15
15
  end
16
16
 
17
+ def parse(tokens)
18
+ body = BlockBody.new
19
+ while parse_body(body, tokens)
20
+ body = @blocks.last.attachment
21
+ end
22
+ end
23
+
17
24
  def nodelist
18
- @blocks.flat_map(&:attachment)
25
+ @blocks.map(&:attachment)
19
26
  end
20
27
 
21
28
  def unknown_tag(tag, markup, tokens)
22
- @nodelist = []
23
29
  case tag
24
30
  when 'when'.freeze
25
31
  record_when_condition(markup)
@@ -37,10 +43,10 @@ module Liquid
37
43
  output = ''
38
44
  @blocks.each do |block|
39
45
  if block.else?
40
- return render_all(block.attachment, context) if execute_else_block
46
+ return block.attachment.render(context) if execute_else_block
41
47
  elsif block.evaluate(context)
42
48
  execute_else_block = false
43
- output << render_all(block.attachment, context)
49
+ output << block.attachment.render(context)
44
50
  end
45
51
  end
46
52
  output
@@ -50,27 +56,28 @@ module Liquid
50
56
  private
51
57
 
52
58
  def record_when_condition(markup)
59
+ body = BlockBody.new
60
+
53
61
  while markup
54
- # Create a new nodelist and assign it to the new block
55
- if not markup =~ WhenSyntax
62
+ unless markup =~ WhenSyntax
56
63
  raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
57
64
  end
58
65
 
59
66
  markup = $2
60
67
 
61
- block = Condition.new(@left, '=='.freeze, $1)
62
- block.attach(@nodelist)
63
- @blocks.push(block)
68
+ block = Condition.new(@left, '=='.freeze, Expression.parse($1))
69
+ block.attach(body)
70
+ @blocks << block
64
71
  end
65
72
  end
66
73
 
67
74
  def record_else_condition(markup)
68
- if not markup.strip.empty?
75
+ unless markup.strip.empty?
69
76
  raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze))
70
77
  end
71
78
 
72
79
  block = ElseCondition.new
73
- block.attach(@nodelist)
80
+ block.attach(BlockBody.new)
74
81
  @blocks << block
75
82
  end
76
83
  end
@@ -1,10 +1,10 @@
1
1
  module Liquid
2
2
  class Comment < Block
3
- def render(context)
3
+ def render(_context)
4
4
  ''.freeze
5
5
  end
6
6
 
7
- def unknown_tag(tag, markup, tokens)
7
+ def unknown_tag(_tag, _markup, _tokens)
8
8
  end
9
9
 
10
10
  def blank?