-
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
249 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
[Filters](tag_reference.md#filters) are usually implemented as simple Python functions. When rendered, Liquid will find the function in [`Environment.filters`](api/environment.md#liquid.Environment.filters), then call it, passing the input value as the first argument, followed by positional and keyword arguments given by the template author. The function's return value then becomes the result of the _filtered expression_. | ||
|
||
Filters can actually be any Python [callable](https://docs.python.org/3/glossary.html#term-callable). Implementing a filter as a class with a `__call__` method or as a closure can be useful if you want to configure the filter before registering it with an [`Environment`](environment.md). | ||
|
||
Also see the [filter helpers](api/filter.md) API documentation. | ||
|
||
!!! tip | ||
|
||
See [liquid/builtin/filters](https://github.com/jg-rp/liquid/tree/main/liquid/builtin/filters) for lots of examples. | ||
|
||
## Add a filter | ||
|
||
To add a filter, add an item to [`Environment.filters`](api/environment.md#liquid.Environment.filters). It's a regular dictionary mapping filter names to callables. | ||
|
||
In this example we add `ends_with`, a filter that delegates to Python's `str.endswith`. The `@string_filter` decorator coerces the input value to a string, if it is not one already. | ||
|
||
```python | ||
from liquid import Environment | ||
from liquid.filter import string_filter | ||
|
||
|
||
@string_filter | ||
def ends_with(left: str, val: str) -> bool: | ||
return left.endswith(val) | ||
|
||
|
||
env = Environment() | ||
env.filters["ends_with"] = ends_with | ||
|
||
source = """\ | ||
{% assign foo = "foobar" | ends_with: "bar" %} | ||
{% if foo %} | ||
do something | ||
{% endif %}""" | ||
|
||
template = env.from_string(source) | ||
print(template.render()) | ||
``` | ||
|
||
### With context | ||
|
||
Sometimes a filter will need access to the current [render context](render_context.md). Use the `@with_context` decorator to have an instance of [`RenderContext`](api/render_context.md) passed to your filter callable as a keyword argument named `context`. | ||
|
||
Here we use the render context to resolve a variable called "handle". | ||
|
||
```python | ||
from liquid import Environment | ||
from liquid.filter import string_filter | ||
from liquid.filter import with_context | ||
|
||
|
||
@string_filter | ||
@with_context | ||
def link_to_tag(label, tag, *, context): | ||
handle = context.resolve("handle", default="") | ||
return ( | ||
f'<a title="Show tag {tag}" href="/collections/{handle}/{tag}">{label}</a>' | ||
) | ||
|
||
class MyEnvironment(Environment): | ||
def register_tags_and_filters(self): | ||
super().register_tags_and_filters() | ||
self.filters["link_to_tag"] = link_to_tag | ||
|
||
env = MyEnvironment() | ||
# ... | ||
``` | ||
|
||
### With environment | ||
|
||
Use the `@with_environment` decorator to have the current [`Environment`](api/environment.md) passed to your filter callable as a keyword argument named `environment`. | ||
|
||
```python | ||
import re | ||
|
||
from markupsafe import Markup | ||
from markupsafe import escape as markupsafe_escape | ||
|
||
from liquid import Environment | ||
from liquid.filter import string_filter | ||
from liquid.filter import with_environment | ||
|
||
RE_LINETERM = re.compile(r"\r?\n") | ||
|
||
|
||
@with_environment | ||
@string_filter | ||
def strip_newlines(val: str, *, environment: Environment) -> str: | ||
if environment.autoescape: | ||
val = markupsafe_escape(val) | ||
return Markup(RE_LINETERM.sub("", val)) | ||
return RE_LINETERM.sub("", val) | ||
|
||
# ... | ||
``` | ||
|
||
## Replace a filter | ||
|
||
To replace a default filter implementation with your own, simply update the [`filters`](api/environment.md#liquid.Environment.filters) dictionary on your Liquid [Environment](environment.md). | ||
|
||
Here we replace the default `slice` filter with one which uses start and stop values instead of start and length, and is a bit more forgiving in terms of allowed inputs. | ||
|
||
```python | ||
from liquid import Environment | ||
from liquid.filter import int_arg | ||
from liquid.filter import sequence_filter | ||
|
||
@sequence_filter | ||
def myslice(val, start, stop=None): | ||
start = int_arg(start) | ||
|
||
if stop is None: | ||
return val[start] | ||
|
||
stop = int_arg(stop) | ||
return val[start:stop] | ||
|
||
|
||
env = Environment() | ||
env.filters["slice"] = myslice | ||
# ... | ||
``` | ||
|
||
## Remove a filter | ||
|
||
Remove a built-in filter by deleting it from your [environment's](environment.md) [`filters`](api/environment.md#liquid.Environment.filters) dictionary. | ||
|
||
```python | ||
from liquid import Environment | ||
|
||
env = Environment() | ||
del env.filters["safe"] | ||
|
||
# ... | ||
``` | ||
|
||
!!! tip | ||
|
||
You can add, remove and replace filters on `liquid.DEFAULT_ENVIRONMENT` too. Convenience functions [`parse()`](api/convenience.md#liquid.parse) and [`render()`](api/convenience.md#liquid.render) use `DEFAULT_ENVIRONMENT` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
This page documents known compatibility issues between Python Liquid's default [`Environment`](api/environment.md) and [Shopify/liquid](https://shopify.github.io/liquid/), the reference implementation written in Ruby. We strive to be 100% compatible with Shopify/liquid. That is, given an equivalent render context, a template rendered with Python Liquid should produce the same output as when rendered with Ruby Liquid. | ||
|
||
## Coercing Strings to Integers Inside Filters | ||
|
||
**_See issue [#49](https://github.com/jg-rp/liquid/issues/49)_** | ||
|
||
Many filters built in to Liquid will automatically convert a string representation of a number to an integer or float as needed. When converting integers, Ruby Liquid uses [Ruby's String.to_i method](https://ruby-doc.org/core-3.1.1/String.html#method-i-to_i), which will disregard trailing non-digit characters. In the following example, `'7,42'` is converted to `7` | ||
|
||
**template:** | ||
|
||
```liquid | ||
{{ 3.14 | plus: '7,42' }} | ||
{{ '123abcdef45' | plus: '1,,,,..!@qwerty' }} | ||
``` | ||
|
||
**output** | ||
|
||
```plain | ||
10.14 | ||
124 | ||
``` | ||
|
||
Python Liquid currently falls back to `0` for any string that can't be converted to an integer in its entirety. As is the case in Ruby Liquid for strings without leading digits. | ||
|
||
This does not apply to parsing of integer literals, only converting strings to integers (not floats) inside filters. | ||
|
||
## The Date Filter | ||
|
||
The built-in [`date`](filter_reference.md#date) filter uses [dateutil](https://dateutil.readthedocs.io/en/stable/) for parsing strings to `datetime`s, and `strftime` for formatting. There are likely to be some inconsistencies between this and the reference implementation's equivalent parsing and formatting of dates and times. | ||
|
||
## Orphaned `{% break %}` and `{% continue %}` | ||
|
||
**_See issue [#76](https://github.com/jg-rp/liquid/issues/76)_** | ||
|
||
Shopify/liquid shows some unintuitive behavior when `{% break %}` or `{% continue %}` are found outside a `{% for %}` tag block. | ||
|
||
```liquid | ||
{%- if true -%} | ||
before | ||
{%- if true %} | ||
hello{% break %}goodbye | ||
{% endif -%} | ||
after | ||
{%- endif -%} | ||
{% for x in (1..3) %} | ||
{{ x }} | ||
{% endfor %} | ||
{% for x in (1..3) %} | ||
{{ x }} | ||
{% endfor %} | ||
``` | ||
|
||
Shopify/iquid output in both strict and lax modes: | ||
|
||
```plain | ||
before | ||
hello | ||
``` | ||
|
||
Python Liquid raises a `LiquidSyntaxError` in strict mode and jumps over the entire outer `{% if %}` block in lax mode. | ||
|
||
```plain | ||
1 | ||
2 | ||
3 | ||
1 | ||
2 | ||
3 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
TODO: examples of registering these filters | ||
|
||
## currency | ||
|
||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters