Skip to content

Commit d085cf6

Browse files
committed
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
1 parent 67c9cb8 commit d085cf6

File tree

6 files changed

+139
-125
lines changed

6 files changed

+139
-125
lines changed

Gemfile.lock

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
1-
GIT
2-
remote: https://github.com/Shopify/liquid-c.git
3-
revision: 5a786af7284df55e013ea20551c4b688d02e8326
4-
ref: main
5-
specs:
6-
liquid-c (4.2.0)
7-
liquid (>= 5.0.1)
8-
91
PATH
102
remote: .
113
specs:
12-
liquid (5.6.0.alpha)
4+
liquid (5.8.7)
5+
bigdecimal
6+
strscan (>= 3.1.1)
137

148
GEM
159
remote: https://rubygems.org/
1610
specs:
1711
ast (2.4.2)
1812
base64 (0.2.0)
1913
benchmark-ips (2.13.0)
14+
bigdecimal (3.2.3)
2015
json (2.7.2)
2116
language_server-protocol (3.17.0.3)
17+
lru_redux (1.1.0)
2218
memory_profiler (1.0.1)
2319
minitest (5.22.3)
2420
parallel (1.24.0)
@@ -50,6 +46,7 @@ GEM
5046
rubocop (~> 1.44)
5147
ruby-progressbar (1.13.0)
5248
stackprof (0.2.26)
49+
strscan (3.1.5)
5350
terminal-table (3.0.2)
5451
unicode-display_width (>= 1.1.1, < 3)
5552
unicode-display_width (2.5.0)
@@ -62,7 +59,7 @@ DEPENDENCIES
6259
base64
6360
benchmark-ips
6461
liquid!
65-
liquid-c!
62+
lru_redux
6663
memory_profiler
6764
minitest
6865
rake (~> 13.0)

example/server/templates/index.liquid

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,20 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
66
<title>Inline Snippets</title>
77

8-
98
<body>
109
<div class="liquid" style="font-size: 56px;">
10+
1111
{% assign foo = true %}
12-
{% assign link = "variable" %}
13-
12+
{% assign linktext = "variable" %}
13+
1414
{% snippet main %}
1515
{% assign foo = false %}
1616
<p>Hi {{ arg | upcase }}!!!</p>
1717

1818
<p>This is an inline snippet</p>
1919

2020
<ul>
21-
<li><a href="/wow-a-link">wow a {{ link }}</a></li>
21+
<li><a href="/wow-a-link">wow a {{ linktext }}</a></li>
2222
<li>1 + 1 = {{ 1 | plus: 1 }}</li>
2323
<li>{% if true %}Yes!{% endif %}</li>
2424
<li>foo = {% if foo %}true{%else%}false{% endif %}</li>
@@ -29,6 +29,15 @@
2929
{% render main, arg: 'lsf', ... %}
3030
{{ foo }}
3131

32+
{% snippet listitem %}
33+
<li>{{ forloop.index }}: {{ emoji }}</li>
34+
{% endsnippet %}
35+
36+
{% assign emojis = "🌼,🌳,🌸" | split: "," %}
37+
<ul style="list-style-type: none; display: flex; gap: 1em;">
38+
{%- render listitem for emojis as emoji -%}
39+
</ul>
40+
3241
</div>
3342
</body>
3443
</html>

lib/liquid/tags/render.rb

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,32 @@ def render_to_output_buffer(context, output)
5252

5353
def render_tag(context, output)
5454
template_name = @template_name_expr
55+
is_inline = template_name.is_a?(VariableLookup)
56+
is_file = template_name.is_a?(String)
5557

56-
# For inline snippets, @template_name_expr is a VariableLookup
57-
if template_name.is_a?(VariableLookup)
58+
if is_inline
59+
template_name = template_name.name
60+
snippet_drop = context[template_name]
61+
raise ::ArgumentError unless snippet_drop.is_a?(Liquid::SnippetDrop)
5862

59-
snippet_drop = context[template_name.name]
63+
partial = snippet_drop.body
64+
else
65+
raise ::ArgumentError unless is_file
6066

61-
raise ::ArgumentError unless snippet_drop.is_a?(Liquid::SnippetDrop)
67+
partial = PartialCache.load(template_name, context: context, parse_context: parse_context)
68+
end
69+
70+
context_variable_name = @alias_name || template_name.split('/').last
6271

72+
render_partial_func = ->(var, forloop) {
6373
inner_context = context.new_isolated_subcontext
6474

65-
if inherit_context?
75+
if is_file
76+
inner_context.template_name = partial.name
77+
inner_context.partial = true
78+
end
79+
80+
if is_inline && inherit_context?
6681
context.scopes.each do |scope|
6782
scope.each do |key, value|
6883
inner_context[key] = value
@@ -74,30 +89,9 @@ def render_tag(context, output)
7489
inner_context[key] = context.evaluate(value)
7590
end
7691

77-
return output << snippet_drop.body.render(inner_context)
78-
end
79-
80-
# Otherwise, the expression should be a String literal, which parses to a String object
81-
raise ::ArgumentError unless template_name.is_a?(String)
82-
83-
partial = PartialCache.load(
84-
template_name,
85-
context: context,
86-
parse_context: parse_context,
87-
)
88-
89-
context_variable_name = @alias_name || template_name.split('/').last
90-
91-
render_partial_func = ->(var, forloop) {
92-
inner_context = context.new_isolated_subcontext
93-
inner_context.template_name = partial.name
94-
inner_context.partial = true
95-
inner_context['forloop'] = forloop if forloop
96-
97-
@attributes.each do |key, value|
98-
inner_context[key] = context.evaluate(value)
99-
end
10092
inner_context[context_variable_name] = var unless var.nil?
93+
inner_context['forloop'] = forloop if forloop
94+
10195
partial.render_to_output_buffer(inner_context, output)
10296
forloop&.send(:increment!)
10397
}
@@ -137,7 +131,7 @@ def rigid_parse(markup)
137131
p.consume?(:comma)
138132

139133
@inherit_context = false
140-
# ... syntax for inline snippets
134+
# ... inline snippets syntax
141135
if p.look(:dotdot)
142136
p.consume(:dotdot)
143137
p.consume(:dot)

lib/liquid/tags/snippet.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ class Snippet < Block
1818

1919
def initialize(tag_name, markup, options)
2020
super
21-
if markup =~ SYNTAX
22-
@to = Regexp.last_match(1)
21+
p = @parse_context.new_parser(markup)
22+
if p.look(:id)
23+
@to = p.consume(:id)
2324
else
2425
raise SyntaxError, options[:locale].t("errors.syntax.snippet")
2526
end

test/integration/tags/render_tag_test.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ def test_sub_contexts_count_towards_the_same_recursion_limit
102102
end
103103

104104
def test_dynamically_choosen_templates_are_not_allowed
105-
assert_syntax_error("{% assign name = 'snippet' %}{% render name %}")
105+
assert_raises(::ArgumentError) do
106+
Template.parse('{% assign name = "snippet" %}{% render name %}').render!
107+
end
106108
end
107109

108110
def test_rigid_parsing_errors

test/integration/tags/snippet_test.rb

Lines changed: 90 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -466,84 +466,95 @@ def test_render_snippets_as_arguments
466466

467467
assert_template_result(expected, template)
468468
end
469+
470+
def test_render_inline_snippet_forloop
471+
template = <<~LIQUID.strip
472+
{% snippet item %}
473+
<li>{{ forloop.index }}: {{ item }}</li>
474+
{% endsnippet %}
475+
476+
{% assign items = "A,B,C" | split: "," %}
477+
{%- render item for items -%}
478+
LIQUID
479+
expected = <<~OUTPUT
480+
481+
482+
483+
<li>1: A</li>
484+
485+
<li>2: B</li>
486+
487+
<li>3: C</li>
488+
OUTPUT
489+
490+
assert_template_result(expected, template)
491+
end
492+
493+
def test_render_inline_snippet_with
494+
template = <<~LIQUID.strip
495+
{% snippet header %}
496+
<div>{{ header }}</div>
497+
{% endsnippet %}
498+
499+
{% assign product = "Apple" %}
500+
{%- render header with product -%}
501+
LIQUID
502+
expected = <<~OUTPUT
503+
504+
505+
506+
<div>Apple</div>
507+
OUTPUT
508+
509+
assert_template_result(expected, template)
510+
end
469511

470-
# def test_render_inline_snippet_inside_loop
471-
# template = <<~LIQUID.strip
472-
# {% assign color_scheme = 'dark' %}
473-
# {% assign array = '1,2,3' | split: ',' %}
474-
475-
# {% for i in array %}
476-
# {% snippet header %}
477-
# <div class="header header--{{ color_scheme }}">
478-
# {{ message }} {{ i }}
479-
# </div>
480-
# {% endsnippet %}
481-
# {% endfor %}
482-
483-
# {% render header, ..., message: '👉' %}
484-
# LIQUID
485-
# expected = <<~OUTPUT
486-
487-
# <div class="header header--dark">
488-
# 👉 3
489-
# </div>
490-
# OUTPUT
491-
492-
# assert_template_result(expected, template)
493-
# end
494-
495-
# def test_render_inline_snippet_forloop
496-
# template = <<~LIQUID.strip
497-
# {% snippet item %}
498-
# <li>{{ forloop.index }}: {{ item }}</li>
499-
# {% endsnippet %}
500-
501-
# {% assign items = "A,B,C" | split: "," %}
502-
# {%- render item for items -%}
503-
# LIQUID
504-
# expected = <<~OUTPUT
505-
506-
# <li>1: A</li>
507-
508-
# <li>2: B</li>
509-
510-
# <li>3: C</li>
511-
# OUTPUT
512-
513-
# assert_template_result(expected, template)
514-
# end
515-
516-
# def test_render_inline_snippet_with
517-
# template = <<~LIQUID.strip
518-
# {% snippet header %}
519-
# <div>{{ header }}</div>
520-
# {% endsnippet %}
521-
522-
# {% assign product = "Apple" %}
523-
# {%- render header with product -%}
524-
# LIQUID
525-
# expected = <<~OUTPUT
526-
527-
# <div>Apple</div>
528-
# OUTPUT
529-
530-
# assert_template_result(expected, template)
531-
# end
532-
533-
# def test_render_inline_snippet_alias
534-
# template = <<~LIQUID.strip
535-
# {% snippet product_card %}
536-
# <div class="product">{{ item }}</div>
537-
# {% endsnippet %}
538-
539-
# {% assign featured = "Apple" %}
540-
# {%- render product_card with featured as item -%}
541-
# LIQUID
542-
# expected = <<~OUTPUT
543-
544-
# <div class="product">Apple</div>
545-
# OUTPUT
546-
547-
# assert_template_result(expected, template)
548-
# end
512+
def test_render_inline_snippet_alias
513+
template = <<~LIQUID.strip
514+
{% snippet product_card %}
515+
<div class="product">{{ item }}</div>
516+
{% endsnippet %}
517+
518+
{% assign featured = "Apple" %}
519+
{%- render product_card with featured as item -%}
520+
LIQUID
521+
expected = <<~OUTPUT
522+
523+
524+
525+
<div class="product">Apple</div>
526+
OUTPUT
527+
528+
assert_template_result(expected, template)
529+
end
530+
531+
def test_render_inline_snippet_inside_loop
532+
template = <<~LIQUID.strip
533+
{% assign color_scheme = 'dark' %}
534+
{% assign array = '1,2,3' | split: ',' %}
535+
536+
{% for i in array %}
537+
{% snippet header %}
538+
<div class="header header--{{ color_scheme }}">
539+
{{ message }} {{ i }}
540+
</div>
541+
{% endsnippet %}
542+
{% endfor %}
543+
544+
{% render header, ..., message: '👉' %}
545+
LIQUID
546+
expected = <<~OUTPUT
547+
548+
549+
550+
551+
552+
553+
<div class="header header--dark">
554+
👉
555+
</div>
556+
OUTPUT
557+
558+
assert_template_result(expected, template)
559+
end
549560
end

0 commit comments

Comments
 (0)