mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-12 23:40:45 -07:00
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:
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user