Skip to content

Commit

Permalink
allow using comparison operator symbols as tests
Browse files Browse the repository at this point in the history
add tests and aliases for all comparison operators
adjust docs to prefer short names for compare tests
closes #664
  • Loading branch information
davidism committed Jul 6, 2017
1 parent 05b55d5 commit d62f050
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 48 deletions.
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

0 comments on commit d62f050

Please sign in to comment.