WIP - Implement indentation whitespace control

This commit is contained in:
Chris AtLee
2024-11-26 18:03:11 -05:00
parent 42b6763546
commit 7997c11c1a
9 changed files with 203 additions and 15 deletions
+3 -1
View File
@@ -26,7 +26,9 @@ module Liquid
ArgumentSeparator = ',' ArgumentSeparator = ','
FilterArgumentSeparator = ':' FilterArgumentSeparator = ':'
VariableAttributeSeparator = '.' VariableAttributeSeparator = '.'
WhitespaceControl = '-' WhitespaceControl = '(?:[-~])'
WhitespaceTrim = '-'
WhitespaceTrimIndent = '~'
TagStart = /\{\%/ TagStart = /\{\%/
TagEnd = /\%\}/ TagEnd = /\%\}/
TagName = /#|\w+/ TagName = /#|\w+/
+12 -3
View File
@@ -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
+1 -1
View File
@@ -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)
+4 -3
View File
@@ -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
+13 -2
View File
@@ -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
+2 -1
View File
@@ -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
+14 -1
View File
@@ -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
View File
@@ -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
+135
View File
@@ -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