Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Version 2.2.0 (unreleased)

- Added the `{% snippet %}` tag.
- Added an **experimental** `{% snippet %}` tag. Shopify/liquid released then quickly removed `{% snippet %}`. We're calling it "experimental" and keeping it disabled by default pending more activity from Shopify/liquid.
- Improved static analysis of partial templates. Previously we would visit a partial template only once, regardless of how many times it is rendered with `{% render %}`. Now we visit partial templates once for each distinct set of arguments passed to `{% render %}`, potentially reporting "global" variables that we'd previously missed.

## Version 2.1.0
Expand Down
80 changes: 80 additions & 0 deletions docs/experimental_tags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
The tags described on this page are considered experimental and might change without warning and not follow semantic versioning.

## snippet

**_New in version 2.2.0_**

```plain
{% snippet <identifier> %}
<liquid markup>
{% endsnippet %}
```

A snippet is a reusable block of Liquid markup. Traditionally we'd save a snippet to a file and include it in a template with the `{% render %}` tag.

```liquid
{% render "some_snippet" %}
```

With the `{% snippet %}` tag we can define blocks for reuse inside a single template, potentially reducing the number of snippet files we need.

```liquid
{% snippet div %}
<div>
{{ content }}
</div>
{% endsnippet %}
```

Defining a snippet does not render it. We use `{% render snippet_name %}` to render a snippet, where `snippet_name` is the name of your snippet without quotes (file-based snippet names must be quoted).

```liquid
{% snippet div %}
<div>
{{ content }}
</div>
{% endsnippet %}

{% render div, content: "Some content" %}
{% render div, content: "Other content" %}
```

```html title="output"
<div>Some content</div>

<div>Other content</div>
```

Inline snippets share the same namespace as variables defined with `{% assign %}` and `{% capture %}`, so be wary of accidentally overwriting snippets with non-snippet data.

```liquid
{% snippet foo %}Hello{% endsnippet %}
{% foo = 42 %}
{% render foo %} {% # error %}
```

Snippets can be nested and follow the same scoping rules as file-based snippets.

```liquid
{% snippet a %}
b
{% snippet c %}
d
{% endsnippet %}
{% render c %}
{% endsnippet %}

{% render a %}
{% render c %} {% # error, c is out of scope %}
```

Snippet blocks are bound to their names late. You can conditionally define multiple snippets with the same name and pick one at render time.

```liquid
{% if x %}
{% snippet a %}b{% endsnippet %}
{% else %}
{% snippet a %}c{% endsnippet %}
{% endif %}
{% render a %}
```
79 changes: 0 additions & 79 deletions docs/tag_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -540,85 +540,6 @@ Additional keyword arguments given to the `render` tag will be added to the rend
{% render "partial_template" greeting: "Hello", num: 3, skip: 2 %}
```

## snippet

**_New in version 2.2.0_**

```plain
{% snippet <identifier> %}
<liquid markup>
{% endsnippet %}
```

A snippet is a reusable block of Liquid markup. Traditionally we'd save a snippet to a file and include it in a template with the `{% render %}` tag.

```liquid
{% render "some_snippet" %}
```

With the `{% snippet %}` tag we can define blocks for reuse inside a single template, potentially reducing the number of snippet files we need.

```liquid
{% snippet div %}
<div>
{{ content }}
</div>
{% endsnippet %}
```

Defining a snippet does not render it. We use `{% render snippet_name %}` to render a snippet, where `snippet_name` is the name of your snippet without quotes (file-based snippet names must be quoted).

```liquid
{% snippet div %}
<div>
{{ content }}
</div>
{% endsnippet %}

{% render div, content: "Some content" %}
{% render div, content: "Other content" %}
```

```html title="output"
<div>Some content</div>

<div>Other content</div>
```

Inline snippets share the same namespace as variables defined with `{% assign %}` and `{% capture %}`, so be wary of accidentally overwriting snippets with non-snippet data.

```liquid
{% snippet foo %}Hello{% endsnippet %}
{% foo = 42 %}
{% render foo %} {% # error %}
```

Snippets can be nested and follow the same scoping rules as file-based snippets.

```liquid
{% snippet a %}
b
{% snippet c %}
d
{% endsnippet %}
{% render c %}
{% endsnippet %}

{% render a %}
{% render c %} {% # error, c is out of scope %}
```

Snippet blocks are bound to their names late. You can conditionally define multiple snippets with the same name and pick one at render time.

```liquid
{% if x %}
{% snippet a %}b{% endsnippet %}
{% else %}
{% snippet a %}c{% endsnippet %}
{% endif %}
{% render a %}
```

## tablerow

```plain
Expand Down
2 changes: 0 additions & 2 deletions liquid/builtin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@
from .tags import inline_comment_tag
from .tags import liquid_tag
from .tags import render_tag
from .tags import snippet
from .tags import tablerow_tag
from .tags import unless_tag

Expand Down Expand Up @@ -134,7 +133,6 @@ def register(env: Environment) -> None: # noqa: PLR0915
env.add_tag(ifchanged_tag.IfChangedTag)
env.add_tag(inline_comment_tag.InlineCommentTag)
env.add_tag(doc_tag.DocTag)
env.add_tag(snippet.SnippetTag)

env.add_filter("abs", abs_)
env.add_filter("at_most", at_most)
Expand Down
6 changes: 3 additions & 3 deletions liquid/builtin/tags/case_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from liquid.ast import BlockNode
from liquid.ast import Node
from liquid.builtin.expressions import parse_primitive
from liquid.builtin.expressions.logical import _eq
from liquid.builtin.expressions.logical import _eq # type: ignore
from liquid.exceptions import LiquidSyntaxError
from liquid.expression import Expression
from liquid.parser import get_parser
Expand Down Expand Up @@ -85,7 +85,7 @@ def render_to_output(self, context: RenderContext, buffer: TextIO) -> int:
count += count_
# Only render `else` blocks if all preceding `when` blocks are falsy.
# Multiple `else` blocks are OK.
elif isinstance(block, BlockNode) and default:
elif default:
count += block.render(context, buffer)

return count
Expand All @@ -106,7 +106,7 @@ async def render_to_output_async(
count += count_
# Only render `else` blocks if all preceding `when` blocks are falsy.
# Multiple `else` blocks are OK.
elif isinstance(block, BlockNode) and default:
elif default:
count += await block.render_async(context, buffer)

return count
Expand Down
2 changes: 1 addition & 1 deletion liquid/builtin/tags/if_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def parse(self, stream: TokenStream) -> Node:
condition = BooleanExpression.parse(self.env, tokens)
parse_block = get_parser(self.env).parse_block
consequence = parse_block(stream, ENDIFBLOCK)
alternatives = []
alternatives: list[ConditionalBlockNode] = []

while stream.current.is_tag(TAG_ELSIF):
# If the expression can't be parsed, eat the "elsif" block and
Expand Down
3 changes: 2 additions & 1 deletion liquid/extra/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from .tags import CallTag
from .tags import ExtendsTag
from .tags import MacroTag
from .tags import SnippetTag
from .tags import TranslateTag
from .tags import WithTag

Expand All @@ -38,7 +39,6 @@
"DateTime",
"ExtendsTag",
"GetText",
"IfNotTag",
"index",
"JSON",
"MacroTag",
Expand All @@ -49,6 +49,7 @@
"script_tag",
"sort_numeric",
"stylesheet_tag",
"SnippetTag",
"Translate",
"Unit",
"WithTag",
Expand Down
2 changes: 2 additions & 0 deletions liquid/extra/tags/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .extends_tag import ExtendsTag
from .macro_tag import CallTag
from .macro_tag import MacroTag
from .snippet_tag import SnippetTag
from .translate_tag import TranslateTag

__all__ = (
Expand All @@ -11,5 +12,6 @@
"ExtendsTag",
"CallTag",
"MacroTag",
"SnippetTag",
"TranslateTag",
)
File renamed without changes.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ nav:
- Tag reference:
- Default tags: "tag_reference.md"
- Extra tags: "optional_tags.md"
- Experimental tags: "experimental_tags.md"
- Filter reference:
- Default filters: "filter_reference.md"
- Extra filters: "optional_filters.md"
Expand Down
6 changes: 3 additions & 3 deletions tests/test_compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ class Case:

name: str
template: str
data: dict[str, Any] = field(default_factory=dict)
data: dict[str, Any] = field(default_factory=dict) # type: ignore
templates: Optional[dict[str, str]] = None
result: Optional[str] = None
results: Optional[list[str]] = None
invalid: Optional[bool] = None
tags: list[str] = field(default_factory=list)
tags: list[str] = field(default_factory=list) # type: ignore


FILENAME = "tests/golden-liquid/golden_liquid.json"

SKIP = {
"filters, has, array of ints, default value": "Ruby behavioral quirk",
"tags, case, unexpected when token, rigid": "TODO",
"tags, case, unexpected when token, strict2": "TODO",
}


Expand Down
5 changes: 4 additions & 1 deletion tests/test_static_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from liquid import Environment
from liquid.builtin import DictLoader
from liquid.exceptions import TemplateNotFoundError
from liquid.extra import SnippetTag
from liquid.span import Span
from liquid.static_analysis import Variable

Expand All @@ -25,7 +26,9 @@ class MockEnv(Environment):

@pytest.fixture
def env() -> Environment: # noqa: D103
return MockEnv(extra=True)
_env = MockEnv(extra=True)
_env.add_tag(SnippetTag) # Experimental tag
return _env


def _assert(
Expand Down