Merge pull request #218 from Shopify/dont_render_blank_blocks

Dont render blank blocks
This commit is contained in:
Florian Weingarten
2013-07-02 15:15:54 -07:00
10 changed files with 134 additions and 23 deletions
+13 -4
View File
@@ -1,17 +1,20 @@
module Liquid module Liquid
class Block < Tag class Block < Tag
IsTag = /^#{TagStart}/o IsTag = /^#{TagStart}/o
IsVariable = /^#{VariableStart}/o IsVariable = /^#{VariableStart}/o
FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o FullToken = /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/o
ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/o ContentOfVariable = /^#{VariableStart}(.*)#{VariableEnd}$/o
def blank?
@blank || false
end
def parse(tokens) def parse(tokens)
@blank = true
@nodelist ||= [] @nodelist ||= []
@nodelist.clear @nodelist.clear
while token = tokens.shift while token = tokens.shift
case token case token
when IsTag when IsTag
if token =~ FullToken if token =~ FullToken
@@ -25,7 +28,9 @@ module Liquid
# fetch the tag from registered blocks # fetch the tag from registered blocks
if tag = Template.tags[$1] if tag = Template.tags[$1]
@nodelist << tag.new($1, $2, tokens) new_tag = tag.new($1, $2, tokens)
@blank &&= new_tag.blank?
@nodelist << new_tag
else else
# this tag is not registered with the system # this tag is not registered with the system
# pass it to the current block for special handling or error reporting # pass it to the current block for special handling or error reporting
@@ -36,10 +41,12 @@ module Liquid
end end
when IsVariable when IsVariable
@nodelist << create_variable(token) @nodelist << create_variable(token)
@blank = false
when '' when ''
# pass # pass
else else
@nodelist << token @nodelist << token
@blank &&= (token =~ /\A\s*\z/)
end end
end end
@@ -112,7 +119,9 @@ module Liquid
context.resource_limits[:reached] = true context.resource_limits[:reached] = true
raise MemoryError.new("Memory limits exceeded") raise MemoryError.new("Memory limits exceeded")
end end
output << token_output unless token.is_a?(Block) && token.blank?
output << token_output
end
rescue MemoryError => e rescue MemoryError => e
raise e raise e
rescue ::StandardError => e rescue ::StandardError => e
+3 -3
View File
@@ -1,7 +1,5 @@
module Liquid module Liquid
class Tag class Tag
attr_accessor :nodelist attr_accessor :nodelist
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, tokens)
@@ -21,6 +19,8 @@ module Liquid
'' ''
end end
def blank?
@blank || true
end
end # Tag end # Tag
end # Liquid end # Liquid
+4
View File
@@ -30,6 +30,10 @@ module Liquid
context.resource_limits[:assign_score_current] += (output.respond_to?(:length) ? output.length : 1) context.resource_limits[:assign_score_current] += (output.respond_to?(:length) ? output.length : 1)
'' ''
end end
def blank?
true
end
end end
Template.register_tag('capture', Capture) Template.register_tag('capture', Capture)
-3
View File
@@ -62,7 +62,6 @@ module Liquid
end end
def record_else_condition(markup) def record_else_condition(markup)
if not markup.strip.empty? if not markup.strip.empty?
raise SyntaxError.new("Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) ") raise SyntaxError.new("Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) ")
end end
@@ -71,8 +70,6 @@ module Liquid
block.attach(@nodelist) block.attach(@nodelist)
@blocks << block @blocks << block
end end
end end
Template.register_tag('case', Case) Template.register_tag('case', Case)
+4
View File
@@ -3,6 +3,10 @@ module Liquid
def render(context) def render(context)
'' ''
end end
def blank?
true
end
end end
Template.register_tag('comment', Comment) Template.register_tag('comment', Comment)
+4 -3
View File
@@ -1,5 +1,4 @@
module Liquid module Liquid
# Cycle is usually used within a loop to alternate between values, like colors or DOM classes. # Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
# #
# {% for item in items %} # {% for item in items %}
@@ -44,15 +43,17 @@ module Liquid
end end
end end
private def blank?
false
end
private
def variables_from_string(markup) def variables_from_string(markup)
markup.split(',').collect do |var| markup.split(',').collect do |var|
var =~ /\s*(#{QuotedFragment})\s*/o var =~ /\s*(#{QuotedFragment})\s*/o
$1 ? $1 : nil $1 ? $1 : nil
end.compact end.compact
end end
end end
Template.register_tag('cycle', Cycle) Template.register_tag('cycle', Cycle)
-6
View File
@@ -1,5 +1,4 @@
module Liquid module Liquid
# If is the conditional block # If is the conditional block
# #
# {% if user.admin %} # {% if user.admin %}
@@ -10,7 +9,6 @@ module Liquid
# #
# There are {% if count < 5 %} less {% else %} more {% endif %} items than you need. # There are {% if count < 5 %} less {% else %} more {% endif %} items than you need.
# #
#
class If < Block class If < Block
SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]" SyntaxHelp = "Syntax Error in tag 'if' - Valid syntax: if [expression]"
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
@@ -18,9 +16,7 @@ module Liquid
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, tokens)
@blocks = [] @blocks = []
push_block('if', markup) push_block('if', markup)
super super
end end
@@ -71,8 +67,6 @@ module Liquid
@blocks.push(block) @blocks.push(block)
@nodelist = block.attach(Array.new) @nodelist = block.attach(Array.new)
end end
end end
Template.register_tag('if', If) Template.register_tag('if', If)
+4
View File
@@ -38,6 +38,10 @@ module Liquid
def parse(tokens) def parse(tokens)
end end
def blank?
false
end
def render(context) def render(context)
partial = load_cached_partial(context) partial = load_cached_partial(context)
variable = context[@variable_name || @template_name[1..-2]] variable = context[@variable_name || @template_name[1..-2]]
+4 -4
View File
@@ -1,12 +1,11 @@
module Liquid module Liquid
# increment is used in a place where one needs to insert a counter # increment is used in a place where one needs to insert a counter
# into a template, and needs the counter to survive across # into a template, and needs the counter to survive across
# multiple instantiations of the template. # multiple instantiations of the template.
# (To achieve the survival, the application must keep the context) # (To achieve the survival, the application must keep the context)
# #
# if the variable does not exist, it is created with value 0. # if the variable does not exist, it is created with value 0.
#
# Hello: {% increment variable %} # Hello: {% increment variable %}
# #
# gives you: # gives you:
@@ -18,7 +17,6 @@ module Liquid
class Increment < Tag class Increment < Tag
def initialize(tag_name, markup, tokens) def initialize(tag_name, markup, tokens)
@variable = markup.strip @variable = markup.strip
super super
end end
@@ -28,7 +26,9 @@ module Liquid
value.to_s value.to_s
end end
private def blank?
false
end
end end
Template.register_tag('increment', Increment) Template.register_tag('increment', Increment)
+98
View File
@@ -0,0 +1,98 @@
require 'test_helper'
class BlankTestFileSystem
def read_template_file(template_path, context)
template_path
end
end
class BlankTest < Test::Unit::TestCase
include Liquid
N = 10
def wrap_in_for(body)
"{% for i in (1..#{N}) %}#{body}{% endfor %}"
end
def wrap_in_if(body)
"{% if true %}#{body}{% endif %}"
end
def wrap(body)
wrap_in_for(body) + wrap_in_if(body)
end
def test_loops_are_blank
assert_template_result("", wrap_in_for(" "))
end
def test_if_else_are_blank
assert_template_result("", "{% if true %} {% elsif false %} {% else %} {% endif %}")
end
def test_unless_is_blank
assert_template_result("", wrap("{% unless true %} {% endunless %}"))
end
def test_mark_as_blank_only_during_parsing
assert_template_result(" "*(N+1), wrap(" {% if false %} this never happens, but still, this block is not blank {% endif %}"))
end
def test_comments_are_blank
assert_template_result("", wrap(" {% comment %} whatever {% endcomment %} "))
end
def test_captures_are_blank
assert_template_result("", wrap(" {% capture foo %} whatever {% endcapture %} "))
end
def test_nested_blocks_are_blank_but_only_if_all_children_are
assert_template_result("", wrap(wrap(" ")))
assert_template_result("\n but this is not "*(N+1),
wrap(%q{{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}
{% if true %} but this is not {% endif %}}))
end
def test_assigns_are_blank
assert_template_result("", wrap(' {% assign foo = "bar" %} '))
end
def test_whitespace_is_blank
assert_template_result("", wrap(" "))
assert_template_result("", wrap("\t"))
end
def test_whitespace_is_not_blank_if_other_stuff_is_present
body = " x "
assert_template_result(body*(N+1), wrap(body))
end
def test_increment_is_not_blank
assert_template_result(" 0"*2*(N+1), wrap("{% assign foo = 0 %} {% increment foo %} {% decrement foo %}"))
end
def test_cycle_is_not_blank
assert_template_result("12"*((N+1)/2)+"1", wrap("{% cycle '1', '2' %}"))
end
def test_raw_is_not_blank
assert_template_result(" "*(N+1), wrap(" {% raw %} {% endraw %}"))
end
def test_variables_are_not_blank
assert_template_result(" "*(N+1), wrap(' {{ "" }} '))
assert_template_result(" "*(N+1), wrap("{% assign foo = ' ' %}{{ foo }}"))
end
def test_include_is_blank
Liquid::Template.file_system = BlankTestFileSystem.new
assert_equal "foobar"*(N+1), Template.parse(wrap("{% include 'foobar' %}")).render()
assert_equal " foobar "*(N+1), Template.parse(wrap("{% include ' foobar ' %}")).render()
assert_equal " ", Template.parse(" {% include ' ' %} ").render()
end
def test_case_is_blank
assert_template_result("", wrap(" {% assign foo = 'bar' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
assert_template_result("", wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
end
end