Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow using comparison operator symbols as tests #665

Merged
merged 1 commit into from Jul 6, 2017
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES
Expand Up @@ -26,10 +26,13 @@ Version 2.10
produce a new random choice each time the template is rendered. (`#478`_)
- Add a ``unique`` filter. (`#469`_)
- Add ``min`` and ``max`` filters. (`#475`_)
- Add tests for all comparison operators: ``eq``, ``ne``, ``lt``, ``le``,
``gt``, ``ge``. (`#665`_)

.. _#469: https://github.com/pallets/jinja/pull/469
.. _#475: https://github.com/pallets/jinja/pull/475
.. _#478: https://github.com/pallets/jinja/pull/478
.. _#665: https://github.com/pallets/jinja/pull/665

Version 2.9.6
-------------
Expand Down
20 changes: 16 additions & 4 deletions docs/jinjaext.py
Expand Up @@ -12,6 +12,9 @@
import os
import re
import inspect

import logging

import jinja2
from itertools import islice
from types import BuiltinFunctionType
Expand Down Expand Up @@ -108,26 +111,35 @@ def format_function(name, aliases, func):


def dump_functions(mapping):
def directive(dirname, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
def directive(
dirname, arguments, options, content, lineno, content_offset,
block_text, state, state_machine
):
reverse_mapping = {}

for name, func in mapping.items():
reverse_mapping.setdefault(func, []).append(name)

filters = []
compare_ops = set(('lt', 'le', 'eq', 'ne', 'ge', 'gt'))

for func, names in reverse_mapping.items():
aliases = sorted(names, key=lambda x: len(x))
aliases = sorted(names, key=len)
aliases = sorted(aliases, key=lambda x: x in compare_ops)
name = aliases.pop()
filters.append((name, aliases, func))
filters.sort()

filters.sort()
result = ViewList()

for name, aliases, func in filters:
for item in format_function(name, aliases, func):
result.append(item, '<jinjaext>')

node = nodes.paragraph()
state.nested_parse(result, content_offset, node)
return node.children

return directive


Expand Down
53 changes: 17 additions & 36 deletions jinja2/tests.py
Expand Up @@ -8,6 +8,7 @@
:copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import operator
import re
from collections import Mapping
from jinja2.runtime import Undefined
Expand Down Expand Up @@ -103,28 +104,6 @@ def test_sequence(value):
return True


def test_equalto(value, other):
"""Check if an object has the same value as another object:

.. sourcecode:: jinja

{% if foo.expression is equalto 42 %}
the foo attribute evaluates to the constant 42
{% endif %}

This appears to be a useless test as it does exactly the same as the
``==`` operator, but it can be useful when used together with the
`selectattr` function:

.. sourcecode:: jinja

{{ users|selectattr("email", "equalto", "foo@bar.invalid") }}

.. versionadded:: 2.8
"""
return value == other


def test_sameas(value, other):
"""Check if an object points to the same memory address than another
object:
Expand Down Expand Up @@ -152,16 +131,6 @@ def test_escaped(value):
return hasattr(value, '__html__')


def test_greaterthan(value, other):
"""Check if value is greater than other."""
return value > other


def test_lessthan(value, other):
"""Check if value is less than other."""
return value < other


def test_in(value, seq):
"""Check if value is in seq.

Expand All @@ -186,9 +155,21 @@ def test_in(value, seq):
'iterable': test_iterable,
'callable': test_callable,
'sameas': test_sameas,
'equalto': test_equalto,
'escaped': test_escaped,
'greaterthan': test_greaterthan,
'lessthan': test_lessthan,
'in': test_in
'in': test_in,
'==': operator.eq,
'eq': operator.eq,
'equalto': operator.eq,
'!=': operator.ne,
'ne': operator.ne,
'>': operator.gt,
'gt': operator.gt,
'greaterthan': operator.gt,
'ge': operator.ge,
'>=': operator.ge,
'<': operator.lt,
'lt': operator.lt,
'lessthan': operator.lt,
'<=': operator.le,
'le': operator.le,
}
36 changes: 28 additions & 8 deletions tests/test_tests.py
Expand Up @@ -78,17 +78,37 @@ def test_upper(self, env):
assert tmpl.render() == 'True|False'

def test_equalto(self, env):
tmpl = env.from_string('{{ foo is equalto 12 }}|'
'{{ foo is equalto 0 }}|'
'{{ foo is equalto (3 * 4) }}|'
'{{ bar is equalto "baz" }}|'
'{{ bar is equalto "zab" }}|'
'{{ bar is equalto ("ba" + "z") }}|'
'{{ bar is equalto bar }}|'
'{{ bar is equalto foo }}')
tmpl = env.from_string(
'{{ foo is eq 12 }}|'
'{{ foo is eq 0 }}|'
'{{ foo is eq (3 * 4) }}|'
'{{ bar is eq "baz" }}|'
'{{ bar is eq "zab" }}|'
'{{ bar is eq ("ba" + "z") }}|'
'{{ bar is eq bar }}|'
'{{ bar is eq foo }}'
)
assert tmpl.render(foo=12, bar="baz") \
== 'True|False|True|True|False|True|True|False'

@pytest.mark.parametrize('op,expect', (
('eq 2', True),
('eq 3', False),
('ne 3', True),
('ne 2', False),
('lt 3', True),
('lt 2', False),
('le 2', True),
('le 1', False),
('gt 1', True),
('gt 2', False),
('ge 2', True),
('ge 3', False),
))
def test_compare_aliases(self, env, op, expect):
t = env.from_string('{{{{ 2 is {op} }}}}'.format(op=op))
assert t.render() == str(expect)

def test_sameas(self, env):
tmpl = env.from_string('{{ foo is sameas false }}|'
'{{ 0 is sameas false }}')
Expand Down