mirror of
https://github.com/Shopify/liquid.git
synced 2025-08-25 00:00:16 -04:00
132 lines
4.1 KiB
Ruby
132 lines
4.1 KiB
Ruby
module Liquid
|
|
class BlockBody
|
|
FullToken = /\A#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
|
|
ContentOfVariable = /\A#{VariableStart}(.*)#{VariableEnd}\z/om
|
|
TAGSTART = "{%".freeze
|
|
VARSTART = "{{".freeze
|
|
|
|
attr_reader :nodelist
|
|
|
|
def initialize
|
|
@nodelist = []
|
|
@blank = true
|
|
end
|
|
|
|
def parse(tokens, options)
|
|
while token = tokens.shift
|
|
begin
|
|
unless token.empty?
|
|
case
|
|
when token.start_with?(TAGSTART)
|
|
if token =~ FullToken
|
|
tag_name = $1
|
|
markup = $2
|
|
# fetch the tag from registered blocks
|
|
if tag = Template.tags[tag_name]
|
|
markup = token.child(markup) if token.is_a?(Token)
|
|
new_tag = tag.parse(tag_name, markup, tokens, options)
|
|
new_tag.line_number = token.line_number if token.is_a?(Token)
|
|
@blank &&= new_tag.blank?
|
|
@nodelist << new_tag
|
|
else
|
|
# end parsing if we reach an unknown tag and let the caller decide
|
|
# determine how to proceed
|
|
return yield tag_name, markup
|
|
end
|
|
else
|
|
raise_missing_tag_terminator(token, options)
|
|
end
|
|
when token.start_with?(VARSTART)
|
|
new_var = create_variable(token, options)
|
|
new_var.line_number = token.line_number if token.is_a?(Token)
|
|
@nodelist << new_var
|
|
@blank = false
|
|
else
|
|
@nodelist << token
|
|
@blank &&= (token =~ /\A\s*\z/)
|
|
end
|
|
end
|
|
rescue SyntaxError => e
|
|
e.set_line_number_from_token(token)
|
|
raise
|
|
end
|
|
end
|
|
|
|
yield nil, nil
|
|
end
|
|
|
|
def blank?
|
|
@blank
|
|
end
|
|
|
|
def warnings
|
|
all_warnings = []
|
|
nodelist.each do |node|
|
|
all_warnings.concat(node.warnings) if node.respond_to?(:warnings) && node.warnings
|
|
end
|
|
all_warnings
|
|
end
|
|
|
|
def render(context)
|
|
output = []
|
|
context.resource_limits[:render_length_current] = 0
|
|
context.resource_limits[:render_score_current] += @nodelist.length
|
|
|
|
@nodelist.each do |token|
|
|
# Break out if we have any unhanded interrupts.
|
|
break if context.has_interrupt?
|
|
|
|
begin
|
|
# 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
|
|
|
|
token_output = render_token(token, context)
|
|
|
|
unless token.is_a?(Block) && token.blank?
|
|
output << token_output
|
|
end
|
|
rescue MemoryError => e
|
|
raise e
|
|
rescue ::StandardError => e
|
|
output << context.handle_error(e, token)
|
|
end
|
|
end
|
|
|
|
output.join
|
|
end
|
|
|
|
private
|
|
|
|
def render_token(token, context)
|
|
token_output = (token.respond_to?(:render) ? token.render(context) : token)
|
|
context.increment_used_resources(:render_length_current, token_output)
|
|
if context.resource_limits_reached?
|
|
context.resource_limits[:reached] = true
|
|
raise MemoryError.new("Memory limits exceeded".freeze)
|
|
end
|
|
token_output
|
|
end
|
|
|
|
def create_variable(token, options)
|
|
token.scan(ContentOfVariable) do |content|
|
|
markup = token.is_a?(Token) ? token.child(content.first) : content.first
|
|
return Variable.new(markup, options)
|
|
end
|
|
raise_missing_variable_terminator(token, options)
|
|
end
|
|
|
|
def raise_missing_tag_terminator(token, options)
|
|
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
|
|
end
|
|
|
|
def raise_missing_variable_terminator(token, options)
|
|
raise SyntaxError.new(options[:locale].t("errors.syntax.variable_termination".freeze, :token => token, :tag_end => VariableEnd.inspect))
|
|
end
|
|
end
|
|
end
|