liquid 5.0.1 → 5.3.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: 98bb40e18a2bb905a917743968edb79732f903b3beb1f51ab9551e1aaee3f91a
4
- data.tar.gz: 264b8ccd158a79f2b43a0308a055d38b018b0f4132f316007f3589e778f5d9dd
3
+ metadata.gz: c0e965825a9194672f6d2b9c3011db0125eb3dbbb6d44cacf67559140c3f0f9b
4
+ data.tar.gz: d1d98d881037c5ff8cc2b9da99d3b3e002eb1ca1d2bda593c806830c1607a98a
5
5
  SHA512:
6
- metadata.gz: 19b30524fc6b7828de10e3eecc4e28b5595436144eacedad06c36d6472b740d01e971d315f7aa911ebad80dac5c075b75649c0b506f588e50ff8c977f08ed365
7
- data.tar.gz: 53726f629d8a76ebcc4e134bc12abe981df9526fb5baa4d9312b68bc967e691af329bfcabadad86af65b8149034bd94479a2f98a93a9056a090fd28ba2901597
6
+ metadata.gz: fa9caca36072ca79bb727b7bdb9a671e082039c3c999d6cbb79bf8b1feec0d514159daedf2b54f561e66ca5ab7e11273956fb49afbae3580004d3e1d3780b9ab
7
+ data.tar.gz: a766a7b068287a7db0a70222a149f8a52a659e93c6593bb53a31e5cee279cbc65b7db14eaa1cc0e89e816bd76020bf352814cd9c2c6b4e32436f743ab0c8e629
data/History.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Liquid Change Log
2
2
 
3
+ ## 5.3.0 2022-03-22
4
+
5
+ ### Fixes
6
+ * StandardFilter: Fix missing @context on iterations (#1525) [Thierry Joyal]
7
+ * Test under Ruby 3.1 (#1533) [petergoldstein]
8
+ * Fix warning about block and default value in `static_registers.rb` (#1531) [Peter Zhu]
9
+
10
+ ### Deprecation
11
+ * Condition#evaluate to require mandatory context argument in Liquid 6.0.0 (#1527) [Thierry Joyal]
12
+
13
+ ## 5.2.0 2022-03-01
14
+
15
+ ### Features
16
+ * Add `remove_last`, and `replace_last` filters (#1422) [Anders Hagbard]
17
+ * Eagerly cache global filters (#1524) [Jean Boussier]
18
+
19
+ ### Fixes
20
+ * Fix some internal errors in filters from invalid input (#1476) [Dylan Thacker-Smith]
21
+ * Allow dash in filter kwarg name for consistency with Liquid::C (#1518) [CP Clermont]
22
+
23
+ ## 5.1.0 / 2021-09-09
24
+
25
+ ### Features
26
+ * Add `base64_encode`, `base64_decode`, `base64_url_safe_encode`, and `base64_url_safe_decode` filters (#1450) [Daniel Insley]
27
+ * Introduce `to_liquid_value` in `Liquid::Drop` (#1441) [Michael Go]
28
+
29
+ ### Fixes
30
+ * Fix support for using a String subclass for the liquid source (#1421) [Dylan Thacker-Smith]
31
+ * Add `ParseTreeVisitor` to `RangeLookup` (#1470) [CP Clermont]
32
+ * Translate `RangeError` to `Liquid::Error` for `truncatewords` with large int (#1431) [Dylan Thacker-Smith]
33
+
3
34
  ## 5.0.1 / 2021-03-24
4
35
 
5
36
  ### Fixes
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:
@@ -231,8 +231,8 @@ module Liquid
231
231
  end
232
232
 
233
233
  def create_variable(token, parse_context)
234
- token.scan(ContentOfVariable) do |content|
235
- markup = content.first
234
+ if token =~ ContentOfVariable
235
+ markup = Regexp.last_match(1)
236
236
  return Variable.new(markup, parse_context)
237
237
  end
238
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) },
@@ -61,7 +61,7 @@ module Liquid
61
61
  @child_condition = nil
62
62
  end
63
63
 
64
- def evaluate(context = Context.new)
64
+ def evaluate(context = deprecated_default_context)
65
65
  condition = self
66
66
  result = nil
67
67
  loop do
@@ -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
 
@@ -150,6 +150,12 @@ module Liquid
150
150
  end
151
151
  end
152
152
 
153
+ def deprecated_default_context
154
+ warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated" \
155
+ " and will be removed from Liquid 6.0.0.")
156
+ Context.new
157
+ end
158
+
153
159
  class ParseTreeVisitor < Liquid::ParseTreeVisitor
154
160
  def children
155
161
  [
@@ -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
@@ -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
@@ -93,7 +115,13 @@ module Liquid
93
115
  words = Utils.to_integer(words)
94
116
  words = 1 if words <= 0
95
117
 
96
- wordlist = input.split(" ", words + 1)
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
97
125
  return input if wordlist.length <= words
98
126
 
99
127
  wordlist.pop
@@ -185,17 +213,23 @@ module Liquid
185
213
 
186
214
  if ary.empty?
187
215
  []
188
- elsif ary.first.respond_to?(:[]) && target_value.nil?
189
- begin
190
- ary.select { |item| item[property] }
216
+ elsif target_value.nil?
217
+ ary.select do |item|
218
+ item[property]
191
219
  rescue TypeError
192
220
  raise_property_error(property)
221
+ rescue NoMethodError
222
+ return nil unless item.respond_to?(:[])
223
+ raise
193
224
  end
194
- elsif ary.first.respond_to?(:[])
195
- begin
196
- ary.select { |item| item[property] == target_value }
225
+ else
226
+ ary.select do |item|
227
+ item[property] == target_value
197
228
  rescue TypeError
198
229
  raise_property_error(property)
230
+ rescue NoMethodError
231
+ return nil unless item.respond_to?(:[])
232
+ raise
199
233
  end
200
234
  end
201
235
  end
@@ -209,11 +243,14 @@ module Liquid
209
243
  ary.uniq
210
244
  elsif ary.empty? # The next two cases assume a non-empty array.
211
245
  []
212
- elsif ary.first.respond_to?(:[])
213
- begin
214
- ary.uniq { |a| a[property] }
246
+ else
247
+ ary.uniq do |item|
248
+ item[property]
215
249
  rescue TypeError
216
250
  raise_property_error(property)
251
+ rescue NoMethodError
252
+ return nil unless item.respond_to?(:[])
253
+ raise
217
254
  end
218
255
  end
219
256
  end
@@ -249,11 +286,14 @@ module Liquid
249
286
  ary.compact
250
287
  elsif ary.empty? # The next two cases assume a non-empty array.
251
288
  []
252
- elsif ary.first.respond_to?(:[])
253
- begin
254
- ary.reject { |a| a[property].nil? }
289
+ else
290
+ ary.reject do |item|
291
+ item[property].nil?
255
292
  rescue TypeError
256
293
  raise_property_error(property)
294
+ rescue NoMethodError
295
+ return nil unless item.respond_to?(:[])
296
+ raise
257
297
  end
258
298
  end
259
299
  end
@@ -268,14 +308,34 @@ module Liquid
268
308
  input.to_s.sub(string.to_s, replacement.to_s)
269
309
  end
270
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
+
271
326
  # remove a substring
272
327
  def remove(input, string)
273
- input.to_s.gsub(string.to_s, '')
328
+ replace(input, string, '')
274
329
  end
275
330
 
276
331
  # remove the first occurrences of a substring
277
332
  def remove_first(input, string)
278
- 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, '')
279
339
  end
280
340
 
281
341
  # add one string to another
@@ -440,7 +500,7 @@ module Liquid
440
500
  #
441
501
  def default(input, default_value = '', options = {})
442
502
  options = {} unless options.is_a?(Hash)
443
- false_check = options['allow_false'] ? input.nil? : !input
503
+ false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
444
504
  false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
445
505
  end
446
506
 
@@ -458,10 +518,16 @@ module Liquid
458
518
  end
459
519
 
460
520
  def nil_safe_compare(a, b)
461
- if !a.nil? && !b.nil?
462
- a <=> b
521
+ result = a <=> b
522
+
523
+ if result
524
+ result
525
+ elsif a.nil?
526
+ 1
527
+ elsif b.nil?
528
+ -1
463
529
  else
464
- a.nil? ? 1 : -1
530
+ raise Liquid::ArgumentError, "cannot sort values of incompatible types"
465
531
  end
466
532
  end
467
533
 
@@ -516,8 +582,9 @@ module Liquid
516
582
 
517
583
  def each
518
584
  @input.each do |e|
585
+ e = e.respond_to?(:to_liquid) ? e.to_liquid : e
519
586
  e.context = @context if e.respond_to?(:context=)
520
- yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
587
+ yield(e)
521
588
  end
522
589
  end
523
590
  end
@@ -31,7 +31,11 @@ module Liquid
31
31
  if @registers.key?(key)
32
32
  @registers.fetch(key)
33
33
  elsif default != UNDEFINED
34
- @static.fetch(key, default, &block)
34
+ if block_given?
35
+ @static.fetch(key, &block)
36
+ else
37
+ @static.fetch(key, default)
38
+ end
35
39
  else
36
40
  @static.fetch(key, &block)
37
41
  end
@@ -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
@@ -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
@@ -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.1"
5
+ VERSION = "5.3.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'
@@ -24,7 +24,7 @@ class ContextSensitiveDrop < Liquid::Drop
24
24
  end
25
25
  end
26
26
 
27
- class Category < Liquid::Drop
27
+ class Category
28
28
  attr_accessor :name
29
29
 
30
30
  def initialize(name)
@@ -36,8 +36,9 @@ class Category < Liquid::Drop
36
36
  end
37
37
  end
38
38
 
39
- class CategoryDrop
39
+ class CategoryDrop < Liquid::Drop
40
40
  attr_accessor :category, :context
41
+
41
42
  def initialize(category)
42
43
  @category = category
43
44
  end
@@ -405,45 +406,42 @@ class ContextTest < Minitest::Test
405
406
  end
406
407
 
407
408
  def test_lambda_is_called_once
409
+ @global = 0
410
+
408
411
  @context['callcount'] = proc {
409
- @global ||= 0
410
- @global += 1
412
+ @global += 1
411
413
  @global.to_s
412
414
  }
413
415
 
414
416
  assert_equal('1', @context['callcount'])
415
417
  assert_equal('1', @context['callcount'])
416
418
  assert_equal('1', @context['callcount'])
417
-
418
- @global = nil
419
419
  end
420
420
 
421
421
  def test_nested_lambda_is_called_once
422
+ @global = 0
423
+
422
424
  @context['callcount'] = { "lambda" => proc {
423
- @global ||= 0
424
- @global += 1
425
+ @global += 1
425
426
  @global.to_s
426
427
  } }
427
428
 
428
429
  assert_equal('1', @context['callcount.lambda'])
429
430
  assert_equal('1', @context['callcount.lambda'])
430
431
  assert_equal('1', @context['callcount.lambda'])
431
-
432
- @global = nil
433
432
  end
434
433
 
435
434
  def test_lambda_in_array_is_called_once
435
+ @global = 0
436
+
436
437
  @context['callcount'] = [1, 2, proc {
437
- @global ||= 0
438
- @global += 1
438
+ @global += 1
439
439
  @global.to_s
440
440
  }, 4, 5]
441
441
 
442
442
  assert_equal('1', @context['callcount[2]'])
443
443
  assert_equal('1', @context['callcount[2]'])
444
444
  assert_equal('1', @context['callcount[2]'])
445
-
446
- @global = nil
447
445
  end
448
446
 
449
447
  def test_access_to_context_from_proc