1"""Built-in template tests used with the ``is`` operator."""
2import operator
3import typing as t
4from collections import abc
5from numbers import Number
6
7from .runtime import Undefined
8from .utils import pass_environment
9
10if t.TYPE_CHECKING:
11    from .environment import Environment
12
13
14def test_odd(value: int) -> bool:
15    """Return true if the variable is odd."""
16    return value % 2 == 1
17
18
19def test_even(value: int) -> bool:
20    """Return true if the variable is even."""
21    return value % 2 == 0
22
23
24def test_divisibleby(value: int, num: int) -> bool:
25    """Check if a variable is divisible by a number."""
26    return value % num == 0
27
28
29def test_defined(value: t.Any) -> bool:
30    """Return true if the variable is defined:
31
32    .. sourcecode:: jinja
33
34        {% if variable is defined %}
35            value of variable: {{ variable }}
36        {% else %}
37            variable is not defined
38        {% endif %}
39
40    See the :func:`default` filter for a simple way to set undefined
41    variables.
42    """
43    return not isinstance(value, Undefined)
44
45
46def test_undefined(value: t.Any) -> bool:
47    """Like :func:`defined` but the other way round."""
48    return isinstance(value, Undefined)
49
50
51@pass_environment
52def test_filter(env: "Environment", value: str) -> bool:
53    """Check if a filter exists by name. Useful if a filter may be
54    optionally available.
55
56    .. code-block:: jinja
57
58        {% if 'markdown' is filter %}
59            {{ value | markdown }}
60        {% else %}
61            {{ value }}
62        {% endif %}
63
64    .. versionadded:: 3.0
65    """
66    return value in env.filters
67
68
69@pass_environment
70def test_test(env: "Environment", value: str) -> bool:
71    """Check if a test exists by name. Useful if a test may be
72    optionally available.
73
74    .. code-block:: jinja
75
76        {% if 'loud' is test %}
77            {% if value is loud %}
78                {{ value|upper }}
79            {% else %}
80                {{ value|lower }}
81            {% endif %}
82        {% else %}
83            {{ value }}
84        {% endif %}
85
86    .. versionadded:: 3.0
87    """
88    return value in env.tests
89
90
91def test_none(value: t.Any) -> bool:
92    """Return true if the variable is none."""
93    return value is None
94
95
96def test_boolean(value: t.Any) -> bool:
97    """Return true if the object is a boolean value.
98
99    .. versionadded:: 2.11
100    """
101    return value is True or value is False
102
103
104def test_false(value: t.Any) -> bool:
105    """Return true if the object is False.
106
107    .. versionadded:: 2.11
108    """
109    return value is False
110
111
112def test_true(value: t.Any) -> bool:
113    """Return true if the object is True.
114
115    .. versionadded:: 2.11
116    """
117    return value is True
118
119
120# NOTE: The existing 'number' test matches booleans and floats
121def test_integer(value: t.Any) -> bool:
122    """Return true if the object is an integer.
123
124    .. versionadded:: 2.11
125    """
126    return isinstance(value, int) and value is not True and value is not False
127
128
129# NOTE: The existing 'number' test matches booleans and integers
130def test_float(value: t.Any) -> bool:
131    """Return true if the object is a float.
132
133    .. versionadded:: 2.11
134    """
135    return isinstance(value, float)
136
137
138def test_lower(value: str) -> bool:
139    """Return true if the variable is lowercased."""
140    return str(value).islower()
141
142
143def test_upper(value: str) -> bool:
144    """Return true if the variable is uppercased."""
145    return str(value).isupper()
146
147
148def test_string(value: t.Any) -> bool:
149    """Return true if the object is a string."""
150    return isinstance(value, str)
151
152
153def test_mapping(value: t.Any) -> bool:
154    """Return true if the object is a mapping (dict etc.).
155
156    .. versionadded:: 2.6
157    """
158    return isinstance(value, abc.Mapping)
159
160
161def test_number(value: t.Any) -> bool:
162    """Return true if the variable is a number."""
163    return isinstance(value, Number)
164
165
166def test_sequence(value: t.Any) -> bool:
167    """Return true if the variable is a sequence. Sequences are variables
168    that are iterable.
169    """
170    try:
171        len(value)
172        value.__getitem__
173    except Exception:
174        return False
175
176    return True
177
178
179def test_sameas(value: t.Any, other: t.Any) -> bool:
180    """Check if an object points to the same memory address than another
181    object:
182
183    .. sourcecode:: jinja
184
185        {% if foo.attribute is sameas false %}
186            the foo attribute really is the `False` singleton
187        {% endif %}
188    """
189    return value is other
190
191
192def test_iterable(value: t.Any) -> bool:
193    """Check if it's possible to iterate over an object."""
194    try:
195        iter(value)
196    except TypeError:
197        return False
198
199    return True
200
201
202def test_escaped(value: t.Any) -> bool:
203    """Check if the value is escaped."""
204    return hasattr(value, "__html__")
205
206
207def test_in(value: t.Any, seq: t.Container) -> bool:
208    """Check if value is in seq.
209
210    .. versionadded:: 2.10
211    """
212    return value in seq
213
214
215TESTS = {
216    "odd": test_odd,
217    "even": test_even,
218    "divisibleby": test_divisibleby,
219    "defined": test_defined,
220    "undefined": test_undefined,
221    "filter": test_filter,
222    "test": test_test,
223    "none": test_none,
224    "boolean": test_boolean,
225    "false": test_false,
226    "true": test_true,
227    "integer": test_integer,
228    "float": test_float,
229    "lower": test_lower,
230    "upper": test_upper,
231    "string": test_string,
232    "mapping": test_mapping,
233    "number": test_number,
234    "sequence": test_sequence,
235    "iterable": test_iterable,
236    "callable": callable,
237    "sameas": test_sameas,
238    "escaped": test_escaped,
239    "in": test_in,
240    "==": operator.eq,
241    "eq": operator.eq,
242    "equalto": operator.eq,
243    "!=": operator.ne,
244    "ne": operator.ne,
245    ">": operator.gt,
246    "gt": operator.gt,
247    "greaterthan": operator.gt,
248    "ge": operator.ge,
249    ">=": operator.ge,
250    "<": operator.lt,
251    "lt": operator.lt,
252    "lessthan": operator.lt,
253    "<=": operator.le,
254    "le": operator.le,
255}
256