added interrupt class for continue/break statements

When a continue or break statement is executed it pushes an interrupt to a
stack in context. If any non-handled interrupts are present blocks will cease
to execute. The for loop can handle the most recent interrupt in the stack.
This commit is contained in:
Jon Daniel
2012-08-21 13:14:27 -04:00
parent 484fd18612
commit 9c183bea83
11 changed files with 171 additions and 37 deletions
+1
View File
@@ -48,6 +48,7 @@ end
require 'liquid/drop'
require 'liquid/extensions'
require 'liquid/errors'
require 'liquid/interrupts'
require 'liquid/strainer'
require 'liquid/context'
require 'liquid/tag'
+18 -4
View File
@@ -89,13 +89,27 @@ module Liquid
end
def render_all(list, context)
list.collect do |token|
output = []
list.each do |token|
# Break out if we have any unhanded interrupts.
break if context.has_interrupt?
begin
token.respond_to?(:render) ? token.render(context) : token
# If we get an Interrupt that means the block must stop processing. An
# Interrupt is any command that stops block execution such as {% break %}
# or {% continue %}
if token.is_a? Continue or token.is_a? Break
context.push_interrupt(token.interrupt)
break
end
output << (token.respond_to?(:render) ? token.render(context) : token)
rescue ::StandardError => e
context.handle_error(e)
output << (context.handle_error(e))
end
end.join
end
output.join
end
end
end
+17
View File
@@ -22,6 +22,8 @@ module Liquid
@errors = []
@rethrow_errors = rethrow_errors
squash_instance_assigns_with_environments
@interrupts = []
end
def strainer
@@ -41,6 +43,21 @@ module Liquid
end
end
# are there any not handled interrupts?
def has_interrupt?
!@interrupts.empty?
end
# push an interrupt to the stack. this interrupt is considered not handled.
def push_interrupt(e)
@interrupts.push(e)
end
# pop an interrupt from the stack
def pop_interrupt
@interrupts.pop
end
def handle_error(e)
errors.push(e)
raise if @rethrow_errors
-5
View File
@@ -8,9 +8,4 @@ module Liquid
class StandardError < Error; end
class SyntaxError < Error; end
class StackLevelError < Error; end
class Interrupt < Error; end
class BreakInterrupt < Interrupt; end
class ContinueInterrupt < Interrupt; end
end
+17
View File
@@ -0,0 +1,17 @@
module Liquid
# An interrupt is any command that breaks processing of a block (ex: a for loop).
class Interrupt
attr_reader :message
def initialize(message=nil)
@message = message || "interrupt"
end
end
# Interrupt that is thrown whenever a {% break %} is called.
class BreakInterrupt < Interrupt; end
# Interrupt that is thrown whenever a {% continue %} is called.
class ContinueInterrupt < Interrupt; end
end
+4 -7
View File
@@ -9,15 +9,12 @@ module Liquid
# {% endif %}
# {% endfor %}
#
class Break < Tag
class Break < Tag
##
# Add an interrupt to context errors so a for loop can check
# for interrupts.
def render(context)
context.handle_error(BreakInterrupt.new)
def interrupt
BreakInterrupt.new
end
end
Template.register_tag('break', Break)
+3 -6
View File
@@ -11,13 +11,10 @@ module Liquid
#
class Continue < Tag
##
# Add an interrupt to context errors so a for loop can check
# for interrupts.
def render(context)
context.handle_error(ContinueInterrupt.new)
def interrupt
ContinueInterrupt.new
end
end
Template.register_tag('continue', Continue)
+6 -11
View File
@@ -114,19 +114,14 @@ module Liquid
'first' => (index == 0),
'last' => (index == length - 1) }
rendered = render_all(@for_block, context)
result << render_all(@for_block, context)
if context.errors.last.is_a? BreakInterrupt
context.errors.pop
break
# Handle any interrupts if they exist.
if context.has_interrupt?
interrupt = context.pop_interrupt
break if interrupt.is_a? BreakInterrupt
next if interrupt.is_a? ContinueInterrupt
end
if context.errors.last.is_a? ContinueInterrupt
context.errors.pop
next
end
result << rendered
end
end
result