liquid 4.0.0.rc3 → 5.0.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 (123) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +93 -2
  3. data/README.md +8 -0
  4. data/lib/liquid.rb +18 -5
  5. data/lib/liquid/block.rb +47 -20
  6. data/lib/liquid/block_body.rb +190 -76
  7. data/lib/liquid/condition.rb +69 -29
  8. data/lib/liquid/context.rb +122 -76
  9. data/lib/liquid/document.rb +47 -9
  10. data/lib/liquid/drop.rb +4 -2
  11. data/lib/liquid/errors.rb +20 -25
  12. data/lib/liquid/expression.rb +30 -31
  13. data/lib/liquid/extensions.rb +8 -0
  14. data/lib/liquid/file_system.rb +6 -4
  15. data/lib/liquid/forloop_drop.rb +11 -4
  16. data/lib/liquid/i18n.rb +5 -3
  17. data/lib/liquid/interrupts.rb +3 -1
  18. data/lib/liquid/lexer.rb +35 -26
  19. data/lib/liquid/locales/en.yml +4 -2
  20. data/lib/liquid/parse_context.rb +17 -4
  21. data/lib/liquid/parse_tree_visitor.rb +42 -0
  22. data/lib/liquid/parser.rb +30 -18
  23. data/lib/liquid/parser_switching.rb +17 -3
  24. data/lib/liquid/partial_cache.rb +24 -0
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/profiler/hooks.rb +26 -14
  27. data/lib/liquid/range_lookup.rb +5 -3
  28. data/lib/liquid/register.rb +6 -0
  29. data/lib/liquid/resource_limits.rb +47 -8
  30. data/lib/liquid/standardfilters.rb +171 -57
  31. data/lib/liquid/static_registers.rb +44 -0
  32. data/lib/liquid/strainer_factory.rb +36 -0
  33. data/lib/liquid/strainer_template.rb +53 -0
  34. data/lib/liquid/tablerowloop_drop.rb +6 -4
  35. data/lib/liquid/tag.rb +28 -6
  36. data/lib/liquid/tag/disableable.rb +22 -0
  37. data/lib/liquid/tag/disabler.rb +21 -0
  38. data/lib/liquid/tags/assign.rb +32 -10
  39. data/lib/liquid/tags/break.rb +8 -3
  40. data/lib/liquid/tags/capture.rb +11 -8
  41. data/lib/liquid/tags/case.rb +41 -27
  42. data/lib/liquid/tags/comment.rb +5 -3
  43. data/lib/liquid/tags/continue.rb +8 -3
  44. data/lib/liquid/tags/cycle.rb +35 -16
  45. data/lib/liquid/tags/decrement.rb +6 -3
  46. data/lib/liquid/tags/echo.rb +26 -0
  47. data/lib/liquid/tags/for.rb +79 -47
  48. data/lib/liquid/tags/if.rb +53 -30
  49. data/lib/liquid/tags/ifchanged.rb +11 -10
  50. data/lib/liquid/tags/include.rb +42 -44
  51. data/lib/liquid/tags/increment.rb +7 -3
  52. data/lib/liquid/tags/raw.rb +14 -11
  53. data/lib/liquid/tags/render.rb +84 -0
  54. data/lib/liquid/tags/table_row.rb +32 -20
  55. data/lib/liquid/tags/unless.rb +15 -15
  56. data/lib/liquid/template.rb +60 -71
  57. data/lib/liquid/template_factory.rb +9 -0
  58. data/lib/liquid/tokenizer.rb +17 -9
  59. data/lib/liquid/usage.rb +8 -0
  60. data/lib/liquid/utils.rb +6 -4
  61. data/lib/liquid/variable.rb +55 -38
  62. data/lib/liquid/variable_lookup.rb +14 -6
  63. data/lib/liquid/version.rb +3 -1
  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 +58 -0
  67. data/test/integration/capture_test.rb +18 -10
  68. data/test/integration/context_test.rb +608 -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 +90 -60
  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 +24 -8
  77. data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
  78. data/test/integration/security_test.rb +41 -18
  79. data/test/integration/standard_filter_test.rb +523 -205
  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 +109 -53
  86. data/test/integration/tags/if_else_tag_test.rb +5 -3
  87. data/test/integration/tags/include_tag_test.rb +83 -52
  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 +128 -121
  97. data/test/integration/trim_mode_test.rb +82 -44
  98. data/test/integration/variable_test.rb +46 -31
  99. data/test/test_helper.rb +75 -23
  100. data/test/unit/block_unit_test.rb +19 -24
  101. data/test/unit/condition_unit_test.rb +82 -72
  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 +12 -10
  105. data/test/unit/parse_tree_visitor_test.rb +247 -0
  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 +19 -17
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +83 -50
  121. data/lib/liquid/strainer.rb +0 -65
  122. data/test/unit/context_unit_test.rb +0 -483
  123. data/test/unit/strainer_unit_test.rb +0 -136
@@ -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
  #
@@ -15,32 +17,43 @@ module Liquid
15
17
  SimpleSyntax = /\A#{QuotedFragment}+/o
16
18
  NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
17
19
 
20
+ attr_reader :variables
21
+
18
22
  def initialize(tag_name, markup, options)
19
23
  super
20
24
  case markup
21
25
  when NamedSyntax
22
- @variables = variables_from_string($2)
23
- @name = Expression.parse($1)
26
+ @variables = variables_from_string(Regexp.last_match(2))
27
+ @name = parse_expression(Regexp.last_match(1))
24
28
  when SimpleSyntax
25
29
  @variables = variables_from_string(markup)
26
- @name = @variables.to_s
30
+ @name = @variables.to_s
27
31
  else
28
- raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
32
+ raise SyntaxError, options[:locale].t("errors.syntax.cycle")
29
33
  end
30
34
  end
31
35
 
32
- def render(context)
33
- context.registers[:cycle] ||= Hash.new(0)
34
-
35
- context.stack do
36
- key = context.evaluate(@name)
37
- iteration = context.registers[:cycle][key]
38
- result = context.evaluate(@variables[iteration])
39
- iteration += 1
40
- iteration = 0 if iteration >= @variables.size
41
- context.registers[:cycle][key] = iteration
42
- result
36
+ def render_to_output_buffer(context, output)
37
+ context.registers[:cycle] ||= {}
38
+
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
43
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
44
57
  end
45
58
 
46
59
  private
@@ -48,9 +61,15 @@ module Liquid
48
61
  def variables_from_string(markup)
49
62
  markup.split(',').collect do |var|
50
63
  var =~ /\s*(#{QuotedFragment})\s*/o
51
- $1 ? Expression.parse($1) : nil
64
+ Regexp.last_match(1) ? parse_expression(Regexp.last_match(1)) : nil
52
65
  end.compact
53
66
  end
67
+
68
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
69
+ def children
70
+ Array(@node.variables)
71
+ end
72
+ end
54
73
  end
55
74
 
56
75
  Template.register_tag('cycle', Cycle)
@@ -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,26 @@
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
+ def initialize(tag_name, markup, parse_context)
16
+ super
17
+ @variable = Variable.new(markup, parse_context)
18
+ end
19
+
20
+ def render(context)
21
+ @variable.render_to_output_buffer(context, +'')
22
+ end
23
+ end
24
+
25
+ Template.register_tag('echo', Echo)
26
+ 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.
@@ -23,7 +25,7 @@ module Liquid
23
25
  # {{ item.name }}
24
26
  # {% end %}
25
27
  #
26
- # To reverse the for loop simply use {% for item in collection reversed %}
28
+ # To reverse the for loop simply use {% for item in collection reversed %} (note that the flag's spelling is different to the filter `reverse`)
27
29
  #
28
30
  # == Available variables:
29
31
  #
@@ -46,17 +48,26 @@ module Liquid
46
48
  class For < Block
47
49
  Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
48
50
 
51
+ attr_reader :collection_name, :variable_name, :limit, :from
52
+
49
53
  def initialize(tag_name, markup, options)
50
54
  super
51
55
  @from = @limit = nil
52
56
  parse_with_selected_parser(markup)
53
- @for_block = BlockBody.new
57
+ @for_block = new_body
54
58
  @else_block = nil
55
59
  end
56
60
 
57
61
  def parse(tokens)
58
- return unless parse_body(@for_block, tokens)
59
- 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
60
71
  end
61
72
 
62
73
  def nodelist
@@ -64,49 +75,53 @@ module Liquid
64
75
  end
65
76
 
66
77
  def unknown_tag(tag, markup, tokens)
67
- return super unless tag == 'else'.freeze
68
- @else_block = BlockBody.new
78
+ return super unless tag == 'else'
79
+ @else_block = new_body
69
80
  end
70
81
 
71
- def render(context)
82
+ def render_to_output_buffer(context, output)
72
83
  segment = collection_segment(context)
73
84
 
74
85
  if segment.empty?
75
- render_else(context)
86
+ render_else(context, output)
76
87
  else
77
- render_segment(context, segment)
88
+ render_segment(context, output, segment)
78
89
  end
90
+
91
+ output
79
92
  end
80
93
 
81
94
  protected
82
95
 
83
96
  def lax_parse(markup)
84
97
  if markup =~ Syntax
85
- @variable_name = $1
86
- collection_name = $2
87
- @reversed = !!$3
88
- @name = "#{@variable_name}-#{collection_name}"
89
- @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)
90
103
  markup.scan(TagAttributes) do |key, value|
91
104
  set_attribute(key, value)
92
105
  end
93
106
  else
94
- raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
107
+ raise SyntaxError, options[:locale].t("errors.syntax.for")
95
108
  end
96
109
  end
97
110
 
98
111
  def strict_parse(markup)
99
112
  p = Parser.new(markup)
100
113
  @variable_name = p.consume(:id)
101
- raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
102
- collection_name = p.expression
103
- @name = "#{@variable_name}-#{collection_name}"
104
- @collection_name = Expression.parse(collection_name)
105
- @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')
106
121
 
107
122
  while p.look(:id) && p.look(:colon, 1)
108
- unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
109
- 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")
110
125
  end
111
126
  p.consume
112
127
  set_attribute(attribute, p.expression)
@@ -117,19 +132,28 @@ module Liquid
117
132
  private
118
133
 
119
134
  def collection_segment(context)
120
- offsets = context.registers[:for] ||= Hash.new(0)
135
+ offsets = context.registers[:for] ||= {}
121
136
 
122
137
  from = if @from == :continue
123
138
  offsets[@name].to_i
124
139
  else
125
- 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
126
146
  end
127
147
 
128
148
  collection = context.evaluate(@collection_name)
129
149
  collection = collection.to_a if collection.is_a?(Range)
130
150
 
131
- limit = context.evaluate(@limit)
132
- 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
133
157
 
134
158
  segment = Utils.slice_collection(collection, from, to)
135
159
  segment.reverse! if @reversed
@@ -139,11 +163,9 @@ module Liquid
139
163
  segment
140
164
  end
141
165
 
142
- def render_segment(context, segment)
166
+ def render_segment(context, output, segment)
143
167
  for_stack = context.registers[:for_stack] ||= []
144
- length = segment.length
145
-
146
- result = ''
168
+ length = segment.length
147
169
 
148
170
  context.stack do
149
171
  loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
@@ -151,45 +173,55 @@ module Liquid
151
173
  for_stack.push(loop_vars)
152
174
 
153
175
  begin
154
- context['forloop'.freeze] = loop_vars
176
+ context['forloop'] = loop_vars
155
177
 
156
- segment.each_with_index do |item, index|
178
+ segment.each do |item|
157
179
  context[@variable_name] = item
158
- result << @for_block.render(context)
180
+ @for_block.render_to_output_buffer(context, output)
159
181
  loop_vars.send(:increment!)
160
182
 
161
183
  # Handle any interrupts if they exist.
162
- if context.interrupt?
163
- interrupt = context.pop_interrupt
164
- break if interrupt.is_a? BreakInterrupt
165
- next if interrupt.is_a? ContinueInterrupt
166
- 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)
167
188
  end
168
189
  ensure
169
190
  for_stack.pop
170
191
  end
171
192
  end
172
193
 
173
- result
194
+ output
174
195
  end
175
196
 
176
197
  def set_attribute(key, expr)
177
198
  case key
178
- when 'offset'.freeze
179
- @from = if expr == 'continue'.freeze
199
+ when 'offset'
200
+ @from = if expr == 'continue'
201
+ Usage.increment('for_offset_continue')
180
202
  :continue
181
203
  else
182
- Expression.parse(expr)
204
+ parse_expression(expr)
183
205
  end
184
- when 'limit'.freeze
185
- @limit = Expression.parse(expr)
206
+ when 'limit'
207
+ @limit = parse_expression(expr)
208
+ end
209
+ end
210
+
211
+ def render_else(context, output)
212
+ if @else_block
213
+ @else_block.render_to_output_buffer(context, output)
214
+ else
215
+ output
186
216
  end
187
217
  end
188
218
 
189
- def render_else(context)
190
- @else_block ? @else_block.render(context) : ''.freeze
219
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
220
+ def children
221
+ (super + [@node.limit, @node.from, @node.collection_name]).compact
222
+ end
191
223
  end
192
224
  end
193
225
 
194
- Template.register_tag('for'.freeze, For)
226
+ Template.register_tag('for', For)
195
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,70 +12,82 @@ 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)
17
+ BOOLEAN_OPERATORS = %w(and or).freeze
18
+
19
+ attr_reader :blocks
16
20
 
17
21
  def initialize(tag_name, markup, options)
18
22
  super
19
23
  @blocks = []
20
- push_block('if'.freeze, markup)
24
+ push_block('if', markup)
25
+ end
26
+
27
+ def nodelist
28
+ @blocks.map(&:attachment)
21
29
  end
22
30
 
23
31
  def parse(tokens)
24
32
  while parse_body(@blocks.last.attachment, tokens)
25
33
  end
34
+ @blocks.reverse_each do |block|
35
+ block.attachment.remove_blank_strings if blank?
36
+ block.attachment.freeze
37
+ end
26
38
  end
27
39
 
28
- def nodelist
29
- @blocks.map(&:attachment)
30
- end
40
+ ELSE_TAG_NAMES = ['elsif', 'else'].freeze
41
+ private_constant :ELSE_TAG_NAMES
31
42
 
32
43
  def unknown_tag(tag, markup, tokens)
33
- if ['elsif'.freeze, 'else'.freeze].include?(tag)
44
+ if ELSE_TAG_NAMES.include?(tag)
34
45
  push_block(tag, markup)
35
46
  else
36
47
  super
37
48
  end
38
49
  end
39
50
 
40
- def render(context)
41
- context.stack do
42
- @blocks.each do |block|
43
- if block.evaluate(context)
44
- return block.attachment.render(context)
45
- end
51
+ def render_to_output_buffer(context, output)
52
+ @blocks.each do |block|
53
+ if block.evaluate(context)
54
+ return block.attachment.render_to_output_buffer(context, output)
46
55
  end
47
- ''.freeze
48
56
  end
57
+
58
+ output
49
59
  end
50
60
 
51
61
  private
52
62
 
53
63
  def push_block(tag, markup)
54
- block = if tag == 'else'.freeze
64
+ block = if tag == 'else'
55
65
  ElseCondition.new
56
66
  else
57
67
  parse_with_selected_parser(markup)
58
68
  end
59
69
 
60
70
  @blocks.push(block)
61
- block.attach(BlockBody.new)
71
+ block.attach(new_body)
72
+ end
73
+
74
+ def parse_expression(markup)
75
+ Condition.parse_expression(parse_context, markup)
62
76
  end
63
77
 
64
78
  def lax_parse(markup)
65
79
  expressions = markup.scan(ExpressionsAndOperators)
66
- raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop =~ Syntax
80
+ raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop =~ Syntax
67
81
 
68
- condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
82
+ condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))
69
83
 
70
84
  until expressions.empty?
71
85
  operator = expressions.pop.to_s.strip
72
86
 
73
- raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop.to_s =~ Syntax
87
+ raise SyntaxError, options[:locale].t("errors.syntax.if") unless expressions.pop.to_s =~ Syntax
74
88
 
75
- new_condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
76
- raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator)
89
+ new_condition = Condition.new(parse_expression(Regexp.last_match(1)), Regexp.last_match(2), parse_expression(Regexp.last_match(3)))
90
+ raise SyntaxError, options[:locale].t("errors.syntax.if") unless BOOLEAN_OPERATORS.include?(operator)
77
91
  new_condition.send(operator, condition)
78
92
  condition = new_condition
79
93
  end
@@ -83,29 +97,38 @@ module Liquid
83
97
 
84
98
  def strict_parse(markup)
85
99
  p = Parser.new(markup)
86
- condition = parse_binary_comparison(p)
100
+ condition = parse_binary_comparisons(p)
87
101
  p.consume(:end_of_string)
88
102
  condition
89
103
  end
90
104
 
91
- def parse_binary_comparison(p)
105
+ def parse_binary_comparisons(p)
92
106
  condition = parse_comparison(p)
93
- if op = (p.id?('and'.freeze) || p.id?('or'.freeze))
94
- condition.send(op, parse_binary_comparison(p))
107
+ first_condition = condition
108
+ while (op = (p.id?('and') || p.id?('or')))
109
+ child_condition = parse_comparison(p)
110
+ condition.send(op, child_condition)
111
+ condition = child_condition
95
112
  end
96
- condition
113
+ first_condition
97
114
  end
98
115
 
99
116
  def parse_comparison(p)
100
- a = Expression.parse(p.expression)
101
- if op = p.consume?(:comparison)
102
- b = Expression.parse(p.expression)
117
+ a = parse_expression(p.expression)
118
+ if (op = p.consume?(:comparison))
119
+ b = parse_expression(p.expression)
103
120
  Condition.new(a, op, b)
104
121
  else
105
122
  Condition.new(a)
106
123
  end
107
124
  end
125
+
126
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
127
+ def children
128
+ @node.blocks
129
+ end
130
+ end
108
131
  end
109
132
 
110
- Template.register_tag('if'.freeze, If)
133
+ Template.register_tag('if', If)
111
134
  end