liquid 5.3.0 → 5.5.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.
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
@@ -1,20 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Liquid
4
- # Include allows templates to relate with other templates
5
- #
6
- # Simply include another template:
7
- #
8
- # {% include 'product' %}
9
- #
10
- # Include a template with a local variable:
11
- #
12
- # {% include 'product' with products[0] %}
13
- #
14
- # Include a template for a collection:
15
- #
16
- # {% include 'product' for products %}
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category theme
7
+ # @liquid_name include
8
+ # @liquid_summary
9
+ # Renders a [snippet](/themes/architecture#snippets).
10
+ # @liquid_description
11
+ # Inside the snippet, you can access and alter variables that are [created](/docs/api/liquid/tags/variable-tags) outside of the
12
+ # snippet.
13
+ # @liquid_syntax
14
+ # {% include 'filename' %}
15
+ # @liquid_syntax_keyword filename The name of the snippet to render, without the `.liquid` extension.
16
+ # @liquid_deprecated
17
+ # Deprecated because the way that variables are handled reduces performance and makes code harder to both read and maintain.
17
18
  #
19
+ # The `include` tag has been replaced by [`render`](/docs/api/liquid/tags/render).
18
20
  class Include < Tag
19
21
  prepend Tag::Disableable
20
22
 
@@ -50,12 +52,12 @@ module Liquid
50
52
 
51
53
  def render_to_output_buffer(context, output)
52
54
  template_name = context.evaluate(@template_name_expr)
53
- raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
55
+ raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name.is_a?(String)
54
56
 
55
57
  partial = PartialCache.load(
56
58
  template_name,
57
59
  context: context,
58
- parse_context: parse_context
60
+ parse_context: parse_context,
59
61
  )
60
62
 
61
63
  context_variable_name = @alias_name || template_name.split('/').last
@@ -68,9 +70,11 @@ module Liquid
68
70
 
69
71
  old_template_name = context.template_name
70
72
  old_partial = context.partial
73
+
71
74
  begin
72
- context.template_name = template_name
73
- context.partial = true
75
+ context.template_name = partial.name
76
+ context.partial = true
77
+
74
78
  context.stack do
75
79
  @attributes.each do |key, value|
76
80
  context[key] = context.evaluate(value)
@@ -1,30 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Liquid
4
- # increment is used in a place where one needs to insert a counter
5
- # into a template, and needs the counter to survive across
6
- # multiple instantiations of the template.
7
- # (To achieve the survival, the application must keep the context)
8
- #
9
- # if the variable does not exist, it is created with value 0.
10
- #
11
- # Hello: {% increment variable %}
12
- #
13
- # gives you:
14
- #
15
- # Hello: 0
16
- # Hello: 1
17
- # Hello: 2
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category variable
7
+ # @liquid_name increment
8
+ # @liquid_summary
9
+ # Creates a new variable, with a default value of 0, that's increased by 1 with each subsequent call.
10
+ # @liquid_description
11
+ # Variables that are declared with `increment` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
12
+ # or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
13
+ # [snippets](/themes/architecture#snippets) included in the file.
18
14
  #
15
+ # Similarly, variables that are created with `increment` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)
16
+ # and [`capture`](/docs/api/liquid/tags/capture). However, `increment` and [`decrement`](/docs/api/liquid/tags/decrement) share
17
+ # variables.
18
+ # @liquid_syntax
19
+ # {% increment variable_name %}
20
+ # @liquid_syntax_keyword variable_name The name of the variable being incremented.
19
21
  class Increment < Tag
22
+ attr_reader :variable_name
23
+
20
24
  def initialize(tag_name, markup, options)
21
25
  super
22
- @variable = markup.strip
26
+ @variable_name = markup.strip
23
27
  end
24
28
 
25
29
  def render_to_output_buffer(context, output)
26
- value = context.environments.first[@variable] ||= 0
27
- context.environments.first[@variable] = value + 1
30
+ counter_environment = context.environments.first
31
+ value = counter_environment[@variable_name] || 0
32
+ counter_environment[@variable_name] = value + 1
28
33
 
29
34
  output << value.to_s
30
35
  output
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class InlineComment < Tag
5
+ def initialize(tag_name, markup, options)
6
+ super
7
+
8
+ # Semantically, a comment should only ignore everything after it on the line.
9
+ # Currently, this implementation doesn't support mixing a comment with another tag
10
+ # but we need to reserve future support for this and prevent the introduction
11
+ # of inline comments from being backward incompatible change.
12
+ #
13
+ # As such, we're forcing users to put a # symbol on every line otherwise this
14
+ # tag will throw an error.
15
+ if markup.match?(/\n\s*[^#\s]/)
16
+ raise SyntaxError, options[:locale].t("errors.syntax.inline_comment_invalid")
17
+ end
18
+ end
19
+
20
+ def render_to_output_buffer(_context, output)
21
+ output
22
+ end
23
+
24
+ def blank?
25
+ true
26
+ end
27
+ end
28
+
29
+ Template.register_tag('#', InlineComment)
30
+ end
@@ -1,9 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category syntax
7
+ # @liquid_name raw
8
+ # @liquid_summary
9
+ # Outputs any Liquid code as text instead of rendering it.
10
+ # @liquid_syntax
11
+ # {% raw %}
12
+ # expression
13
+ # {% endraw %}
14
+ # @liquid_syntax_keyword expression The expression to be output without being rendered.
4
15
  class Raw < Block
5
16
  Syntax = /\A\s*\z/
6
- FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
7
17
 
8
18
  def initialize(tag_name, markup, parse_context)
9
19
  super
@@ -14,7 +24,8 @@ module Liquid
14
24
  def parse(tokens)
15
25
  @body = +''
16
26
  while (token = tokens.shift)
17
- if token =~ FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
27
+ if token =~ BlockBody::FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
28
+ parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
18
29
  @body << Regexp.last_match(1) if Regexp.last_match(1) != ""
19
30
  return
20
31
  end
@@ -1,13 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category theme
7
+ # @liquid_name render
8
+ # @liquid_summary
9
+ # Renders a [snippet](/themes/architecture#snippets) or [app block](/themes/architecture/sections/section-schema#render-app-blocks).
10
+ # @liquid_description
11
+ # Inside snippets and app blocks, you can't directly access variables that are [created](/docs/api/liquid/tags/variable-tags) outside
12
+ # of the snippet or app block. However, you can [specify variables as parameters](/docs/api/liquid/tags/render#render-passing-variables-to-a-snippet)
13
+ # to pass outside variables to snippets.
14
+ #
15
+ # While you can't directly access created variables, you can access global objects, as well as any objects that are
16
+ # directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product)
17
+ # can access the [`product` object](/docs/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections)
18
+ # can access the [`section` object](/docs/api/liquid/objects/section).
19
+ #
20
+ # Outside a snippet or app block, you can't access variables created inside the snippet or app block.
21
+ #
22
+ # > Note:
23
+ # > When you render a snippet using the `render` tag, you can't use the [`include` tag](/docs/api/liquid/tags/include)
24
+ # > inside the snippet.
25
+ # @liquid_syntax
26
+ # {% render 'filename' %}
27
+ # @liquid_syntax_keyword filename The name of the snippet to render, without the `.liquid` extension.
4
28
  class Render < Tag
5
29
  FOR = 'for'
6
30
  SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
7
31
 
8
32
  disable_tags "include"
9
33
 
10
- attr_reader :template_name_expr, :attributes
34
+ attr_reader :template_name_expr, :variable_name_expr, :attributes, :alias_name
11
35
 
12
36
  def initialize(tag_name, markup, options)
13
37
  super
@@ -21,7 +45,7 @@ module Liquid
21
45
  @alias_name = Regexp.last_match(6)
22
46
  @variable_name_expr = variable_name ? parse_expression(variable_name) : nil
23
47
  @template_name_expr = parse_expression(template_name)
24
- @for = (with_or_for == FOR)
48
+ @is_for_loop = (with_or_for == FOR)
25
49
 
26
50
  @attributes = {}
27
51
  markup.scan(TagAttributes) do |key, value|
@@ -29,26 +53,30 @@ module Liquid
29
53
  end
30
54
  end
31
55
 
56
+ def for_loop?
57
+ @is_for_loop
58
+ end
59
+
32
60
  def render_to_output_buffer(context, output)
33
61
  render_tag(context, output)
34
62
  end
35
63
 
36
64
  def render_tag(context, output)
37
- # Though we evaluate this here we will only ever parse it as a string literal.
38
- template_name = context.evaluate(@template_name_expr)
39
- raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
65
+ # The expression should be a String literal, which parses to a String object
66
+ template_name = @template_name_expr
67
+ raise ::ArgumentError unless template_name.is_a?(String)
40
68
 
41
69
  partial = PartialCache.load(
42
70
  template_name,
43
71
  context: context,
44
- parse_context: parse_context
72
+ parse_context: parse_context,
45
73
  )
46
74
 
47
75
  context_variable_name = @alias_name || template_name.split('/').last
48
76
 
49
77
  render_partial_func = ->(var, forloop) {
50
78
  inner_context = context.new_isolated_subcontext
51
- inner_context.template_name = template_name
79
+ inner_context.template_name = partial.name
52
80
  inner_context.partial = true
53
81
  inner_context['forloop'] = forloop if forloop
54
82
 
@@ -61,7 +89,7 @@ module Liquid
61
89
  }
62
90
 
63
91
  variable = @variable_name_expr ? context.evaluate(@variable_name_expr) : nil
64
- if @for && variable.respond_to?(:each) && variable.respond_to?(:count)
92
+ if @is_for_loop && variable.respond_to?(:each) && variable.respond_to?(:count)
65
93
  forloop = Liquid::ForloopDrop.new(template_name, variable.count, nil)
66
94
  variable.each { |var| render_partial_func.call(var, forloop) }
67
95
  else
@@ -75,6 +103,7 @@ module Liquid
75
103
  def children
76
104
  [
77
105
  @node.template_name_expr,
106
+ @node.variable_name_expr,
78
107
  ] + @node.attributes.values
79
108
  end
80
109
  end
@@ -1,6 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category iteration
7
+ # @liquid_name tablerow
8
+ # @liquid_summary
9
+ # Generates HTML table rows for every item in an array.
10
+ # @liquid_description
11
+ # The `tablerow` tag must be wrapped in HTML `<table>` and `</table>` tags.
12
+ #
13
+ # > Tip:
14
+ # > Every `tablerow` loop has an associated [`tablerowloop` object](/docs/api/liquid/objects/tablerowloop) with information about the loop.
15
+ # @liquid_syntax
16
+ # {% tablerow variable in array %}
17
+ # expression
18
+ # {% endtablerow %}
19
+ # @liquid_syntax_keyword variable The current item in the array.
20
+ # @liquid_syntax_keyword array The array to iterate over.
21
+ # @liquid_syntax_keyword expression The expression to render.
22
+ # @liquid_optional_param cols [number] The number of columns that the table should have.
23
+ # @liquid_optional_param limit [number] The number of iterations to perform.
24
+ # @liquid_optional_param offset [number] The 1-based index to start iterating at.
25
+ # @liquid_optional_param range [untyped] A custom numeric range to iterate over.
4
26
  class TableRow < Block
5
27
  Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
6
28
 
@@ -23,13 +45,13 @@ module Liquid
23
45
  def render_to_output_buffer(context, output)
24
46
  (collection = context.evaluate(@collection_name)) || (return '')
25
47
 
26
- from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0
27
- to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil
48
+ from = @attributes.key?('offset') ? to_integer(context.evaluate(@attributes['offset'])) : 0
49
+ to = @attributes.key?('limit') ? from + to_integer(context.evaluate(@attributes['limit'])) : nil
28
50
 
29
51
  collection = Utils.slice_collection(collection, from, to)
30
52
  length = collection.length
31
53
 
32
- cols = context.evaluate(@attributes['cols']).to_i
54
+ cols = @attributes.key?('cols') ? to_integer(context.evaluate(@attributes['cols'])) : length
33
55
 
34
56
  output << "<tr class=\"row1\">\n"
35
57
  context.stack do
@@ -60,6 +82,14 @@ module Liquid
60
82
  super + @node.attributes.values + [@node.collection_name]
61
83
  end
62
84
  end
85
+
86
+ private
87
+
88
+ def to_integer(value)
89
+ value.to_i
90
+ rescue NoMethodError
91
+ raise Liquid::ArgumentError, "invalid integer"
92
+ end
63
93
  end
64
94
 
65
95
  Template.register_tag('tablerow', TableRow)
@@ -3,16 +3,27 @@
3
3
  require_relative 'if'
4
4
 
5
5
  module Liquid
6
- # Unless is a conditional just like 'if' but works on the inverse logic.
7
- #
8
- # {% unless x < 0 %} x is greater than zero {% endunless %}
9
- #
6
+ # @liquid_public_docs
7
+ # @liquid_type tag
8
+ # @liquid_category conditional
9
+ # @liquid_name unless
10
+ # @liquid_summary
11
+ # Renders an expression unless a specific condition is `true`.
12
+ # @liquid_description
13
+ # > Tip:
14
+ # > Similar to the [`if` tag](/docs/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag.
15
+ # @liquid_syntax
16
+ # {% unless condition %}
17
+ # expression
18
+ # {% endunless %}
19
+ # @liquid_syntax_keyword condition The condition to evaluate.
20
+ # @liquid_syntax_keyword expression The expression to render unless the condition is met.
10
21
  class Unless < If
11
22
  def render_to_output_buffer(context, output)
12
23
  # First condition is interpreted backwards ( if not )
13
24
  first_block = @blocks.first
14
25
  result = Liquid::Utils.to_liquid_value(
15
- first_block.evaluate(context)
26
+ first_block.evaluate(context),
16
27
  )
17
28
 
18
29
  unless result
@@ -22,7 +33,7 @@ module Liquid
22
33
  # After the first condition unless works just like if
23
34
  @blocks[1..-1].each do |block|
24
35
  result = Liquid::Utils.to_liquid_value(
25
- block.evaluate(context)
36
+ block.evaluate(context),
26
37
  )
27
38
 
28
39
  if result
@@ -15,7 +15,7 @@ module Liquid
15
15
  # template.render('user_name' => 'bob')
16
16
  #
17
17
  class Template
18
- attr_accessor :root
18
+ attr_accessor :root, :name
19
19
  attr_reader :resource_limits, :warnings
20
20
 
21
21
  class TagRegistry
@@ -107,6 +107,12 @@ module Liquid
107
107
  # Returns self for easy chaining
108
108
  def parse(source, options = {})
109
109
  parse_context = configure_options(options)
110
+ source = source.to_s.to_str
111
+
112
+ unless source.valid_encoding?
113
+ raise TemplateEncodingError, parse_context.locale.t("errors.syntax.invalid_template_encoding")
114
+ end
115
+
110
116
  tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
111
117
  @root = Document.parse(tokenizer, parse_context)
112
118
  self
@@ -167,15 +173,14 @@ module Liquid
167
173
 
168
174
  output = nil
169
175
 
170
- context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
171
-
172
176
  case args.last
173
177
  when Hash
174
178
  options = args.pop
175
179
  output = options[:output] if options[:output]
180
+ static_registers = context.registers.static
176
181
 
177
182
  options[:registers]&.each do |key, register|
178
- context_register[key] = register
183
+ static_registers[key] = register
179
184
  end
180
185
 
181
186
  apply_options_to_context(context, options)
@@ -190,6 +195,8 @@ module Liquid
190
195
  @profiler = context.profiler = Liquid::Profiler.new
191
196
  end
192
197
 
198
+ context.template_name ||= name
199
+
193
200
  begin
194
201
  # render the nodelist.
195
202
  @root.render_to_output_buffer(context, output || +'')
@@ -5,14 +5,18 @@ 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.to_s.to_str
8
+ @source = source
9
9
  @line_number = line_number || (line_numbers ? 1 : nil)
10
10
  @for_liquid_tag = for_liquid_tag
11
+ @offset = 0
11
12
  @tokens = tokenize
12
13
  end
13
14
 
14
15
  def shift
15
- (token = @tokens.shift) || return
16
+ token = @tokens[@offset]
17
+ return nil unless token
18
+
19
+ @offset += 1
16
20
 
17
21
  if @line_number
18
22
  @line_number += @for_liquid_tag ? 1 : token.count("\n")
@@ -31,7 +35,9 @@ module Liquid
31
35
  tokens = @source.split(TemplateParser)
32
36
 
33
37
  # removes the rogue empty element at the beginning of the array
34
- tokens.shift if tokens[0]&.empty?
38
+ if tokens[0]&.empty?
39
+ @offset += 1
40
+ end
35
41
 
36
42
  tokens
37
43
  end
@@ -47,7 +47,7 @@ module Liquid
47
47
 
48
48
  name_markup = Regexp.last_match(1)
49
49
  filter_markup = Regexp.last_match(2)
50
- @name = Expression.parse(name_markup)
50
+ @name = parse_context.parse_expression(name_markup)
51
51
  if filter_markup =~ FilterMarkupRegex
52
52
  filters = Regexp.last_match(1).scan(FilterParser)
53
53
  filters.each do |f|
@@ -65,7 +65,7 @@ module Liquid
65
65
 
66
66
  return if p.look(:end_of_string)
67
67
 
68
- @name = Expression.parse(p.expression)
68
+ @name = parse_context.parse_expression(p.expression)
69
69
  while p.consume?(:pipe)
70
70
  filtername = p.consume(:id)
71
71
  filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
@@ -122,9 +122,9 @@ module Liquid
122
122
  unparsed_args.each do |a|
123
123
  if (matches = a.match(JustTagAttributes))
124
124
  keyword_args ||= {}
125
- keyword_args[matches[1]] = Expression.parse(matches[2])
125
+ keyword_args[matches[1]] = parse_context.parse_expression(matches[2])
126
126
  else
127
- filter_args << Expression.parse(a)
127
+ filter_args << parse_context.parse_expression(a)
128
128
  end
129
129
  end
130
130
  result = [filter_name, filter_args]
@@ -2,8 +2,7 @@
2
2
 
3
3
  module Liquid
4
4
  class VariableLookup
5
- SQUARE_BRACKETED = /\A\[(.*)\]\z/m
6
- COMMAND_METHODS = ['size', 'first', 'last'].freeze
5
+ COMMAND_METHODS = ['size', 'first', 'last'].freeze
7
6
 
8
7
  attr_reader :name, :lookups
9
8
 
@@ -15,8 +14,8 @@ module Liquid
15
14
  lookups = markup.scan(VariableParser)
16
15
 
17
16
  name = lookups.shift
18
- if name =~ SQUARE_BRACKETED
19
- name = Expression.parse(Regexp.last_match(1))
17
+ if name&.start_with?('[') && name&.end_with?(']')
18
+ name = Expression.parse(name[1..-2])
20
19
  end
21
20
  @name = name
22
21
 
@@ -25,14 +24,18 @@ module Liquid
25
24
 
26
25
  @lookups.each_index do |i|
27
26
  lookup = lookups[i]
28
- if lookup =~ SQUARE_BRACKETED
29
- lookups[i] = Expression.parse(Regexp.last_match(1))
27
+ if lookup&.start_with?('[') && lookup&.end_with?(']')
28
+ lookups[i] = Expression.parse(lookup[1..-2])
30
29
  elsif COMMAND_METHODS.include?(lookup)
31
30
  @command_flags |= 1 << i
32
31
  end
33
32
  end
34
33
  end
35
34
 
35
+ def lookup_command?(lookup_index)
36
+ @command_flags & (1 << lookup_index) != 0
37
+ end
38
+
36
39
  def evaluate(context)
37
40
  name = context.evaluate(@name)
38
41
  object = context.find_variable(name)
@@ -56,7 +59,7 @@ module Liquid
56
59
  # Some special cases. If the part wasn't in square brackets and
57
60
  # no key with the same name was found we interpret following calls
58
61
  # as commands and call them on the current object
59
- elsif @command_flags & (1 << i) != 0 && object.respond_to?(key)
62
+ elsif lookup_command?(i) && object.respond_to?(key)
60
63
  object = object.send(key).to_liquid
61
64
 
62
65
  # No key was present with the desired value and it wasn't one of the directly supported
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Liquid
5
- VERSION = "5.3.0"
5
+ VERSION = "5.5.0"
6
6
  end
data/lib/liquid.rb CHANGED
@@ -29,6 +29,7 @@ module Liquid
29
29
  WhitespaceControl = '-'
30
30
  TagStart = /\{\%/
31
31
  TagEnd = /\%\}/
32
+ TagName = /#|\w+/
32
33
  VariableSignature = /\(?[\w\-\.\[\]]\)?/
33
34
  VariableSegment = /[\w\-]/
34
35
  VariableStart = /\{\{/
@@ -40,7 +41,7 @@ module Liquid
40
41
  AnyStartingTag = /#{TagStart}|#{VariableStart}/o
41
42
  PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/om
42
43
  TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
43
- VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
44
+ VariableParser = /\[(?>[^\[\]]+|\g<0>)*\]|#{VariableSegment}+\??/o
44
45
 
45
46
  RAISE_EXCEPTION_LAMBDA = ->(_e) { raise }
46
47
 
@@ -83,8 +84,7 @@ require 'liquid/tokenizer'
83
84
  require 'liquid/parse_context'
84
85
  require 'liquid/partial_cache'
85
86
  require 'liquid/usage'
86
- require 'liquid/register'
87
- require 'liquid/static_registers'
87
+ require 'liquid/registers'
88
88
  require 'liquid/template_factory'
89
89
 
90
90
  # Load all the tags of the standard library