Support ... inline snippet syntax

This commit is contained in:
Julia Boutin
2025-10-30 12:00:44 +01:00
committed by Guilherme Carreiro
parent c7ad1c90ca
commit 9bcfd32e65
4 changed files with 146 additions and 20 deletions
+6 -4
View File
@@ -8,7 +8,8 @@
<body>
<div class="liquid" style="font-size: 56px;">
{% assign foo = true %}
{% assign link = "variable" %}
{% snippet main %}
{% assign foo = false %}
@@ -17,15 +18,16 @@
<p>This is an inline snippet</p>
<ul>
<li><a href="/wow-a-link">wow a link</a></li>
<li><a href="/wow-a-link">wow a {{ link }}</a></li>
<li>1 + 1 = {{ 1 | plus: 1 }}</li>
<li>{% if true %}Yes!{% endif %}</li>
<li>{% if foo %}NO{% endif %}</li>
<li>foo = {% if foo %}true{%else%}false{% endif %}</li>
<li>{{ missing_var | default: 'fallback' }}</li>
</ul>
{% endsnippet %}
{% render main, arg: 'lsf' %}
{% render main, arg: 'lsf', ... %}
{{ foo }}
</div>
</body>
+8 -2
View File
@@ -17,6 +17,7 @@ module Liquid
DASH = [:dash, "-"].freeze
DOT = [:dot, "."].freeze
DOTDOT = [:dotdot, ".."].freeze
DOTDOTDOT = [:dotdotdot, "..."].freeze
DOT_ORD = ".".ord
DOUBLE_STRING_LITERAL = /"[^\"]*"/
EOS = [:end_of_string].freeze
@@ -113,10 +114,15 @@ module Liquid
if (special = SPECIAL_TABLE[peeked])
ss.scan_byte
# Special case for ".."
# Special case for ".." and "..."
if special == DOT && ss.peek_byte == DOT_ORD
ss.scan_byte
output << DOTDOT
if ss.peek_byte == DOT_ORD
ss.scan_byte
output << DOTDOTDOT
else
output << DOTDOT
end
elsif special == DASH
# Special case for negative numbers
if (peeked_byte = ss.peek_byte) && NUMBER_TABLE[peeked_byte]
+22 -2
View File
@@ -62,7 +62,13 @@ module Liquid
inner_context = context.new_isolated_subcontext
if inherit_context?
if is_file
inner_context.template_name = partial.name
inner_context.partial = true
end
if is_inline && inherit_context?
context.scopes.each do |scope|
scope.each do |key, value|
inner_context[key] = value
@@ -125,6 +131,14 @@ module Liquid
p.consume?(:comma)
@inherit_context = false
# ... inline snippets syntax
if p.consume?(:dotdotdot)
p.consume?(:comma)
@inherit_context = true
end
@attributes = {}
while p.look(:id)
key = p.consume
@@ -137,7 +151,12 @@ module Liquid
end
def rigid_template_name(p)
p.consume(:string)
if p.look(:string)
p.consume(:string)
# inline snippets use variable identifiers
elsif p.look(:id)
p.consume(:id)
end
end
def strict_parse(markup)
@@ -155,6 +174,7 @@ module Liquid
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
@template_name_expr = parse_expression(template_name)
@is_for_loop = (with_or_for == FOR)
@inherit_context = markup.include?('...')
@attributes = {}
markup.scan(TagAttributes) do |key, value|
+110 -12
View File
@@ -115,6 +115,23 @@ class SnippetTest < Minitest::Test
assert_template_result(expected, template)
end
def test_render_inline_snippet_with_evaluated_assign
template = <<~LIQUID.strip
{% snippet input %}
<h1>{{ greeting }}</h1>
{% endsnippet %}
{%- assign greeting = "hello" | upcase -%}
{%- render input, greeting: greeting -%}
LIQUID
expected = <<~OUTPUT
<h1>HELLO</h1>
OUTPUT
assert_template_result(expected, template)
end
def test_render_inline_snippet_with_multiple_arguments
template = <<~LIQUID.strip
{% snippet input %}
@@ -301,8 +318,36 @@ class SnippetTest < Minitest::Test
assert_template_result(expected, template)
end
def test_render_inline_snippet_with_outside_context_rigid
template = <<~LIQUID.strip
{% assign color_scheme = 'dark' %}
{% snippet header %}
<div class="header header--{{ color_scheme }}">
{{ message }}
</div>
{% endsnippet %}
{% render header, ..., message: 'Welcome!' %}
LIQUID
expected = <<~OUTPUT
<div class="header header--dark">
Welcome!
</div>
OUTPUT
assert_template_result(expected, template, error_mode: :rigid)
end
def test_inline_snippet_local_scope_takes_precedence
template = <<~LIQUID.strip
template = <<~LIQUID
{% assign color_scheme = 'dark' %}
{% snippet header %}
@@ -339,17 +384,17 @@ class SnippetTest < Minitest::Test
end
def test_render_captured_snippet
template = <<~LIQUID.strip
template = <<~LIQUID
{% assign color_scheme = 'dark' %}
{% snippet header %}
<div class="header header--{{ color_scheme }}">
{{ message }}
</div>
<div class="header header--{{ color_scheme }}">
{{ message }}
</div>
{% endsnippet %}
{% capture up_header %}
{% render header, ..., message: 'Welcome!' %}
{%- render header, ..., message: 'Welcome!' -%}
{% endcapture %}
{{ up_header | upcase }}
@@ -366,11 +411,9 @@ class SnippetTest < Minitest::Test
<DIV CLASS="HEADER HEADER--DARK">
WELCOME!
</DIV>
<DIV CLASS="HEADER HEADER--DARK">
WELCOME!
</DIV>
SNIPPETDROP
@@ -395,7 +438,7 @@ class SnippetTest < Minitest::Test
{% assign color_scheme = 'auto' %}
<div class="main main--{{ color_scheme }}">
{% render header, ..., message: 'Welcome!' %}
{% render header, ..., message: 'Welcome!' %}
</div>
{% endsnippet %}
@@ -448,4 +491,59 @@ class SnippetTest < Minitest::Test
# assert_template_result(expected, template)
# end
# def test_render_inline_snippet_forloop
# template = <<~LIQUID.strip
# {% snippet item %}
# <li>{{ forloop.index }}: {{ item }}</li>
# {% endsnippet %}
# {% assign items = "A,B,C" | split: "," %}
# {%- render item for items -%}
# LIQUID
# expected = <<~OUTPUT
# <li>1: A</li>
# <li>2: B</li>
# <li>3: C</li>
# OUTPUT
# assert_template_result(expected, template)
# end
# def test_render_inline_snippet_with
# template = <<~LIQUID.strip
# {% snippet header %}
# <div>{{ header }}</div>
# {% endsnippet %}
# {% assign product = "Apple" %}
# {%- render header with product -%}
# LIQUID
# expected = <<~OUTPUT
# <div>Apple</div>
# OUTPUT
# assert_template_result(expected, template)
# end
# def test_render_inline_snippet_alias
# template = <<~LIQUID.strip
# {% snippet product_card %}
# <div class="product">{{ item }}</div>
# {% endsnippet %}
# {% assign featured = "Apple" %}
# {%- render product_card with featured as item -%}
# LIQUID
# expected = <<~OUTPUT
# <div class="product">Apple</div>
# OUTPUT
# assert_template_result(expected, template)
# end
end