liquid 5.11.0 → 5.13.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88705bb2e645a541fb76afe0a59c727429aaae33824103fb94178b5edbbda54e
4
- data.tar.gz: 5982801da2e3431575b2e32edfd0f46a3371b1c74060dc8936470df75c27c630
3
+ metadata.gz: 23eed44a196b2949d25540274ac7bf9c721f8f620c55f2e0740ff864dbf83488
4
+ data.tar.gz: 561903b058863a29e6f8af0ac12fdda0c7941501b617f9445cb1aa5b7160cb97
5
5
  SHA512:
6
- metadata.gz: 9c8ddeba605175a534a658c96dc749624ce78171f3df420011b3ac0f525301b29dac63b452e07081a8355f231e4643ee49a087daf0bcf0bb550bf07726c17228
7
- data.tar.gz: 150dce06c67bc6aa2efddf094e6b2b745b58503436fcfa8784f0f1d88f2d8447f2b8a025915745a5b0b31109b3a8e0887b552e7a7279ff3cb123182ae4188543
6
+ metadata.gz: b085504dfad17c6a0de0c152f5500eb6fe27efc588fad25b4e7ed40a3c4287642b92fb5e83f912c54534541d15e218d8721b7245d2819432d8b00f81d801a337
7
+ data.tar.gz: 5d8fec9349651ea1fc4799604c3bbceae5373f9dab9c161029496479bc23f2d3450dab4ac0e36842ced63b1e92ee2d659d7ea937b8ee6edfc2efa5b51da23758
data/History.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Liquid Change Log
2
2
 
3
+ ## 5.13.0
4
+
5
+ * Add TruffleRuby in CI [Benoit Daloze]
6
+ * Skip slow test raising many exceptions on non-CRuby [Benoit Daloze]
7
+ * Reject bare-bracket syntax in strict2 and introduce `self` keyword by [Alok Swamy]
8
+ * Add strict2_parse to assign and capture tags by [Alok Swamy]
9
+ * Add strict2_parse to increment and decrement tags by [Alok Swamy]
10
+ * Update liquid-spec adapters for `missing_features` [Ian Ker-Seymer]
11
+ * Prevent `SelfDrop` context mutation across render boundaries [Guilherme Carreiro]
12
+ * Fix `SelfDrop` equality [Guilherme Carreiro]
13
+ * Let environment `self` shadow `SelfDrop` [Ian Ker-Seymer]
14
+
3
15
  ## 5.11.0
4
16
  * Revert the Inline Snippets tag (#2001), treat its inclusion in the latest Liquid release as a bug, and allow for feedback on RFC#1916 to better support Liquid developers [Guilherme Carreiro]
5
17
  * Rename the `:rigid` error mode to `:strict2` and display a warning when users attempt to use the `:rigid` mode [Guilherme Carreiro]
@@ -113,22 +113,65 @@ module Liquid
113
113
 
114
114
  def equal_variables(left, right)
115
115
  if left.is_a?(MethodLiteral)
116
- if right.respond_to?(left.method_name)
117
- return right.send(left.method_name)
118
- else
119
- return nil
120
- end
116
+ return call_method_literal(left, right)
121
117
  end
122
118
 
123
119
  if right.is_a?(MethodLiteral)
124
- if left.respond_to?(right.method_name)
125
- return left.send(right.method_name)
120
+ return call_method_literal(right, left)
121
+ end
122
+
123
+ left == right
124
+ end
125
+
126
+ def call_method_literal(literal, value)
127
+ method_name = literal.method_name
128
+
129
+ # If the object responds to the method (e.g., ActiveSupport is loaded), use it
130
+ if value.respond_to?(method_name)
131
+ value.send(method_name)
132
+ else
133
+ # Emulate ActiveSupport's blank?/empty? to make Liquid invariant
134
+ # to whether ActiveSupport is loaded or not
135
+ case method_name
136
+ when :blank?
137
+ liquid_blank?(value)
138
+ when :empty?
139
+ liquid_empty?(value)
126
140
  else
127
- return nil
141
+ false
128
142
  end
129
143
  end
144
+ end
130
145
 
131
- left == right
146
+ # Implement blank? semantics matching ActiveSupport
147
+ # blank? returns true for nil, false, empty strings, whitespace-only strings,
148
+ # empty arrays, and empty hashes
149
+ def liquid_blank?(value)
150
+ case value
151
+ when NilClass, FalseClass
152
+ true
153
+ when TrueClass, Numeric
154
+ false
155
+ when String
156
+ # Blank if empty or whitespace only (matches ActiveSupport)
157
+ value.empty? || value.match?(/\A\s*\z/)
158
+ when Array, Hash
159
+ value.empty?
160
+ else
161
+ # Fall back to empty? if available, otherwise false
162
+ value.respond_to?(:empty?) ? value.empty? : false
163
+ end
164
+ end
165
+
166
+ # Implement empty? semantics
167
+ # Note: nil is NOT empty. empty? checks if a collection has zero elements.
168
+ def liquid_empty?(value)
169
+ case value
170
+ when String, Array, Hash
171
+ value.empty?
172
+ else
173
+ value.respond_to?(:empty?) ? value.empty? : false
174
+ end
132
175
  end
133
176
 
134
177
  def interpret_condition(left, right, op, context)
@@ -154,8 +197,8 @@ module Liquid
154
197
  end
155
198
 
156
199
  def deprecated_default_context
157
- warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated" \
158
- " and will be removed from Liquid 6.0.0.")
200
+ warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated " \
201
+ "and will be removed from Liquid 6.0.0.")
159
202
  Context.new
160
203
  end
161
204
 
@@ -187,6 +187,15 @@ module Liquid
187
187
  find_variable(key, raise_on_not_found: false) != nil
188
188
  end
189
189
 
190
+ # Checks whether a variable is defined in any scope, including nil-valued keys.
191
+ # Unlike #key?, this uses Hash#key? so that variables explicitly set to nil
192
+ # are still considered defined.
193
+ def variable_defined?(key)
194
+ @scopes.any? { |s| s.key?(key) } ||
195
+ @environments.any? { |e| e.key?(key) } ||
196
+ @static_environments.any? { |e| e.key?(key) }
197
+ end
198
+
190
199
  def evaluate(object)
191
200
  object.respond_to?(:evaluate) ? object.evaluate(self) : object
192
201
  end
@@ -197,12 +206,21 @@ module Liquid
197
206
  # path and find_index() is optimized in MRI to reduce object allocation
198
207
  index = @scopes.find_index { |s| s.key?(key) }
199
208
 
209
+ fallback_to_self_drop = key == Expression::SELF && index.nil?
210
+
200
211
  variable = if index
201
212
  lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
202
213
  else
203
- try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
214
+ try_variable_find_in_environments(
215
+ key,
216
+ raise_on_not_found: raise_on_not_found && !fallback_to_self_drop,
217
+ )
204
218
  end
205
219
 
220
+ # `self` resolves to a SelfDrop (enabling `self['var']` lookups),
221
+ # but only after the normal environment lookup doesn't find a value.
222
+ return @self_drop ||= SelfDrop.new(self) if fallback_to_self_drop && variable.nil?
223
+
206
224
  # update variable's context before invoking #to_liquid
207
225
  variable.context = self if variable.respond_to?(:context=)
208
226
 
data/lib/liquid/drop.rb CHANGED
@@ -31,7 +31,7 @@ module Liquid
31
31
 
32
32
  # Catch all for the method
33
33
  def liquid_method_missing(method)
34
- return nil unless @context&.strict_variables
34
+ return unless @context&.strict_variables
35
35
  raise Liquid::UndefinedDropMethod, "undefined method #{method}"
36
36
  end
37
37
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Liquid
4
4
  class Expression
5
+ SELF = 'self'
6
+
5
7
  LITERALS = {
6
8
  nil => nil,
7
9
  'nil' => nil,
@@ -55,7 +57,7 @@ module Liquid
55
57
  end
56
58
 
57
59
  def inner_parse(markup, ss, cache)
58
- if (markup.start_with?("(") && markup.end_with?(")")) && markup =~ RANGES_REGEX
60
+ if markup.start_with?("(") && markup.end_with?(")") && markup =~ RANGES_REGEX
59
61
  return RangeLookup.parse(
60
62
  Regexp.last_match(1),
61
63
  Regexp.last_match(2),
data/lib/liquid/i18n.rb CHANGED
@@ -28,7 +28,7 @@ module Liquid
28
28
  def interpolate(name, vars)
29
29
  name.gsub(/%\{(\w+)\}/) do
30
30
  # raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
31
- (vars[Regexp.last_match(1).to_sym]).to_s
31
+ vars[Regexp.last_match(1).to_sym].to_s
32
32
  end
33
33
  end
34
34
 
@@ -38,7 +38,7 @@ module Liquid
38
38
 
39
39
  def new_parser(input)
40
40
  @string_scanner.string = input
41
- Parser.new(@string_scanner)
41
+ Parser.new(@string_scanner, reject_bare_brackets: @error_mode == :strict2 || @error_mode == :rigid)
42
42
  end
43
43
 
44
44
  def new_tokenizer(source, start_line_number: nil, for_liquid_tag: false)
data/lib/liquid/parser.rb CHANGED
@@ -2,10 +2,11 @@
2
2
 
3
3
  module Liquid
4
4
  class Parser
5
- def initialize(input)
5
+ def initialize(input, reject_bare_brackets: false)
6
6
  ss = input.is_a?(StringScanner) ? input : StringScanner.new(input)
7
7
  @tokens = Lexer.tokenize(ss)
8
8
  @p = 0 # pointer to current location
9
+ @reject_bare_brackets = reject_bare_brackets
9
10
  end
10
11
 
11
12
  def jump(point)
@@ -53,6 +54,9 @@ module Liquid
53
54
  str = consume
54
55
  str << variable_lookups
55
56
  when :open_square
57
+ if @reject_bare_brackets
58
+ raise SyntaxError, "Bare bracket access is not allowed. Use #{Expression::SELF}['...'] instead"
59
+ end
56
60
  str = consume.dup
57
61
  str << expression
58
62
  str << consume(:close_square)
@@ -2,24 +2,39 @@
2
2
 
3
3
  module Liquid
4
4
  class ResourceLimits
5
- attr_accessor :render_length_limit, :render_score_limit, :assign_score_limit
6
- attr_reader :render_score, :assign_score
5
+ attr_accessor :render_length_limit,
6
+ :render_score_limit,
7
+ :assign_score_limit,
8
+ :cumulative_render_score_limit,
9
+ :cumulative_assign_score_limit
10
+ attr_reader :render_score,
11
+ :assign_score,
12
+ :cumulative_render_score,
13
+ :cumulative_assign_score
7
14
 
8
15
  def initialize(limits)
9
- @render_length_limit = limits[:render_length_limit]
10
- @render_score_limit = limits[:render_score_limit]
11
- @assign_score_limit = limits[:assign_score_limit]
16
+ @render_length_limit = limits[:render_length_limit]
17
+ @render_score_limit = limits[:render_score_limit]
18
+ @assign_score_limit = limits[:assign_score_limit]
19
+ @cumulative_render_score_limit = limits[:cumulative_render_score_limit]
20
+ @cumulative_assign_score_limit = limits[:cumulative_assign_score_limit]
21
+ @cumulative_render_score = 0
22
+ @cumulative_assign_score = 0
12
23
  reset
13
24
  end
14
25
 
15
26
  def increment_render_score(amount)
16
27
  @render_score += amount
28
+ @cumulative_render_score += amount
17
29
  raise_limits_reached if @render_score_limit && @render_score > @render_score_limit
30
+ raise_limits_reached if @cumulative_render_score_limit && @cumulative_render_score > @cumulative_render_score_limit
18
31
  end
19
32
 
20
33
  def increment_assign_score(amount)
21
34
  @assign_score += amount
35
+ @cumulative_assign_score += amount
22
36
  raise_limits_reached if @assign_score_limit && @assign_score > @assign_score_limit
37
+ raise_limits_reached if @cumulative_assign_score_limit && @cumulative_assign_score > @cumulative_assign_score_limit
23
38
  end
24
39
 
25
40
  # update either render_length or assign_score based on whether or not the writes are captured
@@ -47,6 +62,8 @@ module Liquid
47
62
  @reached_limit = false
48
63
  @last_capture_length = nil
49
64
  @render_score = @assign_score = 0
65
+ raise_limits_reached if @cumulative_render_score_limit && @cumulative_render_score > @cumulative_render_score_limit
66
+ raise_limits_reached if @cumulative_assign_score_limit && @cumulative_assign_score > @cumulative_assign_score_limit
50
67
  end
51
68
 
52
69
  def with_capture
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type object
6
+ # @liquid_name self
7
+ # @liquid_summary
8
+ # Provides access to variables through the current scope chain.
9
+ # @liquid_description
10
+ # The `self` object resolves variables through the normal lookup hierarchy
11
+ # (local > file > global) without exposing filters, interrupts, errors,
12
+ # or other context internals. It's used when bare bracket notation
13
+ # (`['variable']`) needs to be replaced with an explicit variable lookup.
14
+ #
15
+ # If `self` is explicitly assigned as a local variable (e.g. `{% assign self = 'value' %}`),
16
+ # then the local value takes precedence over the `self` object.
17
+ # @liquid_access global
18
+ class SelfDrop < Drop
19
+ def initialize(self_context)
20
+ super()
21
+ @self_context = self_context
22
+ end
23
+
24
+ def [](key)
25
+ @self_context.find_variable(key)
26
+ rescue UndefinedVariable
27
+ nil
28
+ end
29
+
30
+ def key?(key)
31
+ @self_context.variable_defined?(key)
32
+ end
33
+
34
+ def to_liquid
35
+ self
36
+ end
37
+
38
+ def ==(other)
39
+ other.is_a?(SelfDrop) && other.self_context.equal?(@self_context)
40
+ end
41
+
42
+ alias_method :eql?, :==
43
+
44
+ def hash
45
+ @self_context.object_id.hash
46
+ end
47
+
48
+ protected
49
+
50
+ attr_reader :self_context
51
+
52
+ undef context=
53
+ end
54
+ end
@@ -8,10 +8,19 @@ module Liquid
8
8
  MAX_I32 = (1 << 31) - 1
9
9
  private_constant :MAX_I32
10
10
 
11
- MIN_I64 = -(1 << 63)
12
- MAX_I64 = (1 << 63) - 1
13
- I64_RANGE = MIN_I64..MAX_I64
14
- private_constant :MIN_I64, :MAX_I64, :I64_RANGE
11
+ supports_64bit_indices = begin
12
+ [][1 << 33, 1 << 33]
13
+ true
14
+ rescue RangeError
15
+ false
16
+ end
17
+
18
+ INDEX_RANGE = if supports_64bit_indices
19
+ (-(1 << 63))..((1 << 63) - 1)
20
+ else
21
+ (-(1 << 31))..((1 << 31) - 1)
22
+ end
23
+ private_constant :INDEX_RANGE
15
24
 
16
25
  HTML_ESCAPE = {
17
26
  '&' => '&amp;',
@@ -214,11 +223,11 @@ module Liquid
214
223
  Utils.to_s(input).slice(offset, length) || ''
215
224
  end
216
225
  rescue RangeError
217
- if I64_RANGE.cover?(length) && I64_RANGE.cover?(offset)
226
+ if INDEX_RANGE.cover?(length) && INDEX_RANGE.cover?(offset)
218
227
  raise # unexpected error
219
228
  end
220
- offset = offset.clamp(I64_RANGE)
221
- length = length.clamp(I64_RANGE)
229
+ offset = offset.clamp(INDEX_RANGE)
230
+ length = length.clamp(INDEX_RANGE)
222
231
  retry
223
232
  end
224
233
  end
@@ -293,6 +302,19 @@ module Liquid
293
302
  input.split(pattern)
294
303
  end
295
304
 
305
+ # @liquid_public_docs
306
+ # @liquid_type filter
307
+ # @liquid_category string
308
+ # @liquid_summary
309
+ # Removes leading and trailing whitespace and collapses consecutive whitespace to a single space.
310
+ # @liquid_syntax string | squish
311
+ # @liquid_return [string]
312
+ def squish(input)
313
+ return if input.nil?
314
+
315
+ Utils.to_s(input).strip.gsub(/\s+/, ' ')
316
+ end
317
+
296
318
  # @liquid_public_docs
297
319
  # @liquid_type filter
298
320
  # @liquid_category string
@@ -768,6 +790,8 @@ module Liquid
768
790
  # @liquid_syntax array | first
769
791
  # @liquid_return [untyped]
770
792
  def first(array)
793
+ # ActiveSupport returns "" for empty strings, not nil
794
+ return array[0] || "" if array.is_a?(String)
771
795
  array.first if array.respond_to?(:first)
772
796
  end
773
797
 
@@ -779,6 +803,8 @@ module Liquid
779
803
  # @liquid_syntax array | last
780
804
  # @liquid_return [untyped]
781
805
  def last(array)
806
+ # ActiveSupport returns "" for empty strings, not nil
807
+ return array[-1] || "" if array.is_a?(String)
782
808
  array.last if array.respond_to?(:last)
783
809
  end
784
810
 
@@ -18,6 +18,8 @@ module Liquid
18
18
  # @liquid_syntax_keyword variable_name The name of the variable being created.
19
19
  # @liquid_syntax_keyword value The value you want to assign to the variable.
20
20
  class Assign < Tag
21
+ include ParserSwitching
22
+
21
23
  Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
22
24
 
23
25
  # @api private
@@ -29,6 +31,10 @@ module Liquid
29
31
 
30
32
  def initialize(tag_name, markup, parse_context)
31
33
  super
34
+ parse_with_selected_parser(markup)
35
+ end
36
+
37
+ def lax_parse(markup)
32
38
  if markup =~ Syntax
33
39
  @to = Regexp.last_match(1)
34
40
  @from = Variable.new(Regexp.last_match(2), parse_context)
@@ -37,6 +43,25 @@ module Liquid
37
43
  end
38
44
  end
39
45
 
46
+ def strict_parse(markup)
47
+ lax_parse(markup)
48
+ end
49
+
50
+ def strict2_parse(markup)
51
+ unless markup =~ Syntax
52
+ self.class.raise_syntax_error(parse_context)
53
+ end
54
+
55
+ lhs = Regexp.last_match(1).strip
56
+ rhs = Regexp.last_match(2)
57
+
58
+ p = @parse_context.new_parser(lhs)
59
+ @to = p.consume(:id)
60
+ p.consume(:end_of_string)
61
+
62
+ @from = Variable.new(rhs, parse_context)
63
+ end
64
+
40
65
  def render_to_output_buffer(context, output)
41
66
  val = @from.render(context)
42
67
  context.scopes.last[@to] = val
@@ -20,10 +20,18 @@ module Liquid
20
20
  # @liquid_syntax_keyword variable The name of the variable being created.
21
21
  # @liquid_syntax_keyword value The value you want to assign to the variable.
22
22
  class Capture < Block
23
+ include ParserSwitching
24
+
23
25
  Syntax = /(#{VariableSignature}+)/o
24
26
 
27
+ attr_reader :to
28
+
25
29
  def initialize(tag_name, markup, options)
26
30
  super
31
+ parse_with_selected_parser(markup)
32
+ end
33
+
34
+ def lax_parse(markup)
27
35
  if markup =~ Syntax
28
36
  @to = Regexp.last_match(1)
29
37
  else
@@ -31,6 +39,16 @@ module Liquid
31
39
  end
32
40
  end
33
41
 
42
+ def strict_parse(markup)
43
+ lax_parse(markup)
44
+ end
45
+
46
+ def strict2_parse(markup)
47
+ p = @parse_context.new_parser(markup.strip)
48
+ @to = p.consume(:id)
49
+ p.consume(:end_of_string)
50
+ end
51
+
34
52
  def render_to_output_buffer(context, output)
35
53
  context.resource_limits.with_capture do
36
54
  capture_output = render(context)
@@ -118,7 +118,7 @@ module Liquid
118
118
  parser = @parse_context.new_parser(markup)
119
119
 
120
120
  loop do
121
- expr = safe_parse_expression(parser)
121
+ expr = Condition.parse_expression(parse_context, parser.expression, safe: true)
122
122
  block = Condition.new(@left, '==', expr)
123
123
  block.attach(body)
124
124
  @blocks << block
@@ -23,13 +23,29 @@ module Liquid
23
23
  # {% decrement variable_name %}
24
24
  # @liquid_syntax_keyword variable_name The name of the variable being decremented.
25
25
  class Decrement < Tag
26
+ include ParserSwitching
27
+
26
28
  attr_reader :variable_name
27
29
 
28
30
  def initialize(tag_name, markup, options)
29
31
  super
32
+ parse_with_selected_parser(markup)
33
+ end
34
+
35
+ def lax_parse(markup)
30
36
  @variable_name = markup.strip
31
37
  end
32
38
 
39
+ def strict_parse(markup)
40
+ lax_parse(markup)
41
+ end
42
+
43
+ def strict2_parse(markup)
44
+ p = @parse_context.new_parser(markup.strip)
45
+ @variable_name = p.consume(:id)
46
+ p.consume(:end_of_string)
47
+ end
48
+
33
49
  def render_to_output_buffer(context, output)
34
50
  counter_environment = context.environments.first
35
51
  value = counter_environment[@variable_name] || 0
@@ -20,7 +20,8 @@ module Liquid
20
20
  class Include < Tag
21
21
  prepend Tag::Disableable
22
22
 
23
- SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
23
+ FOR = 'for'
24
+ SYNTAX = /(#{QuotedFragment}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
24
25
  Syntax = SYNTAX
25
26
 
26
27
  attr_reader :template_name_expr, :variable_name_expr, :attributes
@@ -84,12 +85,18 @@ module Liquid
84
85
  alias_method :parse_context, :options
85
86
  private :parse_context
86
87
 
88
+ def for_loop?
89
+ @is_for_loop
90
+ end
91
+
87
92
  def strict2_parse(markup)
88
93
  p = @parse_context.new_parser(markup)
89
94
 
90
95
  @template_name_expr = safe_parse_expression(p)
91
- @variable_name_expr = safe_parse_expression(p) if p.id?("for") || p.id?("with")
96
+ with_or_for = p.id?("for") || p.id?("with")
97
+ @variable_name_expr = safe_parse_expression(p) if with_or_for
92
98
  @alias_name = p.consume(:id) if p.id?("as")
99
+ @is_for_loop = (with_or_for == FOR)
93
100
 
94
101
  p.consume?(:comma)
95
102
 
@@ -111,11 +118,13 @@ module Liquid
111
118
  def lax_parse(markup)
112
119
  if markup =~ SYNTAX
113
120
  template_name = Regexp.last_match(1)
114
- variable_name = Regexp.last_match(3)
121
+ with_or_for = Regexp.last_match(3)
122
+ variable_name = Regexp.last_match(4)
115
123
 
116
- @alias_name = Regexp.last_match(5)
124
+ @alias_name = Regexp.last_match(6)
117
125
  @variable_name_expr = variable_name ? parse_expression(variable_name) : nil
118
126
  @template_name_expr = parse_expression(template_name)
127
+ @is_for_loop = (with_or_for == FOR)
119
128
  @attributes = {}
120
129
 
121
130
  markup.scan(TagAttributes) do |key, value|
@@ -23,13 +23,29 @@ module Liquid
23
23
  # {% increment variable_name %}
24
24
  # @liquid_syntax_keyword variable_name The name of the variable being incremented.
25
25
  class Increment < Tag
26
+ include ParserSwitching
27
+
26
28
  attr_reader :variable_name
27
29
 
28
30
  def initialize(tag_name, markup, options)
29
31
  super
32
+ parse_with_selected_parser(markup)
33
+ end
34
+
35
+ def lax_parse(markup)
30
36
  @variable_name = markup.strip
31
37
  end
32
38
 
39
+ def strict_parse(markup)
40
+ lax_parse(markup)
41
+ end
42
+
43
+ def strict2_parse(markup)
44
+ p = @parse_context.new_parser(markup.strip)
45
+ @variable_name = p.consume(:id)
46
+ p.consume(:end_of_string)
47
+ end
48
+
33
49
  def render_to_output_buffer(context, output)
34
50
  counter_environment = context.environments.first
35
51
  value = counter_environment[@variable_name] || 0
@@ -151,8 +151,10 @@ module Liquid
151
151
 
152
152
  c
153
153
  when Liquid::Drop
154
- drop = args.shift
155
- drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
154
+ drop = args.shift
155
+ c = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
156
+ drop.context = c if drop.respond_to?(:context=)
157
+ c
156
158
  when Hash
157
159
  Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
158
160
  when nil
@@ -117,7 +117,7 @@ module Liquid
117
117
  byte_a = byte_b = @ss.scan_byte
118
118
 
119
119
  while byte_b
120
- byte_a = @ss.scan_byte while byte_a && (byte_a != CLOSE_CURLEY && byte_a != OPEN_CURLEY)
120
+ byte_a = @ss.scan_byte while byte_a && byte_a != CLOSE_CURLEY && byte_a != OPEN_CURLEY
121
121
 
122
122
  break unless byte_a
123
123
 
data/lib/liquid/utils.rb CHANGED
@@ -69,7 +69,7 @@ module Liquid
69
69
  return obj if obj.respond_to?(:strftime)
70
70
 
71
71
  if obj.is_a?(String)
72
- return nil if obj.empty?
72
+ return if obj.empty?
73
73
  obj = obj.downcase
74
74
  end
75
75
 
@@ -95,6 +95,8 @@ module Liquid
95
95
 
96
96
  def self.to_s(obj, seen = {})
97
97
  case obj
98
+ when BigDecimal
99
+ obj.to_s("F")
98
100
  when Hash
99
101
  # If the custom hash implementation overrides `#to_s`, use their
100
102
  # custom implementation. Otherwise we use Liquid's default
@@ -37,6 +37,10 @@ module Liquid
37
37
  @markup
38
38
  end
39
39
 
40
+ def ==(other)
41
+ self.class == other.class && name == other.name && filters == other.filters
42
+ end
43
+
40
44
  def markup_context(markup)
41
45
  "in \"{{#{markup}}}\""
42
46
  end
@@ -70,6 +70,11 @@ module Liquid
70
70
  elsif lookup_command?(i) && object.respond_to?(key)
71
71
  object = object.send(key).to_liquid
72
72
 
73
+ # Handle string first/last like ActiveSupport does (returns first/last character)
74
+ # ActiveSupport returns "" for empty strings, not nil
75
+ elsif lookup_command?(i) && object.is_a?(String) && (key == "first" || key == "last")
76
+ object = key == "first" ? (object[0] || "") : (object[-1] || "")
77
+
73
78
  # No key was present with the desired value and it wasn't one of the directly supported
74
79
  # keywords either. The only thing we got left is to return nil or
75
80
  # raise an exception if `strict_variables` option is set to true
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Liquid
5
- VERSION = "5.11.0"
5
+ VERSION = "5.13.0"
6
6
  end
data/lib/liquid.rb CHANGED
@@ -65,6 +65,7 @@ require 'liquid/lexer'
65
65
  require 'liquid/parser'
66
66
  require 'liquid/i18n'
67
67
  require 'liquid/drop'
68
+ require 'liquid/self_drop'
68
69
  require 'liquid/tablerowloop_drop'
69
70
  require 'liquid/forloop_drop'
70
71
  require 'liquid/extensions'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: liquid
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.11.0
4
+ version: 5.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Lütke
@@ -105,6 +105,7 @@ files:
105
105
  - lib/liquid/range_lookup.rb
106
106
  - lib/liquid/registers.rb
107
107
  - lib/liquid/resource_limits.rb
108
+ - lib/liquid/self_drop.rb
108
109
  - lib/liquid/standardfilters.rb
109
110
  - lib/liquid/strainer_template.rb
110
111
  - lib/liquid/tablerowloop_drop.rb
@@ -159,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
160
  - !ruby/object:Gem::Version
160
161
  version: 1.3.7
161
162
  requirements: []
162
- rubygems_version: 3.7.2
163
+ rubygems_version: 4.0.14
163
164
  specification_version: 4
164
165
  summary: A secure, non-evaling end user template engine with aesthetic markup.
165
166
  test_files: []