liquid without the garbage

This commit is contained in:
Florian Weingarten
2019-04-22 16:34:31 -04:00
parent c2ef247be5
commit 2a1ca3152d
25 changed files with 144 additions and 101 deletions
+2 -2
View File
@@ -13,8 +13,8 @@ module Liquid
end end
end end
def render(context) def render(context, output = '')
@body.render(context) @body.render(context, output)
end end
def blank? def blank?
+10 -14
View File
@@ -66,8 +66,7 @@ module Liquid
@blank @blank
end end
def render(context) def render(context, output = '')
output = []
context.resource_limits.render_score += @nodelist.length context.resource_limits.render_score += @nodelist.length
idx = 0 idx = 0
@@ -77,9 +76,9 @@ module Liquid
check_resources(context, node) check_resources(context, node)
output << node output << node
when Variable when Variable
render_node_to_output(node, output, context) render_node(context, output, node)
when Block when Block
render_node_to_output(node, output, context, node.blank?) render_node(context, node.blank? ? '' : output, node)
break if context.interrupt? # might have happened in a for-block break if context.interrupt? # might have happened in a for-block
when Continue, Break when Continue, Break
# If we get an Interrupt that means the block must stop processing. An # If we get an Interrupt that means the block must stop processing. An
@@ -88,34 +87,31 @@ module Liquid
context.push_interrupt(node.interrupt) context.push_interrupt(node.interrupt)
break break
else # Other non-Block tags else # Other non-Block tags
render_node_to_output(node, output, context) render_node(context, output, node)
break if context.interrupt? # might have happened through an include break if context.interrupt? # might have happened through an include
end end
idx += 1 idx += 1
end end
output.join output
end end
private private
def render_node_to_output(node, output, context, skip_output = false) def render_node(context, output, node)
node_output = node.render(context) node.render(context, output)
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s check_resources(context, output)
check_resources(context, node_output)
output << node_output unless skip_output
rescue MemoryError => e rescue MemoryError => e
raise e raise e
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
context.handle_error(e, node.line_number) context.handle_error(e, node.line_number)
output << nil
rescue ::StandardError => e rescue ::StandardError => e
line_number = node.is_a?(String) ? nil : node.line_number line_number = node.is_a?(String) ? nil : node.line_number
output << context.handle_error(e, line_number) output << context.handle_error(e, line_number)
end end
def check_resources(context, node_output) def check_resources(context, output)
context.resource_limits.render_length += node_output.bytesize context.resource_limits.render_length = output.bytesize
return unless context.resource_limits.reached? return unless context.resource_limits.reached?
raise MemoryError.new("Memory limits exceeded".freeze) raise MemoryError.new("Memory limits exceeded".freeze)
end end
+6 -6
View File
@@ -1,19 +1,19 @@
module Liquid module Liquid
class BlockBody class BlockBody
def render_node_with_profiling(node, output, context, skip_output = false) def render_node_with_profiling(context, output, node)
Profiler.profile_node_render(node) do Profiler.profile_node_render(node) do
render_node_without_profiling(node, output, context, skip_output) render_node_without_profiling(context, output, node)
end end
end end
alias_method :render_node_without_profiling, :render_node_to_output alias_method :render_node_without_profiling, :render_node
alias_method :render_node_to_output, :render_node_with_profiling alias_method :render_node, :render_node_with_profiling
end end
class Include < Tag class Include < Tag
def render_with_profiling(context) def render_with_profiling(context, output)
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
render_without_profiling(context) render_without_profiling(context, output)
end end
end end
+2 -2
View File
@@ -32,8 +32,8 @@ module Liquid
self.class.name.downcase self.class.name.downcase
end end
def render(_context) def render(_context, output = '')
''.freeze output
end end
def blank? def blank?
+3 -3
View File
@@ -22,11 +22,11 @@ module Liquid
end end
end end
def render(context) def render(context, output = '')
val = @from.render(context) val = @from.render(context, nil)
context.scopes.last[@to] = val context.scopes.last[@to] = val
context.resource_limits.assign_score += assign_score_of(val) context.resource_limits.assign_score += assign_score_of(val)
''.freeze output
end end
def blank? def blank?
+4 -4
View File
@@ -22,11 +22,11 @@ module Liquid
end end
end end
def render(context) def render(context, output = '')
output = super super
context.scopes.last[@to] = output context.scopes.last[@to] = output
context.resource_limits.assign_score += output.bytesize context.resource_limits.assign_score = output.bytesize
''.freeze output
end end
def blank? def blank?
+5 -5
View File
@@ -38,22 +38,22 @@ module Liquid
end end
end end
def render(context) def render(context, output = '')
context.stack do context.stack do
execute_else_block = true execute_else_block = true
output = ''
@blocks.each do |block| @blocks.each do |block|
if block.else? if block.else?
return block.attachment.render(context) if execute_else_block block.attachment.render(context, output) if execute_else_block
elsif block.evaluate(context) elsif block.evaluate(context)
execute_else_block = false execute_else_block = false
output << block.attachment.render(context) block.attachment.render(context, output)
end end
end end
end
output output
end end
end
private private
+2 -2
View File
@@ -1,7 +1,7 @@
module Liquid module Liquid
class Comment < Block class Comment < Block
def render(_context) def render(_context, output = '')
''.freeze output
end end
def unknown_tag(_tag, _markup, _tokens) def unknown_tag(_tag, _markup, _tokens)
+14 -3
View File
@@ -31,18 +31,29 @@ module Liquid
end end
end end
def render(context) def render(context, output = '')
context.registers[:cycle] ||= {} context.registers[:cycle] ||= {}
context.stack do context.stack do
key = context.evaluate(@name) key = context.evaluate(@name)
iteration = context.registers[:cycle][key].to_i iteration = context.registers[:cycle][key].to_i
result = context.evaluate(@variables[iteration])
val = context.evaluate(@variables[iteration])
if val.is_a?(Array)
val = val.join
elsif !val.is_a?(String)
val = val.to_s
end
output << val
iteration += 1 iteration += 1
iteration = 0 if iteration >= @variables.size iteration = 0 if iteration >= @variables.size
context.registers[:cycle][key] = iteration context.registers[:cycle][key] = iteration
result
end end
output
end end
private private
+3 -2
View File
@@ -23,11 +23,12 @@ module Liquid
@variable = markup.strip @variable = markup.strip
end end
def render(context) def render(context, output = '')
value = context.environments.first[@variable] ||= 0 value = context.environments.first[@variable] ||= 0
value -= 1 value -= 1
context.environments.first[@variable] = value context.environments.first[@variable] = value
value.to_s output << value.to_s
output
end end
end end
+12 -10
View File
@@ -70,13 +70,13 @@ module Liquid
@else_block = BlockBody.new @else_block = BlockBody.new
end end
def render(context) def render(context, output = '')
segment = collection_segment(context) segment = collection_segment(context)
if segment.empty? if segment.empty?
render_else(context) render_else(context, output)
else else
render_segment(context, segment) render_segment(context, output, segment)
end end
end end
@@ -141,12 +141,10 @@ module Liquid
segment segment
end end
def render_segment(context, segment) def render_segment(context, output, segment)
for_stack = context.registers[:for_stack] ||= [] for_stack = context.registers[:for_stack] ||= []
length = segment.length length = segment.length
result = ''
context.stack do context.stack do
loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1]) loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
@@ -157,7 +155,7 @@ module Liquid
segment.each do |item| segment.each do |item|
context[@variable_name] = item context[@variable_name] = item
result << @for_block.render(context) @for_block.render(context, output)
loop_vars.send(:increment!) loop_vars.send(:increment!)
# Handle any interrupts if they exist. # Handle any interrupts if they exist.
@@ -172,7 +170,7 @@ module Liquid
end end
end end
result output
end end
def set_attribute(key, expr) def set_attribute(key, expr)
@@ -188,8 +186,12 @@ module Liquid
end end
end end
def render_else(context) def render_else(context, output)
@else_block ? @else_block.render(context) : ''.freeze if @else_block
@else_block.render(context, output)
else
output
end
end end
class ParseTreeVisitor < Liquid::ParseTreeVisitor class ParseTreeVisitor < Liquid::ParseTreeVisitor
+4 -3
View File
@@ -39,15 +39,16 @@ module Liquid
end end
end end
def render(context) def render(context, output)
context.stack do context.stack do
@blocks.each do |block| @blocks.each do |block|
if block.evaluate(context) if block.evaluate(context)
return block.attachment.render(context) return block.attachment.render(context, output)
end end
end end
''.freeze
end end
output
end end
private private
+8 -8
View File
@@ -1,16 +1,16 @@
module Liquid module Liquid
class Ifchanged < Block class Ifchanged < Block
def render(context) def render(context, output)
context.stack do context.stack do
output = super block_output = super(context, '')
if block_output != context.registers[:ifchanged]
context.registers[:ifchanged] = block_output
output << block_output
end
end
if output != context.registers[:ifchanged]
context.registers[:ifchanged] = output
output output
else
''.freeze
end
end
end end
end end
+6 -4
View File
@@ -42,7 +42,7 @@ module Liquid
def parse(_tokens) def parse(_tokens)
end end
def render(context) def render(context, output)
template_name = context.evaluate(@template_name_expr) template_name = context.evaluate(@template_name_expr)
raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
@@ -66,19 +66,21 @@ module Liquid
end end
if variable.is_a?(Array) if variable.is_a?(Array)
variable.collect do |var| variable.each do |var|
context[context_variable_name] = var context[context_variable_name] = var
partial.render(context) partial.render(context, output: output)
end end
else else
context[context_variable_name] = variable context[context_variable_name] = variable
partial.render(context) partial.render(context, output: output)
end end
end end
ensure ensure
context.template_name = old_template_name context.template_name = old_template_name
context.partial = old_partial context.partial = old_partial
end end
output
end end
private private
+3 -2
View File
@@ -20,10 +20,11 @@ module Liquid
@variable = markup.strip @variable = markup.strip
end end
def render(context) def render(context, output = '')
value = context.environments.first[@variable] ||= 0 value = context.environments.first[@variable] ||= 0
context.environments.first[@variable] = value + 1 context.environments.first[@variable] = value + 1
value.to_s output << value.to_s
output
end end
end end
+3 -2
View File
@@ -22,8 +22,9 @@ module Liquid
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name)) raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
end end
def render(_context) def render(_context, output = '')
@body output << @body
output
end end
def nodelist def nodelist
+9 -6
View File
@@ -18,7 +18,7 @@ module Liquid
end end
end end
def render(context) def render(context, output = '')
collection = context.evaluate(@collection_name) or return ''.freeze collection = context.evaluate(@collection_name) or return ''.freeze
from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0 from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0
@@ -30,7 +30,7 @@ module Liquid
cols = context.evaluate(@attributes['cols'.freeze]).to_i cols = context.evaluate(@attributes['cols'.freeze]).to_i
result = "<tr class=\"row1\">\n" output << "<tr class=\"row1\">\n"
context.stack do context.stack do
tablerowloop = Liquid::TablerowloopDrop.new(length, cols) tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
context['tablerowloop'.freeze] = tablerowloop context['tablerowloop'.freeze] = tablerowloop
@@ -38,17 +38,20 @@ module Liquid
collection.each do |item| collection.each do |item|
context[@variable_name] = item context[@variable_name] = item
result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>' output << "<td class=\"col#{tablerowloop.col}\">"
super
output << '</td>'
if tablerowloop.col_last && !tablerowloop.last if tablerowloop.col_last && !tablerowloop.last
result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">" output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
end end
tablerowloop.send(:increment!) tablerowloop.send(:increment!)
end end
end end
result << "</tr>\n"
result output << "</tr>\n"
output
end end
class ParseTreeVisitor < Liquid::ParseTreeVisitor class ParseTreeVisitor < Liquid::ParseTreeVisitor
+5 -5
View File
@@ -6,23 +6,23 @@ module Liquid
# {% unless x < 0 %} x is greater than zero {% endunless %} # {% unless x < 0 %} x is greater than zero {% endunless %}
# #
class Unless < If class Unless < If
def render(context) def render(context, output = '')
context.stack do context.stack do
# First condition is interpreted backwards ( if not ) # First condition is interpreted backwards ( if not )
first_block = @blocks.first first_block = @blocks.first
unless first_block.evaluate(context) unless first_block.evaluate(context)
return first_block.attachment.render(context) return first_block.attachment.render(context, output)
end end
# After the first condition unless works just like if # After the first condition unless works just like if
@blocks[1..-1].each do |block| @blocks[1..-1].each do |block|
if block.evaluate(context) if block.evaluate(context)
return block.attachment.render(context) return block.attachment.render(context, output)
end
end end
end end
''.freeze output
end
end end
end end
+9 -1
View File
@@ -187,9 +187,12 @@ module Liquid
raise ArgumentError, "Expected Hash or Liquid::Context as parameter" raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
end end
output = nil
case args.last case args.last
when Hash when Hash
options = args.pop options = args.pop
output = options[:output] if options[:output]
registers.merge!(options[:registers]) if options[:registers].is_a?(Hash) registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
@@ -205,7 +208,8 @@ module Liquid
# render the nodelist. # render the nodelist.
# for performance reasons we get an array back here. join will make a string out of it. # for performance reasons we get an array back here. join will make a string out of it.
result = with_profiling(context) do result = with_profiling(context) do
@root.render(context) output ||= self.class.output_buffer.clear
@root.render(context, output)
end end
result.respond_to?(:join) ? result.join : result result.respond_to?(:join) ? result.join : result
rescue Liquid::MemoryError => e rescue Liquid::MemoryError => e
@@ -215,6 +219,10 @@ module Liquid
end end
end end
def self.output_buffer
@output_buffer ||= String.new(capacity: 1024)
end
def render!(*args) def render!(*args)
@rethrow_errors = true @rethrow_errors = true
render(*args) render(*args)
+14 -1
View File
@@ -78,7 +78,7 @@ module Liquid
filterargs filterargs
end end
def render(context) def render(context, output = '')
obj = @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)| obj = @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs) filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
context.invoke(filter_name, output, *filter_args) context.invoke(filter_name, output, *filter_args)
@@ -88,8 +88,21 @@ module Liquid
taint_check(context, obj) taint_check(context, obj)
if output
if obj.is_a?(Array)
output << obj.join
elsif obj.nil?
elsif !obj.is_a?(String)
output << obj.to_s
else
output << obj
end
output
else
obj obj
end end
end
private private
+4 -2
View File
@@ -12,7 +12,7 @@ class CommentForm < Liquid::Block
end end
end end
def render(context) def render(context, output = '')
article = context[@variable_name] article = context[@variable_name]
context.stack do context.stack do
@@ -23,7 +23,9 @@ class CommentForm < Liquid::Block
'email' => context['comment.email'], 'email' => context['comment.email'],
'body' => context['comment.body'] 'body' => context['comment.body']
} }
wrap_in_form(article, render_all(@nodelist, context))
output << wrap_in_form(article, render_all(@nodelist, context, output))
output
end end
end end
+1 -1
View File
@@ -21,7 +21,7 @@ class Paginate < Liquid::Block
end end
end end
def render(context) def render(context, output = '')
@context = context @context = context
context.stack do context.stack do
+3 -2
View File
@@ -1,8 +1,9 @@
require 'test_helper' require 'test_helper'
class FoobarTag < Liquid::Tag class FoobarTag < Liquid::Tag
def render(*args) def render(context, output = '')
" " output << ' '
output
end end
Liquid::Template.register_tag('foobar', FoobarTag) Liquid::Template.register_tag('foobar', FoobarTag)
+3 -2
View File
@@ -66,8 +66,9 @@ class CustomInclude < Liquid::Tag
def parse(tokens) def parse(tokens)
end end
def render(context) def render(context, output = '')
@template_name[1..-2] output << @template_name[1..-2]
output
end end
end end
+9 -9
View File
@@ -177,31 +177,31 @@ class TemplateTest < Minitest::Test
def test_render_length_persists_between_blocks def test_render_length_persists_between_blocks
t = Template.parse("{% if true %}aaaa{% endif %}") t = Template.parse("{% if true %}aaaa{% endif %}")
t.resource_limits.render_length_limit = 7 t.resource_limits.render_length_limit = 3
assert_equal "Liquid error: Memory limits exceeded", t.render assert_equal "Liquid error: Memory limits exceeded", t.render
t.resource_limits.render_length_limit = 8 t.resource_limits.render_length_limit = 4
assert_equal "aaaa", t.render assert_equal "aaaa", t.render
t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}") t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}")
t.resource_limits.render_length_limit = 13 t.resource_limits.render_length_limit = 6
assert_equal "Liquid error: Memory limits exceeded", t.render assert_equal "Liquid error: Memory limits exceeded", t.render
t.resource_limits.render_length_limit = 14 t.resource_limits.render_length_limit = 7
assert_equal "aaaabbb", t.render assert_equal "aaaabbb", t.render
t = Template.parse("{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}") t = Template.parse("{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}")
t.resource_limits.render_length_limit = 4
assert_equal "Liquid error: Memory limits exceeded", t.render
t.resource_limits.render_length_limit = 5 t.resource_limits.render_length_limit = 5
assert_equal "Liquid error: Memory limits exceeded", t.render assert_equal "Liquid error: Memory limits exceeded", t.render
t.resource_limits.render_length_limit = 11 t.resource_limits.render_length_limit = 6
assert_equal "Liquid error: Memory limits exceeded", t.render
t.resource_limits.render_length_limit = 12
assert_equal "ababab", t.render assert_equal "ababab", t.render
end end
def test_render_length_uses_number_of_bytes_not_characters def test_render_length_uses_number_of_bytes_not_characters
t = Template.parse("{% if true %}すごい{% endif %}") t = Template.parse("{% if true %}すごい{% endif %}")
t.resource_limits.render_length_limit = 10 t.resource_limits.render_length_limit = 8
assert_equal "Liquid error: Memory limits exceeded", t.render assert_equal "Liquid error: Memory limits exceeded", t.render
t.resource_limits.render_length_limit = 18 t.resource_limits.render_length_limit = 9
assert_equal "すごい", t.render assert_equal "すごい", t.render
end end