liquid 2.6.1 → 4.0.3

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 (130) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +194 -29
  3. data/{MIT-LICENSE → LICENSE} +0 -0
  4. data/README.md +60 -2
  5. data/lib/liquid.rb +25 -14
  6. data/lib/liquid/block.rb +47 -96
  7. data/lib/liquid/block_body.rb +143 -0
  8. data/lib/liquid/condition.rb +70 -39
  9. data/lib/liquid/context.rb +116 -157
  10. data/lib/liquid/document.rb +19 -9
  11. data/lib/liquid/drop.rb +31 -14
  12. data/lib/liquid/errors.rb +54 -10
  13. data/lib/liquid/expression.rb +49 -0
  14. data/lib/liquid/extensions.rb +19 -7
  15. data/lib/liquid/file_system.rb +25 -14
  16. data/lib/liquid/forloop_drop.rb +42 -0
  17. data/lib/liquid/i18n.rb +39 -0
  18. data/lib/liquid/interrupts.rb +2 -3
  19. data/lib/liquid/lexer.rb +55 -0
  20. data/lib/liquid/locales/en.yml +26 -0
  21. data/lib/liquid/parse_context.rb +38 -0
  22. data/lib/liquid/parse_tree_visitor.rb +42 -0
  23. data/lib/liquid/parser.rb +90 -0
  24. data/lib/liquid/parser_switching.rb +31 -0
  25. data/lib/liquid/profiler.rb +158 -0
  26. data/lib/liquid/profiler/hooks.rb +23 -0
  27. data/lib/liquid/range_lookup.rb +37 -0
  28. data/lib/liquid/resource_limits.rb +23 -0
  29. data/lib/liquid/standardfilters.rb +311 -77
  30. data/lib/liquid/strainer.rb +39 -26
  31. data/lib/liquid/tablerowloop_drop.rb +62 -0
  32. data/lib/liquid/tag.rb +28 -11
  33. data/lib/liquid/tags/assign.rb +34 -10
  34. data/lib/liquid/tags/break.rb +1 -4
  35. data/lib/liquid/tags/capture.rb +11 -9
  36. data/lib/liquid/tags/case.rb +37 -22
  37. data/lib/liquid/tags/comment.rb +10 -3
  38. data/lib/liquid/tags/continue.rb +1 -4
  39. data/lib/liquid/tags/cycle.rb +20 -14
  40. data/lib/liquid/tags/decrement.rb +4 -8
  41. data/lib/liquid/tags/for.rb +121 -60
  42. data/lib/liquid/tags/if.rb +73 -30
  43. data/lib/liquid/tags/ifchanged.rb +3 -5
  44. data/lib/liquid/tags/include.rb +77 -46
  45. data/lib/liquid/tags/increment.rb +4 -8
  46. data/lib/liquid/tags/raw.rb +35 -10
  47. data/lib/liquid/tags/table_row.rb +62 -0
  48. data/lib/liquid/tags/unless.rb +6 -9
  49. data/lib/liquid/template.rb +130 -32
  50. data/lib/liquid/tokenizer.rb +31 -0
  51. data/lib/liquid/truffle.rb +5 -0
  52. data/lib/liquid/utils.rb +57 -4
  53. data/lib/liquid/variable.rb +121 -30
  54. data/lib/liquid/variable_lookup.rb +88 -0
  55. data/lib/liquid/version.rb +2 -1
  56. data/test/fixtures/en_locale.yml +9 -0
  57. data/test/integration/assign_test.rb +48 -0
  58. data/test/integration/blank_test.rb +106 -0
  59. data/test/integration/block_test.rb +12 -0
  60. data/test/{liquid → integration}/capture_test.rb +13 -3
  61. data/test/integration/context_test.rb +32 -0
  62. data/test/integration/document_test.rb +19 -0
  63. data/test/integration/drop_test.rb +273 -0
  64. data/test/integration/error_handling_test.rb +260 -0
  65. data/test/integration/filter_test.rb +178 -0
  66. data/test/integration/hash_ordering_test.rb +23 -0
  67. data/test/integration/output_test.rb +123 -0
  68. data/test/integration/parse_tree_visitor_test.rb +247 -0
  69. data/test/integration/parsing_quirks_test.rb +122 -0
  70. data/test/integration/render_profiling_test.rb +154 -0
  71. data/test/integration/security_test.rb +80 -0
  72. data/test/integration/standard_filter_test.rb +776 -0
  73. data/test/{liquid → integration}/tags/break_tag_test.rb +2 -3
  74. data/test/{liquid → integration}/tags/continue_tag_test.rb +1 -2
  75. data/test/integration/tags/for_tag_test.rb +410 -0
  76. data/test/integration/tags/if_else_tag_test.rb +188 -0
  77. data/test/integration/tags/include_tag_test.rb +253 -0
  78. data/test/integration/tags/increment_tag_test.rb +23 -0
  79. data/test/{liquid → integration}/tags/raw_tag_test.rb +9 -2
  80. data/test/integration/tags/standard_tag_test.rb +296 -0
  81. data/test/integration/tags/statements_test.rb +111 -0
  82. data/test/{liquid/tags/html_tag_test.rb → integration/tags/table_row_test.rb} +25 -24
  83. data/test/integration/tags/unless_else_tag_test.rb +26 -0
  84. data/test/integration/template_test.rb +332 -0
  85. data/test/integration/trim_mode_test.rb +529 -0
  86. data/test/integration/variable_test.rb +96 -0
  87. data/test/test_helper.rb +106 -19
  88. data/test/truffle/truffle_test.rb +9 -0
  89. data/test/{liquid/block_test.rb → unit/block_unit_test.rb} +9 -9
  90. data/test/unit/condition_unit_test.rb +166 -0
  91. data/test/{liquid/context_test.rb → unit/context_unit_test.rb} +85 -74
  92. data/test/unit/file_system_unit_test.rb +35 -0
  93. data/test/unit/i18n_unit_test.rb +37 -0
  94. data/test/unit/lexer_unit_test.rb +51 -0
  95. data/test/unit/parser_unit_test.rb +82 -0
  96. data/test/{liquid/regexp_test.rb → unit/regexp_unit_test.rb} +4 -4
  97. data/test/unit/strainer_unit_test.rb +164 -0
  98. data/test/unit/tag_unit_test.rb +21 -0
  99. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  100. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  101. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  102. data/test/unit/template_unit_test.rb +78 -0
  103. data/test/unit/tokenizer_unit_test.rb +55 -0
  104. data/test/unit/variable_unit_test.rb +162 -0
  105. metadata +157 -77
  106. data/lib/extras/liquid_view.rb +0 -51
  107. data/lib/liquid/htmltags.rb +0 -74
  108. data/lib/liquid/module_ex.rb +0 -62
  109. data/test/liquid/assign_test.rb +0 -21
  110. data/test/liquid/condition_test.rb +0 -127
  111. data/test/liquid/drop_test.rb +0 -180
  112. data/test/liquid/error_handling_test.rb +0 -81
  113. data/test/liquid/file_system_test.rb +0 -29
  114. data/test/liquid/filter_test.rb +0 -125
  115. data/test/liquid/hash_ordering_test.rb +0 -25
  116. data/test/liquid/module_ex_test.rb +0 -87
  117. data/test/liquid/output_test.rb +0 -116
  118. data/test/liquid/parsing_quirks_test.rb +0 -52
  119. data/test/liquid/security_test.rb +0 -64
  120. data/test/liquid/standard_filter_test.rb +0 -251
  121. data/test/liquid/strainer_test.rb +0 -52
  122. data/test/liquid/tags/for_tag_test.rb +0 -297
  123. data/test/liquid/tags/if_else_tag_test.rb +0 -166
  124. data/test/liquid/tags/include_tag_test.rb +0 -166
  125. data/test/liquid/tags/increment_tag_test.rb +0 -24
  126. data/test/liquid/tags/standard_tag_test.rb +0 -295
  127. data/test/liquid/tags/statements_test.rb +0 -134
  128. data/test/liquid/tags/unless_else_tag_test.rb +0 -26
  129. data/test/liquid/template_test.rb +0 -146
  130. data/test/liquid/variable_test.rb +0 -186
@@ -1,9 +1,16 @@
1
1
  module Liquid
2
2
  class Comment < Block
3
- def render(context)
4
- ''
3
+ def render(_context)
4
+ ''.freeze
5
+ end
6
+
7
+ def unknown_tag(_tag, _markup, _tokens)
8
+ end
9
+
10
+ def blank?
11
+ true
5
12
  end
6
13
  end
7
14
 
8
- Template.register_tag('comment', Comment)
15
+ Template.register_tag('comment'.freeze, Comment)
9
16
  end
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Continue tag to be used to break out of a for loop.
4
3
  #
5
4
  # == Basic Usage:
@@ -10,12 +9,10 @@ module Liquid
10
9
  # {% endfor %}
11
10
  #
12
11
  class Continue < Tag
13
-
14
12
  def interrupt
15
13
  ContinueInterrupt.new
16
14
  end
17
-
18
15
  end
19
16
 
20
- Template.register_tag('continue', Continue)
17
+ Template.register_tag('continue'.freeze, Continue)
21
18
  end
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
4
3
  #
5
4
  # {% for item in items %}
@@ -13,32 +12,34 @@ module Liquid
13
12
  # <div class="green"> Item five</div>
14
13
  #
15
14
  class Cycle < Tag
16
- SimpleSyntax = /^#{QuotedFragment}+/o
17
- NamedSyntax = /^(#{QuotedFragment})\s*\:\s*(.*)/o
15
+ SimpleSyntax = /\A#{QuotedFragment}+/o
16
+ NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
17
+
18
+ attr_reader :variables
18
19
 
19
- def initialize(tag_name, markup, tokens)
20
+ def initialize(tag_name, markup, options)
21
+ super
20
22
  case markup
21
23
  when NamedSyntax
22
24
  @variables = variables_from_string($2)
23
- @name = $1
25
+ @name = Expression.parse($1)
24
26
  when SimpleSyntax
25
27
  @variables = variables_from_string(markup)
26
- @name = "'#{@variables.to_s}'"
28
+ @name = @variables.to_s
27
29
  else
28
- raise SyntaxError.new("Syntax Error in 'cycle' - Valid syntax: cycle [name :] var [, var2, var3 ...]")
30
+ raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
29
31
  end
30
- super
31
32
  end
32
33
 
33
34
  def render(context)
34
- context.registers[:cycle] ||= Hash.new(0)
35
+ context.registers[:cycle] ||= {}
35
36
 
36
37
  context.stack do
37
- key = context[@name]
38
- iteration = context.registers[:cycle][key]
39
- result = context[@variables[iteration]]
38
+ key = context.evaluate(@name)
39
+ iteration = context.registers[:cycle][key].to_i
40
+ result = context.evaluate(@variables[iteration])
40
41
  iteration += 1
41
- iteration = 0 if iteration >= @variables.size
42
+ iteration = 0 if iteration >= @variables.size
42
43
  context.registers[:cycle][key] = iteration
43
44
  result
44
45
  end
@@ -49,10 +50,15 @@ module Liquid
49
50
  def variables_from_string(markup)
50
51
  markup.split(',').collect do |var|
51
52
  var =~ /\s*(#{QuotedFragment})\s*/o
52
- $1 ? $1 : nil
53
+ $1 ? Expression.parse($1) : nil
53
54
  end.compact
54
55
  end
55
56
 
57
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
58
+ def children
59
+ Array(@node.variables)
60
+ end
61
+ end
56
62
  end
57
63
 
58
64
  Template.register_tag('cycle', Cycle)
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # decrement is used in a place where one needs to insert a counter
4
3
  # into a template, and needs the counter to survive across
5
4
  # multiple instantiations of the template.
@@ -19,21 +18,18 @@ module Liquid
19
18
  # Hello: -3
20
19
  #
21
20
  class Decrement < Tag
22
- def initialize(tag_name, markup, tokens)
23
- @variable = markup.strip
24
-
21
+ def initialize(tag_name, markup, options)
25
22
  super
23
+ @variable = markup.strip
26
24
  end
27
25
 
28
26
  def render(context)
29
27
  value = context.environments.first[@variable] ||= 0
30
- value = value - 1
28
+ value -= 1
31
29
  context.environments.first[@variable] = value
32
30
  value.to_s
33
31
  end
34
-
35
- private
36
32
  end
37
33
 
38
- Template.register_tag('decrement', Decrement)
34
+ Template.register_tag('decrement'.freeze, Decrement)
39
35
  end
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # "For" iterates over an array or collection.
4
3
  # Several useful variables are available to you within the loop.
5
4
  #
@@ -24,7 +23,7 @@ module Liquid
24
23
  # {{ item.name }}
25
24
  # {% end %}
26
25
  #
27
- # To reverse the for loop simply use {% for item in collection reversed %}
26
+ # To reverse the for loop simply use {% for item in collection reversed %} (note that the flag's spelling is different to the filter `reverse`)
28
27
  #
29
28
  # == Available variables:
30
29
  #
@@ -42,101 +41,163 @@ module Liquid
42
41
  # where 0 is the last item.
43
42
  # forloop.first:: Returns true if the item is the first item.
44
43
  # forloop.last:: Returns true if the item is the last item.
44
+ # forloop.parentloop:: Provides access to the parent loop, if present.
45
45
  #
46
46
  class For < Block
47
- Syntax = /\A(\w+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
47
+ Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
48
48
 
49
- def initialize(tag_name, markup, tokens)
50
- if markup =~ Syntax
51
- @variable_name = $1
52
- @collection_name = $2
53
- @name = "#{$1}-#{$2}"
54
- @reversed = $3
55
- @attributes = {}
56
- markup.scan(TagAttributes) do |key, value|
57
- @attributes[key] = value
58
- end
59
- else
60
- raise SyntaxError.new("Syntax Error in 'for loop' - Valid syntax: for [item] in [collection]")
61
- end
49
+ attr_reader :collection_name, :variable_name, :limit, :from
62
50
 
63
- @nodelist = @for_block = []
51
+ def initialize(tag_name, markup, options)
64
52
  super
53
+ @from = @limit = nil
54
+ parse_with_selected_parser(markup)
55
+ @for_block = BlockBody.new
56
+ @else_block = nil
57
+ end
58
+
59
+ def parse(tokens)
60
+ return unless parse_body(@for_block, tokens)
61
+ parse_body(@else_block, tokens)
62
+ end
63
+
64
+ def nodelist
65
+ @else_block ? [@for_block, @else_block] : [@for_block]
65
66
  end
66
67
 
67
68
  def unknown_tag(tag, markup, tokens)
68
- return super unless tag == 'else'
69
- @nodelist = @else_block = []
69
+ return super unless tag == 'else'.freeze
70
+ @else_block = BlockBody.new
70
71
  end
71
72
 
72
73
  def render(context)
73
- context.registers[:for] ||= Hash.new(0)
74
+ segment = collection_segment(context)
74
75
 
75
- collection = context[@collection_name]
76
- collection = collection.to_a if collection.is_a?(Range)
76
+ if segment.empty?
77
+ render_else(context)
78
+ else
79
+ render_segment(context, segment)
80
+ end
81
+ end
77
82
 
78
- # Maintains Ruby 1.8.7 String#each behaviour on 1.9
79
- return render_else(context) unless iterable?(collection)
83
+ protected
80
84
 
81
- from = if @attributes['offset'] == 'continue'
82
- context.registers[:for][@name].to_i
85
+ def lax_parse(markup)
86
+ 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)
92
+ markup.scan(TagAttributes) do |key, value|
93
+ set_attribute(key, value)
94
+ end
83
95
  else
84
- context[@attributes['offset']].to_i
96
+ raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
85
97
  end
98
+ end
86
99
 
87
- limit = context[@attributes['limit']]
88
- to = limit ? limit.to_i + from : nil
100
+ def strict_parse(markup)
101
+ p = Parser.new(markup)
102
+ @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)
108
+
109
+ 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))
112
+ end
113
+ p.consume
114
+ set_attribute(attribute, p.expression)
115
+ end
116
+ p.consume(:end_of_string)
117
+ end
89
118
 
119
+ private
120
+
121
+ def collection_segment(context)
122
+ offsets = context.registers[:for] ||= {}
123
+
124
+ from = if @from == :continue
125
+ offsets[@name].to_i
126
+ else
127
+ context.evaluate(@from).to_i
128
+ end
90
129
 
91
- segment = Utils.slice_collection_using_each(collection, from, to)
130
+ collection = context.evaluate(@collection_name)
131
+ collection = collection.to_a if collection.is_a?(Range)
92
132
 
93
- return render_else(context) if segment.empty?
133
+ limit = context.evaluate(@limit)
134
+ to = limit ? limit.to_i + from : nil
94
135
 
136
+ segment = Utils.slice_collection(collection, from, to)
95
137
  segment.reverse! if @reversed
96
138
 
97
- result = ''
139
+ offsets[@name] = from + segment.length
140
+
141
+ segment
142
+ end
98
143
 
144
+ def render_segment(context, segment)
145
+ for_stack = context.registers[:for_stack] ||= []
99
146
  length = segment.length
100
147
 
101
- # Store our progress through the collection for the continue flag
102
- context.registers[:for][@name] = from + segment.length
148
+ result = ''
103
149
 
104
150
  context.stack do
105
- segment.each_with_index do |item, index|
106
- context[@variable_name] = item
107
- context['forloop'] = {
108
- 'name' => @name,
109
- 'length' => length,
110
- 'index' => index + 1,
111
- 'index0' => index,
112
- 'rindex' => length - index,
113
- 'rindex0' => length - index - 1,
114
- 'first' => (index == 0),
115
- 'last' => (index == length - 1) }
116
-
117
- result << render_all(@for_block, context)
118
-
119
- # Handle any interrupts if they exist.
120
- if context.has_interrupt?
121
- interrupt = context.pop_interrupt
122
- break if interrupt.is_a? BreakInterrupt
123
- next if interrupt.is_a? ContinueInterrupt
151
+ loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
152
+
153
+ for_stack.push(loop_vars)
154
+
155
+ begin
156
+ context['forloop'.freeze] = loop_vars
157
+
158
+ segment.each do |item|
159
+ context[@variable_name] = item
160
+ result << @for_block.render(context)
161
+ loop_vars.send(:increment!)
162
+
163
+ # 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
124
169
  end
170
+ ensure
171
+ for_stack.pop
125
172
  end
126
173
  end
174
+
127
175
  result
128
176
  end
129
177
 
130
- private
131
-
132
- def render_else(context)
133
- return @else_block ? [render_all(@else_block, context)] : ''
178
+ def set_attribute(key, expr)
179
+ case key
180
+ when 'offset'.freeze
181
+ @from = if expr == 'continue'.freeze
182
+ :continue
183
+ else
184
+ Expression.parse(expr)
185
+ end
186
+ when 'limit'.freeze
187
+ @limit = Expression.parse(expr)
134
188
  end
189
+ end
135
190
 
136
- def iterable?(collection)
137
- collection.respond_to?(:each) || Utils.non_blank_string?(collection)
191
+ def render_else(context)
192
+ @else_block ? @else_block.render(context) : ''.freeze
193
+ end
194
+
195
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
196
+ def children
197
+ (super + [@node.limit, @node.from, @node.collection_name]).compact
138
198
  end
199
+ end
139
200
  end
140
201
 
141
- Template.register_tag('for', For)
202
+ Template.register_tag('for'.freeze, For)
142
203
  end
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # If is the conditional block
4
3
  #
5
4
  # {% if user.admin %}
@@ -10,23 +9,30 @@ module Liquid
10
9
  #
11
10
  # There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
12
11
  #
13
- #
14
12
  class If < Block
15
- SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
16
13
  Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
17
14
  ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
18
- BOOLEAN_OPERATORS = %w(and or)
15
+ BOOLEAN_OPERATORS = %w(and or).freeze
19
16
 
20
- def initialize(tag_name, markup, tokens)
17
+ attr_reader :blocks
18
+
19
+ def initialize(tag_name, markup, options)
20
+ super
21
21
  @blocks = []
22
+ push_block('if'.freeze, markup)
23
+ end
22
24
 
23
- push_block('if', markup)
25
+ def nodelist
26
+ @blocks.map(&:attachment)
27
+ end
24
28
 
25
- super
29
+ def parse(tokens)
30
+ while parse_body(@blocks.last.attachment, tokens)
31
+ end
26
32
  end
27
33
 
28
34
  def unknown_tag(tag, markup, tokens)
29
- if ['elsif', 'else'].include?(tag)
35
+ if ['elsif'.freeze, 'else'.freeze].include?(tag)
30
36
  push_block(tag, markup)
31
37
  else
32
38
  super
@@ -37,43 +43,80 @@ module Liquid
37
43
  context.stack do
38
44
  @blocks.each do |block|
39
45
  if block.evaluate(context)
40
- return render_all(block.attachment, context)
46
+ return block.attachment.render(context)
41
47
  end
42
48
  end
43
- ''
49
+ ''.freeze
44
50
  end
45
51
  end
46
52
 
47
53
  private
48
54
 
49
- def push_block(tag, markup)
50
- block = if tag == 'else'
51
- ElseCondition.new
52
- else
55
+ def push_block(tag, markup)
56
+ block = if tag == 'else'.freeze
57
+ ElseCondition.new
58
+ else
59
+ parse_with_selected_parser(markup)
60
+ end
53
61
 
54
- expressions = markup.scan(ExpressionsAndOperators).reverse
55
- raise(SyntaxError, SyntaxHelp) unless expressions.shift =~ Syntax
62
+ @blocks.push(block)
63
+ block.attach(BlockBody.new)
64
+ end
56
65
 
57
- condition = Condition.new($1, $2, $3)
66
+ def lax_parse(markup)
67
+ expressions = markup.scan(ExpressionsAndOperators)
68
+ raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop =~ Syntax
58
69
 
59
- while not expressions.empty?
60
- operator = (expressions.shift).to_s.strip
70
+ condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
61
71
 
62
- raise(SyntaxError, SyntaxHelp) unless expressions.shift.to_s =~ Syntax
72
+ until expressions.empty?
73
+ operator = expressions.pop.to_s.strip
63
74
 
64
- new_condition = Condition.new($1, $2, $3)
65
- raise SyntaxError, "invalid boolean operator" unless BOOLEAN_OPERATORS.include?(operator)
66
- new_condition.send(operator, condition)
67
- condition = new_condition
68
- end
75
+ raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop.to_s =~ Syntax
69
76
 
70
- condition
71
- end
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)
79
+ new_condition.send(operator, condition)
80
+ condition = new_condition
81
+ end
82
+
83
+ condition
84
+ end
72
85
 
73
- @blocks.push(block)
74
- @nodelist = block.attach(Array.new)
86
+ def strict_parse(markup)
87
+ p = Parser.new(markup)
88
+ condition = parse_binary_comparisons(p)
89
+ p.consume(:end_of_string)
90
+ condition
91
+ end
92
+
93
+ def parse_binary_comparisons(p)
94
+ condition = parse_comparison(p)
95
+ first_condition = condition
96
+ while op = (p.id?('and'.freeze) || p.id?('or'.freeze))
97
+ child_condition = parse_comparison(p)
98
+ condition.send(op, child_condition)
99
+ condition = child_condition
75
100
  end
101
+ first_condition
102
+ end
103
+
104
+ def parse_comparison(p)
105
+ a = Expression.parse(p.expression)
106
+ if op = p.consume?(:comparison)
107
+ b = Expression.parse(p.expression)
108
+ Condition.new(a, op, b)
109
+ else
110
+ Condition.new(a)
111
+ end
112
+ end
113
+
114
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
115
+ def children
116
+ @node.blocks
117
+ end
118
+ end
76
119
  end
77
120
 
78
- Template.register_tag('if', If)
121
+ Template.register_tag('if'.freeze, If)
79
122
  end