liquid 5.3.0 → 5.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +20 -1
  3. data/README.md +5 -5
  4. data/lib/liquid/block.rb +8 -4
  5. data/lib/liquid/block_body.rb +21 -6
  6. data/lib/liquid/condition.rb +9 -4
  7. data/lib/liquid/context.rb +13 -5
  8. data/lib/liquid/drop.rb +4 -0
  9. data/lib/liquid/errors.rb +16 -15
  10. data/lib/liquid/expression.rb +4 -1
  11. data/lib/liquid/forloop_drop.rb +45 -5
  12. data/lib/liquid/lexer.rb +2 -3
  13. data/lib/liquid/locales/en.yml +7 -5
  14. data/lib/liquid/partial_cache.rb +15 -6
  15. data/lib/liquid/range_lookup.rb +11 -1
  16. data/lib/liquid/{static_registers.rb → registers.rb} +13 -10
  17. data/lib/liquid/standardfilters.rb +480 -68
  18. data/lib/liquid/strainer_factory.rb +4 -0
  19. data/lib/liquid/strainer_template.rb +4 -0
  20. data/lib/liquid/tablerowloop_drop.rb +58 -1
  21. data/lib/liquid/tag/disabler.rb +0 -8
  22. data/lib/liquid/tag.rb +10 -3
  23. data/lib/liquid/tags/assign.rb +12 -8
  24. data/lib/liquid/tags/break.rb +8 -0
  25. data/lib/liquid/tags/capture.rb +13 -10
  26. data/lib/liquid/tags/case.rb +22 -1
  27. data/lib/liquid/tags/comment.rb +72 -0
  28. data/lib/liquid/tags/continue.rb +8 -9
  29. data/lib/liquid/tags/cycle.rb +12 -11
  30. data/lib/liquid/tags/decrement.rb +22 -20
  31. data/lib/liquid/tags/echo.rb +16 -9
  32. data/lib/liquid/tags/for.rb +25 -46
  33. data/lib/liquid/tags/if.rb +12 -10
  34. data/lib/liquid/tags/include.rb +21 -17
  35. data/lib/liquid/tags/increment.rb +22 -17
  36. data/lib/liquid/tags/inline_comment.rb +30 -0
  37. data/lib/liquid/tags/raw.rb +13 -2
  38. data/lib/liquid/tags/render.rb +37 -8
  39. data/lib/liquid/tags/table_row.rb +33 -3
  40. data/lib/liquid/tags/unless.rb +17 -6
  41. data/lib/liquid/template.rb +11 -4
  42. data/lib/liquid/tokenizer.rb +9 -3
  43. data/lib/liquid/variable.rb +4 -4
  44. data/lib/liquid/variable_lookup.rb +10 -7
  45. data/lib/liquid/version.rb +1 -1
  46. data/lib/liquid.rb +3 -3
  47. metadata +7 -123
  48. data/lib/liquid/register.rb +0 -6
  49. data/test/fixtures/en_locale.yml +0 -9
  50. data/test/integration/assign_test.rb +0 -117
  51. data/test/integration/blank_test.rb +0 -109
  52. data/test/integration/block_test.rb +0 -58
  53. data/test/integration/capture_test.rb +0 -58
  54. data/test/integration/context_test.rb +0 -634
  55. data/test/integration/document_test.rb +0 -21
  56. data/test/integration/drop_test.rb +0 -257
  57. data/test/integration/error_handling_test.rb +0 -272
  58. data/test/integration/expression_test.rb +0 -46
  59. data/test/integration/filter_kwarg_test.rb +0 -24
  60. data/test/integration/filter_test.rb +0 -189
  61. data/test/integration/hash_ordering_test.rb +0 -25
  62. data/test/integration/output_test.rb +0 -125
  63. data/test/integration/parsing_quirks_test.rb +0 -134
  64. data/test/integration/profiler_test.rb +0 -240
  65. data/test/integration/security_test.rb +0 -89
  66. data/test/integration/standard_filter_test.rb +0 -925
  67. data/test/integration/tag/disableable_test.rb +0 -59
  68. data/test/integration/tag_test.rb +0 -45
  69. data/test/integration/tags/break_tag_test.rb +0 -17
  70. data/test/integration/tags/continue_tag_test.rb +0 -17
  71. data/test/integration/tags/echo_test.rb +0 -13
  72. data/test/integration/tags/for_tag_test.rb +0 -466
  73. data/test/integration/tags/if_else_tag_test.rb +0 -190
  74. data/test/integration/tags/include_tag_test.rb +0 -269
  75. data/test/integration/tags/increment_tag_test.rb +0 -25
  76. data/test/integration/tags/liquid_tag_test.rb +0 -116
  77. data/test/integration/tags/raw_tag_test.rb +0 -34
  78. data/test/integration/tags/render_tag_test.rb +0 -213
  79. data/test/integration/tags/standard_tag_test.rb +0 -303
  80. data/test/integration/tags/statements_test.rb +0 -113
  81. data/test/integration/tags/table_row_test.rb +0 -66
  82. data/test/integration/tags/unless_else_tag_test.rb +0 -28
  83. data/test/integration/template_test.rb +0 -340
  84. data/test/integration/trim_mode_test.rb +0 -563
  85. data/test/integration/variable_test.rb +0 -138
  86. data/test/test_helper.rb +0 -207
  87. data/test/unit/block_unit_test.rb +0 -53
  88. data/test/unit/condition_unit_test.rb +0 -181
  89. data/test/unit/file_system_unit_test.rb +0 -37
  90. data/test/unit/i18n_unit_test.rb +0 -39
  91. data/test/unit/lexer_unit_test.rb +0 -53
  92. data/test/unit/parse_tree_visitor_test.rb +0 -261
  93. data/test/unit/parser_unit_test.rb +0 -84
  94. data/test/unit/partial_cache_unit_test.rb +0 -128
  95. data/test/unit/regexp_unit_test.rb +0 -46
  96. data/test/unit/static_registers_unit_test.rb +0 -156
  97. data/test/unit/strainer_factory_unit_test.rb +0 -101
  98. data/test/unit/strainer_template_unit_test.rb +0 -82
  99. data/test/unit/tag_unit_test.rb +0 -23
  100. data/test/unit/tags/case_tag_unit_test.rb +0 -12
  101. data/test/unit/tags/for_tag_unit_test.rb +0 -15
  102. data/test/unit/tags/if_tag_unit_test.rb +0 -10
  103. data/test/unit/template_factory_unit_test.rb +0 -12
  104. data/test/unit/template_unit_test.rb +0 -87
  105. data/test/unit/tokenizer_unit_test.rb +0 -62
  106. data/test/unit/variable_unit_test.rb +0 -164
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0e965825a9194672f6d2b9c3011db0125eb3dbbb6d44cacf67559140c3f0f9b
4
- data.tar.gz: d1d98d881037c5ff8cc2b9da99d3b3e002eb1ca1d2bda593c806830c1607a98a
3
+ metadata.gz: 4d5699469b4a46eb15532bf2d5000925eeeff4f2eb5fef683d53bf09f0ec8825
4
+ data.tar.gz: df2bebc1a371dc250c768d46996c861fc22db9a1e2afc77f2f12247baa9a2e71
5
5
  SHA512:
6
- metadata.gz: fa9caca36072ca79bb727b7bdb9a671e082039c3c999d6cbb79bf8b1feec0d514159daedf2b54f561e66ca5ab7e11273956fb49afbae3580004d3e1d3780b9ab
7
- data.tar.gz: a766a7b068287a7db0a70222a149f8a52a659e93c6593bb53a31e5cee279cbc65b7db14eaa1cc0e89e816bd76020bf352814cd9c2c6b4e32436f743ab0c8e629
6
+ metadata.gz: e953d07e402f2325e56953a1cce6f4d334a183548fd70db1a472695b491f3f2efb6318625c1582e708362f5d2cff81f932d9c0d30c3da23d63fdfdab12b50725
7
+ data.tar.gz: d6abf81f05029169f17da65b9c93af90c96c93fccd6179cff3a02d050f5736e177c2686348b1d62af4caa432e6332ffc19c37c86004d139e422876049856fc97
data/History.md CHANGED
@@ -1,10 +1,29 @@
1
1
  # Liquid Change Log
2
2
 
3
+ ## 5.5.0 2024-03-21
4
+
5
+ Please reference the GitHub release for more information.
6
+
7
+ ## 5.4.0 2022-07-29
8
+
9
+ ### Breaking Changes
10
+ * Drop support for end-of-life Ruby versions (2.5 and 2.6) (#1578) [Andy Waite]
11
+
12
+ ### Features
13
+ * Allow `#` to be used as an inline comment tag (#1498) [CP Clermont]
14
+
15
+ ### Fixes
16
+ * `PartialCache` now shares snippet cache with subcontexts by default (#1553) [Chris AtLee]
17
+ * Hash registers no longer leak into subcontexts as static registers (#1564) [Chris AtLee]
18
+ * Fix `ParseTreeVisitor` for `with` variable expressions in `Render` tag (#1596) [CP Clermont]
19
+
20
+ ### Changed
21
+ * Liquid::Context#registers now always returns a Liquid::Registers object, though supports the most used Hash functions for compatibility (#1553)
22
+
3
23
  ## 5.3.0 2022-03-22
4
24
 
5
25
  ### Fixes
6
26
  * StandardFilter: Fix missing @context on iterations (#1525) [Thierry Joyal]
7
- * Test under Ruby 3.1 (#1533) [petergoldstein]
8
27
  * Fix warning about block and default value in `static_registers.rb` (#1531) [Peter Zhu]
9
28
 
10
29
  ### Deprecation
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
- [![Build Status](https://api.travis-ci.org/Shopify/liquid.svg?branch=master)](http://travis-ci.org/Shopify/liquid)
1
+ [![Build status](https://github.com/Shopify/liquid/actions/workflows/liquid.yml/badge.svg)](https://github.com/Shopify/liquid/actions/workflows/liquid.yml)
2
2
  [![Inline docs](http://inch-ci.org/github/Shopify/liquid.svg?branch=master)](http://inch-ci.org/github/Shopify/liquid)
3
3
 
4
4
  # Liquid template engine
5
5
 
6
6
  * [Contributing guidelines](CONTRIBUTING.md)
7
7
  * [Version history](History.md)
8
- * [Liquid documentation from Shopify](https://shopify.dev/api/liquid)
8
+ * [Liquid documentation from Shopify](https://shopify.dev/docs/api/liquid)
9
9
  * [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
10
10
  * [Website](http://liquidmarkup.org/)
11
11
 
@@ -63,13 +63,13 @@ when templates are invalid. You can enable this new parser like this:
63
63
 
64
64
  ```ruby
65
65
  Liquid::Template.error_mode = :strict # Raises a SyntaxError when invalid syntax is used
66
- Liquid::Template.error_mode = :warn # Adds errors to template.errors but continues as normal
66
+ Liquid::Template.error_mode = :warn # Adds strict errors to template.errors but continues as normal
67
67
  Liquid::Template.error_mode = :lax # The default mode, accepts almost anything.
68
68
  ```
69
69
 
70
70
  If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`:
71
71
  ```ruby
72
- Liquid::Template.parse(source, :error_mode => :strict)
72
+ Liquid::Template.parse(source, error_mode: :strict)
73
73
  ```
74
74
  This is useful for doing things like enabling strict mode only in the theme editor.
75
75
 
@@ -111,4 +111,4 @@ template.render!({ 'x' => 1}, { strict_variables: true })
111
111
 
112
112
  To help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you.
113
113
 
114
- Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.
114
+ Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.
data/lib/liquid/block.rb CHANGED
@@ -36,13 +36,17 @@ module Liquid
36
36
  # @api private
37
37
  def self.raise_unknown_tag(tag, block_name, block_delimiter, parse_context)
38
38
  if tag == 'else'
39
- raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else",
40
- block_name: block_name)
39
+ raise SyntaxError, parse_context.locale.t(
40
+ "errors.syntax.unexpected_else",
41
+ block_name: block_name,
42
+ )
41
43
  elsif tag.start_with?('end')
42
- raise SyntaxError, parse_context.locale.t("errors.syntax.invalid_delimiter",
44
+ raise SyntaxError, parse_context.locale.t(
45
+ "errors.syntax.invalid_delimiter",
43
46
  tag: tag,
44
47
  block_name: block_name,
45
- block_delimiter: block_delimiter)
48
+ block_delimiter: block_delimiter,
49
+ )
46
50
  else
47
51
  raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
48
52
  end
@@ -4,8 +4,9 @@ require 'English'
4
4
 
5
5
  module Liquid
6
6
  class BlockBody
7
- LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
8
- FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
7
+ LiquidTagToken = /\A\s*(#{TagName})\s*(.*?)\z/o
8
+ FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(#{TagName})(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
9
+ FullTokenPossiblyInvalid = /\A(.*)#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*)?#{WhitespaceControl}?#{TagEnd}\z/om
9
10
  ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
10
11
  WhitespaceOrNothing = /\A\s*\z/
11
12
  TAGSTART = "{%"
@@ -37,7 +38,7 @@ module Liquid
37
38
 
38
39
  private def parse_for_liquid_tag(tokenizer, parse_context)
39
40
  while (token = tokenizer.shift)
40
- unless token.empty? || token =~ WhitespaceOrNothing
41
+ unless token.empty? || token.match?(WhitespaceOrNothing)
41
42
  unless token =~ LiquidTagToken
42
43
  # line isn't empty but didn't match tag syntax, yield and let the
43
44
  # caller raise a syntax error
@@ -45,6 +46,12 @@ module Liquid
45
46
  end
46
47
  tag_name = Regexp.last_match(1)
47
48
  markup = Regexp.last_match(2)
49
+
50
+ if tag_name == 'liquid'
51
+ parse_context.line_number -= 1
52
+ next parse_liquid_tag(markup, parse_context)
53
+ end
54
+
48
55
  unless (tag = registered_tags[tag_name])
49
56
  # end parsing if we reach an unknown tag and let the caller decide
50
57
  # determine how to proceed
@@ -109,14 +116,22 @@ module Liquid
109
116
  end
110
117
  end
111
118
 
112
- private def parse_for_document(tokenizer, parse_context)
119
+ private def handle_invalid_tag_token(token, parse_context)
120
+ if token.end_with?('%}')
121
+ yield token, token
122
+ else
123
+ BlockBody.raise_missing_tag_terminator(token, parse_context)
124
+ end
125
+ end
126
+
127
+ private def parse_for_document(tokenizer, parse_context, &block)
113
128
  while (token = tokenizer.shift)
114
129
  next if token.empty?
115
130
  case
116
131
  when token.start_with?(TAGSTART)
117
132
  whitespace_handler(token, parse_context)
118
133
  unless token =~ FullToken
119
- BlockBody.raise_missing_tag_terminator(token, parse_context)
134
+ return handle_invalid_tag_token(token, parse_context, &block)
120
135
  end
121
136
  tag_name = Regexp.last_match(2)
122
137
  markup = Regexp.last_match(4)
@@ -150,7 +165,7 @@ module Liquid
150
165
  end
151
166
  parse_context.trim_whitespace = false
152
167
  @nodelist << token
153
- @blank &&= !!(token =~ WhitespaceOrNothing)
168
+ @blank &&= token.match?(WhitespaceOrNothing)
154
169
  end
155
170
  parse_context.line_number = tokenizer.line_number
156
171
  end
@@ -24,6 +24,9 @@ module Liquid
24
24
  else
25
25
  false
26
26
  end
27
+ rescue Encoding::CompatibilityError
28
+ # "✅".b.include?("✅") raises Encoding::CompatibilityError despite being materially equal
29
+ left.b.include?(right.b)
27
30
  end,
28
31
  }
29
32
 
@@ -69,9 +72,9 @@ module Liquid
69
72
 
70
73
  case condition.child_relation
71
74
  when :or
72
- break if result
75
+ break if Liquid::Utils.to_liquid_value(result)
73
76
  when :and
74
- break unless result
77
+ break unless Liquid::Utils.to_liquid_value(result)
75
78
  else
76
79
  break
77
80
  end
@@ -159,8 +162,10 @@ module Liquid
159
162
  class ParseTreeVisitor < Liquid::ParseTreeVisitor
160
163
  def children
161
164
  [
162
- @node.left, @node.right,
163
- @node.child_condition, @node.attachment
165
+ @node.left,
166
+ @node.right,
167
+ @node.child_condition,
168
+ @node.attachment
164
169
  ].compact
165
170
  end
166
171
  end
@@ -26,9 +26,9 @@ module Liquid
26
26
  @environments = [environments]
27
27
  @environments.flatten!
28
28
 
29
- @static_environments = [static_environments].flat_map(&:freeze).freeze
29
+ @static_environments = [static_environments].flatten(1).freeze
30
30
  @scopes = [(outer_scope || {})]
31
- @registers = registers
31
+ @registers = registers.is_a?(Registers) ? registers : Registers.new(registers)
32
32
  @errors = []
33
33
  @partial = false
34
34
  @strict_variables = false
@@ -39,6 +39,10 @@ module Liquid
39
39
  @global_filter = nil
40
40
  @disabled_tags = {}
41
41
 
42
+ @registers.static[:cached_partials] ||= {}
43
+ @registers.static[:file_system] ||= Liquid::Template.file_system
44
+ @registers.static[:template_factory] ||= Liquid::TemplateFactory.new
45
+
42
46
  self.exception_renderer = Template.default_exception_renderer
43
47
  if rethrow_errors
44
48
  self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
@@ -140,7 +144,7 @@ module Liquid
140
144
  self.class.build(
141
145
  resource_limits: resource_limits,
142
146
  static_environments: static_environments,
143
- registers: StaticRegisters.new(registers)
147
+ registers: Registers.new(registers),
144
148
  ).tap do |subcontext|
145
149
  subcontext.base_scope_depth = base_scope_depth + 1
146
150
  subcontext.exception_renderer = exception_renderer
@@ -193,10 +197,14 @@ module Liquid
193
197
  try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
194
198
  end
195
199
 
196
- variable = variable.to_liquid
200
+ # update variable's context before invoking #to_liquid
197
201
  variable.context = self if variable.respond_to?(:context=)
198
202
 
199
- variable
203
+ liquid_variable = variable.to_liquid
204
+
205
+ liquid_variable.context = self if variable != liquid_variable && liquid_variable.respond_to?(:context=)
206
+
207
+ liquid_variable
200
208
  end
201
209
 
202
210
  def lookup_and_evaluate(obj, key, raise_on_not_found: true)
data/lib/liquid/drop.rb CHANGED
@@ -25,6 +25,10 @@ module Liquid
25
25
  class Drop
26
26
  attr_writer :context
27
27
 
28
+ def initialize
29
+ @context = nil
30
+ end
31
+
28
32
  # Catch all for the method
29
33
  def liquid_method_missing(method)
30
34
  return nil unless @context&.strict_variables
data/lib/liquid/errors.rb CHANGED
@@ -40,19 +40,20 @@ module Liquid
40
40
  end
41
41
  end
42
42
 
43
- ArgumentError = Class.new(Error)
44
- ContextError = Class.new(Error)
45
- FileSystemError = Class.new(Error)
46
- StandardError = Class.new(Error)
47
- SyntaxError = Class.new(Error)
48
- StackLevelError = Class.new(Error)
49
- MemoryError = Class.new(Error)
50
- ZeroDivisionError = Class.new(Error)
51
- FloatDomainError = Class.new(Error)
52
- UndefinedVariable = Class.new(Error)
53
- UndefinedDropMethod = Class.new(Error)
54
- UndefinedFilter = Class.new(Error)
55
- MethodOverrideError = Class.new(Error)
56
- DisabledError = Class.new(Error)
57
- InternalError = Class.new(Error)
43
+ ArgumentError = Class.new(Error)
44
+ ContextError = Class.new(Error)
45
+ FileSystemError = Class.new(Error)
46
+ StandardError = Class.new(Error)
47
+ SyntaxError = Class.new(Error)
48
+ StackLevelError = Class.new(Error)
49
+ MemoryError = Class.new(Error)
50
+ ZeroDivisionError = Class.new(Error)
51
+ FloatDomainError = Class.new(Error)
52
+ UndefinedVariable = Class.new(Error)
53
+ UndefinedDropMethod = Class.new(Error)
54
+ UndefinedFilter = Class.new(Error)
55
+ MethodOverrideError = Class.new(Error)
56
+ DisabledError = Class.new(Error)
57
+ InternalError = Class.new(Error)
58
+ TemplateEncodingError = Class.new(Error)
58
59
  end
@@ -3,7 +3,10 @@
3
3
  module Liquid
4
4
  class Expression
5
5
  LITERALS = {
6
- nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
6
+ nil => nil,
7
+ 'nil' => nil,
8
+ 'null' => nil,
9
+ '' => nil,
7
10
  'true' => true,
8
11
  'false' => false,
9
12
  'blank' => '',
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type object
6
+ # @liquid_name forloop
7
+ # @liquid_summary
8
+ # Information about a parent [`for` loop](/docs/api/liquid/tags/for).
4
9
  class ForloopDrop < Drop
5
10
  def initialize(name, length, parentloop)
6
11
  @name = name
@@ -9,33 +14,68 @@ module Liquid
9
14
  @index = 0
10
15
  end
11
16
 
12
- attr_reader :length, :parentloop
17
+ # @liquid_public_docs
18
+ # @liquid_name length
19
+ # @liquid_summary
20
+ # The total number of iterations in the loop.
21
+ # @liquid_return [number]
22
+ attr_reader :length
13
23
 
14
- def name
15
- Usage.increment('forloop_drop_name')
16
- @name
17
- end
24
+ # @liquid_public_docs
25
+ # @liquid_name parentloop
26
+ # @liquid_summary
27
+ # The parent `forloop` object.
28
+ # @liquid_description
29
+ # If the current `for` loop isn't nested inside another `for` loop, then `nil` is returned.
30
+ # @liquid_return [forloop]
31
+ attr_reader :parentloop
32
+
33
+ attr_reader :name
18
34
 
35
+ # @liquid_public_docs
36
+ # @liquid_summary
37
+ # The 1-based index of the current iteration.
38
+ # @liquid_return [number]
19
39
  def index
20
40
  @index + 1
21
41
  end
22
42
 
43
+ # @liquid_public_docs
44
+ # @liquid_summary
45
+ # The 0-based index of the current iteration.
46
+ # @liquid_return [number]
23
47
  def index0
24
48
  @index
25
49
  end
26
50
 
51
+ # @liquid_public_docs
52
+ # @liquid_summary
53
+ # The 1-based index of the current iteration, in reverse order.
54
+ # @liquid_return [number]
27
55
  def rindex
28
56
  @length - @index
29
57
  end
30
58
 
59
+ # @liquid_public_docs
60
+ # @liquid_summary
61
+ # The 0-based index of the current iteration, in reverse order.
62
+ # @liquid_return [number]
31
63
  def rindex0
32
64
  @length - @index - 1
33
65
  end
34
66
 
67
+ # @liquid_public_docs
68
+ # @liquid_summary
69
+ # Returns `true` if the current iteration is the first. Returns `false` if not.
70
+ # @liquid_return [boolean]
35
71
  def first
36
72
  @index == 0
37
73
  end
38
74
 
75
+ # @liquid_public_docs
76
+ # @liquid_summary
77
+ # Returns `true` if the current iteration is the last. Returns `false` if not.
78
+ # @liquid_return [boolean]
39
79
  def last
40
80
  @index == @length - 1
41
81
  end
data/lib/liquid/lexer.rb CHANGED
@@ -18,6 +18,7 @@ module Liquid
18
18
  IDENTIFIER = /[a-zA-Z_][\w-]*\??/
19
19
  SINGLE_STRING_LITERAL = /'[^\']*'/
20
20
  DOUBLE_STRING_LITERAL = /"[^\"]*"/
21
+ STRING_LITERAL = Regexp.union(SINGLE_STRING_LITERAL, DOUBLE_STRING_LITERAL)
21
22
  NUMBER_LITERAL = /-?\d+(\.\d+)?/
22
23
  DOTDOT = /\.\./
23
24
  COMPARISON_OPERATOR = /==|!=|<>|<=?|>=?|contains(?=\s)/
@@ -35,9 +36,7 @@ module Liquid
35
36
  break if @ss.eos?
36
37
  tok = if (t = @ss.scan(COMPARISON_OPERATOR))
37
38
  [:comparison, t]
38
- elsif (t = @ss.scan(SINGLE_STRING_LITERAL))
39
- [:string, t]
40
- elsif (t = @ss.scan(DOUBLE_STRING_LITERAL))
39
+ elsif (t = @ss.scan(STRING_LITERAL))
41
40
  [:string, t]
42
41
  elsif (t = @ss.scan(NUMBER_LITERAL))
43
42
  [:number, t]
@@ -13,15 +13,17 @@
13
13
  for_invalid_attribute: "Invalid attribute in for loop. Valid attributes are limit and offset"
14
14
  if: "Syntax Error in tag 'if' - Valid syntax: if [expression]"
15
15
  include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
16
- unknown_tag: "Unknown tag '%{tag}'"
16
+ inline_comment_invalid: "Syntax error in tag '#' - Each line of comments must be prefixed by the '#' character"
17
17
  invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
18
+ invalid_template_encoding: "Invalid template encoding"
19
+ render: "Syntax error in tag 'render' - Template name must be a quoted string"
20
+ table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
21
+ tag_never_closed: "'%{block_name}' tag was never closed"
22
+ tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
18
23
  unexpected_else: "%{block_name} tag does not expect 'else' tag"
19
24
  unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
20
- tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
25
+ unknown_tag: "Unknown tag '%{tag}'"
21
26
  variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
22
- tag_never_closed: "'%{block_name}' tag was never closed"
23
- table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
24
- render: "Syntax error in tag 'render' - Template name must be a quoted string"
25
27
  argument:
26
28
  include: "Argument error in tag 'include' - Illegal template name"
27
29
  disabled:
@@ -3,20 +3,29 @@
3
3
  module Liquid
4
4
  class PartialCache
5
5
  def self.load(template_name, context:, parse_context:)
6
- cached_partials = (context.registers[:cached_partials] ||= {})
7
- cached = cached_partials[template_name]
6
+ cached_partials = context.registers[:cached_partials]
7
+ cache_key = "#{template_name}:#{parse_context.error_mode}"
8
+ cached = cached_partials[cache_key]
8
9
  return cached if cached
9
10
 
10
- file_system = (context.registers[:file_system] ||= Liquid::Template.file_system)
11
+ file_system = context.registers[:file_system]
11
12
  source = file_system.read_template_file(template_name)
12
13
 
13
14
  parse_context.partial = true
14
15
 
15
- template_factory = (context.registers[:template_factory] ||= Liquid::TemplateFactory.new)
16
+ template_factory = context.registers[:template_factory]
16
17
  template = template_factory.for(template_name)
17
18
 
18
- partial = template.parse(source, parse_context)
19
- cached_partials[template_name] = partial
19
+ begin
20
+ partial = template.parse(source, parse_context)
21
+ rescue Liquid::Error => e
22
+ e.template_name = template&.name || template_name
23
+ raise e
24
+ end
25
+
26
+ partial.name ||= template_name
27
+
28
+ cached_partials[cache_key] = partial
20
29
  ensure
21
30
  parse_context.partial = false
22
31
  end
@@ -8,7 +8,17 @@ module Liquid
8
8
  if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
9
9
  new(start_obj, end_obj)
10
10
  else
11
- start_obj.to_i..end_obj.to_i
11
+ begin
12
+ start_obj.to_i..end_obj.to_i
13
+ rescue NoMethodError
14
+ invalid_expr = start_markup unless start_obj.respond_to?(:to_i)
15
+ invalid_expr ||= end_markup unless end_obj.respond_to?(:to_i)
16
+ if invalid_expr
17
+ raise Liquid::SyntaxError, "Invalid expression type '#{invalid_expr}' in range expression"
18
+ end
19
+
20
+ raise
21
+ end
12
22
  end
13
23
  end
14
24
 
@@ -1,35 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Liquid
4
- class StaticRegisters
4
+ class Registers
5
5
  attr_reader :static
6
6
 
7
7
  def initialize(registers = {})
8
- @static = registers.is_a?(StaticRegisters) ? registers.static : registers
9
- @registers = {}
8
+ @static = registers.is_a?(Registers) ? registers.static : registers
9
+ @changes = {}
10
10
  end
11
11
 
12
12
  def []=(key, value)
13
- @registers[key] = value
13
+ @changes[key] = value
14
14
  end
15
15
 
16
16
  def [](key)
17
- if @registers.key?(key)
18
- @registers[key]
17
+ if @changes.key?(key)
18
+ @changes[key]
19
19
  else
20
20
  @static[key]
21
21
  end
22
22
  end
23
23
 
24
24
  def delete(key)
25
- @registers.delete(key)
25
+ @changes.delete(key)
26
26
  end
27
27
 
28
28
  UNDEFINED = Object.new
29
29
 
30
30
  def fetch(key, default = UNDEFINED, &block)
31
- if @registers.key?(key)
32
- @registers.fetch(key)
31
+ if @changes.key?(key)
32
+ @changes.fetch(key)
33
33
  elsif default != UNDEFINED
34
34
  if block_given?
35
35
  @static.fetch(key, &block)
@@ -42,7 +42,10 @@ module Liquid
42
42
  end
43
43
 
44
44
  def key?(key)
45
- @registers.key?(key) || @static.key?(key)
45
+ @changes.key?(key) || @static.key?(key)
46
46
  end
47
47
  end
48
+
49
+ # Alias for backwards compatibility
50
+ StaticRegisters = Registers
48
51
  end