mirror of
https://github.com/Shopify/liquid.git
synced 2026-10-04 01:25:14 -07:00
WIP - Implement indentation whitespace control
This commit is contained in:
+3
-1
@@ -26,7 +26,9 @@ module Liquid
|
|||||||
ArgumentSeparator = ','
|
ArgumentSeparator = ','
|
||||||
FilterArgumentSeparator = ':'
|
FilterArgumentSeparator = ':'
|
||||||
VariableAttributeSeparator = '.'
|
VariableAttributeSeparator = '.'
|
||||||
WhitespaceControl = '-'
|
WhitespaceControl = '(?:[-~])'
|
||||||
|
WhitespaceTrim = '-'
|
||||||
|
WhitespaceTrimIndent = '~'
|
||||||
TagStart = /\{\%/
|
TagStart = /\{\%/
|
||||||
TagEnd = /\%\}/
|
TagEnd = /\%\}/
|
||||||
TagName = /#|\w+/
|
TagName = /#|\w+/
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ module Liquid
|
|||||||
token.lstrip!
|
token.lstrip!
|
||||||
end
|
end
|
||||||
parse_context.trim_whitespace = false
|
parse_context.trim_whitespace = false
|
||||||
|
parse_context.strip_trailing = false
|
||||||
@nodelist << token
|
@nodelist << token
|
||||||
@blank &&= token.match?(WhitespaceOrNothing)
|
@blank &&= token.match?(WhitespaceOrNothing)
|
||||||
end
|
end
|
||||||
@@ -174,7 +175,8 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def whitespace_handler(token, parse_context)
|
def whitespace_handler(token, parse_context)
|
||||||
if token[2] == WhitespaceControl
|
parse_context.indentation = nil
|
||||||
|
if token[2] == WhitespaceTrim
|
||||||
previous_token = @nodelist.last
|
previous_token = @nodelist.last
|
||||||
if previous_token.is_a?(String)
|
if previous_token.is_a?(String)
|
||||||
first_byte = previous_token.getbyte(0)
|
first_byte = previous_token.getbyte(0)
|
||||||
@@ -183,8 +185,15 @@ module Liquid
|
|||||||
previous_token << first_byte
|
previous_token << first_byte
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
elsif token[2] == WhitespaceTrimIndent
|
||||||
|
previous_token = @nodelist.last
|
||||||
|
if previous_token.is_a?(String)
|
||||||
|
start_of_line = previous_token.rindex("\n") || 0
|
||||||
|
parse_context.indentation = previous_token[start_of_line + 1..]
|
||||||
end
|
end
|
||||||
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
end
|
||||||
|
parse_context.trim_whitespace = (token[-3] == WhitespaceTrim)
|
||||||
|
parse_context.strip_trailing = (token[-3] == WhitespaceTrimIndent)
|
||||||
end
|
end
|
||||||
|
|
||||||
def blank?
|
def blank?
|
||||||
@@ -248,7 +257,7 @@ module Liquid
|
|||||||
def create_variable(token, parse_context)
|
def create_variable(token, parse_context)
|
||||||
if token.end_with?("}}")
|
if token.end_with?("}}")
|
||||||
i = 2
|
i = 2
|
||||||
i = 3 if token[i] == "-"
|
i = 3 if token[i] == "-" or token[i] == "~"
|
||||||
parse_end = token.length - 3
|
parse_end = token.length - 3
|
||||||
parse_end -= 1 if token[parse_end] == "-"
|
parse_end -= 1 if token[parse_end] == "-"
|
||||||
markup_end = parse_end - i + 1
|
markup_end = parse_end - i + 1
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
class ParseContext
|
class ParseContext
|
||||||
attr_accessor :locale, :line_number, :trim_whitespace, :depth
|
attr_accessor :locale, :line_number, :trim_whitespace, :depth, :indentation, :strip_trailing
|
||||||
attr_reader :partial, :warnings, :error_mode, :environment
|
attr_reader :partial, :warnings, :error_mode, :environment
|
||||||
|
|
||||||
def initialize(options = Const::EMPTY_HASH)
|
def initialize(options = Const::EMPTY_HASH)
|
||||||
|
|||||||
@@ -63,11 +63,12 @@ module Liquid
|
|||||||
comment_tag_depth -= 1
|
comment_tag_depth -= 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if comment_tag_depth.zero?
|
next unless comment_tag_depth.zero?
|
||||||
parse_context.trim_whitespace = (token[-3] == WhitespaceControl) unless tokenizer.for_liquid_tag
|
|
||||||
|
parse_context.trim_whitespace = (token[-3] == WhitespaceTrim) unless tokenizer.for_liquid_tag
|
||||||
|
parse_context.strip_trailing = (token[-3] == WhitespaceTrimIndent) unless tokenizer.for_liquid_tag
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
raise_tag_never_closed(block_name)
|
raise_tag_never_closed(block_name)
|
||||||
ensure
|
ensure
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ module Liquid
|
|||||||
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
||||||
@template_name_expr = parse_expression(template_name)
|
@template_name_expr = parse_expression(template_name)
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
|
@indentation = options.indentation
|
||||||
|
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@attributes[key] = parse_expression(value)
|
@attributes[key] = parse_expression(value)
|
||||||
@@ -80,15 +81,25 @@ module Liquid
|
|||||||
context[key] = context.evaluate(value)
|
context[key] = context.evaluate(value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
partial_output = +''
|
||||||
if variable.is_a?(Array)
|
if variable.is_a?(Array)
|
||||||
variable.each do |var|
|
variable.each do |var|
|
||||||
context[context_variable_name] = var
|
context[context_variable_name] = var
|
||||||
partial.render_to_output_buffer(context, output)
|
partial.render_to_output_buffer(context, partial_output)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
context[context_variable_name] = variable
|
context[context_variable_name] = variable
|
||||||
partial.render_to_output_buffer(context, output)
|
partial.render_to_output_buffer(context, partial_output)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if @indentation
|
||||||
|
partial_output = partial_output.lines.map.with_index do |line, i|
|
||||||
|
next line if i == 0
|
||||||
|
@indentation + line
|
||||||
|
end.join
|
||||||
|
end
|
||||||
|
|
||||||
|
output << partial_output
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
context.template_name = old_template_name
|
context.template_name = old_template_name
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ module Liquid
|
|||||||
@body = +''
|
@body = +''
|
||||||
while (token = tokens.shift)
|
while (token = tokens.shift)
|
||||||
if token =~ BlockBody::FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
|
if token =~ BlockBody::FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
|
||||||
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
parse_context.trim_whitespace = (token[-3] == WhitespaceTrim)
|
||||||
|
parse_context.strip_trailing = (token[-3] == WhitespaceTrimIndent)
|
||||||
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
|
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ module Liquid
|
|||||||
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
||||||
@template_name_expr = parse_expression(template_name)
|
@template_name_expr = parse_expression(template_name)
|
||||||
@is_for_loop = (with_or_for == FOR)
|
@is_for_loop = (with_or_for == FOR)
|
||||||
|
@indentation = options.indentation
|
||||||
|
@strip_trailing = options.strip_trailing
|
||||||
|
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@@ -84,7 +86,18 @@ module Liquid
|
|||||||
inner_context[key] = context.evaluate(value)
|
inner_context[key] = context.evaluate(value)
|
||||||
end
|
end
|
||||||
inner_context[context_variable_name] = var unless var.nil?
|
inner_context[context_variable_name] = var unless var.nil?
|
||||||
partial.render_to_output_buffer(inner_context, output)
|
partial_output = +''
|
||||||
|
partial.render_to_output_buffer(inner_context, partial_output)
|
||||||
|
if @indentation
|
||||||
|
partial_output = partial_output.lines.map.with_index do |line, i|
|
||||||
|
next line if i == 0
|
||||||
|
@indentation + line
|
||||||
|
end.join
|
||||||
|
end
|
||||||
|
if @strip_trailing
|
||||||
|
partial_output.rstrip!
|
||||||
|
end
|
||||||
|
output << partial_output
|
||||||
forloop&.send(:increment!)
|
forloop&.send(:increment!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-2
@@ -29,6 +29,8 @@ module Liquid
|
|||||||
@name = nil
|
@name = nil
|
||||||
@parse_context = parse_context
|
@parse_context = parse_context
|
||||||
@line_number = parse_context.line_number
|
@line_number = parse_context.line_number
|
||||||
|
@indentation = parse_context.indentation
|
||||||
|
@strip_trailing = parse_context.strip_trailing
|
||||||
|
|
||||||
strict_parse_with_error_mode_fallback(markup)
|
strict_parse_with_error_mode_fallback(markup)
|
||||||
end
|
end
|
||||||
@@ -96,13 +98,27 @@ module Liquid
|
|||||||
def render_to_output_buffer(context, output)
|
def render_to_output_buffer(context, output)
|
||||||
obj = render(context)
|
obj = render(context)
|
||||||
|
|
||||||
|
obj_output = ''
|
||||||
if obj.is_a?(Array)
|
if obj.is_a?(Array)
|
||||||
output << obj.join
|
obj_output = obj.join
|
||||||
elsif obj.nil?
|
elsif obj.nil?
|
||||||
else
|
else
|
||||||
output << obj.to_s
|
obj_output = obj.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if @indentation
|
||||||
|
obj_output = obj_output.lines.map.with_index do |line, i|
|
||||||
|
next line if i == 0
|
||||||
|
@indentation + line
|
||||||
|
end.join
|
||||||
|
end
|
||||||
|
|
||||||
|
if @strip_trailing
|
||||||
|
obj_output.rstrip!
|
||||||
|
end
|
||||||
|
|
||||||
|
output << obj_output
|
||||||
|
|
||||||
output
|
output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -555,4 +555,139 @@ class TrimModeTest < Minitest::Test
|
|||||||
def test_trim_blank
|
def test_trim_blank
|
||||||
assert_template_result('foobar', 'foo {{-}} bar')
|
assert_template_result('foobar', 'foo {{-}} bar')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_trim_indent_variable
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{{~ 'Hello\nWorld' ~}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Hello
|
||||||
|
World
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_trim_indent_variable_trims_trailing_whitespace
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{{~ 'Hello\nWorld\n' ~}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Hello
|
||||||
|
World
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def test_trim_indent_tags
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{%~ echo 'Hello\nWorld' ~%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Hello
|
||||||
|
World
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_trim_indent_include
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{%~ include "snippet" ~%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Hello
|
||||||
|
World
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text, partials: {"snippet" => "Hello\nWorld"})
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_trim_indent_render
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{%~ render "snippet" ~%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Hello
|
||||||
|
World
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text, partials: {"snippet" => "Hello\nWorld"})
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_trim_indent_render_trim_trailing_whitespace
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{%~ render "snippet" ~%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Hello
|
||||||
|
World
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text, partials: {"snippet" => "Hello\nWorld\n"})
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_trim_indent_nested_render
|
||||||
|
text = <<-END_TEMPLATE
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{%~ render "snippet" ~%}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_TEMPLATE
|
||||||
|
expected = <<-END_EXPECTED
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Hello
|
||||||
|
inside
|
||||||
|
World
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
END_EXPECTED
|
||||||
|
assert_template_result(expected, text, partials: {"snippet" => "Hello\n {%~ render \"snippet2\" ~%}\nWorld", "snippet2" => "inside"})
|
||||||
|
end
|
||||||
end # TrimModeTest
|
end # TrimModeTest
|
||||||
|
|||||||
Reference in New Issue
Block a user