mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-20 03:10:39 -07:00
Support with, for, and as inline snippet syntax
This commit updates the render method to share parts of the snippet and block rendering logic to enable inline snippets to support `with`, `for`, and `as` syntax
This commit is contained in:
+7
-10
@@ -1,15 +1,9 @@
|
|||||||
GIT
|
|
||||||
remote: https://github.com/Shopify/liquid-c.git
|
|
||||||
revision: 5a786af7284df55e013ea20551c4b688d02e8326
|
|
||||||
ref: main
|
|
||||||
specs:
|
|
||||||
liquid-c (4.2.0)
|
|
||||||
liquid (>= 5.0.1)
|
|
||||||
|
|
||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
liquid (5.6.0.alpha)
|
liquid (5.8.7)
|
||||||
|
bigdecimal
|
||||||
|
strscan (>= 3.1.1)
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
@@ -17,8 +11,10 @@ GEM
|
|||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
base64 (0.2.0)
|
base64 (0.2.0)
|
||||||
benchmark-ips (2.13.0)
|
benchmark-ips (2.13.0)
|
||||||
|
bigdecimal (3.2.3)
|
||||||
json (2.7.2)
|
json (2.7.2)
|
||||||
language_server-protocol (3.17.0.3)
|
language_server-protocol (3.17.0.3)
|
||||||
|
lru_redux (1.1.0)
|
||||||
memory_profiler (1.0.1)
|
memory_profiler (1.0.1)
|
||||||
minitest (5.22.3)
|
minitest (5.22.3)
|
||||||
parallel (1.24.0)
|
parallel (1.24.0)
|
||||||
@@ -50,6 +46,7 @@ GEM
|
|||||||
rubocop (~> 1.44)
|
rubocop (~> 1.44)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
stackprof (0.2.26)
|
stackprof (0.2.26)
|
||||||
|
strscan (3.1.5)
|
||||||
terminal-table (3.0.2)
|
terminal-table (3.0.2)
|
||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
unicode-display_width (2.5.0)
|
unicode-display_width (2.5.0)
|
||||||
@@ -62,7 +59,7 @@ DEPENDENCIES
|
|||||||
base64
|
base64
|
||||||
benchmark-ips
|
benchmark-ips
|
||||||
liquid!
|
liquid!
|
||||||
liquid-c!
|
lru_redux
|
||||||
memory_profiler
|
memory_profiler
|
||||||
minitest
|
minitest
|
||||||
rake (~> 13.0)
|
rake (~> 13.0)
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Inline Snippets</title>
|
<title>Inline Snippets</title>
|
||||||
|
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="liquid" style="font-size: 56px;">
|
<div class="liquid" style="font-size: 56px;">
|
||||||
|
|
||||||
{% assign foo = true %}
|
{% assign foo = true %}
|
||||||
{% assign link = "variable" %}
|
{% assign linktext = "variable" %}
|
||||||
|
|
||||||
{% snippet main %}
|
{% snippet main %}
|
||||||
{% assign foo = false %}
|
{% assign foo = false %}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<p>This is an inline snippet</p>
|
<p>This is an inline snippet</p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/wow-a-link">wow a {{ link }}</a></li>
|
<li><a href="/wow-a-link">wow a {{ linktext }}</a></li>
|
||||||
<li>1 + 1 = {{ 1 | plus: 1 }}</li>
|
<li>1 + 1 = {{ 1 | plus: 1 }}</li>
|
||||||
<li>{% if true %}Yes!{% endif %}</li>
|
<li>{% if true %}Yes!{% endif %}</li>
|
||||||
<li>foo = {% if foo %}true{%else%}false{% endif %}</li>
|
<li>foo = {% if foo %}true{%else%}false{% endif %}</li>
|
||||||
@@ -29,6 +29,15 @@
|
|||||||
{% render main, arg: 'lsf', ... %}
|
{% render main, arg: 'lsf', ... %}
|
||||||
{{ foo }}
|
{{ foo }}
|
||||||
|
|
||||||
|
{% snippet listitem %}
|
||||||
|
<li>{{ forloop.index }}: {{ emoji }}</li>
|
||||||
|
{% endsnippet %}
|
||||||
|
|
||||||
|
{% assign emojis = "🌼,🌳,🌸" | split: "," %}
|
||||||
|
<ul style="list-style-type: none; display: flex; gap: 1em;">
|
||||||
|
{%- render listitem for emojis as emoji -%}
|
||||||
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+17
-29
@@ -52,14 +52,24 @@ module Liquid
|
|||||||
|
|
||||||
def render_tag(context, output)
|
def render_tag(context, output)
|
||||||
template_name = @template_name_expr
|
template_name = @template_name_expr
|
||||||
|
is_inline = template_name.is_a?(VariableLookup)
|
||||||
|
is_file = template_name.is_a?(String)
|
||||||
|
|
||||||
# For inline snippets, @template_name_expr is a VariableLookup
|
if is_inline
|
||||||
if template_name.is_a?(VariableLookup)
|
template_name = template_name.name
|
||||||
|
snippet_drop = context[template_name]
|
||||||
snippet_drop = context[template_name.name]
|
|
||||||
|
|
||||||
raise ::ArgumentError unless snippet_drop.is_a?(Liquid::SnippetDrop)
|
raise ::ArgumentError unless snippet_drop.is_a?(Liquid::SnippetDrop)
|
||||||
|
|
||||||
|
partial = snippet_drop.body
|
||||||
|
else
|
||||||
|
raise ::ArgumentError unless is_file
|
||||||
|
|
||||||
|
partial = PartialCache.load(template_name, context: context, parse_context: parse_context)
|
||||||
|
end
|
||||||
|
|
||||||
|
context_variable_name = @alias_name || template_name.split('/').last
|
||||||
|
|
||||||
|
render_partial_func = ->(var, forloop) {
|
||||||
inner_context = context.new_isolated_subcontext
|
inner_context = context.new_isolated_subcontext
|
||||||
|
|
||||||
if is_file
|
if is_file
|
||||||
@@ -68,7 +78,6 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
if is_inline && inherit_context?
|
if is_inline && inherit_context?
|
||||||
|
|
||||||
context.scopes.each do |scope|
|
context.scopes.each do |scope|
|
||||||
scope.each do |key, value|
|
scope.each do |key, value|
|
||||||
inner_context[key] = value
|
inner_context[key] = value
|
||||||
@@ -80,30 +89,9 @@ module Liquid
|
|||||||
inner_context[key] = context.evaluate(value)
|
inner_context[key] = context.evaluate(value)
|
||||||
end
|
end
|
||||||
|
|
||||||
return output << snippet_drop.body.render(inner_context)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Otherwise, the expression should be a String literal, which parses to a String object
|
|
||||||
raise ::ArgumentError unless template_name.is_a?(String)
|
|
||||||
|
|
||||||
partial = PartialCache.load(
|
|
||||||
template_name,
|
|
||||||
context: context,
|
|
||||||
parse_context: parse_context,
|
|
||||||
)
|
|
||||||
|
|
||||||
context_variable_name = @alias_name || template_name.split('/').last
|
|
||||||
|
|
||||||
render_partial_func = ->(var, forloop) {
|
|
||||||
inner_context = context.new_isolated_subcontext
|
|
||||||
inner_context.template_name = partial.name
|
|
||||||
inner_context.partial = true
|
|
||||||
inner_context['forloop'] = forloop if forloop
|
|
||||||
|
|
||||||
@attributes.each do |key, value|
|
|
||||||
inner_context[key] = context.evaluate(value)
|
|
||||||
end
|
|
||||||
inner_context[context_variable_name] = var unless var.nil?
|
inner_context[context_variable_name] = var unless var.nil?
|
||||||
|
inner_context['forloop'] = forloop if forloop
|
||||||
|
|
||||||
partial.render_to_output_buffer(inner_context, output)
|
partial.render_to_output_buffer(inner_context, output)
|
||||||
forloop&.send(:increment!)
|
forloop&.send(:increment!)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ module Liquid
|
|||||||
|
|
||||||
def initialize(tag_name, markup, options)
|
def initialize(tag_name, markup, options)
|
||||||
super
|
super
|
||||||
if markup =~ SYNTAX
|
p = @parse_context.new_parser(markup)
|
||||||
@to = Regexp.last_match(1)
|
if p.look(:id)
|
||||||
|
@to = p.consume(:id)
|
||||||
else
|
else
|
||||||
raise SyntaxError, options[:locale].t("errors.syntax.snippet")
|
raise SyntaxError, options[:locale].t("errors.syntax.snippet")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -102,7 +102,9 @@ class RenderTagTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_dynamically_choosen_templates_are_not_allowed
|
def test_dynamically_choosen_templates_are_not_allowed
|
||||||
assert_syntax_error("{% assign name = 'snippet' %}{% render name %}")
|
assert_raises(::ArgumentError) do
|
||||||
|
Template.parse('{% assign name = "snippet" %}{% render name %}').render!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_rigid_parsing_errors
|
def test_rigid_parsing_errors
|
||||||
|
|||||||
@@ -319,31 +319,31 @@ class SnippetTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_render_inline_snippet_with_outside_context_rigid
|
def test_render_inline_snippet_with_outside_context_rigid
|
||||||
template = <<~LIQUID.strip
|
template = <<~LIQUID.strip
|
||||||
{% assign color_scheme = 'dark' %}
|
{% assign color_scheme = 'dark' %}
|
||||||
|
|
||||||
{% snippet header %}
|
{% snippet header %}
|
||||||
<div class="header header--{{ color_scheme }}">
|
<div class="header header--{{ color_scheme }}">
|
||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
{% endsnippet %}
|
{% endsnippet %}
|
||||||
|
|
||||||
|
|
||||||
{% render header, ..., message: 'Welcome!' %}
|
{% render header, ..., message: 'Welcome!' %}
|
||||||
LIQUID
|
LIQUID
|
||||||
expected = <<~OUTPUT
|
expected = <<~OUTPUT
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="header header--dark">
|
<div class="header header--dark">
|
||||||
Welcome!
|
Welcome!
|
||||||
</div>
|
</div>
|
||||||
OUTPUT
|
OUTPUT
|
||||||
|
|
||||||
assert_template_result(expected, template, error_mode: :rigid)
|
assert_template_result(expected, template, error_mode: :rigid)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_inline_snippet_local_scope_takes_precedence
|
def test_inline_snippet_local_scope_takes_precedence
|
||||||
@@ -467,83 +467,94 @@ class SnippetTest < Minitest::Test
|
|||||||
assert_template_result(expected, template)
|
assert_template_result(expected, template)
|
||||||
end
|
end
|
||||||
|
|
||||||
# def test_render_inline_snippet_inside_loop
|
def test_render_inline_snippet_forloop
|
||||||
# template = <<~LIQUID.strip
|
template = <<~LIQUID.strip
|
||||||
# {% assign color_scheme = 'dark' %}
|
{% snippet item %}
|
||||||
# {% assign array = '1,2,3' | split: ',' %}
|
<li>{{ forloop.index }}: {{ item }}</li>
|
||||||
|
{% endsnippet %}
|
||||||
|
|
||||||
# {% for i in array %}
|
{% assign items = "A,B,C" | split: "," %}
|
||||||
# {% snippet header %}
|
{%- render item for items -%}
|
||||||
# <div class="header header--{{ color_scheme }}">
|
LIQUID
|
||||||
# {{ message }} {{ i }}
|
expected = <<~OUTPUT
|
||||||
# </div>
|
|
||||||
# {% endsnippet %}
|
|
||||||
# {% endfor %}
|
|
||||||
|
|
||||||
# {% render header, ..., message: '👉' %}
|
|
||||||
# LIQUID
|
|
||||||
# expected = <<~OUTPUT
|
|
||||||
|
|
||||||
# <div class="header header--dark">
|
|
||||||
# 👉 3
|
|
||||||
# </div>
|
|
||||||
# OUTPUT
|
|
||||||
|
|
||||||
# assert_template_result(expected, template)
|
<li>1: A</li>
|
||||||
# end
|
|
||||||
|
|
||||||
# def test_render_inline_snippet_forloop
|
<li>2: B</li>
|
||||||
# template = <<~LIQUID.strip
|
|
||||||
# {% snippet item %}
|
|
||||||
# <li>{{ forloop.index }}: {{ item }}</li>
|
|
||||||
# {% endsnippet %}
|
|
||||||
|
|
||||||
# {% assign items = "A,B,C" | split: "," %}
|
<li>3: C</li>
|
||||||
# {%- render item for items -%}
|
OUTPUT
|
||||||
# LIQUID
|
|
||||||
# expected = <<~OUTPUT
|
|
||||||
|
|
||||||
# <li>1: A</li>
|
assert_template_result(expected, template)
|
||||||
|
end
|
||||||
|
|
||||||
# <li>2: B</li>
|
def test_render_inline_snippet_with
|
||||||
|
template = <<~LIQUID.strip
|
||||||
|
{% snippet header %}
|
||||||
|
<div>{{ header }}</div>
|
||||||
|
{% endsnippet %}
|
||||||
|
|
||||||
# <li>3: C</li>
|
{% assign product = "Apple" %}
|
||||||
# OUTPUT
|
{%- render header with product -%}
|
||||||
|
LIQUID
|
||||||
|
expected = <<~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" %}
|
<div>Apple</div>
|
||||||
# {%- render header with product -%}
|
OUTPUT
|
||||||
# LIQUID
|
|
||||||
# expected = <<~OUTPUT
|
|
||||||
|
|
||||||
# <div>Apple</div>
|
assert_template_result(expected, template)
|
||||||
# OUTPUT
|
end
|
||||||
|
|
||||||
# assert_template_result(expected, template)
|
def test_render_inline_snippet_alias
|
||||||
# end
|
template = <<~LIQUID.strip
|
||||||
|
{% snippet product_card %}
|
||||||
|
<div class="product">{{ item }}</div>
|
||||||
|
{% endsnippet %}
|
||||||
|
|
||||||
# def test_render_inline_snippet_alias
|
{% assign featured = "Apple" %}
|
||||||
# template = <<~LIQUID.strip
|
{%- render product_card with featured as item -%}
|
||||||
# {% snippet product_card %}
|
LIQUID
|
||||||
# <div class="product">{{ item }}</div>
|
expected = <<~OUTPUT
|
||||||
# {% 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)
|
<div class="product">Apple</div>
|
||||||
# end
|
OUTPUT
|
||||||
|
|
||||||
|
assert_template_result(expected, template)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_render_inline_snippet_inside_loop
|
||||||
|
template = <<~LIQUID.strip
|
||||||
|
{% assign color_scheme = 'dark' %}
|
||||||
|
{% assign array = '1,2,3' | split: ',' %}
|
||||||
|
|
||||||
|
{% for i in array %}
|
||||||
|
{% snippet header %}
|
||||||
|
<div class="header header--{{ color_scheme }}">
|
||||||
|
{{ message }} {{ i }}
|
||||||
|
</div>
|
||||||
|
{% endsnippet %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% render header, ..., message: '👉' %}
|
||||||
|
LIQUID
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="header header--dark">
|
||||||
|
👉#{" "}
|
||||||
|
</div>
|
||||||
|
OUTPUT
|
||||||
|
|
||||||
|
assert_template_result(expected, template)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user