liquid 3.0.0 → 4.0.0

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 (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?