liquid 4.0.0 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +101 -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 +192 -76
  7. data/lib/liquid/condition.rb +69 -29
  8. data/lib/liquid/context.rb +110 -53
  9. data/lib/liquid/document.rb +47 -9
  10. data/lib/liquid/drop.rb +4 -2
  11. data/lib/liquid/errors.rb +20 -18
  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 +21 -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 +170 -63
  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 +34 -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 +53 -72
  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 +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 +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 +513 -210
  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 +123 -120
  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 +254 -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 +26 -19
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +79 -46
  121. data/lib/liquid/strainer.rb +0 -66
  122. data/test/unit/context_unit_test.rb +0 -483
  123. data/test/unit/strainer_unit_test.rb +0 -148
@@ -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,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.
@@ -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