liquid 5.12.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: ede4a234547978b59f059b4a2939bfb3c91092952c45ef8616f7874c2f9902e3
4
- data.tar.gz: 4f489720f234a848498a390a76ba89e97bfa6b5d8c708ac9b6de293748ce6dd3
3
+ metadata.gz: 23eed44a196b2949d25540274ac7bf9c721f8f620c55f2e0740ff864dbf83488
4
+ data.tar.gz: 561903b058863a29e6f8af0ac12fdda0c7941501b617f9445cb1aa5b7160cb97
5
5
  SHA512:
6
- metadata.gz: c0417cca5aece94bb341d38ba6e20deac2437fa453e926c296b6725858a35e845f25c548d1ddb364c675223667284ee088cf1fe76c151ef905b70cda08b977d3
7
- data.tar.gz: ec169349a4b973bc11d5f52a2f4f20e7467eaecae1521cdec014b9e42f2de2cc955b1789f44e0249db95f9d8fc5d7659fed6897c699753f8f4111ce16c742b36
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]
@@ -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
 
@@ -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,
@@ -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)
@@ -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
@@ -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)
@@ -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
@@ -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
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Liquid
5
- VERSION = "5.12.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.12.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: 4.0.8
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: []