liquid 5.4.0 → 5.6.4

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +11 -0
  3. data/README.md +48 -6
  4. data/lib/liquid/block.rb +8 -4
  5. data/lib/liquid/block_body.rb +28 -10
  6. data/lib/liquid/condition.rb +9 -4
  7. data/lib/liquid/const.rb +8 -0
  8. data/lib/liquid/context.rb +24 -14
  9. data/lib/liquid/deprecations.rb +22 -0
  10. data/lib/liquid/drop.rb +4 -0
  11. data/lib/liquid/environment.rb +159 -0
  12. data/lib/liquid/errors.rb +16 -15
  13. data/lib/liquid/expression.rb +101 -22
  14. data/lib/liquid/forloop_drop.rb +2 -5
  15. data/lib/liquid/lexer.rb +155 -44
  16. data/lib/liquid/locales/en.yml +1 -0
  17. data/lib/liquid/parse_context.rb +29 -6
  18. data/lib/liquid/parse_tree_visitor.rb +1 -1
  19. data/lib/liquid/parser.rb +3 -3
  20. data/lib/liquid/partial_cache.rb +12 -3
  21. data/lib/liquid/range_lookup.rb +14 -4
  22. data/lib/liquid/standardfilters.rb +82 -21
  23. data/lib/liquid/tablerowloop_drop.rb +1 -1
  24. data/lib/liquid/tag/disabler.rb +0 -8
  25. data/lib/liquid/tag.rb +13 -3
  26. data/lib/liquid/tags/assign.rb +1 -3
  27. data/lib/liquid/tags/break.rb +1 -3
  28. data/lib/liquid/tags/capture.rb +0 -2
  29. data/lib/liquid/tags/case.rb +1 -3
  30. data/lib/liquid/tags/comment.rb +60 -3
  31. data/lib/liquid/tags/continue.rb +1 -3
  32. data/lib/liquid/tags/cycle.rb +14 -4
  33. data/lib/liquid/tags/decrement.rb +8 -7
  34. data/lib/liquid/tags/echo.rb +2 -4
  35. data/lib/liquid/tags/for.rb +6 -8
  36. data/lib/liquid/tags/if.rb +3 -5
  37. data/lib/liquid/tags/ifchanged.rb +0 -2
  38. data/lib/liquid/tags/include.rb +8 -8
  39. data/lib/liquid/tags/increment.rb +8 -7
  40. data/lib/liquid/tags/inline_comment.rb +0 -15
  41. data/lib/liquid/tags/raw.rb +2 -4
  42. data/lib/liquid/tags/render.rb +14 -12
  43. data/lib/liquid/tags/table_row.rb +18 -6
  44. data/lib/liquid/tags/unless.rb +3 -5
  45. data/lib/liquid/tags.rb +47 -0
  46. data/lib/liquid/template.rb +60 -57
  47. data/lib/liquid/tokenizer.rb +127 -11
  48. data/lib/liquid/variable.rb +14 -8
  49. data/lib/liquid/variable_lookup.rb +13 -5
  50. data/lib/liquid/version.rb +1 -1
  51. data/lib/liquid.rb +15 -16
  52. metadata +37 -10
  53. data/lib/liquid/strainer_factory.rb +0 -41
@@ -6,7 +6,14 @@ require 'bigdecimal'
6
6
 
7
7
  module Liquid
8
8
  module StandardFilters
9
- MAX_INT = (1 << 31) - 1
9
+ MAX_I32 = (1 << 31) - 1
10
+ private_constant :MAX_I32
11
+
12
+ MIN_I64 = -(1 << 63)
13
+ MAX_I64 = (1 << 63) - 1
14
+ I64_RANGE = MIN_I64..MAX_I64
15
+ private_constant :MIN_I64, :MAX_I64, :I64_RANGE
16
+
10
17
  HTML_ESCAPE = {
11
18
  '&' => '&amp;',
12
19
  '>' => '&gt;',
@@ -18,10 +25,23 @@ module Liquid
18
25
  STRIP_HTML_BLOCKS = Regexp.union(
19
26
  %r{<script.*?</script>}m,
20
27
  /<!--.*?-->/m,
21
- %r{<style.*?</style>}m
28
+ %r{<style.*?</style>}m,
22
29
  )
23
30
  STRIP_HTML_TAGS = /<.*?>/m
24
31
 
32
+ class << self
33
+ def try_coerce_encoding(input, encoding:)
34
+ original_encoding = input.encoding
35
+ if input.encoding != encoding
36
+ input.force_encoding(encoding)
37
+ unless input.valid_encoding?
38
+ input.force_encoding(original_encoding)
39
+ end
40
+ end
41
+ input
42
+ end
43
+ end
44
+
25
45
  # @liquid_public_docs
26
46
  # @liquid_type filter
27
47
  # @liquid_category array
@@ -62,7 +82,7 @@ module Liquid
62
82
  # @liquid_type filter
63
83
  # @liquid_category string
64
84
  # @liquid_summary
65
- # Capitalizes the first word in a string.
85
+ # Capitalizes the first word in a string and downcases the remaining characters.
66
86
  # @liquid_syntax string | capitalize
67
87
  # @liquid_return [string]
68
88
  def capitalize(input)
@@ -73,7 +93,7 @@ module Liquid
73
93
  # @liquid_type filter
74
94
  # @liquid_category string
75
95
  # @liquid_summary
76
- # Escapes a string.
96
+ # Escapes special characters in HTML, such as `<>`, `'`, and `&`, and converts characters into escape sequences. The filter doesn't effect characters within the string that don’t have a corresponding escape sequence.".
77
97
  # @liquid_syntax string | escape
78
98
  # @liquid_return [string]
79
99
  def escape(input)
@@ -143,7 +163,8 @@ module Liquid
143
163
  # @liquid_syntax string | base64_decode
144
164
  # @liquid_return [string]
145
165
  def base64_decode(input)
146
- Base64.strict_decode64(input.to_s)
166
+ input = input.to_s
167
+ StandardFilters.try_coerce_encoding(Base64.strict_decode64(input), encoding: input.encoding)
147
168
  rescue ::ArgumentError
148
169
  raise Liquid::ArgumentError, "invalid base64 provided to base64_decode"
149
170
  end
@@ -167,7 +188,8 @@ module Liquid
167
188
  # @liquid_syntax string | base64_url_safe_decode
168
189
  # @liquid_return [string]
169
190
  def base64_url_safe_decode(input)
170
- Base64.urlsafe_decode64(input.to_s)
191
+ input = input.to_s
192
+ StandardFilters.try_coerce_encoding(Base64.urlsafe_decode64(input), encoding: input.encoding)
171
193
  rescue ::ArgumentError
172
194
  raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode"
173
195
  end
@@ -186,10 +208,19 @@ module Liquid
186
208
  offset = Utils.to_integer(offset)
187
209
  length = length ? Utils.to_integer(length) : 1
188
210
 
189
- if input.is_a?(Array)
190
- input.slice(offset, length) || []
191
- else
192
- input.to_s.slice(offset, length) || ''
211
+ begin
212
+ if input.is_a?(Array)
213
+ input.slice(offset, length) || []
214
+ else
215
+ input.to_s.slice(offset, length) || ''
216
+ end
217
+ rescue RangeError
218
+ if I64_RANGE.cover?(length) && I64_RANGE.cover?(offset)
219
+ raise # unexpected error
220
+ end
221
+ offset = offset.clamp(I64_RANGE)
222
+ length = length.clamp(I64_RANGE)
223
+ retry
193
224
  end
194
225
  end
195
226
 
@@ -239,9 +270,9 @@ module Liquid
239
270
  wordlist = begin
240
271
  input.split(" ", words + 1)
241
272
  rescue RangeError
242
- raise if words + 1 < MAX_INT
243
- # e.g. integer #{words} too big to convert to `int'
244
- raise Liquid::ArgumentError, "integer #{words} too big for truncatewords"
273
+ # integer too big for String#split, but we can semantically assume no truncation is needed
274
+ return input if words + 1 > MAX_I32
275
+ raise # unexpected error
245
276
  end
246
277
  return input if wordlist.length <= words
247
278
 
@@ -599,7 +630,7 @@ module Liquid
599
630
  # @liquid_description
600
631
  # > Note:
601
632
  # > The `concat` filter won't filter out duplicates. If you want to remove duplicates, then you need to use the
602
- # > [`uniq` filter](/api/liquid/filters#uniq).
633
+ # > [`uniq` filter](/docs/api/liquid/filters/uniq).
603
634
  # @liquid_syntax array | concat: array
604
635
  # @liquid_return [array[untyped]]
605
636
  def concat(input, array)
@@ -741,7 +772,7 @@ module Liquid
741
772
  # @liquid_type filter
742
773
  # @liquid_category math
743
774
  # @liquid_summary
744
- # Divides a number by a given number.
775
+ # Divides a number by a given number. The `divided_by` filter produces a result of the same type as the divisor. This means if you divide by an integer, the result will be an integer, and if you divide by a float, the result will be a float.
745
776
  # @liquid_syntax number | divided_by: number
746
777
  # @liquid_return [number]
747
778
  def divided_by(input, operand)
@@ -841,18 +872,48 @@ module Liquid
841
872
  # @liquid_summary
842
873
  # Sets a default value for any variable whose value is one of the following:
843
874
  #
844
- # - [`empty`](/api/liquid/basics#empty)
845
- # - [`false`](/api/liquid/basics#truthy-and-falsy)
846
- # - [`nil`](/api/liquid/basics#nil)
875
+ # - [`empty`](/docs/api/liquid/basics#empty)
876
+ # - [`false`](/docs/api/liquid/basics#truthy-and-falsy)
877
+ # - [`nil`](/docs/api/liquid/basics#nil)
847
878
  # @liquid_syntax variable | default: variable
848
879
  # @liquid_return [untyped]
849
- # @liquid_optional_param allow_false [boolean] Whether to use false values instead of the default.
880
+ # @liquid_optional_param allow_false: [boolean] Whether to use false values instead of the default.
850
881
  def default(input, default_value = '', options = {})
851
882
  options = {} unless options.is_a?(Hash)
852
883
  false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
853
884
  false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
854
885
  end
855
886
 
887
+ # @liquid_public_docs
888
+ # @liquid_type filter
889
+ # @liquid_category array
890
+ # @liquid_summary
891
+ # Returns the sum of all elements in an array.
892
+ # @liquid_syntax array | sum
893
+ # @liquid_return [number]
894
+ def sum(input, property = nil)
895
+ ary = InputIterator.new(input, context)
896
+ return 0 if ary.empty?
897
+
898
+ values_for_sum = ary.map do |item|
899
+ if property.nil?
900
+ item
901
+ elsif item.respond_to?(:[])
902
+ item[property]
903
+ else
904
+ 0
905
+ end
906
+ rescue TypeError
907
+ raise_property_error(property)
908
+ end
909
+
910
+ result = InputIterator.new(values_for_sum, context).sum do |item|
911
+ Utils.to_number(item)
912
+ end
913
+
914
+ result.is_a?(BigDecimal) ? result.to_f : result
915
+ end
916
+
856
917
  private
857
918
 
858
919
  attr_reader :context
@@ -883,6 +944,8 @@ module Liquid
883
944
  def nil_safe_casecmp(a, b)
884
945
  if !a.nil? && !b.nil?
885
946
  a.to_s.casecmp(b.to_s)
947
+ elsif a.nil? && b.nil?
948
+ 0
886
949
  else
887
950
  a.nil? ? 1 : -1
888
951
  end
@@ -938,6 +1001,4 @@ module Liquid
938
1001
  end
939
1002
  end
940
1003
  end
941
-
942
- Template.register_filter(StandardFilters)
943
1004
  end
@@ -5,7 +5,7 @@ module Liquid
5
5
  # @liquid_type object
6
6
  # @liquid_name tablerowloop
7
7
  # @liquid_summary
8
- # Information about a parent [`tablerow` loop](/api/liquid/tags#tablerow).
8
+ # Information about a parent [`tablerow` loop](/docs/api/liquid/tags/tablerow).
9
9
  class TablerowloopDrop < Drop
10
10
  def initialize(length, cols)
11
11
  @length = length
@@ -3,14 +3,6 @@
3
3
  module Liquid
4
4
  class Tag
5
5
  module Disabler
6
- module ClassMethods
7
- attr_reader :disabled_tags
8
- end
9
-
10
- def self.prepended(base)
11
- base.extend(ClassMethods)
12
- end
13
-
14
6
  def render_to_output_buffer(context, output)
15
7
  context.with_disabled_tags(self.class.disabled_tags) do
16
8
  super
data/lib/liquid/tag.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'liquid/tag/disabler'
4
+ require 'liquid/tag/disableable'
5
+
3
6
  module Liquid
4
7
  class Tag
5
8
  attr_reader :nodelist, :tag_name, :line_number, :parse_context
@@ -14,12 +17,18 @@ module Liquid
14
17
  end
15
18
 
16
19
  def disable_tags(*tag_names)
17
- @disabled_tags ||= []
18
- @disabled_tags.concat(tag_names)
20
+ tag_names += disabled_tags
21
+ define_singleton_method(:disabled_tags) { tag_names }
19
22
  prepend(Disabler)
20
23
  end
21
24
 
22
25
  private :new
26
+
27
+ protected
28
+
29
+ def disabled_tags
30
+ []
31
+ end
23
32
  end
24
33
 
25
34
  def initialize(tag_name, markup, parse_context)
@@ -48,7 +57,8 @@ module Liquid
48
57
  # of the `render_to_output_buffer` method will become the default and the `render`
49
58
  # method will be removed.
50
59
  def render_to_output_buffer(context, output)
51
- output << render(context)
60
+ render_result = render(context)
61
+ output << render_result if render_result
52
62
  output
53
63
  end
54
64
 
@@ -8,7 +8,7 @@ module Liquid
8
8
  # @liquid_summary
9
9
  # Creates a new variable.
10
10
  # @liquid_description
11
- # You can create variables of any [basic type](/api/liquid/basics#types), [object](/api/liquid/objects), or object property.
11
+ # You can create variables of any [basic type](/docs/api/liquid/basics#types), [object](/docs/api/liquid/objects), or object property.
12
12
  # @liquid_syntax
13
13
  # {% assign variable_name = value %}
14
14
  # @liquid_syntax_keyword variable_name The name of the variable being created.
@@ -72,6 +72,4 @@ module Liquid
72
72
  end
73
73
  end
74
74
  end
75
-
76
- Template.register_tag('assign', Assign)
77
75
  end
@@ -15,7 +15,7 @@ module Liquid
15
15
  # @liquid_category iteration
16
16
  # @liquid_name break
17
17
  # @liquid_summary
18
- # Stops a [`for` loop](/api/liquid/tags#for) from iterating.
18
+ # Stops a [`for` loop](/docs/api/liquid/tags/for) from iterating.
19
19
  # @liquid_syntax
20
20
  # {% break %}
21
21
  class Break < Tag
@@ -26,6 +26,4 @@ module Liquid
26
26
  output
27
27
  end
28
28
  end
29
-
30
- Template.register_tag('break', Break)
31
29
  end
@@ -39,6 +39,4 @@ module Liquid
39
39
  true
40
40
  end
41
41
  end
42
-
43
- Template.register_tag('capture', Capture)
44
42
  end
@@ -77,7 +77,7 @@ module Liquid
77
77
  end
78
78
 
79
79
  result = Liquid::Utils.to_liquid_value(
80
- block.evaluate(context)
80
+ block.evaluate(context),
81
81
  )
82
82
 
83
83
  if result
@@ -123,6 +123,4 @@ module Liquid
123
123
  end
124
124
  end
125
125
  end
126
-
127
- Template.register_tag('case', Case)
128
126
  end
@@ -8,7 +8,7 @@ module Liquid
8
8
  # @liquid_summary
9
9
  # Prevents an expression from being rendered or output.
10
10
  # @liquid_description
11
- # Any text inside `comment` tags won't be output, and any Liquid code won't be rendered.
11
+ # Any text inside `comment` tags won't be output, and any Liquid code will be parsed, but not executed.
12
12
  # @liquid_syntax
13
13
  # {% comment %}
14
14
  # content
@@ -25,7 +25,64 @@ module Liquid
25
25
  def blank?
26
26
  true
27
27
  end
28
- end
29
28
 
30
- Template.register_tag('comment', Comment)
29
+ private
30
+
31
+ def parse_body(body, tokenizer)
32
+ if parse_context.depth >= MAX_DEPTH
33
+ raise StackLevelError, "Nesting too deep"
34
+ end
35
+
36
+ parse_context.depth += 1
37
+ comment_tag_depth = 1
38
+
39
+ begin
40
+ # Consume tokens without creating child nodes.
41
+ # The children tag doesn't require to be a valid Liquid except the comment and raw tag.
42
+ # The child comment and raw tag must be closed.
43
+ while (token = tokenizer.send(:shift))
44
+ tag_name = if tokenizer.for_liquid_tag
45
+ next if token.empty? || token.match?(BlockBody::WhitespaceOrNothing)
46
+
47
+ tag_name_match = BlockBody::LiquidTagToken.match(token)
48
+
49
+ next if tag_name_match.nil?
50
+
51
+ tag_name_match[1]
52
+ else
53
+ token =~ BlockBody::FullToken
54
+ Regexp.last_match(2)
55
+ end
56
+
57
+ case tag_name
58
+ when "raw"
59
+ parse_raw_tag_body(tokenizer)
60
+ when "comment"
61
+ comment_tag_depth += 1
62
+ when "endcomment"
63
+ comment_tag_depth -= 1
64
+ end
65
+
66
+ if comment_tag_depth.zero?
67
+ parse_context.trim_whitespace = (token[-3] == WhitespaceControl) unless tokenizer.for_liquid_tag
68
+ return false
69
+ end
70
+ end
71
+
72
+ raise_tag_never_closed(block_name)
73
+ ensure
74
+ parse_context.depth -= 1
75
+ end
76
+
77
+ false
78
+ end
79
+
80
+ def parse_raw_tag_body(tokenizer)
81
+ while (token = tokenizer.send(:shift))
82
+ return if token =~ BlockBody::FullTokenPossiblyInvalid && "endraw" == Regexp.last_match(2)
83
+ end
84
+
85
+ raise_tag_never_closed("raw")
86
+ end
87
+ end
31
88
  end
@@ -6,7 +6,7 @@ module Liquid
6
6
  # @liquid_category iteration
7
7
  # @liquid_name continue
8
8
  # @liquid_summary
9
- # Causes a [`for` loop](/api/liquid/tags#for) to skip to the next iteration.
9
+ # Causes a [`for` loop](/docs/api/liquid/tags/for) to skip to the next iteration.
10
10
  # @liquid_syntax
11
11
  # {% continue %}
12
12
  class Continue < Tag
@@ -17,6 +17,4 @@ module Liquid
17
17
  output
18
18
  end
19
19
  end
20
-
21
- Template.register_tag('continue', Continue)
22
20
  end
@@ -6,7 +6,7 @@ module Liquid
6
6
  # @liquid_category iteration
7
7
  # @liquid_name cycle
8
8
  # @liquid_summary
9
- # Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/api/liquid/tags#for).
9
+ # Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/docs/api/liquid/tags/for).
10
10
  # @liquid_description
11
11
  # The `cycle` tag must be used inside a `for` loop.
12
12
  #
@@ -26,14 +26,20 @@ module Liquid
26
26
  when NamedSyntax
27
27
  @variables = variables_from_string(Regexp.last_match(2))
28
28
  @name = parse_expression(Regexp.last_match(1))
29
+ @is_named = true
29
30
  when SimpleSyntax
30
31
  @variables = variables_from_string(markup)
31
32
  @name = @variables.to_s
33
+ @is_named = !@name.match?(/\w+:0x\h{8}/)
32
34
  else
33
35
  raise SyntaxError, options[:locale].t("errors.syntax.cycle")
34
36
  end
35
37
  end
36
38
 
39
+ def named?
40
+ @is_named
41
+ end
42
+
37
43
  def render_to_output_buffer(context, output)
38
44
  context.registers[:cycle] ||= {}
39
45
 
@@ -62,7 +68,13 @@ module Liquid
62
68
  def variables_from_string(markup)
63
69
  markup.split(',').collect do |var|
64
70
  var =~ /\s*(#{QuotedFragment})\s*/o
65
- Regexp.last_match(1) ? parse_expression(Regexp.last_match(1)) : nil
71
+ next unless Regexp.last_match(1)
72
+
73
+ # Expression Parser returns cached objects, and we need to dup them to
74
+ # start the cycle over for each new cycle call.
75
+ # Liquid-C does not have a cache, so we don't need to dup the object.
76
+ var = parse_expression(Regexp.last_match(1))
77
+ var.is_a?(VariableLookup) ? var.dup : var
66
78
  end.compact
67
79
  end
68
80
 
@@ -72,6 +84,4 @@ module Liquid
72
84
  end
73
85
  end
74
86
  end
75
-
76
- Template.register_tag('cycle', Cycle)
77
87
  end
@@ -12,26 +12,27 @@ module Liquid
12
12
  # or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
13
13
  # [snippets](/themes/architecture#snippets) included in the file.
14
14
  #
15
- # Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/api/liquid/tags#assign)
16
- # and [`capture`](/api/liquid/tags#capture). However, `decrement` and [`increment`](/api/liquid/tags#increment) share
15
+ # Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/docs/api/liquid/tags/assign)
16
+ # and [`capture`](/docs/api/liquid/tags/capture). However, `decrement` and [`increment`](/docs/api/liquid/tags/increment) share
17
17
  # variables.
18
18
  # @liquid_syntax
19
19
  # {% decrement variable_name %}
20
20
  # @liquid_syntax_keyword variable_name The name of the variable being decremented.
21
21
  class Decrement < Tag
22
+ attr_reader :variable_name
23
+
22
24
  def initialize(tag_name, markup, options)
23
25
  super
24
- @variable = markup.strip
26
+ @variable_name = markup.strip
25
27
  end
26
28
 
27
29
  def render_to_output_buffer(context, output)
28
- value = context.environments.first[@variable] ||= 0
30
+ counter_environment = context.environments.first
31
+ value = counter_environment[@variable_name] || 0
29
32
  value -= 1
30
- context.environments.first[@variable] = value
33
+ counter_environment[@variable_name] = value
31
34
  output << value.to_s
32
35
  output
33
36
  end
34
37
  end
35
-
36
- Template.register_tag('decrement', Decrement)
37
38
  end
@@ -9,10 +9,10 @@ module Liquid
9
9
  # Outputs an expression.
10
10
  # @liquid_description
11
11
  # Using the `echo` tag is the same as wrapping an expression in curly brackets (`{{` and `}}`). However, unlike the curly
12
- # bracket method, you can use the `echo` tag inside [`liquid` tags](/api/liquid/tags#liquid).
12
+ # bracket method, you can use the `echo` tag inside [`liquid` tags](/docs/api/liquid/tags/liquid).
13
13
  #
14
14
  # > Tip:
15
- # > You can use [filters](/api/liquid/filters) on expressions inside `echo` tags.
15
+ # > You can use [filters](/docs/api/liquid/filters) on expressions inside `echo` tags.
16
16
  # @liquid_syntax
17
17
  # {% liquid
18
18
  # echo expression
@@ -36,6 +36,4 @@ module Liquid
36
36
  end
37
37
  end
38
38
  end
39
-
40
- Template.register_tag('echo', Echo)
41
39
  end
@@ -9,10 +9,10 @@ module Liquid
9
9
  # Renders an expression for every item in an array.
10
10
  # @liquid_description
11
11
  # You can do a maximum of 50 iterations with a `for` loop. If you need to iterate over more than 50 items, then use the
12
- # [`paginate` tag](/api/liquid/tags#paginate) to split the items over multiple pages.
12
+ # [`paginate` tag](/docs/api/liquid/tags/paginate) to split the items over multiple pages.
13
13
  #
14
14
  # > Tip:
15
- # > Every `for` loop has an associated [`forloop` object](/api/liquid/objects#forloop) with information about the loop.
15
+ # > Every `for` loop has an associated [`forloop` object](/docs/api/liquid/objects/forloop) with information about the loop.
16
16
  # @liquid_syntax
17
17
  # {% for variable in array %}
18
18
  # expression
@@ -88,7 +88,7 @@ module Liquid
88
88
  end
89
89
 
90
90
  def strict_parse(markup)
91
- p = Parser.new(markup)
91
+ p = @parse_context.new_parser(markup)
92
92
  @variable_name = p.consume(:id)
93
93
  raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
94
94
 
@@ -98,11 +98,12 @@ module Liquid
98
98
  @name = "#{@variable_name}-#{collection_name}"
99
99
  @reversed = p.id?('reversed')
100
100
 
101
- while p.look(:id) && p.look(:colon, 1)
101
+ while p.look(:comma) || p.look(:id)
102
+ p.consume?(:comma)
102
103
  unless (attribute = p.id?('limit') || p.id?('offset'))
103
104
  raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute")
104
105
  end
105
- p.consume
106
+ p.consume(:colon)
106
107
  set_attribute(attribute, p.expression)
107
108
  end
108
109
  p.consume(:end_of_string)
@@ -177,7 +178,6 @@ module Liquid
177
178
  case key
178
179
  when 'offset'
179
180
  @from = if expr == 'continue'
180
- Usage.increment('for_offset_continue')
181
181
  :continue
182
182
  else
183
183
  parse_expression(expr)
@@ -201,6 +201,4 @@ module Liquid
201
201
  end
202
202
  end
203
203
  end
204
-
205
- Template.register_tag('for', For)
206
204
  end
@@ -53,7 +53,7 @@ module Liquid
53
53
  def render_to_output_buffer(context, output)
54
54
  @blocks.each do |block|
55
55
  result = Liquid::Utils.to_liquid_value(
56
- block.evaluate(context)
56
+ block.evaluate(context),
57
57
  )
58
58
 
59
59
  if result
@@ -102,7 +102,7 @@ module Liquid
102
102
  end
103
103
 
104
104
  def strict_parse(markup)
105
- p = Parser.new(markup)
105
+ p = @parse_context.new_parser(markup)
106
106
  condition = parse_binary_comparisons(p)
107
107
  p.consume(:end_of_string)
108
108
  condition
@@ -111,7 +111,7 @@ module Liquid
111
111
  def parse_binary_comparisons(p)
112
112
  condition = parse_comparison(p)
113
113
  first_condition = condition
114
- while (op = (p.id?('and') || p.id?('or')))
114
+ while (op = p.id?('and') || p.id?('or'))
115
115
  child_condition = parse_comparison(p)
116
116
  condition.send(op, child_condition)
117
117
  condition = child_condition
@@ -135,6 +135,4 @@ module Liquid
135
135
  end
136
136
  end
137
137
  end
138
-
139
- Template.register_tag('if', If)
140
138
  end
@@ -14,6 +14,4 @@ module Liquid
14
14
  output
15
15
  end
16
16
  end
17
-
18
- Template.register_tag('ifchanged', Ifchanged)
19
17
  end
@@ -8,7 +8,7 @@ module Liquid
8
8
  # @liquid_summary
9
9
  # Renders a [snippet](/themes/architecture#snippets).
10
10
  # @liquid_description
11
- # Inside the snippet, you can access and alter variables that are [created](/api/liquid/tags#variable-tags) outside of the
11
+ # Inside the snippet, you can access and alter variables that are [created](/docs/api/liquid/tags/variable-tags) outside of the
12
12
  # snippet.
13
13
  # @liquid_syntax
14
14
  # {% include 'filename' %}
@@ -16,7 +16,7 @@ module Liquid
16
16
  # @liquid_deprecated
17
17
  # Deprecated because the way that variables are handled reduces performance and makes code harder to both read and maintain.
18
18
  #
19
- # The `include` tag has been replaced by [`render`](/api/liquid/tags#render).
19
+ # The `include` tag has been replaced by [`render`](/docs/api/liquid/tags/render).
20
20
  class Include < Tag
21
21
  prepend Tag::Disableable
22
22
 
@@ -52,12 +52,12 @@ module Liquid
52
52
 
53
53
  def render_to_output_buffer(context, output)
54
54
  template_name = context.evaluate(@template_name_expr)
55
- 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)
56
56
 
57
57
  partial = PartialCache.load(
58
58
  template_name,
59
59
  context: context,
60
- parse_context: parse_context
60
+ parse_context: parse_context,
61
61
  )
62
62
 
63
63
  context_variable_name = @alias_name || template_name.split('/').last
@@ -70,9 +70,11 @@ module Liquid
70
70
 
71
71
  old_template_name = context.template_name
72
72
  old_partial = context.partial
73
+
73
74
  begin
74
- context.template_name = template_name
75
- context.partial = true
75
+ context.template_name = partial.name
76
+ context.partial = true
77
+
76
78
  context.stack do
77
79
  @attributes.each do |key, value|
78
80
  context[key] = context.evaluate(value)
@@ -108,6 +110,4 @@ module Liquid
108
110
  end
109
111
  end
110
112
  end
111
-
112
- Template.register_tag('include', Include)
113
113
  end