liquid 4.0.4 → 5.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +32 -4
  3. data/README.md +6 -0
  4. data/lib/liquid/block.rb +31 -14
  5. data/lib/liquid/block_body.rb +164 -54
  6. data/lib/liquid/condition.rb +39 -18
  7. data/lib/liquid/context.rb +106 -51
  8. data/lib/liquid/document.rb +47 -9
  9. data/lib/liquid/drop.rb +4 -2
  10. data/lib/liquid/errors.rb +20 -18
  11. data/lib/liquid/expression.rb +29 -34
  12. data/lib/liquid/extensions.rb +2 -0
  13. data/lib/liquid/file_system.rb +6 -4
  14. data/lib/liquid/forloop_drop.rb +11 -4
  15. data/lib/liquid/i18n.rb +5 -3
  16. data/lib/liquid/interrupts.rb +3 -1
  17. data/lib/liquid/lexer.rb +30 -23
  18. data/lib/liquid/locales/en.yml +3 -1
  19. data/lib/liquid/parse_context.rb +16 -4
  20. data/lib/liquid/parse_tree_visitor.rb +2 -2
  21. data/lib/liquid/parser.rb +30 -18
  22. data/lib/liquid/parser_switching.rb +17 -3
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +26 -14
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/range_lookup.rb +5 -3
  27. data/lib/liquid/register.rb +6 -0
  28. data/lib/liquid/resource_limits.rb +47 -8
  29. data/lib/liquid/standardfilters.rb +62 -43
  30. data/lib/liquid/static_registers.rb +44 -0
  31. data/lib/liquid/strainer_factory.rb +36 -0
  32. data/lib/liquid/strainer_template.rb +53 -0
  33. data/lib/liquid/tablerowloop_drop.rb +6 -4
  34. data/lib/liquid/tag/disableable.rb +22 -0
  35. data/lib/liquid/tag/disabler.rb +21 -0
  36. data/lib/liquid/tag.rb +28 -6
  37. data/lib/liquid/tags/assign.rb +24 -10
  38. data/lib/liquid/tags/break.rb +8 -3
  39. data/lib/liquid/tags/capture.rb +11 -8
  40. data/lib/liquid/tags/case.rb +33 -27
  41. data/lib/liquid/tags/comment.rb +5 -3
  42. data/lib/liquid/tags/continue.rb +8 -3
  43. data/lib/liquid/tags/cycle.rb +25 -14
  44. data/lib/liquid/tags/decrement.rb +6 -3
  45. data/lib/liquid/tags/echo.rb +26 -0
  46. data/lib/liquid/tags/for.rb +68 -44
  47. data/lib/liquid/tags/if.rb +35 -23
  48. data/lib/liquid/tags/ifchanged.rb +11 -10
  49. data/lib/liquid/tags/include.rb +34 -47
  50. data/lib/liquid/tags/increment.rb +7 -3
  51. data/lib/liquid/tags/raw.rb +14 -11
  52. data/lib/liquid/tags/render.rb +84 -0
  53. data/lib/liquid/tags/table_row.rb +23 -19
  54. data/lib/liquid/tags/unless.rb +15 -15
  55. data/lib/liquid/template.rb +55 -69
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +17 -9
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +5 -3
  60. data/lib/liquid/variable.rb +47 -19
  61. data/lib/liquid/variable_lookup.rb +8 -6
  62. data/lib/liquid/version.rb +2 -1
  63. data/lib/liquid.rb +17 -5
  64. data/test/integration/assign_test.rb +74 -5
  65. data/test/integration/blank_test.rb +11 -8
  66. data/test/integration/block_test.rb +47 -1
  67. data/test/integration/capture_test.rb +18 -10
  68. data/test/integration/context_test.rb +608 -5
  69. data/test/integration/document_test.rb +4 -2
  70. data/test/integration/drop_test.rb +67 -57
  71. data/test/integration/error_handling_test.rb +73 -61
  72. data/test/integration/expression_test.rb +46 -0
  73. data/test/integration/filter_test.rb +53 -42
  74. data/test/integration/hash_ordering_test.rb +5 -3
  75. data/test/integration/output_test.rb +26 -24
  76. data/test/integration/parsing_quirks_test.rb +19 -7
  77. data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
  78. data/test/integration/security_test.rb +30 -21
  79. data/test/integration/standard_filter_test.rb +339 -281
  80. data/test/integration/tag/disableable_test.rb +59 -0
  81. data/test/integration/tag_test.rb +45 -0
  82. data/test/integration/tags/break_tag_test.rb +4 -2
  83. data/test/integration/tags/continue_tag_test.rb +4 -2
  84. data/test/integration/tags/echo_test.rb +13 -0
  85. data/test/integration/tags/for_tag_test.rb +107 -51
  86. data/test/integration/tags/if_else_tag_test.rb +5 -3
  87. data/test/integration/tags/include_tag_test.rb +70 -54
  88. data/test/integration/tags/increment_tag_test.rb +4 -2
  89. data/test/integration/tags/liquid_tag_test.rb +116 -0
  90. data/test/integration/tags/raw_tag_test.rb +14 -11
  91. data/test/integration/tags/render_tag_test.rb +213 -0
  92. data/test/integration/tags/standard_tag_test.rb +38 -31
  93. data/test/integration/tags/statements_test.rb +23 -21
  94. data/test/integration/tags/table_row_test.rb +2 -0
  95. data/test/integration/tags/unless_else_tag_test.rb +4 -2
  96. data/test/integration/template_test.rb +118 -124
  97. data/test/integration/trim_mode_test.rb +78 -44
  98. data/test/integration/variable_test.rb +43 -32
  99. data/test/test_helper.rb +75 -14
  100. data/test/unit/block_unit_test.rb +19 -24
  101. data/test/unit/condition_unit_test.rb +79 -77
  102. data/test/unit/file_system_unit_test.rb +6 -4
  103. data/test/unit/i18n_unit_test.rb +7 -5
  104. data/test/unit/lexer_unit_test.rb +11 -9
  105. data/test/{integration → unit}/parse_tree_visitor_test.rb +1 -1
  106. data/test/unit/parser_unit_test.rb +37 -35
  107. data/test/unit/partial_cache_unit_test.rb +128 -0
  108. data/test/unit/regexp_unit_test.rb +17 -15
  109. data/test/unit/static_registers_unit_test.rb +156 -0
  110. data/test/unit/strainer_factory_unit_test.rb +100 -0
  111. data/test/unit/strainer_template_unit_test.rb +82 -0
  112. data/test/unit/tag_unit_test.rb +5 -3
  113. data/test/unit/tags/case_tag_unit_test.rb +3 -1
  114. data/test/unit/tags/for_tag_unit_test.rb +4 -2
  115. data/test/unit/tags/if_tag_unit_test.rb +3 -1
  116. data/test/unit/template_factory_unit_test.rb +12 -0
  117. data/test/unit/template_unit_test.rb +19 -10
  118. data/test/unit/tokenizer_unit_test.rb +19 -17
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +75 -47
  121. data/lib/liquid/strainer.rb +0 -66
  122. data/test/unit/context_unit_test.rb +0 -490
  123. data/test/unit/strainer_unit_test.rb +0 -164
@@ -1,20 +1,22 @@
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
- '&'.freeze => '&'.freeze,
8
- '>'.freeze => '>'.freeze,
9
- '<'.freeze => '&lt;'.freeze,
10
- '"'.freeze => '&quot;'.freeze,
11
- "'".freeze => '&#39;'.freeze
9
+ '&' => '&amp;',
10
+ '>' => '&gt;',
11
+ '<' => '&lt;',
12
+ '"' => '&quot;',
13
+ "'" => '&#39;',
12
14
  }.freeze
13
15
  HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
14
- STRIP_HTML_BLOCKS = Regexp.union(
15
- /<script.*?<\/script>/m,
16
+ STRIP_HTML_BLOCKS = Regexp.union(
17
+ %r{<script.*?</script>}m,
16
18
  /<!--.*?-->/m,
17
- /<style.*?<\/style>/m
19
+ %r{<style.*?</style>}m
18
20
  )
19
21
  STRIP_HTML_TAGS = /<.*?>/m
20
22
 
@@ -72,23 +74,28 @@ module Liquid
72
74
  end
73
75
 
74
76
  # Truncate a string down to x characters
75
- def truncate(input, length = 50, truncate_string = "...".freeze)
77
+ def truncate(input, length = 50, truncate_string = "...")
76
78
  return if input.nil?
77
79
  input_str = input.to_s
78
- length = Utils.to_integer(length)
80
+ length = Utils.to_integer(length)
81
+
79
82
  truncate_string_str = truncate_string.to_s
83
+
80
84
  l = length - truncate_string_str.length
81
85
  l = 0 if l < 0
82
- input_str.length > length ? input_str[0...l] + truncate_string_str : input_str
86
+
87
+ input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
83
88
  end
84
89
 
85
- def truncatewords(input, words = 15, truncate_string = "...".freeze)
90
+ def truncatewords(input, words = 15, truncate_string = "...")
86
91
  return if input.nil?
87
92
  wordlist = input.to_s.split
88
- words = Utils.to_integer(words)
93
+ words = Utils.to_integer(words)
94
+
89
95
  l = words - 1
90
96
  l = 0 if l < 0
91
- wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string.to_s : input
97
+
98
+ wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input
92
99
  end
93
100
 
94
101
  # Split input string into an array of substrings separated by given pattern.
@@ -113,7 +120,7 @@ module Liquid
113
120
  end
114
121
 
115
122
  def strip_html(input)
116
- empty = ''.freeze
123
+ empty = ''
117
124
  result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
118
125
  result.gsub!(STRIP_HTML_TAGS, empty)
119
126
  result
@@ -121,18 +128,18 @@ module Liquid
121
128
 
122
129
  # Remove all newlines from the string
123
130
  def strip_newlines(input)
124
- input.to_s.gsub(/\r?\n/, ''.freeze)
131
+ input.to_s.gsub(/\r?\n/, '')
125
132
  end
126
133
 
127
134
  # Join elements of the array with certain character between them
128
- def join(input, glue = ' '.freeze)
129
- InputIterator.new(input).join(glue)
135
+ def join(input, glue = ' ')
136
+ InputIterator.new(input, context).join(glue)
130
137
  end
131
138
 
132
139
  # Sort elements of the array
133
140
  # provide optional property with which to sort an array of hashes or drops
134
141
  def sort(input, property = nil)
135
- ary = InputIterator.new(input)
142
+ ary = InputIterator.new(input, context)
136
143
 
137
144
  return [] if ary.empty?
138
145
 
@@ -152,7 +159,7 @@ module Liquid
152
159
  # Sort elements of an array ignoring case if strings
153
160
  # provide optional property with which to sort an array of hashes or drops
154
161
  def sort_natural(input, property = nil)
155
- ary = InputIterator.new(input)
162
+ ary = InputIterator.new(input, context)
156
163
 
157
164
  return [] if ary.empty?
158
165
 
@@ -172,7 +179,7 @@ module Liquid
172
179
  # Filter the elements of an array to those with a certain property value.
173
180
  # By default the target is any truthy value.
174
181
  def where(input, property, target_value = nil)
175
- ary = InputIterator.new(input)
182
+ ary = InputIterator.new(input, context)
176
183
 
177
184
  if ary.empty?
178
185
  []
@@ -194,7 +201,7 @@ module Liquid
194
201
  # Remove duplicate elements from an array
195
202
  # provide optional property with which to determine uniqueness
196
203
  def uniq(input, property = nil)
197
- ary = InputIterator.new(input)
204
+ ary = InputIterator.new(input, context)
198
205
 
199
206
  if property.nil?
200
207
  ary.uniq
@@ -211,16 +218,16 @@ module Liquid
211
218
 
212
219
  # Reverse the elements of an array
213
220
  def reverse(input)
214
- ary = InputIterator.new(input)
221
+ ary = InputIterator.new(input, context)
215
222
  ary.reverse
216
223
  end
217
224
 
218
225
  # map/collect on a given property
219
226
  def map(input, property)
220
- InputIterator.new(input).map do |e|
227
+ InputIterator.new(input, context).map do |e|
221
228
  e = e.call if e.is_a?(Proc)
222
229
 
223
- if property == "to_liquid".freeze
230
+ if property == "to_liquid"
224
231
  e
225
232
  elsif e.respond_to?(:[])
226
233
  r = e[property]
@@ -234,7 +241,7 @@ module Liquid
234
241
  # Remove nils within an array
235
242
  # provide optional property with which to check for nil
236
243
  def compact(input, property = nil)
237
- ary = InputIterator.new(input)
244
+ ary = InputIterator.new(input, context)
238
245
 
239
246
  if property.nil?
240
247
  ary.compact
@@ -250,23 +257,23 @@ module Liquid
250
257
  end
251
258
 
252
259
  # Replace occurrences of a string with another
253
- def replace(input, string, replacement = ''.freeze)
260
+ def replace(input, string, replacement = '')
254
261
  input.to_s.gsub(string.to_s, replacement.to_s)
255
262
  end
256
263
 
257
264
  # Replace the first occurrences of a string with another
258
- def replace_first(input, string, replacement = ''.freeze)
265
+ def replace_first(input, string, replacement = '')
259
266
  input.to_s.sub(string.to_s, replacement.to_s)
260
267
  end
261
268
 
262
269
  # remove a substring
263
270
  def remove(input, string)
264
- input.to_s.gsub(string.to_s, ''.freeze)
271
+ input.to_s.gsub(string.to_s, '')
265
272
  end
266
273
 
267
274
  # remove the first occurrences of a substring
268
275
  def remove_first(input, string)
269
- input.to_s.sub(string.to_s, ''.freeze)
276
+ input.to_s.sub(string.to_s, '')
270
277
  end
271
278
 
272
279
  # add one string to another
@@ -276,9 +283,9 @@ module Liquid
276
283
 
277
284
  def concat(input, array)
278
285
  unless array.respond_to?(:to_ary)
279
- raise ArgumentError.new("concat filter requires an array argument")
286
+ raise ArgumentError, "concat filter requires an array argument"
280
287
  end
281
- InputIterator.new(input).concat(array)
288
+ InputIterator.new(input, context).concat(array)
282
289
  end
283
290
 
284
291
  # prepend a string to another
@@ -288,7 +295,7 @@ module Liquid
288
295
 
289
296
  # Add <br /> tags in front of all newlines in input string
290
297
  def newline_to_br(input)
291
- input.to_s.gsub(/\n/, "<br />\n".freeze)
298
+ input.to_s.gsub(/\n/, "<br />\n")
292
299
  end
293
300
 
294
301
  # Reformat a date using Ruby's core Time#strftime( string ) -> string
@@ -325,7 +332,7 @@ module Liquid
325
332
  def date(input, format)
326
333
  return input if format.to_s.empty?
327
334
 
328
- return input unless date = Utils.to_date(input)
335
+ return input unless (date = Utils.to_date(input))
329
336
 
330
337
  date.strftime(format.to_s)
331
338
  end
@@ -419,18 +426,28 @@ module Liquid
419
426
  result.is_a?(BigDecimal) ? result.to_f : result
420
427
  end
421
428
 
422
- def default(input, default_value = ''.freeze)
423
- if !input || input.respond_to?(:empty?) && input.empty?
424
- default_value
425
- else
426
- input
427
- end
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
428
443
  end
429
444
 
430
445
  private
431
446
 
447
+ attr_reader :context
448
+
432
449
  def raise_property_error(property)
433
- raise Liquid::ArgumentError.new("cannot select the property '#{property}'")
450
+ raise Liquid::ArgumentError, "cannot select the property '#{property}'"
434
451
  end
435
452
 
436
453
  def apply_operation(input, operand, operation)
@@ -457,8 +474,9 @@ module Liquid
457
474
  class InputIterator
458
475
  include Enumerable
459
476
 
460
- def initialize(input)
461
- @input = if input.is_a?(Array)
477
+ def initialize(input, context)
478
+ @context = context
479
+ @input = if input.is_a?(Array)
462
480
  input.flatten
463
481
  elsif input.is_a?(Hash)
464
482
  [input]
@@ -496,6 +514,7 @@ module Liquid
496
514
 
497
515
  def each
498
516
  @input.each do |e|
517
+ e.context = @context if e.respond_to?(:context=)
499
518
  yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
500
519
  end
501
520
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class StaticRegisters
5
+ attr_reader :static
6
+
7
+ def initialize(registers = {})
8
+ @static = registers.is_a?(StaticRegisters) ? registers.static : registers
9
+ @registers = {}
10
+ end
11
+
12
+ def []=(key, value)
13
+ @registers[key] = value
14
+ end
15
+
16
+ def [](key)
17
+ if @registers.key?(key)
18
+ @registers[key]
19
+ else
20
+ @static[key]
21
+ end
22
+ end
23
+
24
+ def delete(key)
25
+ @registers.delete(key)
26
+ end
27
+
28
+ UNDEFINED = Object.new
29
+
30
+ def fetch(key, default = UNDEFINED, &block)
31
+ if @registers.key?(key)
32
+ @registers.fetch(key)
33
+ elsif default != UNDEFINED
34
+ @static.fetch(key, default, &block)
35
+ else
36
+ @static.fetch(key, &block)
37
+ end
38
+ end
39
+
40
+ def key?(key)
41
+ @registers.key?(key) || @static.key?(key)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ # StrainerFactory is the factory for the filters system.
5
+ module StrainerFactory
6
+ extend self
7
+
8
+ def add_global_filter(filter)
9
+ strainer_class_cache.clear
10
+ global_filters << filter
11
+ end
12
+
13
+ def create(context, filters = [])
14
+ strainer_from_cache(filters).new(context)
15
+ end
16
+
17
+ private
18
+
19
+ def global_filters
20
+ @global_filters ||= []
21
+ end
22
+
23
+ def strainer_from_cache(filters)
24
+ strainer_class_cache[filters] ||= begin
25
+ klass = Class.new(StrainerTemplate)
26
+ global_filters.each { |f| klass.add_filter(f) }
27
+ filters.each { |f| klass.add_filter(f) }
28
+ klass
29
+ end
30
+ end
31
+
32
+ def strainer_class_cache
33
+ @strainer_class_cache ||= {}
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Liquid
6
+ # StrainerTemplate is the computed class for the filters system.
7
+ # New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
8
+ #
9
+ # The Strainer only allows method calls defined in filters given to it via StrainerFactory.add_global_filter,
10
+ # Context#add_filters or Template.register_filter
11
+ class StrainerTemplate
12
+ def initialize(context)
13
+ @context = context
14
+ end
15
+
16
+ class << self
17
+ def add_filter(filter)
18
+ return if include?(filter)
19
+
20
+ invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
21
+ if invokable_non_public_methods.any?
22
+ raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
23
+ end
24
+
25
+ include(filter)
26
+
27
+ filter_methods.merge(filter.public_instance_methods.map(&:to_s))
28
+ end
29
+
30
+ def invokable?(method)
31
+ filter_methods.include?(method.to_s)
32
+ end
33
+
34
+ private
35
+
36
+ def filter_methods
37
+ @filter_methods ||= Set.new
38
+ end
39
+ end
40
+
41
+ def invoke(method, *args)
42
+ if self.class.invokable?(method)
43
+ send(method, *args)
44
+ elsif @context.strict_filters
45
+ raise Liquid::UndefinedFilter, "undefined filter #{method}"
46
+ else
47
+ args.first
48
+ end
49
+ rescue ::ArgumentError => e
50
+ raise Liquid::ArgumentError, e.message, e.backtrace
51
+ end
52
+ end
53
+ end
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class TablerowloopDrop < Drop
3
5
  def initialize(length, cols)
4
6
  @length = length
5
- @row = 1
6
- @col = 1
7
- @cols = cols
8
- @index = 0
7
+ @row = 1
8
+ @col = 1
9
+ @cols = cols
10
+ @index = 0
9
11
  end
10
12
 
11
13
  attr_reader :length, :col, :row
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class Tag
5
+ module Disableable
6
+ def render_to_output_buffer(context, output)
7
+ if context.tag_disabled?(tag_name)
8
+ output << disabled_error(context)
9
+ return
10
+ end
11
+ super
12
+ end
13
+
14
+ def disabled_error(context)
15
+ # raise then rescue the exception so that the Context#exception_renderer can re-raise it
16
+ raise DisabledError, "#{tag_name} #{parse_context[:locale].t('errors.disabled.tag')}"
17
+ rescue DisabledError => exc
18
+ context.handle_error(exc, line_number)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class Tag
5
+ module Disabler
6
+ module ClassMethods
7
+ attr_reader :disabled_tags
8
+ end
9
+
10
+ def self.prepended(base)
11
+ base.extend(ClassMethods)
12
+ end
13
+
14
+ def render_to_output_buffer(context, output)
15
+ context.with_disabled_tags(self.class.disabled_tags) do
16
+ super
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
data/lib/liquid/tag.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Tag
3
5
  attr_reader :nodelist, :tag_name, :line_number, :parse_context
@@ -5,20 +7,26 @@ module Liquid
5
7
  include ParserSwitching
6
8
 
7
9
  class << self
8
- def parse(tag_name, markup, tokenizer, options)
9
- tag = new(tag_name, markup, options)
10
+ def parse(tag_name, markup, tokenizer, parse_context)
11
+ tag = new(tag_name, markup, parse_context)
10
12
  tag.parse(tokenizer)
11
13
  tag
12
14
  end
13
15
 
16
+ def disable_tags(*tag_names)
17
+ @disabled_tags ||= []
18
+ @disabled_tags.concat(tag_names)
19
+ prepend(Disabler)
20
+ end
21
+
14
22
  private :new
15
23
  end
16
24
 
17
25
  def initialize(tag_name, markup, parse_context)
18
- @tag_name = tag_name
19
- @markup = markup
26
+ @tag_name = tag_name
27
+ @markup = markup
20
28
  @parse_context = parse_context
21
- @line_number = parse_context.line_number
29
+ @line_number = parse_context.line_number
22
30
  end
23
31
 
24
32
  def parse(_tokens)
@@ -33,11 +41,25 @@ module Liquid
33
41
  end
34
42
 
35
43
  def render(_context)
36
- ''.freeze
44
+ ''
45
+ end
46
+
47
+ # For backwards compatibility with custom tags. In a future release, the semantics
48
+ # of the `render_to_output_buffer` method will become the default and the `render`
49
+ # method will be removed.
50
+ def render_to_output_buffer(context, output)
51
+ output << render(context)
52
+ output
37
53
  end
38
54
 
39
55
  def blank?
40
56
  false
41
57
  end
58
+
59
+ private
60
+
61
+ def parse_expression(markup)
62
+ parse_context.parse_expression(markup)
63
+ end
42
64
  end
43
65
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Assign sets a variable in your template.
3
5
  #
@@ -10,23 +12,28 @@ module Liquid
10
12
  class Assign < Tag
11
13
  Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
12
14
 
15
+ # @api private
16
+ def self.raise_syntax_error(parse_context)
17
+ raise Liquid::SyntaxError, parse_context.locale.t('errors.syntax.assign')
18
+ end
19
+
13
20
  attr_reader :to, :from
14
21
 
15
- def initialize(tag_name, markup, options)
22
+ def initialize(tag_name, markup, parse_context)
16
23
  super
17
24
  if markup =~ Syntax
18
- @to = $1
19
- @from = Variable.new($2, options)
25
+ @to = Regexp.last_match(1)
26
+ @from = Variable.new(Regexp.last_match(2), parse_context)
20
27
  else
21
- raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
28
+ self.class.raise_syntax_error(parse_context)
22
29
  end
23
30
  end
24
31
 
25
- def render(context)
32
+ def render_to_output_buffer(context, output)
26
33
  val = @from.render(context)
27
34
  context.scopes.last[@to] = val
28
- context.resource_limits.assign_score += assign_score_of(val)
29
- ''.freeze
35
+ context.resource_limits.increment_assign_score(assign_score_of(val))
36
+ output
30
37
  end
31
38
 
32
39
  def blank?
@@ -37,12 +44,19 @@ module Liquid
37
44
 
38
45
  def assign_score_of(val)
39
46
  if val.instance_of?(String)
40
- val.length
41
- elsif val.instance_of?(Array) || val.instance_of?(Hash)
47
+ val.bytesize
48
+ elsif val.instance_of?(Array)
42
49
  sum = 1
43
50
  # Uses #each to avoid extra allocations.
44
51
  val.each { |child| sum += assign_score_of(child) }
45
52
  sum
53
+ elsif val.instance_of?(Hash)
54
+ sum = 1
55
+ val.each do |key, entry_value|
56
+ sum += assign_score_of(key)
57
+ sum += assign_score_of(entry_value)
58
+ end
59
+ sum
46
60
  else
47
61
  1
48
62
  end
@@ -55,5 +69,5 @@ module Liquid
55
69
  end
56
70
  end
57
71
 
58
- Template.register_tag('assign'.freeze, Assign)
72
+ Template.register_tag('assign', Assign)
59
73
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Break tag to be used to break out of a for loop.
3
5
  #
@@ -9,10 +11,13 @@ module Liquid
9
11
  # {% endfor %}
10
12
  #
11
13
  class Break < Tag
12
- def interrupt
13
- BreakInterrupt.new
14
+ INTERRUPT = BreakInterrupt.new.freeze
15
+
16
+ def render_to_output_buffer(context, output)
17
+ context.push_interrupt(INTERRUPT)
18
+ output
14
19
  end
15
20
  end
16
21
 
17
- Template.register_tag('break'.freeze, Break)
22
+ Template.register_tag('break', Break)
18
23
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Capture stores the result of a block into a variable without rendering it inplace.
3
5
  #
@@ -16,17 +18,18 @@ module Liquid
16
18
  def initialize(tag_name, markup, options)
17
19
  super
18
20
  if markup =~ Syntax
19
- @to = $1
21
+ @to = Regexp.last_match(1)
20
22
  else
21
- raise SyntaxError.new(options[:locale].t("errors.syntax.capture"))
23
+ raise SyntaxError, options[:locale].t("errors.syntax.capture")
22
24
  end
23
25
  end
24
26
 
25
- def render(context)
26
- output = super
27
- context.scopes.last[@to] = output
28
- context.resource_limits.assign_score += output.length
29
- ''.freeze
27
+ def render_to_output_buffer(context, output)
28
+ context.resource_limits.with_capture do
29
+ capture_output = render(context)
30
+ context.scopes.last[@to] = capture_output
31
+ end
32
+ output
30
33
  end
31
34
 
32
35
  def blank?
@@ -34,5 +37,5 @@ module Liquid
34
37
  end
35
38
  end
36
39
 
37
- Template.register_tag('capture'.freeze, Capture)
40
+ Template.register_tag('capture', Capture)
38
41
  end