From d62f0500a70aaab5762691d4feb6b9bb2724a1ba Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 19 Jan 2017 20:40:31 -0800 Subject: [PATCH] allow using comparison operator symbols as tests add tests and aliases for all comparison operators adjust docs to prefer short names for compare tests closes #664 --- CHANGES | 3 +++ docs/jinjaext.py | 20 +++++++++++++---- jinja2/tests.py | 53 +++++++++++++++------------------------------ tests/test_tests.py | 36 +++++++++++++++++++++++------- 4 files changed, 64 insertions(+), 48 deletions(-) diff --git a/CHANGES b/CHANGES index 31a5bb8aa..6276b1157 100644 --- a/CHANGES +++ b/CHANGES @@ -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 ------------- diff --git a/docs/jinjaext.py b/docs/jinjaext.py index cdacba9d9..bb5080892 100644 --- a/docs/jinjaext.py +++ b/docs/jinjaext.py @@ -12,6 +12,9 @@ import os import re import inspect + +import logging + import jinja2 from itertools import islice from types import BuiltinFunctionType @@ -108,19 +111,27 @@ 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, '') @@ -128,6 +139,7 @@ def directive(dirname, arguments, options, content, lineno, node = nodes.paragraph() state.nested_parse(result, content_offset, node) return node.children + return directive diff --git a/jinja2/tests.py b/jinja2/tests.py index 40b2b35a6..0adc3d4db 100644 --- a/jinja2/tests.py +++ b/jinja2/tests.py @@ -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 @@ -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: @@ -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. @@ -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, } diff --git a/tests/test_tests.py b/tests/test_tests.py index 3539a1fd3..84df5ea75 100644 --- a/tests/test_tests.py +++ b/tests/test_tests.py @@ -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 }}')