liquid 5.0.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b1c036e6dd6d55418e6364b008551f5d3ba91f6e7dbc1c0ac3be43b235bd957
4
- data.tar.gz: a6279802ed388bcffc8980c49bcb0034a0f98de7cd6730bf97f603c07bf13dcd
3
+ metadata.gz: 0e15bedf89a61f8c69c63cb18007fea0639ba1da3e06126dbcc569bea596c300
4
+ data.tar.gz: 8bee4895c2d61314d9e1bede8bf9839c5fd1771031452ee9a07cac47dcfa460b
5
5
  SHA512:
6
- metadata.gz: 0c5094a47d46c8de3ac8ac632dbec8b813c1e47af834c0037ed9b868f5a2dde4da8dfdd143ff34c41505c1425524f1879ca909525676028cadd9900cbd028e63
7
- data.tar.gz: 7195f154c81283e8b7d99a07b82a2d9976b41be761efc528945f79ac0d77c2ff9c7632b3455f871b6db0471979c35e633f87e1c33900e4f9898012f836fd1496
6
+ metadata.gz: da978fffe487c1256d718df59a34bdb86f5c8dc9dd66b85d8169dfef0ab5d0fbf3feffb932b1ea30bea71ef573ceee8d8c021b78ee12f2988f365c0d0fa39e87
7
+ data.tar.gz: 45360ea60c3059c64508caf7e14cf7fbac0dd6198c6fba7ac4a525653f8f508348223770174b2872f8e54cd5a80ab0bfa70d13ad62975cefe0dd93247e694480
data/History.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Liquid Change Log
2
2
 
3
+ ## 5.2.0 2021-03-01
4
+
5
+ ### Features
6
+ * Add `remove_last`, and `replace_last` filters (#1422) [Anders Hagbard]
7
+ * Eagerly cache global filters (#1524) [Jean Boussier]
8
+
9
+ ### Fixes
10
+ * Fix some internal errors in filters from invalid input (#1476) [Dylan Thacker-Smith]
11
+ * Allow dash in filter kwarg name for consistency with Liquid::C (#1518) [CP Clermont]
12
+
13
+
14
+ ## 5.1.0 / 2021-09-09
15
+
16
+ ### Features
17
+ * Add `base64_encode`, `base64_decode`, `base64_url_safe_encode`, and `base64_url_safe_decode` filters (#1450) [Daniel Insley]
18
+ * Introduce `to_liquid_value` in `Liquid::Drop` (#1441) [Michael Go]
19
+
20
+ ### Fixes
21
+ * Fix support for using a String subclass for the liquid source (#1421) [Dylan Thacker-Smith]
22
+ * Add `ParseTreeVisitor` to `RangeLookup` (#1470) [CP Clermont]
23
+ * Translate `RangeError` to `Liquid::Error` for `truncatewords` with large int (#1431) [Dylan Thacker-Smith]
24
+
25
+ ## 5.0.1 / 2021-03-24
26
+
27
+ ### Fixes
28
+ * Add ParseTreeVisitor to Echo tag (#1414) [CP Clermont]
29
+ * Test with ruby 3.0 as the latest ruby version (#1398) [Dylan Thacker-Smith]
30
+ * Handle carriage return in newlines_to_br (#1391) [Unending]
31
+
32
+ ### Performance Improvements
33
+ * Use split limit in truncatewords (#1361) [Dylan Thacker-Smith]
34
+
3
35
  ## 5.0.0 / 2021-01-06
4
36
 
5
37
  ### Features
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  * [Contributing guidelines](CONTRIBUTING.md)
7
7
  * [Version history](History.md)
8
- * [Liquid documentation from Shopify](http://docs.shopify.com/themes/liquid-basics)
8
+ * [Liquid documentation from Shopify](https://shopify.dev/api/liquid)
9
9
  * [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
10
10
  * [Website](http://liquidmarkup.org/)
11
11
 
@@ -56,7 +56,7 @@ For standard use you can just pass it the content of a file and call render with
56
56
 
57
57
  Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
58
58
  Normally the parser is very lax and will accept almost anything without error. Unfortunately this can make
59
- it very hard to debug and can lead to unexpected behaviour.
59
+ it very hard to debug and can lead to unexpected behaviour.
60
60
 
61
61
  Liquid also comes with a stricter parser that can be used when editing templates to give better error messages
62
62
  when templates are invalid. You can enable this new parser like this:
@@ -99,7 +99,9 @@ module Liquid
99
99
  end
100
100
 
101
101
  private def parse_liquid_tag(markup, parse_context)
102
- liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
102
+ liquid_tag_tokenizer = parse_context.new_tokenizer(
103
+ markup, start_line_number: parse_context.line_number, for_liquid_tag: true
104
+ )
103
105
  parse_for_liquid_tag(liquid_tag_tokenizer, parse_context) do |end_tag_name, _end_tag_markup|
104
106
  if end_tag_name
105
107
  BlockBody.unknown_tag_in_liquid_tag(end_tag_name, parse_context)
@@ -229,8 +231,8 @@ module Liquid
229
231
  end
230
232
 
231
233
  def create_variable(token, parse_context)
232
- token.scan(ContentOfVariable) do |content|
233
- markup = content.first
234
+ if token =~ ContentOfVariable
235
+ markup = Regexp.last_match(1)
234
236
  return Variable.new(markup, parse_context)
235
237
  end
236
238
  BlockBody.raise_missing_variable_terminator(token, parse_context)
@@ -8,7 +8,7 @@ module Liquid
8
8
  # c = Condition.new(1, '==', 1)
9
9
  # c.evaluate #=> true
10
10
  #
11
- class Condition #:nodoc:
11
+ class Condition # :nodoc:
12
12
  @@operators = {
13
13
  '==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
14
14
  '!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
@@ -134,8 +134,8 @@ module Liquid
134
134
  # return this as the result.
135
135
  return context.evaluate(left) if op.nil?
136
136
 
137
- left = context.evaluate(left)
138
- right = context.evaluate(right)
137
+ left = Liquid::Utils.to_liquid_value(context.evaluate(left))
138
+ right = Liquid::Utils.to_liquid_value(context.evaluate(right))
139
139
 
140
140
  operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")
141
141
 
@@ -124,7 +124,7 @@ module Liquid
124
124
  # context['var'] = 'hi'
125
125
  # end
126
126
  #
127
- # context['var] #=> nil
127
+ # context['var'] #=> nil
128
128
  def stack(new_scope = {})
129
129
  push(new_scope)
130
130
  yield
@@ -10,21 +10,23 @@ module Liquid
10
10
  'empty' => ''
11
11
  }.freeze
12
12
 
13
- SINGLE_QUOTED_STRING = /\A\s*'(.*)'\s*\z/m
14
- DOUBLE_QUOTED_STRING = /\A\s*"(.*)"\s*\z/m
15
- INTEGERS_REGEX = /\A\s*(-?\d+)\s*\z/
16
- FLOATS_REGEX = /\A\s*(-?\d[\d\.]+)\s*\z/
13
+ INTEGERS_REGEX = /\A(-?\d+)\z/
14
+ FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
17
15
 
18
16
  # Use an atomic group (?>...) to avoid pathological backtracing from
19
17
  # malicious input as described in https://github.com/Shopify/liquid/issues/1357
20
- RANGES_REGEX = /\A\s*\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\s*\z/
18
+ RANGES_REGEX = /\A\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\z/
21
19
 
22
20
  def self.parse(markup)
21
+ return nil unless markup
22
+
23
+ markup = markup.strip
24
+ if (markup.start_with?('"') && markup.end_with?('"')) ||
25
+ (markup.start_with?("'") && markup.end_with?("'"))
26
+ return markup[1..-2]
27
+ end
28
+
23
29
  case markup
24
- when nil
25
- nil
26
- when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
27
- Regexp.last_match(1)
28
30
  when INTEGERS_REGEX
29
31
  Regexp.last_match(1).to_i
30
32
  when RANGES_REGEX
@@ -32,7 +34,6 @@ module Liquid
32
34
  when FLOATS_REGEX
33
35
  Regexp.last_match(1).to_f
34
36
  else
35
- markup = markup.strip
36
37
  if LITERALS.key?(markup)
37
38
  LITERALS[markup]
38
39
  else
@@ -23,6 +23,10 @@ module Liquid
23
23
  Liquid::BlockBody.new
24
24
  end
25
25
 
26
+ def new_tokenizer(markup, start_line_number: nil, for_liquid_tag: false)
27
+ Tokenizer.new(markup, line_number: start_line_number, for_liquid_tag: for_liquid_tag)
28
+ end
29
+
26
30
  def parse_expression(markup)
27
31
  Expression.parse(markup)
28
32
  end
@@ -12,6 +12,8 @@ module Liquid
12
12
  end
13
13
  end
14
14
 
15
+ attr_reader :start_obj, :end_obj
16
+
15
17
  def initialize(start_obj, end_obj)
16
18
  @start_obj = start_obj
17
19
  @end_obj = end_obj
@@ -35,5 +37,11 @@ module Liquid
35
37
  Utils.to_integer(input)
36
38
  end
37
39
  end
40
+
41
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
42
+ def children
43
+ [@node.start_obj, @node.end_obj]
44
+ end
45
+ end
38
46
  end
39
47
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'cgi'
4
+ require 'base64'
4
5
  require 'bigdecimal'
5
6
 
6
7
  module Liquid
7
8
  module StandardFilters
9
+ MAX_INT = (1 << 31) - 1
8
10
  HTML_ESCAPE = {
9
11
  '&' => '&amp;',
10
12
  '>' => '&gt;',
@@ -62,6 +64,26 @@ module Liquid
62
64
  result
63
65
  end
64
66
 
67
+ def base64_encode(input)
68
+ Base64.strict_encode64(input.to_s)
69
+ end
70
+
71
+ def base64_decode(input)
72
+ Base64.strict_decode64(input.to_s)
73
+ rescue ::ArgumentError
74
+ raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
75
+ end
76
+
77
+ def base64_url_safe_encode(input)
78
+ Base64.urlsafe_encode64(input.to_s)
79
+ end
80
+
81
+ def base64_url_safe_decode(input)
82
+ Base64.urlsafe_decode64(input.to_s)
83
+ rescue ::ArgumentError
84
+ raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
85
+ end
86
+
65
87
  def slice(input, offset, length = nil)
66
88
  offset = Utils.to_integer(offset)
67
89
  length = length ? Utils.to_integer(length) : 1
@@ -89,13 +111,21 @@ module Liquid
89
111
 
90
112
  def truncatewords(input, words = 15, truncate_string = "...")
91
113
  return if input.nil?
92
- wordlist = input.to_s.split
93
- words = Utils.to_integer(words)
94
-
95
- l = words - 1
96
- l = 0 if l < 0
114
+ input = input.to_s
115
+ words = Utils.to_integer(words)
116
+ words = 1 if words <= 0
117
+
118
+ wordlist = begin
119
+ input.split(" ", words + 1)
120
+ rescue RangeError
121
+ raise if words + 1 < MAX_INT
122
+ # e.g. integer #{words} too big to convert to `int'
123
+ raise Liquid::ArgumentError, "integer #{words} too big for truncatewords"
124
+ end
125
+ return input if wordlist.length <= words
97
126
 
98
- wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input
127
+ wordlist.pop
128
+ wordlist.join(" ").concat(truncate_string.to_s)
99
129
  end
100
130
 
101
131
  # Split input string into an array of substrings separated by given pattern.
@@ -183,17 +213,23 @@ module Liquid
183
213
 
184
214
  if ary.empty?
185
215
  []
186
- elsif ary.first.respond_to?(:[]) && target_value.nil?
187
- begin
188
- ary.select { |item| item[property] }
216
+ elsif target_value.nil?
217
+ ary.select do |item|
218
+ item[property]
189
219
  rescue TypeError
190
220
  raise_property_error(property)
221
+ rescue NoMethodError
222
+ return nil unless item.respond_to?(:[])
223
+ raise
191
224
  end
192
- elsif ary.first.respond_to?(:[])
193
- begin
194
- ary.select { |item| item[property] == target_value }
225
+ else
226
+ ary.select do |item|
227
+ item[property] == target_value
195
228
  rescue TypeError
196
229
  raise_property_error(property)
230
+ rescue NoMethodError
231
+ return nil unless item.respond_to?(:[])
232
+ raise
197
233
  end
198
234
  end
199
235
  end
@@ -207,11 +243,14 @@ module Liquid
207
243
  ary.uniq
208
244
  elsif ary.empty? # The next two cases assume a non-empty array.
209
245
  []
210
- elsif ary.first.respond_to?(:[])
211
- begin
212
- ary.uniq { |a| a[property] }
246
+ else
247
+ ary.uniq do |item|
248
+ item[property]
213
249
  rescue TypeError
214
250
  raise_property_error(property)
251
+ rescue NoMethodError
252
+ return nil unless item.respond_to?(:[])
253
+ raise
215
254
  end
216
255
  end
217
256
  end
@@ -247,11 +286,14 @@ module Liquid
247
286
  ary.compact
248
287
  elsif ary.empty? # The next two cases assume a non-empty array.
249
288
  []
250
- elsif ary.first.respond_to?(:[])
251
- begin
252
- ary.reject { |a| a[property].nil? }
289
+ else
290
+ ary.reject do |item|
291
+ item[property].nil?
253
292
  rescue TypeError
254
293
  raise_property_error(property)
294
+ rescue NoMethodError
295
+ return nil unless item.respond_to?(:[])
296
+ raise
255
297
  end
256
298
  end
257
299
  end
@@ -266,14 +308,34 @@ module Liquid
266
308
  input.to_s.sub(string.to_s, replacement.to_s)
267
309
  end
268
310
 
311
+ # Replace the last occurrences of a string with another
312
+ def replace_last(input, string, replacement)
313
+ input = input.to_s
314
+ string = string.to_s
315
+ replacement = replacement.to_s
316
+
317
+ start_index = input.rindex(string)
318
+
319
+ return input unless start_index
320
+
321
+ output = input.dup
322
+ output[start_index, string.length] = replacement
323
+ output
324
+ end
325
+
269
326
  # remove a substring
270
327
  def remove(input, string)
271
- input.to_s.gsub(string.to_s, '')
328
+ replace(input, string, '')
272
329
  end
273
330
 
274
331
  # remove the first occurrences of a substring
275
332
  def remove_first(input, string)
276
- input.to_s.sub(string.to_s, '')
333
+ replace_first(input, string, '')
334
+ end
335
+
336
+ # remove the last occurences of a substring
337
+ def remove_last(input, string)
338
+ replace_last(input, string, '')
277
339
  end
278
340
 
279
341
  # add one string to another
@@ -295,7 +357,7 @@ module Liquid
295
357
 
296
358
  # Add <br /> tags in front of all newlines in input string
297
359
  def newline_to_br(input)
298
- input.to_s.gsub(/\n/, "<br />\n")
360
+ input.to_s.gsub(/\r?\n/, "<br />\n")
299
361
  end
300
362
 
301
363
  # Reformat a date using Ruby's core Time#strftime( string ) -> string
@@ -438,7 +500,7 @@ module Liquid
438
500
  #
439
501
  def default(input, default_value = '', options = {})
440
502
  options = {} unless options.is_a?(Hash)
441
- false_check = options['allow_false'] ? input.nil? : !input
503
+ false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
442
504
  false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
443
505
  end
444
506
 
@@ -456,10 +518,16 @@ module Liquid
456
518
  end
457
519
 
458
520
  def nil_safe_compare(a, b)
459
- if !a.nil? && !b.nil?
460
- a <=> b
521
+ result = a <=> b
522
+
523
+ if result
524
+ result
525
+ elsif a.nil?
526
+ 1
527
+ elsif b.nil?
528
+ -1
461
529
  else
462
- a.nil? ? 1 : -1
530
+ raise Liquid::ArgumentError, "cannot sort values of incompatible types"
463
531
  end
464
532
  end
465
533
 
@@ -7,25 +7,26 @@ module Liquid
7
7
 
8
8
  def add_global_filter(filter)
9
9
  strainer_class_cache.clear
10
- global_filters << filter
10
+ GlobalCache.add_filter(filter)
11
11
  end
12
12
 
13
13
  def create(context, filters = [])
14
14
  strainer_from_cache(filters).new(context)
15
15
  end
16
16
 
17
- private
17
+ GlobalCache = Class.new(StrainerTemplate)
18
18
 
19
- def global_filters
20
- @global_filters ||= []
21
- end
19
+ private
22
20
 
23
21
  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
22
+ if filters.empty?
23
+ GlobalCache
24
+ else
25
+ strainer_class_cache[filters] ||= begin
26
+ klass = Class.new(GlobalCache)
27
+ filters.each { |f| klass.add_filter(f) }
28
+ klass
29
+ end
29
30
  end
30
31
  end
31
32
 
@@ -31,6 +31,11 @@ module Liquid
31
31
  filter_methods.include?(method.to_s)
32
32
  end
33
33
 
34
+ def inherited(subclass)
35
+ super
36
+ subclass.instance_variable_set(:@filter_methods, @filter_methods.dup)
37
+ end
38
+
34
39
  private
35
40
 
36
41
  def filter_methods
@@ -52,7 +52,14 @@ module Liquid
52
52
  @blocks.each do |block|
53
53
  if block.else?
54
54
  block.attachment.render_to_output_buffer(context, output) if execute_else_block
55
- elsif block.evaluate(context)
55
+ next
56
+ end
57
+
58
+ result = Liquid::Utils.to_liquid_value(
59
+ block.evaluate(context)
60
+ )
61
+
62
+ if result
56
63
  execute_else_block = false
57
64
  block.attachment.render_to_output_buffer(context, output)
58
65
  end
@@ -12,6 +12,8 @@ module Liquid
12
12
  # {% echo user | link %}
13
13
  #
14
14
  class Echo < Tag
15
+ attr_reader :variable
16
+
15
17
  def initialize(tag_name, markup, parse_context)
16
18
  super
17
19
  @variable = Variable.new(markup, parse_context)
@@ -20,6 +22,12 @@ module Liquid
20
22
  def render(context)
21
23
  @variable.render_to_output_buffer(context, +'')
22
24
  end
25
+
26
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
27
+ def children
28
+ [@node.variable]
29
+ end
30
+ end
23
31
  end
24
32
 
25
33
  Template.register_tag('echo', Echo)
@@ -50,7 +50,11 @@ module Liquid
50
50
 
51
51
  def render_to_output_buffer(context, output)
52
52
  @blocks.each do |block|
53
- if block.evaluate(context)
53
+ result = Liquid::Utils.to_liquid_value(
54
+ block.evaluate(context)
55
+ )
56
+
57
+ if result
54
58
  return block.attachment.render_to_output_buffer(context, output)
55
59
  end
56
60
  end
@@ -11,13 +11,21 @@ module Liquid
11
11
  def render_to_output_buffer(context, output)
12
12
  # First condition is interpreted backwards ( if not )
13
13
  first_block = @blocks.first
14
- unless first_block.evaluate(context)
14
+ result = Liquid::Utils.to_liquid_value(
15
+ first_block.evaluate(context)
16
+ )
17
+
18
+ unless result
15
19
  return first_block.attachment.render_to_output_buffer(context, output)
16
20
  end
17
21
 
18
22
  # After the first condition unless works just like if
19
23
  @blocks[1..-1].each do |block|
20
- if block.evaluate(context)
24
+ result = Liquid::Utils.to_liquid_value(
25
+ block.evaluate(context)
26
+ )
27
+
28
+ if result
21
29
  return block.attachment.render_to_output_buffer(context, output)
22
30
  end
23
31
  end
@@ -107,7 +107,8 @@ module Liquid
107
107
  # Returns self for easy chaining
108
108
  def parse(source, options = {})
109
109
  parse_context = configure_options(options)
110
- @root = Document.parse(tokenize(source), parse_context)
110
+ tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
111
+ @root = Document.parse(tokenizer, parse_context)
111
112
  self
112
113
  end
113
114
 
@@ -223,10 +224,6 @@ module Liquid
223
224
  parse_context
224
225
  end
225
226
 
226
- def tokenize(source)
227
- Tokenizer.new(source, @line_numbers)
228
- end
229
-
230
227
  def apply_options_to_context(context, options)
231
228
  context.add_filters(options[:filters]) if options[:filters]
232
229
  context.global_filter = options[:global_filter] if options[:global_filter]
@@ -5,7 +5,7 @@ module Liquid
5
5
  attr_reader :line_number, :for_liquid_tag
6
6
 
7
7
  def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
8
- @source = source
8
+ @source = source.to_s.to_str
9
9
  @line_number = line_number || (line_numbers ? 1 : nil)
10
10
  @for_liquid_tag = for_liquid_tag
11
11
  @tokens = tokenize
@@ -24,7 +24,7 @@ module Liquid
24
24
  private
25
25
 
26
26
  def tokenize
27
- return [] if @source.to_s.empty?
27
+ return [] if @source.empty?
28
28
 
29
29
  return @source.split("\n") if @for_liquid_tag
30
30
 
data/lib/liquid/utils.rb CHANGED
@@ -81,5 +81,13 @@ module Liquid
81
81
  rescue ::ArgumentError
82
82
  nil
83
83
  end
84
+
85
+ def self.to_liquid_value(obj)
86
+ # Enable "obj" to represent itself as a primitive value like integer, string, or boolean
87
+ return obj.to_liquid_value if obj.respond_to?(:to_liquid_value)
88
+
89
+ # Otherwise return the object itself
90
+ obj
91
+ end
84
92
  end
85
93
  end
@@ -40,6 +40,9 @@ module Liquid
40
40
  @lookups.each_index do |i|
41
41
  key = context.evaluate(@lookups[i])
42
42
 
43
+ # Cast "key" to its liquid value to enable it to act as a primitive value
44
+ key = Liquid::Utils.to_liquid_value(key)
45
+
43
46
  # If object is a hash- or array-like object we look for the
44
47
  # presence of the key and if its available we return it
45
48
  if object.respond_to?(:[]) &&
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Liquid
5
- VERSION = "5.0.0"
5
+ VERSION = "5.2.0"
6
6
  end
data/lib/liquid.rb CHANGED
@@ -36,7 +36,7 @@ module Liquid
36
36
  VariableIncompleteEnd = /\}\}?/
37
37
  QuotedString = /"[^"]*"|'[^']*'/
38
38
  QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/o
39
- TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/o
39
+ TagAttributes = /(\w[\w-]*)\s*\:\s*(#{QuotedFragment})/o
40
40
  AnyStartingTag = /#{TagStart}|#{VariableStart}/o
41
41
  PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
42
42
  TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
@@ -59,8 +59,8 @@ require 'liquid/forloop_drop'
59
59
  require 'liquid/extensions'
60
60
  require 'liquid/errors'
61
61
  require 'liquid/interrupts'
62
- require 'liquid/strainer_factory'
63
62
  require 'liquid/strainer_template'
63
+ require 'liquid/strainer_factory'
64
64
  require 'liquid/expression'
65
65
  require 'liquid/context'
66
66
  require 'liquid/parser_switching'
@@ -461,6 +461,7 @@ class ContextTest < Minitest::Test
461
461
  end
462
462
 
463
463
  def test_interrupt_avoids_object_allocations
464
+ @context.interrupt? # ruby 3.0.0 allocates on the first call
464
465
  assert_no_object_allocations do
465
466
  @context.interrupt?
466
467
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class FilterKwargTest < Minitest::Test
6
+ module KwargFilter
7
+ def html_tag(_tag, attributes)
8
+ attributes
9
+ .map { |key, value| "#{key}='#{value}'" }
10
+ .join(' ')
11
+ end
12
+ end
13
+
14
+ include Liquid
15
+
16
+ def test_can_parse_data_kwargs
17
+ with_global_filter(KwargFilter) do
18
+ assert_equal(
19
+ "data-src='src' data-widths='100, 200'",
20
+ Template.parse("{{ 'img' | html_tag: data-src: 'src', data-widths: '100, 200' }}").render(nil, nil)
21
+ )
22
+ end
23
+ end
24
+ end
@@ -145,6 +145,40 @@ class StandardFiltersTest < Minitest::Test
145
145
  assert_equal('&lt;strong&gt;Hulk&lt;/strong&gt;', @filters.escape_once('&lt;strong&gt;Hulk</strong>'))
146
146
  end
147
147
 
148
+ def test_base64_encode
149
+ assert_equal('b25lIHR3byB0aHJlZQ==', @filters.base64_encode('one two three'))
150
+ assert_equal('', @filters.base64_encode(nil))
151
+ end
152
+
153
+ def test_base64_decode
154
+ assert_equal('one two three', @filters.base64_decode('b25lIHR3byB0aHJlZQ=='))
155
+
156
+ exception = assert_raises(Liquid::ArgumentError) do
157
+ @filters.base64_decode("invalidbase64")
158
+ end
159
+
160
+ assert_equal('Liquid error: invalid base64 provided to base64_decode', exception.message)
161
+ end
162
+
163
+ def test_base64_url_safe_encode
164
+ assert_equal(
165
+ 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8',
166
+ @filters.base64_url_safe_encode('abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|')
167
+ )
168
+ assert_equal('', @filters.base64_url_safe_encode(nil))
169
+ end
170
+
171
+ def test_base64_url_safe_decode
172
+ assert_equal(
173
+ 'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|',
174
+ @filters.base64_url_safe_decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8')
175
+ )
176
+ exception = assert_raises(Liquid::ArgumentError) do
177
+ @filters.base64_url_safe_decode("invalidbase64")
178
+ end
179
+ assert_equal('Liquid error: invalid base64 provided to base64_url_safe_decode', exception.message)
180
+ end
181
+
148
182
  def test_url_encode
149
183
  assert_equal('foo%2B1%40example.com', @filters.url_encode('foo+1@example.com'))
150
184
  assert_equal('1', @filters.url_encode(1))
@@ -171,10 +205,17 @@ class StandardFiltersTest < Minitest::Test
171
205
  assert_equal('one two three', @filters.truncatewords('one two three'))
172
206
  assert_equal(
173
207
  'Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221;...',
174
- @filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15)
208
+ @filters.truncatewords('Two small (13&#8221; x 5.5&#8221; x 10&#8221; high) baskets fit inside one large basket (13&#8221; x 16&#8221; x 10.5&#8221; high) with cover.', 15)
175
209
  )
176
210
  assert_equal("测试测试测试测试", @filters.truncatewords('测试测试测试测试', 5))
177
211
  assert_equal('one two1', @filters.truncatewords("one two three", 2, 1))
212
+ assert_equal('one two three...', @filters.truncatewords("one two\tthree\nfour", 3))
213
+ assert_equal('one two...', @filters.truncatewords("one two three four", 2))
214
+ assert_equal('one...', @filters.truncatewords("one two three four", 0))
215
+ exception = assert_raises(Liquid::ArgumentError) do
216
+ @filters.truncatewords("one two three four", 1 << 31)
217
+ end
218
+ assert_equal("Liquid error: integer #{1 << 31} too big for truncatewords", exception.message)
178
219
  end
179
220
 
180
221
  def test_strip_html
@@ -218,8 +259,8 @@ class StandardFiltersTest < Minitest::Test
218
259
  { "price" => 1, "handle" => "gamma" },
219
260
  { "price" => 2, "handle" => "epsilon" },
220
261
  { "price" => 4, "handle" => "alpha" },
221
- { "handle" => "delta" },
222
262
  { "handle" => "beta" },
263
+ { "handle" => "delta" },
223
264
  ]
224
265
  assert_equal(expectation, @filters.sort(input, "price"))
225
266
  end
@@ -498,19 +539,31 @@ class StandardFiltersTest < Minitest::Test
498
539
  end
499
540
 
500
541
  def test_replace
501
- assert_equal('2 2 2 2', @filters.replace('1 1 1 1', '1', 2))
542
+ assert_equal('b b b b', @filters.replace('a a a a', 'a', 'b'))
502
543
  assert_equal('2 2 2 2', @filters.replace('1 1 1 1', 1, 2))
503
- assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', '1', 2))
544
+ assert_equal('1 1 1 1', @filters.replace('1 1 1 1', 2, 3))
545
+ assert_template_result('2 2 2 2', "{{ '1 1 1 1' | replace: '1', 2 }}")
546
+
547
+ assert_equal('b a a a', @filters.replace_first('a a a a', 'a', 'b'))
504
548
  assert_equal('2 1 1 1', @filters.replace_first('1 1 1 1', 1, 2))
549
+ assert_equal('1 1 1 1', @filters.replace_first('1 1 1 1', 2, 3))
505
550
  assert_template_result('2 1 1 1', "{{ '1 1 1 1' | replace_first: '1', 2 }}")
551
+
552
+ assert_equal('a a a b', @filters.replace_last('a a a a', 'a', 'b'))
553
+ assert_equal('1 1 1 2', @filters.replace_last('1 1 1 1', 1, 2))
554
+ assert_equal('1 1 1 1', @filters.replace_last('1 1 1 1', 2, 3))
555
+ assert_template_result('1 1 1 2', "{{ '1 1 1 1' | replace_last: '1', 2 }}")
506
556
  end
507
557
 
508
558
  def test_remove
509
559
  assert_equal(' ', @filters.remove("a a a a", 'a'))
510
- assert_equal(' ', @filters.remove("1 1 1 1", 1))
511
- assert_equal('a a a', @filters.remove_first("a a a a", 'a '))
512
- assert_equal(' 1 1 1', @filters.remove_first("1 1 1 1", 1))
513
- assert_template_result('a a a', "{{ 'a a a a' | remove_first: 'a ' }}")
560
+ assert_template_result(' ', "{{ '1 1 1 1' | remove: 1 }}")
561
+
562
+ assert_equal('b a a', @filters.remove_first("a b a a", 'a '))
563
+ assert_template_result(' 1 1 1', "{{ '1 1 1 1' | remove_first: 1 }}")
564
+
565
+ assert_equal('a a b', @filters.remove_last("a a b a", ' a'))
566
+ assert_template_result('1 1 1 ', "{{ '1 1 1 1' | remove_last: 1 }}")
514
567
  end
515
568
 
516
569
  def test_pipes_in_string_arguments
@@ -539,6 +592,7 @@ class StandardFiltersTest < Minitest::Test
539
592
 
540
593
  def test_newlines_to_br
541
594
  assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc")
595
+ assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\r\nb\nc")
542
596
  end
543
597
 
544
598
  def test_plus
@@ -686,6 +740,8 @@ class StandardFiltersTest < Minitest::Test
686
740
  assert_equal("bar", @filters.default([], "bar"))
687
741
  assert_equal("bar", @filters.default({}, "bar"))
688
742
  assert_template_result('bar', "{{ false | default: 'bar' }}")
743
+ assert_template_result('bar', "{{ drop | default: 'bar' }}", 'drop' => BooleanDrop.new(false))
744
+ assert_template_result('Yay', "{{ drop | default: 'bar' }}", 'drop' => BooleanDrop.new(true))
689
745
  end
690
746
 
691
747
  def test_default_handle_false
@@ -696,6 +752,8 @@ class StandardFiltersTest < Minitest::Test
696
752
  assert_equal("bar", @filters.default([], "bar", "allow_false" => true))
697
753
  assert_equal("bar", @filters.default({}, "bar", "allow_false" => true))
698
754
  assert_template_result('false', "{{ false | default: 'bar', allow_false: true }}")
755
+ assert_template_result('Nay', "{{ drop | default: 'bar', allow_false: true }}", 'drop' => BooleanDrop.new(false))
756
+ assert_template_result('Yay', "{{ drop | default: 'bar', allow_false: true }}", 'drop' => BooleanDrop.new(true))
699
757
  end
700
758
 
701
759
  def test_cannot_access_private_methods
@@ -724,6 +782,18 @@ class StandardFiltersTest < Minitest::Test
724
782
  assert_equal(expectation, @filters.where(input, "ok"))
725
783
  end
726
784
 
785
+ def test_where_string_keys
786
+ input = [
787
+ "alpha", "beta", "gamma", "delta"
788
+ ]
789
+
790
+ expectation = [
791
+ "beta",
792
+ ]
793
+
794
+ assert_equal(expectation, @filters.where(input, "be"))
795
+ end
796
+
727
797
  def test_where_no_key_set
728
798
  input = [
729
799
  { "handle" => "alpha", "ok" => true },
@@ -794,19 +864,14 @@ class StandardFiltersTest < Minitest::Test
794
864
  { 1 => "bar" },
795
865
  ["foo", 123, nil, true, false, Drop, ["foo"], { foo: "bar" }],
796
866
  ]
797
- test_types.each do |first|
798
- test_types.each do |other|
799
- (@filters.methods - Object.methods).each do |method|
800
- arg_count = @filters.method(method).arity
801
- arg_count *= -1 if arg_count < 0
802
- inputs = [first]
803
- inputs << ([other] * (arg_count - 1)) if arg_count > 1
804
- begin
805
- @filters.send(method, *inputs)
806
- rescue Liquid::ArgumentError, Liquid::ZeroDivisionError
807
- nil
808
- end
809
- end
867
+ StandardFilters.public_instance_methods(false).each do |method|
868
+ arg_count = @filters.method(method).arity
869
+ arg_count *= -1 if arg_count < 0
870
+
871
+ test_types.repeated_permutation(arg_count) do |args|
872
+ @filters.send(method, *args)
873
+ rescue Liquid::Error
874
+ nil
810
875
  end
811
876
  end
812
877
  end
@@ -96,12 +96,12 @@ class IncludeTagTest < Minitest::Test
96
96
 
97
97
  def test_include_tag_with_alias
98
98
  assert_template_result("Product: Draft 151cm ",
99
- "{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
99
+ "{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
100
100
  end
101
101
 
102
102
  def test_include_tag_for_alias
103
103
  assert_template_result("Product: Draft 151cm Product: Element 155cm ",
104
- "{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
104
+ "{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
105
105
  end
106
106
 
107
107
  def test_include_tag_with_default_name
@@ -151,7 +151,7 @@ class RenderTagTest < Minitest::Test
151
151
  )
152
152
 
153
153
  assert_template_result("Product: Draft 151cm ",
154
- "{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
154
+ "{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
155
155
  end
156
156
 
157
157
  def test_render_tag_with_alias
@@ -161,7 +161,7 @@ class RenderTagTest < Minitest::Test
161
161
  )
162
162
 
163
163
  assert_template_result("Product: Draft 151cm ",
164
- "{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
164
+ "{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
165
165
  end
166
166
 
167
167
  def test_render_tag_for_alias
@@ -171,7 +171,7 @@ class RenderTagTest < Minitest::Test
171
171
  )
172
172
 
173
173
  assert_template_result("Product: Draft 151cm Product: Element 155cm ",
174
- "{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
174
+ "{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
175
175
  end
176
176
 
177
177
  def test_render_tag_for
@@ -181,7 +181,7 @@ class RenderTagTest < Minitest::Test
181
181
  )
182
182
 
183
183
  assert_template_result("Product: Draft 151cm Product: Element 155cm ",
184
- "{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
184
+ "{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
185
185
  end
186
186
 
187
187
  def test_render_tag_forloop
@@ -190,7 +190,7 @@ class RenderTagTest < Minitest::Test
190
190
  )
191
191
 
192
192
  assert_template_result("Product: Draft 151cm first index:1 Product: Element 155cm last index:2 ",
193
- "{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
193
+ "{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
194
194
  end
195
195
 
196
196
  def test_render_tag_for_drop
@@ -199,7 +199,7 @@ class RenderTagTest < Minitest::Test
199
199
  )
200
200
 
201
201
  assert_template_result("123",
202
- "{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
202
+ "{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
203
203
  end
204
204
 
205
205
  def test_render_tag_with_drop
@@ -208,6 +208,6 @@ class RenderTagTest < Minitest::Test
208
208
  )
209
209
 
210
210
  assert_template_result("TestEnumerable",
211
- "{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
211
+ "{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
212
212
  end
213
213
  end
@@ -323,4 +323,18 @@ class TemplateTest < Minitest::Test
323
323
  result = t.render('x' => 1, 'y' => 5)
324
324
  assert_equal('12345', result)
325
325
  end
326
+
327
+ def test_source_string_subclass
328
+ string_subclass = Class.new(String) do
329
+ # E.g. ActiveSupport::SafeBuffer does this, so don't just rely on to_s to return a String
330
+ def to_s
331
+ self
332
+ end
333
+ end
334
+ source = string_subclass.new("{% assign x = 2 -%} x= {{- x }}")
335
+ assert_instance_of(string_subclass, source)
336
+ output = Template.parse(source).render!
337
+ assert_equal("x=2", output)
338
+ assert_instance_of(String, output)
339
+ end
326
340
  end
@@ -15,6 +15,33 @@ class VariableTest < Minitest::Test
15
15
  assert_template_result('foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new)
16
16
  end
17
17
 
18
+ def test_variable_lookup_calls_to_liquid_value
19
+ assert_template_result('1', '{{ foo }}', 'foo' => IntegerDrop.new('1'))
20
+ assert_template_result('2', '{{ list[foo] }}', 'foo' => IntegerDrop.new('1'), 'list' => [1, 2, 3])
21
+ assert_template_result('one', '{{ list[foo] }}', 'foo' => IntegerDrop.new('1'), 'list' => { 1 => 'one' })
22
+ assert_template_result('Yay', '{{ foo }}', 'foo' => BooleanDrop.new(true))
23
+ assert_template_result('YAY', '{{ foo | upcase }}', 'foo' => BooleanDrop.new(true))
24
+ end
25
+
26
+ def test_if_tag_calls_to_liquid_value
27
+ assert_template_result('one', '{% if foo == 1 %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
28
+ assert_template_result('one', '{% if 0 < foo %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
29
+ assert_template_result('one', '{% if foo > 0 %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
30
+ assert_template_result('true', '{% if foo == true %}true{% endif %}', 'foo' => BooleanDrop.new(true))
31
+ assert_template_result('true', '{% if foo %}true{% endif %}', 'foo' => BooleanDrop.new(true))
32
+
33
+ assert_template_result('', '{% if foo %}true{% endif %}', 'foo' => BooleanDrop.new(false))
34
+ assert_template_result('', '{% if foo == true %}True{% endif %}', 'foo' => BooleanDrop.new(false))
35
+ end
36
+
37
+ def test_unless_tag_calls_to_liquid_value
38
+ assert_template_result('', '{% unless foo %}true{% endunless %}', 'foo' => BooleanDrop.new(true))
39
+ end
40
+
41
+ def test_case_tag_calls_to_liquid_value
42
+ assert_template_result('One', '{% case foo %}{% when 1 %}One{% endcase %}', 'foo' => IntegerDrop.new('1'))
43
+ end
44
+
18
45
  def test_simple_with_whitespaces
19
46
  template = Template.parse(%( {{ test }} ))
20
47
  assert_equal(' worked ', template.render!('test' => 'worked'))
@@ -104,4 +131,8 @@ class VariableTest < Minitest::Test
104
131
  def test_dynamic_find_var
105
132
  assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
106
133
  end
134
+
135
+ def test_raw_value_variable
136
+ assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
137
+ end
107
138
  end
data/test/test_helper.rb CHANGED
@@ -72,21 +72,21 @@ module Minitest
72
72
  end
73
73
 
74
74
  def with_global_filter(*globals)
75
- original_global_filters = Liquid::StrainerFactory.instance_variable_get(:@global_filters)
76
- Liquid::StrainerFactory.instance_variable_set(:@global_filters, [])
77
- globals.each do |global|
78
- Liquid::StrainerFactory.add_global_filter(global)
79
- end
80
-
81
- Liquid::StrainerFactory.send(:strainer_class_cache).clear
75
+ original_global_cache = Liquid::StrainerFactory::GlobalCache
76
+ Liquid::StrainerFactory.send(:remove_const, :GlobalCache)
77
+ Liquid::StrainerFactory.const_set(:GlobalCache, Class.new(Liquid::StrainerTemplate))
82
78
 
83
79
  globals.each do |global|
84
80
  Liquid::Template.register_filter(global)
85
81
  end
86
- yield
87
- ensure
88
82
  Liquid::StrainerFactory.send(:strainer_class_cache).clear
89
- Liquid::StrainerFactory.instance_variable_set(:@global_filters, original_global_filters)
83
+ begin
84
+ yield
85
+ ensure
86
+ Liquid::StrainerFactory.send(:remove_const, :GlobalCache)
87
+ Liquid::StrainerFactory.const_set(:GlobalCache, original_global_cache)
88
+ Liquid::StrainerFactory.send(:strainer_class_cache).clear
89
+ end
90
90
  end
91
91
 
92
92
  def with_error_mode(mode)
@@ -119,6 +119,44 @@ class ThingWithToLiquid
119
119
  end
120
120
  end
121
121
 
122
+ class IntegerDrop < Liquid::Drop
123
+ def initialize(value)
124
+ super()
125
+ @value = value.to_i
126
+ end
127
+
128
+ def ==(other)
129
+ @value == other
130
+ end
131
+
132
+ def to_s
133
+ @value.to_s
134
+ end
135
+
136
+ def to_liquid_value
137
+ @value
138
+ end
139
+ end
140
+
141
+ class BooleanDrop < Liquid::Drop
142
+ def initialize(value)
143
+ super()
144
+ @value = value
145
+ end
146
+
147
+ def ==(other)
148
+ @value == other
149
+ end
150
+
151
+ def to_liquid_value
152
+ @value
153
+ end
154
+
155
+ def to_s
156
+ @value ? "Yay" : "Nay"
157
+ end
158
+ end
159
+
122
160
  class ErrorDrop < Liquid::Drop
123
161
  def standard_error
124
162
  raise Liquid::StandardError, 'standard error'
@@ -26,6 +26,13 @@ class ParseTreeVisitorTest < Minitest::Test
26
26
  )
27
27
  end
28
28
 
29
+ def test_echo
30
+ assert_equal(
31
+ ["test"],
32
+ visit(%({% echo test %}))
33
+ )
34
+ end
35
+
29
36
  def test_if_condition
30
37
  assert_equal(
31
38
  ["test"],
@@ -152,6 +159,13 @@ class ParseTreeVisitorTest < Minitest::Test
152
159
  )
153
160
  end
154
161
 
162
+ def test_for_range
163
+ assert_equal(
164
+ ["test"],
165
+ visit(%({% for x in (1..test) %}{% endfor %}))
166
+ )
167
+ end
168
+
155
169
  def test_tablerow_in
156
170
  assert_equal(
157
171
  ["test"],
@@ -52,7 +52,8 @@ class StrainerFactoryUnitTest < Minitest::Test
52
52
  /\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
53
53
  exception.message
54
54
  )
55
- assert_equal(exception.backtrace[0].split(':')[0], __FILE__)
55
+ source = AccessScopeFilters.instance_method(:public_filter).source_location
56
+ assert_equal(source.map(&:to_s), exception.backtrace[0].split(':')[0..1])
56
57
  end
57
58
 
58
59
  def test_strainer_only_invokes_public_filter_methods
@@ -57,8 +57,8 @@ class StrainerTemplateUnitTest < Minitest::Test
57
57
  end
58
58
 
59
59
  def test_add_filter_does_not_raise_when_module_overrides_previously_registered_method
60
- strainer = Context.new.strainer
61
60
  with_global_filter do
61
+ strainer = Context.new.strainer
62
62
  strainer.class.add_filter(PublicMethodOverrideFilter)
63
63
  assert(strainer.class.send(:filter_methods).include?('public_filter'))
64
64
  end
@@ -32,21 +32,26 @@ class TokenizerTest < Minitest::Test
32
32
 
33
33
  private
34
34
 
35
+ def new_tokenizer(source, parse_context: Liquid::ParseContext.new, start_line_number: nil)
36
+ parse_context.new_tokenizer(source, start_line_number: start_line_number)
37
+ end
38
+
35
39
  def tokenize(source)
36
- tokenizer = Liquid::Tokenizer.new(source)
40
+ tokenizer = new_tokenizer(source)
37
41
  tokens = []
38
- while (t = tokenizer.shift)
42
+ # shift is private in Liquid::C::Tokenizer, since it is only for unit testing
43
+ while (t = tokenizer.send(:shift))
39
44
  tokens << t
40
45
  end
41
46
  tokens
42
47
  end
43
48
 
44
49
  def tokenize_line_numbers(source)
45
- tokenizer = Liquid::Tokenizer.new(source, true)
50
+ tokenizer = new_tokenizer(source, start_line_number: 1)
46
51
  line_numbers = []
47
52
  loop do
48
53
  line_number = tokenizer.line_number
49
- if tokenizer.shift
54
+ if tokenizer.send(:shift)
50
55
  line_numbers << line_number
51
56
  else
52
57
  break
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: liquid
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 5.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Lütke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-06 00:00:00.000000000 Z
11
+ date: 2022-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -120,6 +120,7 @@ files:
120
120
  - test/integration/drop_test.rb
121
121
  - test/integration/error_handling_test.rb
122
122
  - test/integration/expression_test.rb
123
+ - test/integration/filter_kwarg_test.rb
123
124
  - test/integration/filter_test.rb
124
125
  - test/integration/hash_ordering_test.rb
125
126
  - test/integration/output_test.rb
@@ -187,65 +188,66 @@ required_rubygems_version: !ruby/object:Gem::Requirement
187
188
  - !ruby/object:Gem::Version
188
189
  version: 1.3.7
189
190
  requirements: []
190
- rubygems_version: 3.0.3
191
+ rubygems_version: 3.2.20
191
192
  signing_key:
192
193
  specification_version: 4
193
194
  summary: A secure, non-evaling end user template engine with aesthetic markup.
194
195
  test_files:
195
- - test/unit/lexer_unit_test.rb
196
- - test/unit/condition_unit_test.rb
197
- - test/unit/template_factory_unit_test.rb
198
- - test/unit/tag_unit_test.rb
199
- - test/unit/tokenizer_unit_test.rb
200
- - test/unit/regexp_unit_test.rb
201
- - test/unit/file_system_unit_test.rb
202
- - test/unit/block_unit_test.rb
203
- - test/unit/strainer_factory_unit_test.rb
204
- - test/unit/i18n_unit_test.rb
205
- - test/unit/variable_unit_test.rb
206
- - test/unit/static_registers_unit_test.rb
207
- - test/unit/strainer_template_unit_test.rb
208
- - test/unit/template_unit_test.rb
209
- - test/unit/partial_cache_unit_test.rb
210
- - test/unit/parse_tree_visitor_test.rb
211
- - test/unit/tags/if_tag_unit_test.rb
212
- - test/unit/tags/case_tag_unit_test.rb
213
- - test/unit/tags/for_tag_unit_test.rb
214
- - test/unit/parser_unit_test.rb
215
- - test/test_helper.rb
216
- - test/integration/expression_test.rb
196
+ - test/integration/tag/disableable_test.rb
217
197
  - test/integration/parsing_quirks_test.rb
218
- - test/integration/security_test.rb
219
- - test/integration/template_test.rb
220
- - test/integration/filter_test.rb
221
- - test/integration/drop_test.rb
222
- - test/integration/blank_test.rb
223
- - test/integration/capture_test.rb
224
198
  - test/integration/context_test.rb
225
- - test/integration/document_test.rb
226
- - test/integration/block_test.rb
227
- - test/integration/tag_test.rb
228
- - test/integration/tag/disableable_test.rb
229
- - test/integration/standard_filter_test.rb
230
- - test/integration/output_test.rb
231
- - test/integration/assign_test.rb
232
- - test/integration/profiler_test.rb
199
+ - test/integration/filter_kwarg_test.rb
200
+ - test/integration/capture_test.rb
233
201
  - test/integration/trim_mode_test.rb
234
- - test/integration/error_handling_test.rb
235
- - test/integration/tags/echo_test.rb
202
+ - test/integration/output_test.rb
236
203
  - test/integration/tags/raw_tag_test.rb
237
- - test/integration/tags/statements_test.rb
238
- - test/integration/tags/for_tag_test.rb
239
- - test/integration/tags/standard_tag_test.rb
240
- - test/integration/tags/render_tag_test.rb
204
+ - test/integration/tags/continue_tag_test.rb
205
+ - test/integration/tags/increment_tag_test.rb
206
+ - test/integration/tags/if_else_tag_test.rb
241
207
  - test/integration/tags/table_row_test.rb
208
+ - test/integration/tags/include_tag_test.rb
242
209
  - test/integration/tags/break_tag_test.rb
243
- - test/integration/tags/if_else_tag_test.rb
244
210
  - test/integration/tags/unless_else_tag_test.rb
245
- - test/integration/tags/continue_tag_test.rb
246
- - test/integration/tags/include_tag_test.rb
211
+ - test/integration/tags/standard_tag_test.rb
212
+ - test/integration/tags/for_tag_test.rb
213
+ - test/integration/tags/statements_test.rb
247
214
  - test/integration/tags/liquid_tag_test.rb
248
- - test/integration/tags/increment_tag_test.rb
215
+ - test/integration/tags/render_tag_test.rb
216
+ - test/integration/tags/echo_test.rb
217
+ - test/integration/drop_test.rb
218
+ - test/integration/error_handling_test.rb
219
+ - test/integration/template_test.rb
220
+ - test/integration/expression_test.rb
221
+ - test/integration/standard_filter_test.rb
222
+ - test/integration/tag_test.rb
249
223
  - test/integration/hash_ordering_test.rb
224
+ - test/integration/security_test.rb
225
+ - test/integration/blank_test.rb
226
+ - test/integration/filter_test.rb
227
+ - test/integration/document_test.rb
228
+ - test/integration/block_test.rb
229
+ - test/integration/profiler_test.rb
250
230
  - test/integration/variable_test.rb
231
+ - test/integration/assign_test.rb
232
+ - test/unit/template_unit_test.rb
233
+ - test/unit/tag_unit_test.rb
234
+ - test/unit/condition_unit_test.rb
235
+ - test/unit/strainer_template_unit_test.rb
236
+ - test/unit/lexer_unit_test.rb
237
+ - test/unit/partial_cache_unit_test.rb
238
+ - test/unit/template_factory_unit_test.rb
239
+ - test/unit/tags/if_tag_unit_test.rb
240
+ - test/unit/tags/case_tag_unit_test.rb
241
+ - test/unit/tags/for_tag_unit_test.rb
242
+ - test/unit/static_registers_unit_test.rb
243
+ - test/unit/regexp_unit_test.rb
244
+ - test/unit/i18n_unit_test.rb
245
+ - test/unit/parse_tree_visitor_test.rb
246
+ - test/unit/variable_unit_test.rb
247
+ - test/unit/tokenizer_unit_test.rb
248
+ - test/unit/file_system_unit_test.rb
249
+ - test/unit/block_unit_test.rb
250
+ - test/unit/strainer_factory_unit_test.rb
251
+ - test/unit/parser_unit_test.rb
252
+ - test/test_helper.rb
251
253
  - test/fixtures/en_locale.yml