mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-12 23:40:45 -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:
committed by
Guilherme Carreiro
parent
9bcfd32e65
commit
99116638fd
+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
|
||||
remote: .
|
||||
specs:
|
||||
liquid (5.6.0.alpha)
|
||||
liquid (5.8.7)
|
||||
bigdecimal
|
||||
strscan (>= 3.1.1)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
@@ -17,8 +11,10 @@ GEM
|
||||
ast (2.4.2)
|
||||
base64 (0.2.0)
|
||||
benchmark-ips (2.13.0)
|
||||
bigdecimal (3.2.3)
|
||||
json (2.7.2)
|
||||
language_server-protocol (3.17.0.3)
|
||||
lru_redux (1.1.0)
|
||||
memory_profiler (1.0.1)
|
||||
minitest (5.22.3)
|
||||
parallel (1.24.0)
|
||||
@@ -50,6 +46,7 @@ GEM
|
||||
rubocop (~> 1.44)
|
||||
ruby-progressbar (1.13.0)
|
||||
stackprof (0.2.26)
|
||||
strscan (3.1.5)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
unicode-display_width (2.5.0)
|
||||
@@ -62,7 +59,7 @@ DEPENDENCIES
|
||||
base64
|
||||
benchmark-ips
|
||||
liquid!
|
||||
liquid-c!
|
||||
lru_redux
|
||||
memory_profiler
|
||||
minitest
|
||||
rake (~> 13.0)
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Inline Snippets</title>
|
||||
|
||||
|
||||
<body>
|
||||
<div class="liquid" style="font-size: 56px;">
|
||||
|
||||
{% assign foo = true %}
|
||||
{% assign link = "variable" %}
|
||||
|
||||
{% assign linktext = "variable" %}
|
||||
|
||||
{% snippet main %}
|
||||
{% assign foo = false %}
|
||||
<p>Hi {{ arg | upcase }}!!!</p>
|
||||
@@ -18,7 +18,7 @@
|
||||
<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 {{ linktext }}</a></li>
|
||||
<li>1 + 1 = {{ 1 | plus: 1 }}</li>
|
||||
<li>{% if true %}Yes!{% endif %}</li>
|
||||
<li>foo = {% if foo %}true{%else%}false{% endif %}</li>
|
||||
@@ -29,6 +29,15 @@
|
||||
{% render main, arg: 'lsf', ... %}
|
||||
{{ 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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+17
-29
@@ -52,14 +52,24 @@ module Liquid
|
||||
|
||||
def render_tag(context, output)
|
||||
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 template_name.is_a?(VariableLookup)
|
||||
|
||||
snippet_drop = context[template_name.name]
|
||||
|
||||
if is_inline
|
||||
template_name = template_name.name
|
||||
snippet_drop = context[template_name]
|
||||
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
|
||||
|
||||
if is_file
|
||||
@@ -68,7 +78,6 @@ module Liquid
|
||||
end
|
||||
|
||||
if is_inline && inherit_context?
|
||||
|
||||
context.scopes.each do |scope|
|
||||
scope.each do |key, value|
|
||||
inner_context[key] = value
|
||||
@@ -80,30 +89,9 @@ module Liquid
|
||||
inner_context[key] = context.evaluate(value)
|
||||
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['forloop'] = forloop if forloop
|
||||
|
||||
partial.render_to_output_buffer(inner_context, output)
|
||||
forloop&.send(:increment!)
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ module Liquid
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
if markup =~ SYNTAX
|
||||
@to = Regexp.last_match(1)
|
||||
p = @parse_context.new_parser(markup)
|
||||
if p.look(:id)
|
||||
@to = p.consume(:id)
|
||||
else
|
||||
raise SyntaxError, options[:locale].t("errors.syntax.snippet")
|
||||
end
|
||||
|
||||
@@ -102,7 +102,9 @@ class RenderTagTest < Minitest::Test
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def test_rigid_parsing_errors
|
||||
|
||||
@@ -319,31 +319,31 @@ class SnippetTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_render_inline_snippet_with_outside_context_rigid
|
||||
template = <<~LIQUID.strip
|
||||
{% assign color_scheme = 'dark' %}
|
||||
template = <<~LIQUID.strip
|
||||
{% assign color_scheme = 'dark' %}
|
||||
|
||||
{% snippet header %}
|
||||
<div class="header header--{{ color_scheme }}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endsnippet %}
|
||||
{% snippet header %}
|
||||
<div class="header header--{{ color_scheme }}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endsnippet %}
|
||||
|
||||
|
||||
{% render header, ..., message: 'Welcome!' %}
|
||||
LIQUID
|
||||
expected = <<~OUTPUT
|
||||
{% render header, ..., message: 'Welcome!' %}
|
||||
LIQUID
|
||||
expected = <<~OUTPUT
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="header header--dark">
|
||||
Welcome!
|
||||
</div>
|
||||
OUTPUT
|
||||
<div class="header header--dark">
|
||||
Welcome!
|
||||
</div>
|
||||
OUTPUT
|
||||
|
||||
assert_template_result(expected, template, error_mode: :rigid)
|
||||
assert_template_result(expected, template, error_mode: :rigid)
|
||||
end
|
||||
|
||||
def test_inline_snippet_local_scope_takes_precedence
|
||||
@@ -467,83 +467,94 @@ class SnippetTest < Minitest::Test
|
||||
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: ',' %}
|
||||
def test_render_inline_snippet_forloop
|
||||
template = <<~LIQUID.strip
|
||||
{% snippet item %}
|
||||
<li>{{ forloop.index }}: {{ item }}</li>
|
||||
{% endsnippet %}
|
||||
|
||||
# {% for i in array %}
|
||||
# {% snippet header %}
|
||||
# <div class="header header--{{ color_scheme }}">
|
||||
# {{ message }} {{ i }}
|
||||
# </div>
|
||||
# {% endsnippet %}
|
||||
# {% endfor %}
|
||||
{% assign items = "A,B,C" | split: "," %}
|
||||
{%- render item for items -%}
|
||||
LIQUID
|
||||
expected = <<~OUTPUT
|
||||
|
||||
# {% render header, ..., message: '👉' %}
|
||||
# LIQUID
|
||||
# expected = <<~OUTPUT
|
||||
|
||||
# <div class="header header--dark">
|
||||
# 👉 3
|
||||
# </div>
|
||||
# OUTPUT
|
||||
|
||||
# assert_template_result(expected, template)
|
||||
# end
|
||||
<li>1: A</li>
|
||||
|
||||
# def test_render_inline_snippet_forloop
|
||||
# template = <<~LIQUID.strip
|
||||
# {% snippet item %}
|
||||
# <li>{{ forloop.index }}: {{ item }}</li>
|
||||
# {% endsnippet %}
|
||||
<li>2: B</li>
|
||||
|
||||
# {% assign items = "A,B,C" | split: "," %}
|
||||
# {%- render item for items -%}
|
||||
# LIQUID
|
||||
# expected = <<~OUTPUT
|
||||
<li>3: C</li>
|
||||
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>
|
||||
# OUTPUT
|
||||
{% assign product = "Apple" %}
|
||||
{%- 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" %}
|
||||
# {%- render header with product -%}
|
||||
# LIQUID
|
||||
# expected = <<~OUTPUT
|
||||
<div>Apple</div>
|
||||
OUTPUT
|
||||
|
||||
# <div>Apple</div>
|
||||
# OUTPUT
|
||||
assert_template_result(expected, template)
|
||||
end
|
||||
|
||||
# assert_template_result(expected, template)
|
||||
# end
|
||||
def test_render_inline_snippet_alias
|
||||
template = <<~LIQUID.strip
|
||||
{% snippet product_card %}
|
||||
<div class="product">{{ item }}</div>
|
||||
{% endsnippet %}
|
||||
|
||||
# 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
|
||||
|
||||
# {% 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
|
||||
<div class="product">Apple</div>
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user