liquid 4.0.3 → 5.1.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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +54 -0
  3. data/README.md +6 -0
  4. data/lib/liquid/block.rb +31 -14
  5. data/lib/liquid/block_body.rb +166 -54
  6. data/lib/liquid/condition.rb +41 -20
  7. data/lib/liquid/context.rb +107 -52
  8. data/lib/liquid/document.rb +47 -9
  9. data/lib/liquid/drop.rb +4 -2
  10. data/lib/liquid/errors.rb +20 -18
  11. data/lib/liquid/expression.rb +29 -34
  12. data/lib/liquid/extensions.rb +2 -0
  13. data/lib/liquid/file_system.rb +6 -4
  14. data/lib/liquid/forloop_drop.rb +11 -4
  15. data/lib/liquid/i18n.rb +5 -3
  16. data/lib/liquid/interrupts.rb +3 -1
  17. data/lib/liquid/lexer.rb +30 -23
  18. data/lib/liquid/locales/en.yml +3 -1
  19. data/lib/liquid/parse_context.rb +20 -4
  20. data/lib/liquid/parse_tree_visitor.rb +2 -2
  21. data/lib/liquid/parser.rb +30 -18
  22. data/lib/liquid/parser_switching.rb +17 -3
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +26 -14
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/range_lookup.rb +13 -3
  27. data/lib/liquid/register.rb +6 -0
  28. data/lib/liquid/resource_limits.rb +47 -8
  29. data/lib/liquid/standardfilters.rb +95 -46
  30. data/lib/liquid/static_registers.rb +44 -0
  31. data/lib/liquid/strainer_factory.rb +36 -0
  32. data/lib/liquid/strainer_template.rb +53 -0
  33. data/lib/liquid/tablerowloop_drop.rb +6 -4
  34. data/lib/liquid/tag/disableable.rb +22 -0
  35. data/lib/liquid/tag/disabler.rb +21 -0
  36. data/lib/liquid/tag.rb +28 -6
  37. data/lib/liquid/tags/assign.rb +24 -10
  38. data/lib/liquid/tags/break.rb +8 -3
  39. data/lib/liquid/tags/capture.rb +11 -8
  40. data/lib/liquid/tags/case.rb +40 -27
  41. data/lib/liquid/tags/comment.rb +5 -3
  42. data/lib/liquid/tags/continue.rb +8 -3
  43. data/lib/liquid/tags/cycle.rb +25 -14
  44. data/lib/liquid/tags/decrement.rb +6 -3
  45. data/lib/liquid/tags/echo.rb +34 -0
  46. data/lib/liquid/tags/for.rb +68 -44
  47. data/lib/liquid/tags/if.rb +39 -23
  48. data/lib/liquid/tags/ifchanged.rb +11 -10
  49. data/lib/liquid/tags/include.rb +34 -47
  50. data/lib/liquid/tags/increment.rb +7 -3
  51. data/lib/liquid/tags/raw.rb +14 -11
  52. data/lib/liquid/tags/render.rb +84 -0
  53. data/lib/liquid/tags/table_row.rb +23 -19
  54. data/lib/liquid/tags/unless.rb +23 -15
  55. data/lib/liquid/template.rb +53 -72
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +18 -10
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +13 -3
  60. data/lib/liquid/variable.rb +46 -41
  61. data/lib/liquid/variable_lookup.rb +11 -6
  62. data/lib/liquid/version.rb +2 -1
  63. data/lib/liquid.rb +17 -5
  64. data/test/integration/assign_test.rb +74 -5
  65. data/test/integration/blank_test.rb +11 -8
  66. data/test/integration/block_test.rb +47 -1
  67. data/test/integration/capture_test.rb +18 -10
  68. data/test/integration/context_test.rb +609 -5
  69. data/test/integration/document_test.rb +4 -2
  70. data/test/integration/drop_test.rb +67 -83
  71. data/test/integration/error_handling_test.rb +73 -61
  72. data/test/integration/expression_test.rb +46 -0
  73. data/test/integration/filter_test.rb +53 -42
  74. data/test/integration/hash_ordering_test.rb +5 -3
  75. data/test/integration/output_test.rb +26 -24
  76. data/test/integration/parsing_quirks_test.rb +19 -7
  77. data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
  78. data/test/integration/security_test.rb +30 -21
  79. data/test/integration/standard_filter_test.rb +385 -281
  80. data/test/integration/tag/disableable_test.rb +59 -0
  81. data/test/integration/tag_test.rb +45 -0
  82. data/test/integration/tags/break_tag_test.rb +4 -2
  83. data/test/integration/tags/continue_tag_test.rb +4 -2
  84. data/test/integration/tags/echo_test.rb +13 -0
  85. data/test/integration/tags/for_tag_test.rb +107 -51
  86. data/test/integration/tags/if_else_tag_test.rb +5 -3
  87. data/test/integration/tags/include_tag_test.rb +70 -54
  88. data/test/integration/tags/increment_tag_test.rb +4 -2
  89. data/test/integration/tags/liquid_tag_test.rb +116 -0
  90. data/test/integration/tags/raw_tag_test.rb +14 -11
  91. data/test/integration/tags/render_tag_test.rb +213 -0
  92. data/test/integration/tags/standard_tag_test.rb +38 -31
  93. data/test/integration/tags/statements_test.rb +23 -21
  94. data/test/integration/tags/table_row_test.rb +2 -0
  95. data/test/integration/tags/unless_else_tag_test.rb +4 -2
  96. data/test/integration/template_test.rb +132 -124
  97. data/test/integration/trim_mode_test.rb +78 -44
  98. data/test/integration/variable_test.rb +74 -32
  99. data/test/test_helper.rb +113 -22
  100. data/test/unit/block_unit_test.rb +19 -24
  101. data/test/unit/condition_unit_test.rb +79 -77
  102. data/test/unit/file_system_unit_test.rb +6 -4
  103. data/test/unit/i18n_unit_test.rb +7 -5
  104. data/test/unit/lexer_unit_test.rb +11 -9
  105. data/test/{integration → unit}/parse_tree_visitor_test.rb +16 -2
  106. data/test/unit/parser_unit_test.rb +37 -35
  107. data/test/unit/partial_cache_unit_test.rb +128 -0
  108. data/test/unit/regexp_unit_test.rb +17 -15
  109. data/test/unit/static_registers_unit_test.rb +156 -0
  110. data/test/unit/strainer_factory_unit_test.rb +100 -0
  111. data/test/unit/strainer_template_unit_test.rb +82 -0
  112. data/test/unit/tag_unit_test.rb +5 -3
  113. data/test/unit/tags/case_tag_unit_test.rb +3 -1
  114. data/test/unit/tags/for_tag_unit_test.rb +4 -2
  115. data/test/unit/tags/if_tag_unit_test.rb +3 -1
  116. data/test/unit/template_factory_unit_test.rb +12 -0
  117. data/test/unit/template_unit_test.rb +19 -10
  118. data/test/unit/tokenizer_unit_test.rb +26 -19
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +76 -50
  121. data/lib/liquid/strainer.rb +0 -66
  122. data/lib/liquid/truffle.rb +0 -5
  123. data/test/truffle/truffle_test.rb +0 -9
  124. data/test/unit/context_unit_test.rb +0 -489
  125. data/test/unit/strainer_unit_test.rb +0 -164
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Capture stores the result of a block into a variable without rendering it inplace.
3
5
  #
@@ -16,17 +18,18 @@ module Liquid
16
18
  def initialize(tag_name, markup, options)
17
19
  super
18
20
  if markup =~ Syntax
19
- @to = $1
21
+ @to = Regexp.last_match(1)
20
22
  else
21
- raise SyntaxError.new(options[:locale].t("errors.syntax.capture"))
23
+ raise SyntaxError, options[:locale].t("errors.syntax.capture")
22
24
  end
23
25
  end
24
26
 
25
- def render(context)
26
- output = super
27
- context.scopes.last[@to] = output
28
- context.resource_limits.assign_score += output.length
29
- ''.freeze
27
+ def render_to_output_buffer(context, output)
28
+ context.resource_limits.with_capture do
29
+ capture_output = render(context)
30
+ context.scopes.last[@to] = capture_output
31
+ end
32
+ output
30
33
  end
31
34
 
32
35
  def blank?
@@ -34,5 +37,5 @@ module Liquid
34
37
  end
35
38
  end
36
39
 
37
- Template.register_tag('capture'.freeze, Capture)
40
+ Template.register_tag('capture', Capture)
38
41
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Case < Block
3
5
  Syntax = /(#{QuotedFragment})/o
@@ -10,17 +12,23 @@ module Liquid
10
12
  @blocks = []
11
13
 
12
14
  if markup =~ Syntax
13
- @left = Expression.parse($1)
15
+ @left = parse_expression(Regexp.last_match(1))
14
16
  else
15
- raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
17
+ raise SyntaxError, options[:locale].t("errors.syntax.case")
16
18
  end
17
19
  end
18
20
 
19
21
  def parse(tokens)
20
- body = BlockBody.new
21
- while parse_body(body, tokens)
22
- body = @blocks.last.attachment
22
+ body = case_body = new_body
23
+ body = @blocks.last.attachment while parse_body(body, tokens)
24
+ @blocks.reverse_each do |condition|
25
+ body = condition.attachment
26
+ unless body.frozen?
27
+ body.remove_blank_strings if blank?
28
+ body.freeze
29
+ end
23
30
  end
31
+ case_body.freeze
24
32
  end
25
33
 
26
34
  def nodelist
@@ -29,45 +37,50 @@ module Liquid
29
37
 
30
38
  def unknown_tag(tag, markup, tokens)
31
39
  case tag
32
- when 'when'.freeze
40
+ when 'when'
33
41
  record_when_condition(markup)
34
- when 'else'.freeze
42
+ when 'else'
35
43
  record_else_condition(markup)
36
44
  else
37
45
  super
38
46
  end
39
47
  end
40
48
 
41
- def render(context)
42
- context.stack do
43
- execute_else_block = true
44
-
45
- output = ''
46
- @blocks.each do |block|
47
- if block.else?
48
- return block.attachment.render(context) if execute_else_block
49
- elsif block.evaluate(context)
50
- execute_else_block = false
51
- output << block.attachment.render(context)
52
- end
49
+ def render_to_output_buffer(context, output)
50
+ execute_else_block = true
51
+
52
+ @blocks.each do |block|
53
+ if block.else?
54
+ block.attachment.render_to_output_buffer(context, output) if execute_else_block
55
+ next
56
+ end
57
+
58
+ result = Liquid::Utils.to_liquid_value(
59
+ block.evaluate(context)
60
+ )
61
+
62
+ if result
63
+ execute_else_block = false
64
+ block.attachment.render_to_output_buffer(context, output)
53
65
  end
54
- output
55
66
  end
67
+
68
+ output
56
69
  end
57
70
 
58
71
  private
59
72
 
60
73
  def record_when_condition(markup)
61
- body = BlockBody.new
74
+ body = new_body
62
75
 
63
76
  while markup
64
77
  unless markup =~ WhenSyntax
65
- raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
78
+ raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_when")
66
79
  end
67
80
 
68
- markup = $2
81
+ markup = Regexp.last_match(2)
69
82
 
70
- block = Condition.new(@left, '=='.freeze, Expression.parse($1))
83
+ block = Condition.new(@left, '==', Condition.parse_expression(parse_context, Regexp.last_match(1)))
71
84
  block.attach(body)
72
85
  @blocks << block
73
86
  end
@@ -75,11 +88,11 @@ module Liquid
75
88
 
76
89
  def record_else_condition(markup)
77
90
  unless markup.strip.empty?
78
- raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze))
91
+ raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_else")
79
92
  end
80
93
 
81
94
  block = ElseCondition.new
82
- block.attach(BlockBody.new)
95
+ block.attach(new_body)
83
96
  @blocks << block
84
97
  end
85
98
 
@@ -90,5 +103,5 @@ module Liquid
90
103
  end
91
104
  end
92
105
 
93
- Template.register_tag('case'.freeze, Case)
106
+ Template.register_tag('case', Case)
94
107
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Comment < Block
3
- def render(_context)
4
- ''.freeze
5
+ def render_to_output_buffer(_context, output)
6
+ output
5
7
  end
6
8
 
7
9
  def unknown_tag(_tag, _markup, _tokens)
@@ -12,5 +14,5 @@ module Liquid
12
14
  end
13
15
  end
14
16
 
15
- Template.register_tag('comment'.freeze, Comment)
17
+ Template.register_tag('comment', Comment)
16
18
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Continue tag to be used to break out of a for loop.
3
5
  #
@@ -9,10 +11,13 @@ module Liquid
9
11
  # {% endfor %}
10
12
  #
11
13
  class Continue < Tag
12
- def interrupt
13
- ContinueInterrupt.new
14
+ INTERRUPT = ContinueInterrupt.new.freeze
15
+
16
+ def render_to_output_buffer(context, output)
17
+ context.push_interrupt(INTERRUPT)
18
+ output
14
19
  end
15
20
  end
16
21
 
17
- Template.register_tag('continue'.freeze, Continue)
22
+ Template.register_tag('continue', Continue)
18
23
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
3
5
  #
@@ -21,28 +23,37 @@ module Liquid
21
23
  super
22
24
  case markup
23
25
  when NamedSyntax
24
- @variables = variables_from_string($2)
25
- @name = Expression.parse($1)
26
+ @variables = variables_from_string(Regexp.last_match(2))
27
+ @name = parse_expression(Regexp.last_match(1))
26
28
  when SimpleSyntax
27
29
  @variables = variables_from_string(markup)
28
- @name = @variables.to_s
30
+ @name = @variables.to_s
29
31
  else
30
- raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
32
+ raise SyntaxError, options[:locale].t("errors.syntax.cycle")
31
33
  end
32
34
  end
33
35
 
34
- def render(context)
36
+ def render_to_output_buffer(context, output)
35
37
  context.registers[:cycle] ||= {}
36
38
 
37
- context.stack do
38
- key = context.evaluate(@name)
39
- iteration = context.registers[:cycle][key].to_i
40
- result = context.evaluate(@variables[iteration])
41
- iteration += 1
42
- iteration = 0 if iteration >= @variables.size
43
- context.registers[:cycle][key] = iteration
44
- result
39
+ key = context.evaluate(@name)
40
+ iteration = context.registers[:cycle][key].to_i
41
+
42
+ val = context.evaluate(@variables[iteration])
43
+
44
+ if val.is_a?(Array)
45
+ val = val.join
46
+ elsif !val.is_a?(String)
47
+ val = val.to_s
45
48
  end
49
+
50
+ output << val
51
+
52
+ iteration += 1
53
+ iteration = 0 if iteration >= @variables.size
54
+
55
+ context.registers[:cycle][key] = iteration
56
+ output
46
57
  end
47
58
 
48
59
  private
@@ -50,7 +61,7 @@ module Liquid
50
61
  def variables_from_string(markup)
51
62
  markup.split(',').collect do |var|
52
63
  var =~ /\s*(#{QuotedFragment})\s*/o
53
- $1 ? Expression.parse($1) : nil
64
+ Regexp.last_match(1) ? parse_expression(Regexp.last_match(1)) : nil
54
65
  end.compact
55
66
  end
56
67
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # decrement is used in a place where one needs to insert a counter
3
5
  # into a template, and needs the counter to survive across
@@ -23,13 +25,14 @@ module Liquid
23
25
  @variable = markup.strip
24
26
  end
25
27
 
26
- def render(context)
28
+ def render_to_output_buffer(context, output)
27
29
  value = context.environments.first[@variable] ||= 0
28
30
  value -= 1
29
31
  context.environments.first[@variable] = value
30
- value.to_s
32
+ output << value.to_s
33
+ output
31
34
  end
32
35
  end
33
36
 
34
- Template.register_tag('decrement'.freeze, Decrement)
37
+ Template.register_tag('decrement', Decrement)
35
38
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ # Echo outputs an expression
5
+ #
6
+ # {% echo monkey %}
7
+ # {% echo user.name %}
8
+ #
9
+ # This is identical to variable output syntax, like {{ foo }}, but works
10
+ # inside {% liquid %} tags. The full syntax is supported, including filters:
11
+ #
12
+ # {% echo user | link %}
13
+ #
14
+ class Echo < Tag
15
+ attr_reader :variable
16
+
17
+ def initialize(tag_name, markup, parse_context)
18
+ super
19
+ @variable = Variable.new(markup, parse_context)
20
+ end
21
+
22
+ def render(context)
23
+ @variable.render_to_output_buffer(context, +'')
24
+ end
25
+
26
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
27
+ def children
28
+ [@node.variable]
29
+ end
30
+ end
31
+ end
32
+
33
+ Template.register_tag('echo', Echo)
34
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # "For" iterates over an array or collection.
3
5
  # Several useful variables are available to you within the loop.
@@ -52,13 +54,20 @@ module Liquid
52
54
  super
53
55
  @from = @limit = nil
54
56
  parse_with_selected_parser(markup)
55
- @for_block = BlockBody.new
57
+ @for_block = new_body
56
58
  @else_block = nil
57
59
  end
58
60
 
59
61
  def parse(tokens)
60
- return unless parse_body(@for_block, tokens)
61
- parse_body(@else_block, tokens)
62
+ if parse_body(@for_block, tokens)
63
+ parse_body(@else_block, tokens)
64
+ end
65
+ if blank?
66
+ @else_block&.remove_blank_strings
67
+ @for_block.remove_blank_strings
68
+ end
69
+ @else_block&.freeze
70
+ @for_block.freeze
62
71
  end
63
72
 
64
73
  def nodelist
@@ -66,49 +75,53 @@ module Liquid
66
75
  end
67
76
 
68
77
  def unknown_tag(tag, markup, tokens)
69
- return super unless tag == 'else'.freeze
70
- @else_block = BlockBody.new
78
+ return super unless tag == 'else'
79
+ @else_block = new_body
71
80
  end
72
81
 
73
- def render(context)
82
+ def render_to_output_buffer(context, output)
74
83
  segment = collection_segment(context)
75
84
 
76
85
  if segment.empty?
77
- render_else(context)
86
+ render_else(context, output)
78
87
  else
79
- render_segment(context, segment)
88
+ render_segment(context, output, segment)
80
89
  end
90
+
91
+ output
81
92
  end
82
93
 
83
94
  protected
84
95
 
85
96
  def lax_parse(markup)
86
97
  if markup =~ Syntax
87
- @variable_name = $1
88
- collection_name = $2
89
- @reversed = !!$3
90
- @name = "#{@variable_name}-#{collection_name}"
91
- @collection_name = Expression.parse(collection_name)
98
+ @variable_name = Regexp.last_match(1)
99
+ collection_name = Regexp.last_match(2)
100
+ @reversed = !!Regexp.last_match(3)
101
+ @name = "#{@variable_name}-#{collection_name}"
102
+ @collection_name = parse_expression(collection_name)
92
103
  markup.scan(TagAttributes) do |key, value|
93
104
  set_attribute(key, value)
94
105
  end
95
106
  else
96
- raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
107
+ raise SyntaxError, options[:locale].t("errors.syntax.for")
97
108
  end
98
109
  end
99
110
 
100
111
  def strict_parse(markup)
101
112
  p = Parser.new(markup)
102
113
  @variable_name = p.consume(:id)
103
- raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
104
- collection_name = p.expression
105
- @name = "#{@variable_name}-#{collection_name}"
106
- @collection_name = Expression.parse(collection_name)
107
- @reversed = p.id?('reversed'.freeze)
114
+ raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
115
+
116
+ collection_name = p.expression
117
+ @collection_name = parse_expression(collection_name)
118
+
119
+ @name = "#{@variable_name}-#{collection_name}"
120
+ @reversed = p.id?('reversed')
108
121
 
109
122
  while p.look(:id) && p.look(:colon, 1)
110
- unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
111
- raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze))
123
+ unless (attribute = p.id?('limit') || p.id?('offset'))
124
+ raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute")
112
125
  end
113
126
  p.consume
114
127
  set_attribute(attribute, p.expression)
@@ -124,14 +137,23 @@ module Liquid
124
137
  from = if @from == :continue
125
138
  offsets[@name].to_i
126
139
  else
127
- context.evaluate(@from).to_i
140
+ from_value = context.evaluate(@from)
141
+ if from_value.nil?
142
+ 0
143
+ else
144
+ Utils.to_integer(from_value)
145
+ end
128
146
  end
129
147
 
130
148
  collection = context.evaluate(@collection_name)
131
149
  collection = collection.to_a if collection.is_a?(Range)
132
150
 
133
- limit = context.evaluate(@limit)
134
- to = limit ? limit.to_i + from : nil
151
+ limit_value = context.evaluate(@limit)
152
+ to = if limit_value.nil?
153
+ nil
154
+ else
155
+ Utils.to_integer(limit_value) + from
156
+ end
135
157
 
136
158
  segment = Utils.slice_collection(collection, from, to)
137
159
  segment.reverse! if @reversed
@@ -141,11 +163,9 @@ module Liquid
141
163
  segment
142
164
  end
143
165
 
144
- def render_segment(context, segment)
166
+ def render_segment(context, output, segment)
145
167
  for_stack = context.registers[:for_stack] ||= []
146
- length = segment.length
147
-
148
- result = ''
168
+ length = segment.length
149
169
 
150
170
  context.stack do
151
171
  loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
@@ -153,43 +173,47 @@ module Liquid
153
173
  for_stack.push(loop_vars)
154
174
 
155
175
  begin
156
- context['forloop'.freeze] = loop_vars
176
+ context['forloop'] = loop_vars
157
177
 
158
178
  segment.each do |item|
159
179
  context[@variable_name] = item
160
- result << @for_block.render(context)
180
+ @for_block.render_to_output_buffer(context, output)
161
181
  loop_vars.send(:increment!)
162
182
 
163
183
  # Handle any interrupts if they exist.
164
- if context.interrupt?
165
- interrupt = context.pop_interrupt
166
- break if interrupt.is_a? BreakInterrupt
167
- next if interrupt.is_a? ContinueInterrupt
168
- end
184
+ next unless context.interrupt?
185
+ interrupt = context.pop_interrupt
186
+ break if interrupt.is_a?(BreakInterrupt)
187
+ next if interrupt.is_a?(ContinueInterrupt)
169
188
  end
170
189
  ensure
171
190
  for_stack.pop
172
191
  end
173
192
  end
174
193
 
175
- result
194
+ output
176
195
  end
177
196
 
178
197
  def set_attribute(key, expr)
179
198
  case key
180
- when 'offset'.freeze
181
- @from = if expr == 'continue'.freeze
199
+ when 'offset'
200
+ @from = if expr == 'continue'
201
+ Usage.increment('for_offset_continue')
182
202
  :continue
183
203
  else
184
- Expression.parse(expr)
204
+ parse_expression(expr)
185
205
  end
186
- when 'limit'.freeze
187
- @limit = Expression.parse(expr)
206
+ when 'limit'
207
+ @limit = parse_expression(expr)
188
208
  end
189
209
  end
190
210
 
191
- def render_else(context)
192
- @else_block ? @else_block.render(context) : ''.freeze
211
+ def render_else(context, output)
212
+ if @else_block
213
+ @else_block.render_to_output_buffer(context, output)
214
+ else
215
+ output
216
+ end
193
217
  end
194
218
 
195
219
  class ParseTreeVisitor < Liquid::ParseTreeVisitor
@@ -199,5 +223,5 @@ module Liquid
199
223
  end
200
224
  end
201
225
 
202
- Template.register_tag('for'.freeze, For)
226
+ Template.register_tag('for', For)
203
227
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # If is the conditional block
3
5
  #
@@ -10,16 +12,16 @@ module Liquid
10
12
  # There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
11
13
  #
12
14
  class If < Block
13
- Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
15
+ Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
14
16
  ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
15
- BOOLEAN_OPERATORS = %w(and or).freeze
17
+ BOOLEAN_OPERATORS = %w(and or).freeze
16
18
 
17
19
  attr_reader :blocks
18
20
 
19
21
  def initialize(tag_name, markup, options)
20
22
  super
21
23
  @blocks = []
22
- push_block('if'.freeze, markup)
24
+ push_block('if', markup)
23
25
  end
24
26
 
25
27
  def nodelist
@@ -29,53 +31,67 @@ module Liquid
29
31
  def parse(tokens)
30
32
  while parse_body(@blocks.last.attachment, tokens)
31
33
  end
34
+ @blocks.reverse_each do |block|
35
+ block.attachment.remove_blank_strings if blank?
36
+ block.attachment.freeze
37
+ end
32
38
  end
33
39
 
40
+ ELSE_TAG_NAMES = ['elsif', 'else'].freeze
41
+ private_constant :ELSE_TAG_NAMES
42
+
34
43
  def unknown_tag(tag, markup, tokens)
35
- if ['elsif'.freeze, 'else'.freeze].include?(tag)
44
+ if ELSE_TAG_NAMES.include?(tag)
36
45
  push_block(tag, markup)
37
46
  else
38
47
  super
39
48
  end
40
49
  end
41
50
 
42
- def render(context)
43
- context.stack do
44
- @blocks.each do |block|
45
- if block.evaluate(context)
46
- return block.attachment.render(context)
47
- end
51
+ def render_to_output_buffer(context, output)
52
+ @blocks.each do |block|
53
+ result = Liquid::Utils.to_liquid_value(
54
+ block.evaluate(context)
55
+ )
56
+
57
+ if result
58
+ return block.attachment.render_to_output_buffer(context, output)
48
59
  end
49
- ''.freeze
50
60
  end
61
+
62
+ output
51
63
  end
52
64
 
53
65
  private
54
66
 
55
67
  def push_block(tag, markup)
56
- block = if tag == 'else'.freeze
68
+ block = if tag == 'else'
57
69
  ElseCondition.new
58
70
  else
59
71
  parse_with_selected_parser(markup)
60
72
  end
61
73
 
62
74
  @blocks.push(block)
63
- block.attach(BlockBody.new)
75
+ block.attach(new_body)
76
+ end
77
+
78
+ def parse_expression(markup)
79
+ Condition.parse_expression(parse_context, markup)
64
80
  end
65
81
 
66
82
  def lax_parse(markup)
67
83
  expressions = markup.scan(ExpressionsAndOperators)
68
- raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop =~ Syntax
84
+ raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop =~ Syntax
69
85
 
70
- condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
86
+ condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))
71
87
 
72
88
  until expressions.empty?
73
89
  operator = expressions.pop.to_s.strip
74
90
 
75
- raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop.to_s =~ Syntax
91
+ raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop.to_s =~ Syntax
76
92
 
77
- new_condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
78
- raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator)
93
+ new_condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))
94
+ raise SyntaxError, options[:locale].t("errors.syntax.if") unless BOOLEAN_OPERATORS.include?(operator)
79
95
  new_condition.send(operator, condition)
80
96
  condition = new_condition
81
97
  end
@@ -93,7 +109,7 @@ module Liquid
93
109
  def parse_binary_comparisons(p)
94
110
  condition = parse_comparison(p)
95
111
  first_condition = condition
96
- while op = (p.id?('and'.freeze) || p.id?('or'.freeze))
112
+ while (op = (p.id?('and') || p.id?('or')))
97
113
  child_condition = parse_comparison(p)
98
114
  condition.send(op, child_condition)
99
115
  condition = child_condition
@@ -102,9 +118,9 @@ module Liquid
102
118
  end
103
119
 
104
120
  def parse_comparison(p)
105
- a = Expression.parse(p.expression)
106
- if op = p.consume?(:comparison)
107
- b = Expression.parse(p.expression)
121
+ a = parse_expression(p.expression)
122
+ if (op = p.consume?(:comparison))
123
+ b = parse_expression(p.expression)
108
124
  Condition.new(a, op, b)
109
125
  else
110
126
  Condition.new(a)
@@ -118,5 +134,5 @@ module Liquid
118
134
  end
119
135
  end
120
136
 
121
- Template.register_tag('if'.freeze, If)
137
+ Template.register_tag('if', If)
122
138
  end