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