Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jinja2/filters.py: 49%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

529 statements  

1"""Built-in template filters used with the ``|`` operator.""" 

2 

3import math 

4import random 

5import re 

6import typing 

7import typing as t 

8from collections import abc 

9from itertools import chain 

10from itertools import groupby 

11 

12from markupsafe import escape 

13from markupsafe import Markup 

14from markupsafe import soft_str 

15 

16from .async_utils import async_variant 

17from .async_utils import auto_aiter 

18from .async_utils import auto_await 

19from .async_utils import auto_to_list 

20from .exceptions import FilterArgumentError 

21from .runtime import Undefined 

22from .utils import htmlsafe_json_dumps 

23from .utils import pass_context 

24from .utils import pass_environment 

25from .utils import pass_eval_context 

26from .utils import pformat 

27from .utils import url_quote 

28from .utils import urlize 

29 

30if t.TYPE_CHECKING: 

31 import typing_extensions as te 

32 

33 from .environment import Environment 

34 from .nodes import EvalContext 

35 from .runtime import Context 

36 from .sandbox import SandboxedEnvironment # noqa: F401 

37 

38 class HasHTML(te.Protocol): 

39 def __html__(self) -> str: 

40 pass 

41 

42 

43F = t.TypeVar("F", bound=t.Callable[..., t.Any]) 

44K = t.TypeVar("K") 

45V = t.TypeVar("V") 

46 

47 

48def ignore_case(value: V) -> V: 

49 """For use as a postprocessor for :func:`make_attrgetter`. Converts strings 

50 to lowercase and returns other types as-is.""" 

51 if isinstance(value, str): 

52 return t.cast(V, value.lower()) 

53 

54 return value 

55 

56 

57def make_attrgetter( 

58 environment: "Environment", 

59 attribute: t.Optional[t.Union[str, int]], 

60 postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None, 

61 default: t.Optional[t.Any] = None, 

62) -> t.Callable[[t.Any], t.Any]: 

63 """Returns a callable that looks up the given attribute from a 

64 passed object with the rules of the environment. Dots are allowed 

65 to access attributes of attributes. Integer parts in paths are 

66 looked up as integers. 

67 """ 

68 parts = _prepare_attribute_parts(attribute) 

69 

70 def attrgetter(item: t.Any) -> t.Any: 

71 for part in parts: 

72 item = environment.getitem(item, part) 

73 

74 if default is not None and isinstance(item, Undefined): 

75 item = default 

76 

77 if postprocess is not None: 

78 item = postprocess(item) 

79 

80 return item 

81 

82 return attrgetter 

83 

84 

85def make_multi_attrgetter( 

86 environment: "Environment", 

87 attribute: t.Optional[t.Union[str, int]], 

88 postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None, 

89) -> t.Callable[[t.Any], t.List[t.Any]]: 

90 """Returns a callable that looks up the given comma separated 

91 attributes from a passed object with the rules of the environment. 

92 Dots are allowed to access attributes of each attribute. Integer 

93 parts in paths are looked up as integers. 

94 

95 The value returned by the returned callable is a list of extracted 

96 attribute values. 

97 

98 Examples of attribute: "attr1,attr2", "attr1.inner1.0,attr2.inner2.0", etc. 

99 """ 

100 if isinstance(attribute, str): 

101 split: t.Sequence[t.Union[str, int, None]] = attribute.split(",") 

102 else: 

103 split = [attribute] 

104 

105 parts = [_prepare_attribute_parts(item) for item in split] 

106 

107 def attrgetter(item: t.Any) -> t.List[t.Any]: 

108 items = [None] * len(parts) 

109 

110 for i, attribute_part in enumerate(parts): 

111 item_i = item 

112 

113 for part in attribute_part: 

114 item_i = environment.getitem(item_i, part) 

115 

116 if postprocess is not None: 

117 item_i = postprocess(item_i) 

118 

119 items[i] = item_i 

120 

121 return items 

122 

123 return attrgetter 

124 

125 

126def _prepare_attribute_parts( 

127 attr: t.Optional[t.Union[str, int]], 

128) -> t.List[t.Union[str, int]]: 

129 if attr is None: 

130 return [] 

131 

132 if isinstance(attr, str): 

133 return [int(x) if x.isdigit() else x for x in attr.split(".")] 

134 

135 return [attr] 

136 

137 

138def do_forceescape(value: "t.Union[str, HasHTML]") -> Markup: 

139 """Enforce HTML escaping. This will probably double escape variables.""" 

140 if hasattr(value, "__html__"): 

141 value = t.cast("HasHTML", value).__html__() 

142 

143 return escape(str(value)) 

144 

145 

146def do_urlencode( 

147 value: t.Union[str, t.Mapping[str, t.Any], t.Iterable[t.Tuple[str, t.Any]]], 

148) -> str: 

149 """Quote data for use in a URL path or query using UTF-8. 

150 

151 Basic wrapper around :func:`urllib.parse.quote` when given a 

152 string, or :func:`urllib.parse.urlencode` for a dict or iterable. 

153 

154 :param value: Data to quote. A string will be quoted directly. A 

155 dict or iterable of ``(key, value)`` pairs will be joined as a 

156 query string. 

157 

158 When given a string, "/" is not quoted. HTTP servers treat "/" and 

159 "%2F" equivalently in paths. If you need quoted slashes, use the 

160 ``|replace("/", "%2F")`` filter. 

161 

162 .. versionadded:: 2.7 

163 """ 

164 if isinstance(value, str) or not isinstance(value, abc.Iterable): 

165 return url_quote(value) 

166 

167 if isinstance(value, dict): 

168 items: t.Iterable[t.Tuple[str, t.Any]] = value.items() 

169 else: 

170 items = value # type: ignore 

171 

172 return "&".join( 

173 f"{url_quote(k, for_qs=True)}={url_quote(v, for_qs=True)}" for k, v in items 

174 ) 

175 

176 

177@pass_eval_context 

178def do_replace( 

179 eval_ctx: "EvalContext", s: str, old: str, new: str, count: t.Optional[int] = None 

180) -> str: 

181 """Return a copy of the value with all occurrences of a substring 

182 replaced with a new one. The first argument is the substring 

183 that should be replaced, the second is the replacement string. 

184 If the optional third argument ``count`` is given, only the first 

185 ``count`` occurrences are replaced: 

186 

187 .. sourcecode:: jinja 

188 

189 {{ "Hello World"|replace("Hello", "Goodbye") }} 

190 -> Goodbye World 

191 

192 {{ "aaaaargh"|replace("a", "d'oh, ", 2) }} 

193 -> d'oh, d'oh, aaargh 

194 """ 

195 if count is None: 

196 count = -1 

197 

198 if not eval_ctx.autoescape: 

199 return str(s).replace(str(old), str(new), count) 

200 

201 if ( 

202 hasattr(old, "__html__") 

203 or hasattr(new, "__html__") 

204 and not hasattr(s, "__html__") 

205 ): 

206 s = escape(s) 

207 else: 

208 s = soft_str(s) 

209 

210 return s.replace(soft_str(old), soft_str(new), count) 

211 

212 

213def do_upper(s: str) -> str: 

214 """Convert a value to uppercase.""" 

215 return soft_str(s).upper() 

216 

217 

218def do_lower(s: str) -> str: 

219 """Convert a value to lowercase.""" 

220 return soft_str(s).lower() 

221 

222 

223def do_items(value: t.Union[t.Mapping[K, V], Undefined]) -> t.Iterator[t.Tuple[K, V]]: 

224 """Return an iterator over the ``(key, value)`` items of a mapping. 

225 

226 ``x|items`` is the same as ``x.items()``, except if ``x`` is 

227 undefined an empty iterator is returned. 

228 

229 This filter is useful if you expect the template to be rendered with 

230 an implementation of Jinja in another programming language that does 

231 not have a ``.items()`` method on its mapping type. 

232 

233 .. code-block:: html+jinja 

234 

235 <dl> 

236 {% for key, value in my_dict|items %} 

237 <dt>{{ key }} 

238 <dd>{{ value }} 

239 {% endfor %} 

240 </dl> 

241 

242 .. versionadded:: 3.1 

243 """ 

244 if isinstance(value, Undefined): 

245 return 

246 

247 if not isinstance(value, abc.Mapping): 

248 raise TypeError("Can only get item pairs from a mapping.") 

249 

250 yield from value.items() 

251 

252 

253# Check for characters that would move the parser state from key to value. 

254# https://html.spec.whatwg.org/#attribute-name-state 

255_attr_key_re = re.compile(r"[\s/>=]", flags=re.ASCII) 

256 

257 

258@pass_eval_context 

259def do_xmlattr( 

260 eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True 

261) -> str: 

262 """Create an SGML/XML attribute string based on the items in a dict. 

263 

264 **Values** that are neither ``none`` nor ``undefined`` are automatically 

265 escaped, safely allowing untrusted user input. 

266 

267 User input should not be used as **keys** to this filter. If any key 

268 contains a space, ``/`` solidus, ``>`` greater-than sign, or ``=`` equals 

269 sign, this fails with a ``ValueError``. Regardless of this, user input 

270 should never be used as keys to this filter, or must be separately validated 

271 first. 

272 

273 .. sourcecode:: html+jinja 

274 

275 <ul{{ {'class': 'my_list', 'missing': none, 

276 'id': 'list-%d'|format(variable)}|xmlattr }}> 

277 ... 

278 </ul> 

279 

280 Results in something like this: 

281 

282 .. sourcecode:: html 

283 

284 <ul class="my_list" id="list-42"> 

285 ... 

286 </ul> 

287 

288 As you can see it automatically prepends a space in front of the item 

289 if the filter returned something unless the second parameter is false. 

290 

291 .. versionchanged:: 3.1.4 

292 Keys with ``/`` solidus, ``>`` greater-than sign, or ``=`` equals sign 

293 are not allowed. 

294 

295 .. versionchanged:: 3.1.3 

296 Keys with spaces are not allowed. 

297 """ 

298 items = [] 

299 

300 for key, value in d.items(): 

301 if value is None or isinstance(value, Undefined): 

302 continue 

303 

304 if _attr_key_re.search(key) is not None: 

305 raise ValueError(f"Invalid character in attribute name: {key!r}") 

306 

307 items.append(f'{escape(key)}="{escape(value)}"') 

308 

309 rv = " ".join(items) 

310 

311 if autospace and rv: 

312 rv = " " + rv 

313 

314 if eval_ctx.autoescape: 

315 rv = Markup(rv) 

316 

317 return rv 

318 

319 

320def do_capitalize(s: str) -> str: 

321 """Capitalize a value. The first character will be uppercase, all others 

322 lowercase. 

323 """ 

324 return soft_str(s).capitalize() 

325 

326 

327_word_beginning_split_re = re.compile(r"([-\s({\[<]+)") 

328 

329 

330def do_title(s: str) -> str: 

331 """Return a titlecased version of the value. I.e. words will start with 

332 uppercase letters, all remaining characters are lowercase. 

333 """ 

334 return "".join( 

335 [ 

336 item[0].upper() + item[1:].lower() 

337 for item in _word_beginning_split_re.split(soft_str(s)) 

338 if item 

339 ] 

340 ) 

341 

342 

343def do_dictsort( 

344 value: t.Mapping[K, V], 

345 case_sensitive: bool = False, 

346 by: 'te.Literal["key", "value"]' = "key", 

347 reverse: bool = False, 

348) -> t.List[t.Tuple[K, V]]: 

349 """Sort a dict and yield (key, value) pairs. Python dicts may not 

350 be in the order you want to display them in, so sort them first. 

351 

352 .. sourcecode:: jinja 

353 

354 {% for key, value in mydict|dictsort %} 

355 sort the dict by key, case insensitive 

356 

357 {% for key, value in mydict|dictsort(reverse=true) %} 

358 sort the dict by key, case insensitive, reverse order 

359 

360 {% for key, value in mydict|dictsort(true) %} 

361 sort the dict by key, case sensitive 

362 

363 {% for key, value in mydict|dictsort(false, 'value') %} 

364 sort the dict by value, case insensitive 

365 """ 

366 if by == "key": 

367 pos = 0 

368 elif by == "value": 

369 pos = 1 

370 else: 

371 raise FilterArgumentError('You can only sort by either "key" or "value"') 

372 

373 def sort_func(item: t.Tuple[t.Any, t.Any]) -> t.Any: 

374 value = item[pos] 

375 

376 if not case_sensitive: 

377 value = ignore_case(value) 

378 

379 return value 

380 

381 return sorted(value.items(), key=sort_func, reverse=reverse) 

382 

383 

384@pass_environment 

385def do_sort( 

386 environment: "Environment", 

387 value: "t.Iterable[V]", 

388 reverse: bool = False, 

389 case_sensitive: bool = False, 

390 attribute: t.Optional[t.Union[str, int]] = None, 

391) -> "t.List[V]": 

392 """Sort an iterable using Python's :func:`sorted`. 

393 

394 .. sourcecode:: jinja 

395 

396 {% for city in cities|sort %} 

397 ... 

398 {% endfor %} 

399 

400 :param reverse: Sort descending instead of ascending. 

401 :param case_sensitive: When sorting strings, sort upper and lower 

402 case separately. 

403 :param attribute: When sorting objects or dicts, an attribute or 

404 key to sort by. Can use dot notation like ``"address.city"``. 

405 Can be a list of attributes like ``"age,name"``. 

406 

407 The sort is stable, it does not change the relative order of 

408 elements that compare equal. This makes it is possible to chain 

409 sorts on different attributes and ordering. 

410 

411 .. sourcecode:: jinja 

412 

413 {% for user in users|sort(attribute="name") 

414 |sort(reverse=true, attribute="age") %} 

415 ... 

416 {% endfor %} 

417 

418 As a shortcut to chaining when the direction is the same for all 

419 attributes, pass a comma separate list of attributes. 

420 

421 .. sourcecode:: jinja 

422 

423 {% for user in users|sort(attribute="age,name") %} 

424 ... 

425 {% endfor %} 

426 

427 .. versionchanged:: 2.11.0 

428 The ``attribute`` parameter can be a comma separated list of 

429 attributes, e.g. ``"age,name"``. 

430 

431 .. versionchanged:: 2.6 

432 The ``attribute`` parameter was added. 

433 """ 

434 key_func = make_multi_attrgetter( 

435 environment, attribute, postprocess=ignore_case if not case_sensitive else None 

436 ) 

437 return sorted(value, key=key_func, reverse=reverse) 

438 

439 

440@pass_environment 

441def do_unique( 

442 environment: "Environment", 

443 value: "t.Iterable[V]", 

444 case_sensitive: bool = False, 

445 attribute: t.Optional[t.Union[str, int]] = None, 

446) -> "t.Iterator[V]": 

447 """Returns a list of unique items from the given iterable. 

448 

449 .. sourcecode:: jinja 

450 

451 {{ ['foo', 'bar', 'foobar', 'FooBar']|unique|list }} 

452 -> ['foo', 'bar', 'foobar'] 

453 

454 The unique items are yielded in the same order as their first occurrence in 

455 the iterable passed to the filter. 

456 

457 :param case_sensitive: Treat upper and lower case strings as distinct. 

458 :param attribute: Filter objects with unique values for this attribute. 

459 """ 

460 getter = make_attrgetter( 

461 environment, attribute, postprocess=ignore_case if not case_sensitive else None 

462 ) 

463 seen = set() 

464 

465 for item in value: 

466 key = getter(item) 

467 

468 if key not in seen: 

469 seen.add(key) 

470 yield item 

471 

472 

473def _min_or_max( 

474 environment: "Environment", 

475 value: "t.Iterable[V]", 

476 func: "t.Callable[..., V]", 

477 case_sensitive: bool, 

478 attribute: t.Optional[t.Union[str, int]], 

479) -> "t.Union[V, Undefined]": 

480 it = iter(value) 

481 

482 try: 

483 first = next(it) 

484 except StopIteration: 

485 return environment.undefined("No aggregated item, sequence was empty.") 

486 

487 key_func = make_attrgetter( 

488 environment, attribute, postprocess=ignore_case if not case_sensitive else None 

489 ) 

490 return func(chain([first], it), key=key_func) 

491 

492 

493@pass_environment 

494def do_min( 

495 environment: "Environment", 

496 value: "t.Iterable[V]", 

497 case_sensitive: bool = False, 

498 attribute: t.Optional[t.Union[str, int]] = None, 

499) -> "t.Union[V, Undefined]": 

500 """Return the smallest item from the sequence. 

501 

502 .. sourcecode:: jinja 

503 

504 {{ [1, 2, 3]|min }} 

505 -> 1 

506 

507 :param case_sensitive: Treat upper and lower case strings as distinct. 

508 :param attribute: Get the object with the min value of this attribute. 

509 """ 

510 return _min_or_max(environment, value, min, case_sensitive, attribute) 

511 

512 

513@pass_environment 

514def do_max( 

515 environment: "Environment", 

516 value: "t.Iterable[V]", 

517 case_sensitive: bool = False, 

518 attribute: t.Optional[t.Union[str, int]] = None, 

519) -> "t.Union[V, Undefined]": 

520 """Return the largest item from the sequence. 

521 

522 .. sourcecode:: jinja 

523 

524 {{ [1, 2, 3]|max }} 

525 -> 3 

526 

527 :param case_sensitive: Treat upper and lower case strings as distinct. 

528 :param attribute: Get the object with the max value of this attribute. 

529 """ 

530 return _min_or_max(environment, value, max, case_sensitive, attribute) 

531 

532 

533def do_default( 

534 value: V, 

535 default_value: V = "", # type: ignore 

536 boolean: bool = False, 

537) -> V: 

538 """If the value is undefined it will return the passed default value, 

539 otherwise the value of the variable: 

540 

541 .. sourcecode:: jinja 

542 

543 {{ my_variable|default('my_variable is not defined') }} 

544 

545 This will output the value of ``my_variable`` if the variable was 

546 defined, otherwise ``'my_variable is not defined'``. If you want 

547 to use default with variables that evaluate to false you have to 

548 set the second parameter to `true`: 

549 

550 .. sourcecode:: jinja 

551 

552 {{ ''|default('the string was empty', true) }} 

553 

554 .. versionchanged:: 2.11 

555 It's now possible to configure the :class:`~jinja2.Environment` with 

556 :class:`~jinja2.ChainableUndefined` to make the `default` filter work 

557 on nested elements and attributes that may contain undefined values 

558 in the chain without getting an :exc:`~jinja2.UndefinedError`. 

559 """ 

560 if isinstance(value, Undefined) or (boolean and not value): 

561 return default_value 

562 

563 return value 

564 

565 

566@pass_eval_context 

567def sync_do_join( 

568 eval_ctx: "EvalContext", 

569 value: t.Iterable[t.Any], 

570 d: str = "", 

571 attribute: t.Optional[t.Union[str, int]] = None, 

572) -> str: 

573 """Return a string which is the concatenation of the strings in the 

574 sequence. The separator between elements is an empty string per 

575 default, you can define it with the optional parameter: 

576 

577 .. sourcecode:: jinja 

578 

579 {{ [1, 2, 3]|join('|') }} 

580 -> 1|2|3 

581 

582 {{ [1, 2, 3]|join }} 

583 -> 123 

584 

585 It is also possible to join certain attributes of an object: 

586 

587 .. sourcecode:: jinja 

588 

589 {{ users|join(', ', attribute='username') }} 

590 

591 .. versionadded:: 2.6 

592 The `attribute` parameter was added. 

593 """ 

594 if attribute is not None: 

595 value = map(make_attrgetter(eval_ctx.environment, attribute), value) 

596 

597 # no automatic escaping? joining is a lot easier then 

598 if not eval_ctx.autoescape: 

599 return str(d).join(map(str, value)) 

600 

601 # if the delimiter doesn't have an html representation we check 

602 # if any of the items has. If yes we do a coercion to Markup 

603 if not hasattr(d, "__html__"): 

604 value = list(value) 

605 do_escape = False 

606 

607 for idx, item in enumerate(value): 

608 if hasattr(item, "__html__"): 

609 do_escape = True 

610 else: 

611 value[idx] = str(item) 

612 

613 if do_escape: 

614 d = escape(d) 

615 else: 

616 d = str(d) 

617 

618 return d.join(value) 

619 

620 # no html involved, to normal joining 

621 return soft_str(d).join(map(soft_str, value)) 

622 

623 

624@async_variant(sync_do_join) # type: ignore 

625async def do_join( 

626 eval_ctx: "EvalContext", 

627 value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]], 

628 d: str = "", 

629 attribute: t.Optional[t.Union[str, int]] = None, 

630) -> str: 

631 return sync_do_join(eval_ctx, await auto_to_list(value), d, attribute) 

632 

633 

634def do_center(value: str, width: int = 80) -> str: 

635 """Centers the value in a field of a given width.""" 

636 return soft_str(value).center(width) 

637 

638 

639@pass_environment 

640def sync_do_first( 

641 environment: "Environment", seq: "t.Iterable[V]" 

642) -> "t.Union[V, Undefined]": 

643 """Return the first item of a sequence.""" 

644 try: 

645 return next(iter(seq)) 

646 except StopIteration: 

647 return environment.undefined("No first item, sequence was empty.") 

648 

649 

650@async_variant(sync_do_first) # type: ignore 

651async def do_first( 

652 environment: "Environment", seq: "t.Union[t.AsyncIterable[V], t.Iterable[V]]" 

653) -> "t.Union[V, Undefined]": 

654 try: 

655 return await auto_aiter(seq).__anext__() 

656 except StopAsyncIteration: 

657 return environment.undefined("No first item, sequence was empty.") 

658 

659 

660@pass_environment 

661def do_last( 

662 environment: "Environment", seq: "t.Reversible[V]" 

663) -> "t.Union[V, Undefined]": 

664 """Return the last item of a sequence. 

665 

666 Note: Does not work with generators. You may want to explicitly 

667 convert it to a list: 

668 

669 .. sourcecode:: jinja 

670 

671 {{ data | selectattr('name', '==', 'Jinja') | list | last }} 

672 """ 

673 try: 

674 return next(iter(reversed(seq))) 

675 except StopIteration: 

676 return environment.undefined("No last item, sequence was empty.") 

677 

678 

679# No async do_last, it may not be safe in async mode. 

680 

681 

682@pass_context 

683def do_random(context: "Context", seq: "t.Sequence[V]") -> "t.Union[V, Undefined]": 

684 """Return a random item from the sequence.""" 

685 try: 

686 return random.choice(seq) 

687 except IndexError: 

688 return context.environment.undefined("No random item, sequence was empty.") 

689 

690 

691def do_filesizeformat(value: t.Union[str, float, int], binary: bool = False) -> str: 

692 """Format the value like a 'human-readable' file size (i.e. 13 kB, 

693 4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega, 

694 Giga, etc.), if the second parameter is set to `True` the binary 

695 prefixes are used (Mebi, Gibi). 

696 """ 

697 bytes = float(value) 

698 base = 1024 if binary else 1000 

699 prefixes = [ 

700 ("KiB" if binary else "kB"), 

701 ("MiB" if binary else "MB"), 

702 ("GiB" if binary else "GB"), 

703 ("TiB" if binary else "TB"), 

704 ("PiB" if binary else "PB"), 

705 ("EiB" if binary else "EB"), 

706 ("ZiB" if binary else "ZB"), 

707 ("YiB" if binary else "YB"), 

708 ] 

709 

710 if bytes == 1: 

711 return "1 Byte" 

712 elif bytes < base: 

713 return f"{int(bytes)} Bytes" 

714 else: 

715 for i, prefix in enumerate(prefixes): 

716 unit = base ** (i + 2) 

717 

718 if bytes < unit: 

719 return f"{base * bytes / unit:.1f} {prefix}" 

720 

721 return f"{base * bytes / unit:.1f} {prefix}" 

722 

723 

724def do_pprint(value: t.Any) -> str: 

725 """Pretty print a variable. Useful for debugging.""" 

726 return pformat(value) 

727 

728 

729_uri_scheme_re = re.compile(r"^([\w.+-]{2,}:(/){0,2})$") 

730 

731 

732@pass_eval_context 

733def do_urlize( 

734 eval_ctx: "EvalContext", 

735 value: str, 

736 trim_url_limit: t.Optional[int] = None, 

737 nofollow: bool = False, 

738 target: t.Optional[str] = None, 

739 rel: t.Optional[str] = None, 

740 extra_schemes: t.Optional[t.Iterable[str]] = None, 

741) -> str: 

742 """Convert URLs in text into clickable links. 

743 

744 This may not recognize links in some situations. Usually, a more 

745 comprehensive formatter, such as a Markdown library, is a better 

746 choice. 

747 

748 Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email 

749 addresses. Links with trailing punctuation (periods, commas, closing 

750 parentheses) and leading punctuation (opening parentheses) are 

751 recognized excluding the punctuation. Email addresses that include 

752 header fields are not recognized (for example, 

753 ``mailto:address@example.com?cc=copy@example.com``). 

754 

755 :param value: Original text containing URLs to link. 

756 :param trim_url_limit: Shorten displayed URL values to this length. 

757 :param nofollow: Add the ``rel=nofollow`` attribute to links. 

758 :param target: Add the ``target`` attribute to links. 

759 :param rel: Add the ``rel`` attribute to links. 

760 :param extra_schemes: Recognize URLs that start with these schemes 

761 in addition to the default behavior. Defaults to 

762 ``env.policies["urlize.extra_schemes"]``, which defaults to no 

763 extra schemes. 

764 

765 .. versionchanged:: 3.0 

766 The ``extra_schemes`` parameter was added. 

767 

768 .. versionchanged:: 3.0 

769 Generate ``https://`` links for URLs without a scheme. 

770 

771 .. versionchanged:: 3.0 

772 The parsing rules were updated. Recognize email addresses with 

773 or without the ``mailto:`` scheme. Validate IP addresses. Ignore 

774 parentheses and brackets in more cases. 

775 

776 .. versionchanged:: 2.8 

777 The ``target`` parameter was added. 

778 """ 

779 policies = eval_ctx.environment.policies 

780 rel_parts = set((rel or "").split()) 

781 

782 if nofollow: 

783 rel_parts.add("nofollow") 

784 

785 rel_parts.update((policies["urlize.rel"] or "").split()) 

786 rel = " ".join(sorted(rel_parts)) or None 

787 

788 if target is None: 

789 target = policies["urlize.target"] 

790 

791 if extra_schemes is None: 

792 extra_schemes = policies["urlize.extra_schemes"] or () 

793 

794 for scheme in extra_schemes: 

795 if _uri_scheme_re.fullmatch(scheme) is None: 

796 raise FilterArgumentError(f"{scheme!r} is not a valid URI scheme prefix.") 

797 

798 rv = urlize( 

799 value, 

800 trim_url_limit=trim_url_limit, 

801 rel=rel, 

802 target=target, 

803 extra_schemes=extra_schemes, 

804 ) 

805 

806 if eval_ctx.autoescape: 

807 rv = Markup(rv) 

808 

809 return rv 

810 

811 

812def do_indent( 

813 s: str, width: t.Union[int, str] = 4, first: bool = False, blank: bool = False 

814) -> str: 

815 """Return a copy of the string with each line indented by 4 spaces. The 

816 first line and blank lines are not indented by default. 

817 

818 :param width: Number of spaces, or a string, to indent by. 

819 :param first: Don't skip indenting the first line. 

820 :param blank: Don't skip indenting empty lines. 

821 

822 .. versionchanged:: 3.0 

823 ``width`` can be a string. 

824 

825 .. versionchanged:: 2.10 

826 Blank lines are not indented by default. 

827 

828 Rename the ``indentfirst`` argument to ``first``. 

829 """ 

830 if isinstance(width, str): 

831 indention = width 

832 else: 

833 indention = " " * width 

834 

835 newline = "\n" 

836 

837 if isinstance(s, Markup): 

838 indention = Markup(indention) 

839 newline = Markup(newline) 

840 

841 s += newline # this quirk is necessary for splitlines method 

842 

843 if blank: 

844 rv = (newline + indention).join(s.splitlines()) 

845 else: 

846 lines = s.splitlines() 

847 rv = lines.pop(0) 

848 

849 if lines: 

850 rv += newline + newline.join( 

851 indention + line if line else line for line in lines 

852 ) 

853 

854 if first: 

855 rv = indention + rv 

856 

857 return rv 

858 

859 

860@pass_environment 

861def do_truncate( 

862 env: "Environment", 

863 s: str, 

864 length: int = 255, 

865 killwords: bool = False, 

866 end: str = "...", 

867 leeway: t.Optional[int] = None, 

868) -> str: 

869 """Return a truncated copy of the string. The length is specified 

870 with the first parameter which defaults to ``255``. If the second 

871 parameter is ``true`` the filter will cut the text at length. Otherwise 

872 it will discard the last word. If the text was in fact 

873 truncated it will append an ellipsis sign (``"..."``). If you want a 

874 different ellipsis sign than ``"..."`` you can specify it using the 

875 third parameter. Strings that only exceed the length by the tolerance 

876 margin given in the fourth parameter will not be truncated. 

877 

878 .. sourcecode:: jinja 

879 

880 {{ "foo bar baz qux"|truncate(9) }} 

881 -> "foo..." 

882 {{ "foo bar baz qux"|truncate(9, True) }} 

883 -> "foo ba..." 

884 {{ "foo bar baz qux"|truncate(11) }} 

885 -> "foo bar baz qux" 

886 {{ "foo bar baz qux"|truncate(11, False, '...', 0) }} 

887 -> "foo bar..." 

888 

889 The default leeway on newer Jinja versions is 5 and was 0 before but 

890 can be reconfigured globally. 

891 """ 

892 if leeway is None: 

893 leeway = env.policies["truncate.leeway"] 

894 

895 assert length >= len(end), f"expected length >= {len(end)}, got {length}" 

896 assert leeway >= 0, f"expected leeway >= 0, got {leeway}" 

897 

898 if len(s) <= length + leeway: 

899 return s 

900 

901 if killwords: 

902 return s[: length - len(end)] + end 

903 

904 result = s[: length - len(end)].rsplit(" ", 1)[0] 

905 return result + end 

906 

907 

908@pass_environment 

909def do_wordwrap( 

910 environment: "Environment", 

911 s: str, 

912 width: int = 79, 

913 break_long_words: bool = True, 

914 wrapstring: t.Optional[str] = None, 

915 break_on_hyphens: bool = True, 

916) -> str: 

917 """Wrap a string to the given width. Existing newlines are treated 

918 as paragraphs to be wrapped separately. 

919 

920 :param s: Original text to wrap. 

921 :param width: Maximum length of wrapped lines. 

922 :param break_long_words: If a word is longer than ``width``, break 

923 it across lines. 

924 :param break_on_hyphens: If a word contains hyphens, it may be split 

925 across lines. 

926 :param wrapstring: String to join each wrapped line. Defaults to 

927 :attr:`Environment.newline_sequence`. 

928 

929 .. versionchanged:: 2.11 

930 Existing newlines are treated as paragraphs wrapped separately. 

931 

932 .. versionchanged:: 2.11 

933 Added the ``break_on_hyphens`` parameter. 

934 

935 .. versionchanged:: 2.7 

936 Added the ``wrapstring`` parameter. 

937 """ 

938 import textwrap 

939 

940 if wrapstring is None: 

941 wrapstring = environment.newline_sequence 

942 

943 # textwrap.wrap doesn't consider existing newlines when wrapping. 

944 # If the string has a newline before width, wrap will still insert 

945 # a newline at width, resulting in a short line. Instead, split and 

946 # wrap each paragraph individually. 

947 return wrapstring.join( 

948 [ 

949 wrapstring.join( 

950 textwrap.wrap( 

951 line, 

952 width=width, 

953 expand_tabs=False, 

954 replace_whitespace=False, 

955 break_long_words=break_long_words, 

956 break_on_hyphens=break_on_hyphens, 

957 ) 

958 ) 

959 for line in s.splitlines() 

960 ] 

961 ) 

962 

963 

964_word_re = re.compile(r"\w+") 

965 

966 

967def do_wordcount(s: str) -> int: 

968 """Count the words in that string.""" 

969 return len(_word_re.findall(soft_str(s))) 

970 

971 

972def do_int(value: t.Any, default: int = 0, base: int = 10) -> int: 

973 """Convert the value into an integer. If the 

974 conversion doesn't work it will return ``0``. You can 

975 override this default using the first parameter. You 

976 can also override the default base (10) in the second 

977 parameter, which handles input with prefixes such as 

978 0b, 0o and 0x for bases 2, 8 and 16 respectively. 

979 The base is ignored for decimal numbers and non-string values. 

980 """ 

981 try: 

982 if isinstance(value, str): 

983 return int(value, base) 

984 

985 return int(value) 

986 except (TypeError, ValueError): 

987 # this quirk is necessary so that "42.23"|int gives 42. 

988 try: 

989 return int(float(value)) 

990 except (TypeError, ValueError): 

991 return default 

992 

993 

994def do_float(value: t.Any, default: float = 0.0) -> float: 

995 """Convert the value into a floating point number. If the 

996 conversion doesn't work it will return ``0.0``. You can 

997 override this default using the first parameter. 

998 """ 

999 try: 

1000 return float(value) 

1001 except (TypeError, ValueError): 

1002 return default 

1003 

1004 

1005def do_format(value: str, *args: t.Any, **kwargs: t.Any) -> str: 

1006 """Apply the given values to a `printf-style`_ format string, like 

1007 ``string % values``. 

1008 

1009 .. sourcecode:: jinja 

1010 

1011 {{ "%s, %s!"|format(greeting, name) }} 

1012 Hello, World! 

1013 

1014 In most cases it should be more convenient and efficient to use the 

1015 ``%`` operator or :meth:`str.format`. 

1016 

1017 .. code-block:: text 

1018 

1019 {{ "%s, %s!" % (greeting, name) }} 

1020 {{ "{}, {}!".format(greeting, name) }} 

1021 

1022 .. _printf-style: https://docs.python.org/library/stdtypes.html 

1023 #printf-style-string-formatting 

1024 """ 

1025 if args and kwargs: 

1026 raise FilterArgumentError( 

1027 "can't handle positional and keyword arguments at the same time" 

1028 ) 

1029 

1030 return soft_str(value) % (kwargs or args) 

1031 

1032 

1033def do_trim(value: str, chars: t.Optional[str] = None) -> str: 

1034 """Strip leading and trailing characters, by default whitespace.""" 

1035 return soft_str(value).strip(chars) 

1036 

1037 

1038def do_striptags(value: "t.Union[str, HasHTML]") -> str: 

1039 """Strip SGML/XML tags and replace adjacent whitespace by one space.""" 

1040 if hasattr(value, "__html__"): 

1041 value = t.cast("HasHTML", value).__html__() 

1042 

1043 return Markup(str(value)).striptags() 

1044 

1045 

1046def sync_do_slice( 

1047 value: "t.Collection[V]", slices: int, fill_with: "t.Optional[V]" = None 

1048) -> "t.Iterator[t.List[V]]": 

1049 """Slice an iterator and return a list of lists containing 

1050 those items. Useful if you want to create a div containing 

1051 three ul tags that represent columns: 

1052 

1053 .. sourcecode:: html+jinja 

1054 

1055 <div class="columnwrapper"> 

1056 {%- for column in items|slice(3) %} 

1057 <ul class="column-{{ loop.index }}"> 

1058 {%- for item in column %} 

1059 <li>{{ item }}</li> 

1060 {%- endfor %} 

1061 </ul> 

1062 {%- endfor %} 

1063 </div> 

1064 

1065 If you pass it a second argument it's used to fill missing 

1066 values on the last iteration. 

1067 """ 

1068 seq = list(value) 

1069 length = len(seq) 

1070 items_per_slice = length // slices 

1071 slices_with_extra = length % slices 

1072 offset = 0 

1073 

1074 for slice_number in range(slices): 

1075 start = offset + slice_number * items_per_slice 

1076 

1077 if slice_number < slices_with_extra: 

1078 offset += 1 

1079 

1080 end = offset + (slice_number + 1) * items_per_slice 

1081 tmp = seq[start:end] 

1082 

1083 if fill_with is not None and slice_number >= slices_with_extra: 

1084 tmp.append(fill_with) 

1085 

1086 yield tmp 

1087 

1088 

1089@async_variant(sync_do_slice) # type: ignore 

1090async def do_slice( 

1091 value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", 

1092 slices: int, 

1093 fill_with: t.Optional[t.Any] = None, 

1094) -> "t.Iterator[t.List[V]]": 

1095 return sync_do_slice(await auto_to_list(value), slices, fill_with) 

1096 

1097 

1098def do_batch( 

1099 value: "t.Iterable[V]", linecount: int, fill_with: "t.Optional[V]" = None 

1100) -> "t.Iterator[t.List[V]]": 

1101 """ 

1102 A filter that batches items. It works pretty much like `slice` 

1103 just the other way round. It returns a list of lists with the 

1104 given number of items. If you provide a second parameter this 

1105 is used to fill up missing items. See this example: 

1106 

1107 .. sourcecode:: html+jinja 

1108 

1109 <table> 

1110 {%- for row in items|batch(3, '&nbsp;') %} 

1111 <tr> 

1112 {%- for column in row %} 

1113 <td>{{ column }}</td> 

1114 {%- endfor %} 

1115 </tr> 

1116 {%- endfor %} 

1117 </table> 

1118 """ 

1119 tmp: t.List[V] = [] 

1120 

1121 for item in value: 

1122 if len(tmp) == linecount: 

1123 yield tmp 

1124 tmp = [] 

1125 

1126 tmp.append(item) 

1127 

1128 if tmp: 

1129 if fill_with is not None and len(tmp) < linecount: 

1130 tmp += [fill_with] * (linecount - len(tmp)) 

1131 

1132 yield tmp 

1133 

1134 

1135def do_round( 

1136 value: float, 

1137 precision: int = 0, 

1138 method: 'te.Literal["common", "ceil", "floor"]' = "common", 

1139) -> float: 

1140 """Round the number to a given precision. The first 

1141 parameter specifies the precision (default is ``0``), the 

1142 second the rounding method: 

1143 

1144 - ``'common'`` rounds either up or down 

1145 - ``'ceil'`` always rounds up 

1146 - ``'floor'`` always rounds down 

1147 

1148 If you don't specify a method ``'common'`` is used. 

1149 

1150 .. sourcecode:: jinja 

1151 

1152 {{ 42.55|round }} 

1153 -> 43.0 

1154 {{ 42.55|round(1, 'floor') }} 

1155 -> 42.5 

1156 

1157 Note that even if rounded to 0 precision, a float is returned. If 

1158 you need a real integer, pipe it through `int`: 

1159 

1160 .. sourcecode:: jinja 

1161 

1162 {{ 42.55|round|int }} 

1163 -> 43 

1164 """ 

1165 if method not in {"common", "ceil", "floor"}: 

1166 raise FilterArgumentError("method must be common, ceil or floor") 

1167 

1168 if method == "common": 

1169 return round(value, precision) 

1170 

1171 func = getattr(math, method) 

1172 return t.cast(float, func(value * (10**precision)) / (10**precision)) 

1173 

1174 

1175class _GroupTuple(t.NamedTuple): 

1176 grouper: t.Any 

1177 list: t.List[t.Any] 

1178 

1179 # Use the regular tuple repr to hide this subclass if users print 

1180 # out the value during debugging. 

1181 def __repr__(self) -> str: 

1182 return tuple.__repr__(self) 

1183 

1184 def __str__(self) -> str: 

1185 return tuple.__str__(self) 

1186 

1187 

1188@pass_environment 

1189def sync_do_groupby( 

1190 environment: "Environment", 

1191 value: "t.Iterable[V]", 

1192 attribute: t.Union[str, int], 

1193 default: t.Optional[t.Any] = None, 

1194 case_sensitive: bool = False, 

1195) -> "t.List[_GroupTuple]": 

1196 """Group a sequence of objects by an attribute using Python's 

1197 :func:`itertools.groupby`. The attribute can use dot notation for 

1198 nested access, like ``"address.city"``. Unlike Python's ``groupby``, 

1199 the values are sorted first so only one group is returned for each 

1200 unique value. 

1201 

1202 For example, a list of ``User`` objects with a ``city`` attribute 

1203 can be rendered in groups. In this example, ``grouper`` refers to 

1204 the ``city`` value of the group. 

1205 

1206 .. sourcecode:: html+jinja 

1207 

1208 <ul>{% for city, items in users|groupby("city") %} 

1209 <li>{{ city }} 

1210 <ul>{% for user in items %} 

1211 <li>{{ user.name }} 

1212 {% endfor %}</ul> 

1213 </li> 

1214 {% endfor %}</ul> 

1215 

1216 ``groupby`` yields namedtuples of ``(grouper, list)``, which 

1217 can be used instead of the tuple unpacking above. ``grouper`` is the 

1218 value of the attribute, and ``list`` is the items with that value. 

1219 

1220 .. sourcecode:: html+jinja 

1221 

1222 <ul>{% for group in users|groupby("city") %} 

1223 <li>{{ group.grouper }}: {{ group.list|join(", ") }} 

1224 {% endfor %}</ul> 

1225 

1226 You can specify a ``default`` value to use if an object in the list 

1227 does not have the given attribute. 

1228 

1229 .. sourcecode:: jinja 

1230 

1231 <ul>{% for city, items in users|groupby("city", default="NY") %} 

1232 <li>{{ city }}: {{ items|map(attribute="name")|join(", ") }}</li> 

1233 {% endfor %}</ul> 

1234 

1235 Like the :func:`~jinja-filters.sort` filter, sorting and grouping is 

1236 case-insensitive by default. The ``key`` for each group will have 

1237 the case of the first item in that group of values. For example, if 

1238 a list of users has cities ``["CA", "NY", "ca"]``, the "CA" group 

1239 will have two values. This can be disabled by passing 

1240 ``case_sensitive=True``. 

1241 

1242 .. versionchanged:: 3.1 

1243 Added the ``case_sensitive`` parameter. Sorting and grouping is 

1244 case-insensitive by default, matching other filters that do 

1245 comparisons. 

1246 

1247 .. versionchanged:: 3.0 

1248 Added the ``default`` parameter. 

1249 

1250 .. versionchanged:: 2.6 

1251 The attribute supports dot notation for nested access. 

1252 """ 

1253 expr = make_attrgetter( 

1254 environment, 

1255 attribute, 

1256 postprocess=ignore_case if not case_sensitive else None, 

1257 default=default, 

1258 ) 

1259 out = [ 

1260 _GroupTuple(key, list(values)) 

1261 for key, values in groupby(sorted(value, key=expr), expr) 

1262 ] 

1263 

1264 if not case_sensitive: 

1265 # Return the real key from the first value instead of the lowercase key. 

1266 output_expr = make_attrgetter(environment, attribute, default=default) 

1267 out = [_GroupTuple(output_expr(values[0]), values) for _, values in out] 

1268 

1269 return out 

1270 

1271 

1272@async_variant(sync_do_groupby) # type: ignore 

1273async def do_groupby( 

1274 environment: "Environment", 

1275 value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", 

1276 attribute: t.Union[str, int], 

1277 default: t.Optional[t.Any] = None, 

1278 case_sensitive: bool = False, 

1279) -> "t.List[_GroupTuple]": 

1280 expr = make_attrgetter( 

1281 environment, 

1282 attribute, 

1283 postprocess=ignore_case if not case_sensitive else None, 

1284 default=default, 

1285 ) 

1286 out = [ 

1287 _GroupTuple(key, await auto_to_list(values)) 

1288 for key, values in groupby(sorted(await auto_to_list(value), key=expr), expr) 

1289 ] 

1290 

1291 if not case_sensitive: 

1292 # Return the real key from the first value instead of the lowercase key. 

1293 output_expr = make_attrgetter(environment, attribute, default=default) 

1294 out = [_GroupTuple(output_expr(values[0]), values) for _, values in out] 

1295 

1296 return out 

1297 

1298 

1299@pass_environment 

1300def sync_do_sum( 

1301 environment: "Environment", 

1302 iterable: "t.Iterable[V]", 

1303 attribute: t.Optional[t.Union[str, int]] = None, 

1304 start: V = 0, # type: ignore 

1305) -> V: 

1306 """Returns the sum of a sequence of numbers plus the value of parameter 

1307 'start' (which defaults to 0). When the sequence is empty it returns 

1308 start. 

1309 

1310 It is also possible to sum up only certain attributes: 

1311 

1312 .. sourcecode:: jinja 

1313 

1314 Total: {{ items|sum(attribute='price') }} 

1315 

1316 .. versionchanged:: 2.6 

1317 The ``attribute`` parameter was added to allow summing up over 

1318 attributes. Also the ``start`` parameter was moved on to the right. 

1319 """ 

1320 if attribute is not None: 

1321 iterable = map(make_attrgetter(environment, attribute), iterable) 

1322 

1323 return sum(iterable, start) # type: ignore[no-any-return, call-overload] 

1324 

1325 

1326@async_variant(sync_do_sum) # type: ignore 

1327async def do_sum( 

1328 environment: "Environment", 

1329 iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", 

1330 attribute: t.Optional[t.Union[str, int]] = None, 

1331 start: V = 0, # type: ignore 

1332) -> V: 

1333 rv = start 

1334 

1335 if attribute is not None: 

1336 func = make_attrgetter(environment, attribute) 

1337 else: 

1338 

1339 def func(x: V) -> V: 

1340 return x 

1341 

1342 async for item in auto_aiter(iterable): 

1343 rv += func(item) 

1344 

1345 return rv 

1346 

1347 

1348def sync_do_list(value: "t.Iterable[V]") -> "t.List[V]": 

1349 """Convert the value into a list. If it was a string the returned list 

1350 will be a list of characters. 

1351 """ 

1352 return list(value) 

1353 

1354 

1355@async_variant(sync_do_list) # type: ignore 

1356async def do_list(value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]") -> "t.List[V]": 

1357 return await auto_to_list(value) 

1358 

1359 

1360def do_mark_safe(value: str) -> Markup: 

1361 """Mark the value as safe which means that in an environment with automatic 

1362 escaping enabled this variable will not be escaped. 

1363 """ 

1364 return Markup(value) 

1365 

1366 

1367def do_mark_unsafe(value: str) -> str: 

1368 """Mark a value as unsafe. This is the reverse operation for :func:`safe`.""" 

1369 return str(value) 

1370 

1371 

1372@typing.overload 

1373def do_reverse(value: str) -> str: ... 

1374 

1375 

1376@typing.overload 

1377def do_reverse(value: "t.Iterable[V]") -> "t.Iterable[V]": ... 

1378 

1379 

1380def do_reverse(value: t.Union[str, t.Iterable[V]]) -> t.Union[str, t.Iterable[V]]: 

1381 """Reverse the object or return an iterator that iterates over it the other 

1382 way round. 

1383 """ 

1384 if isinstance(value, str): 

1385 return value[::-1] 

1386 

1387 try: 

1388 return reversed(value) # type: ignore 

1389 except TypeError: 

1390 try: 

1391 rv = list(value) 

1392 rv.reverse() 

1393 return rv 

1394 except TypeError as e: 

1395 raise FilterArgumentError("argument must be iterable") from e 

1396 

1397 

1398@pass_environment 

1399def do_attr( 

1400 environment: "Environment", obj: t.Any, name: str 

1401) -> t.Union[Undefined, t.Any]: 

1402 """Get an attribute of an object. ``foo|attr("bar")`` works like 

1403 ``foo.bar`` just that always an attribute is returned and items are not 

1404 looked up. 

1405 

1406 See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details. 

1407 """ 

1408 try: 

1409 name = str(name) 

1410 except UnicodeError: 

1411 pass 

1412 else: 

1413 try: 

1414 value = getattr(obj, name) 

1415 except AttributeError: 

1416 pass 

1417 else: 

1418 if environment.sandboxed: 

1419 environment = t.cast("SandboxedEnvironment", environment) 

1420 

1421 if not environment.is_safe_attribute(obj, name, value): 

1422 return environment.unsafe_undefined(obj, name) 

1423 

1424 return value 

1425 

1426 return environment.undefined(obj=obj, name=name) 

1427 

1428 

1429@typing.overload 

1430def sync_do_map( 

1431 context: "Context", 

1432 value: t.Iterable[t.Any], 

1433 name: str, 

1434 *args: t.Any, 

1435 **kwargs: t.Any, 

1436) -> t.Iterable[t.Any]: ... 

1437 

1438 

1439@typing.overload 

1440def sync_do_map( 

1441 context: "Context", 

1442 value: t.Iterable[t.Any], 

1443 *, 

1444 attribute: str = ..., 

1445 default: t.Optional[t.Any] = None, 

1446) -> t.Iterable[t.Any]: ... 

1447 

1448 

1449@pass_context 

1450def sync_do_map( 

1451 context: "Context", value: t.Iterable[t.Any], *args: t.Any, **kwargs: t.Any 

1452) -> t.Iterable[t.Any]: 

1453 """Applies a filter on a sequence of objects or looks up an attribute. 

1454 This is useful when dealing with lists of objects but you are really 

1455 only interested in a certain value of it. 

1456 

1457 The basic usage is mapping on an attribute. Imagine you have a list 

1458 of users but you are only interested in a list of usernames: 

1459 

1460 .. sourcecode:: jinja 

1461 

1462 Users on this page: {{ users|map(attribute='username')|join(', ') }} 

1463 

1464 You can specify a ``default`` value to use if an object in the list 

1465 does not have the given attribute. 

1466 

1467 .. sourcecode:: jinja 

1468 

1469 {{ users|map(attribute="username", default="Anonymous")|join(", ") }} 

1470 

1471 Alternatively you can let it invoke a filter by passing the name of the 

1472 filter and the arguments afterwards. A good example would be applying a 

1473 text conversion filter on a sequence: 

1474 

1475 .. sourcecode:: jinja 

1476 

1477 Users on this page: {{ titles|map('lower')|join(', ') }} 

1478 

1479 Similar to a generator comprehension such as: 

1480 

1481 .. code-block:: python 

1482 

1483 (u.username for u in users) 

1484 (getattr(u, "username", "Anonymous") for u in users) 

1485 (do_lower(x) for x in titles) 

1486 

1487 .. versionchanged:: 2.11.0 

1488 Added the ``default`` parameter. 

1489 

1490 .. versionadded:: 2.7 

1491 """ 

1492 if value: 

1493 func = prepare_map(context, args, kwargs) 

1494 

1495 for item in value: 

1496 yield func(item) 

1497 

1498 

1499@typing.overload 

1500def do_map( 

1501 context: "Context", 

1502 value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]], 

1503 name: str, 

1504 *args: t.Any, 

1505 **kwargs: t.Any, 

1506) -> t.Iterable[t.Any]: ... 

1507 

1508 

1509@typing.overload 

1510def do_map( 

1511 context: "Context", 

1512 value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]], 

1513 *, 

1514 attribute: str = ..., 

1515 default: t.Optional[t.Any] = None, 

1516) -> t.Iterable[t.Any]: ... 

1517 

1518 

1519@async_variant(sync_do_map) # type: ignore 

1520async def do_map( 

1521 context: "Context", 

1522 value: t.Union[t.AsyncIterable[t.Any], t.Iterable[t.Any]], 

1523 *args: t.Any, 

1524 **kwargs: t.Any, 

1525) -> t.AsyncIterable[t.Any]: 

1526 if value: 

1527 func = prepare_map(context, args, kwargs) 

1528 

1529 async for item in auto_aiter(value): 

1530 yield await auto_await(func(item)) 

1531 

1532 

1533@pass_context 

1534def sync_do_select( 

1535 context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any 

1536) -> "t.Iterator[V]": 

1537 """Filters a sequence of objects by applying a test to each object, 

1538 and only selecting the objects with the test succeeding. 

1539 

1540 If no test is specified, each object will be evaluated as a boolean. 

1541 

1542 Example usage: 

1543 

1544 .. sourcecode:: jinja 

1545 

1546 {{ numbers|select("odd") }} 

1547 {{ numbers|select("odd") }} 

1548 {{ numbers|select("divisibleby", 3) }} 

1549 {{ numbers|select("lessthan", 42) }} 

1550 {{ strings|select("equalto", "mystring") }} 

1551 

1552 Similar to a generator comprehension such as: 

1553 

1554 .. code-block:: python 

1555 

1556 (n for n in numbers if test_odd(n)) 

1557 (n for n in numbers if test_divisibleby(n, 3)) 

1558 

1559 .. versionadded:: 2.7 

1560 """ 

1561 return select_or_reject(context, value, args, kwargs, lambda x: x, False) 

1562 

1563 

1564@async_variant(sync_do_select) # type: ignore 

1565async def do_select( 

1566 context: "Context", 

1567 value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", 

1568 *args: t.Any, 

1569 **kwargs: t.Any, 

1570) -> "t.AsyncIterator[V]": 

1571 return async_select_or_reject(context, value, args, kwargs, lambda x: x, False) 

1572 

1573 

1574@pass_context 

1575def sync_do_reject( 

1576 context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any 

1577) -> "t.Iterator[V]": 

1578 """Filters a sequence of objects by applying a test to each object, 

1579 and rejecting the objects with the test succeeding. 

1580 

1581 If no test is specified, each object will be evaluated as a boolean. 

1582 

1583 Example usage: 

1584 

1585 .. sourcecode:: jinja 

1586 

1587 {{ numbers|reject("odd") }} 

1588 

1589 Similar to a generator comprehension such as: 

1590 

1591 .. code-block:: python 

1592 

1593 (n for n in numbers if not test_odd(n)) 

1594 

1595 .. versionadded:: 2.7 

1596 """ 

1597 return select_or_reject(context, value, args, kwargs, lambda x: not x, False) 

1598 

1599 

1600@async_variant(sync_do_reject) # type: ignore 

1601async def do_reject( 

1602 context: "Context", 

1603 value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", 

1604 *args: t.Any, 

1605 **kwargs: t.Any, 

1606) -> "t.AsyncIterator[V]": 

1607 return async_select_or_reject(context, value, args, kwargs, lambda x: not x, False) 

1608 

1609 

1610@pass_context 

1611def sync_do_selectattr( 

1612 context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any 

1613) -> "t.Iterator[V]": 

1614 """Filters a sequence of objects by applying a test to the specified 

1615 attribute of each object, and only selecting the objects with the 

1616 test succeeding. 

1617 

1618 If no test is specified, the attribute's value will be evaluated as 

1619 a boolean. 

1620 

1621 Example usage: 

1622 

1623 .. sourcecode:: jinja 

1624 

1625 {{ users|selectattr("is_active") }} 

1626 {{ users|selectattr("email", "none") }} 

1627 

1628 Similar to a generator comprehension such as: 

1629 

1630 .. code-block:: python 

1631 

1632 (u for user in users if user.is_active) 

1633 (u for user in users if test_none(user.email)) 

1634 

1635 .. versionadded:: 2.7 

1636 """ 

1637 return select_or_reject(context, value, args, kwargs, lambda x: x, True) 

1638 

1639 

1640@async_variant(sync_do_selectattr) # type: ignore 

1641async def do_selectattr( 

1642 context: "Context", 

1643 value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", 

1644 *args: t.Any, 

1645 **kwargs: t.Any, 

1646) -> "t.AsyncIterator[V]": 

1647 return async_select_or_reject(context, value, args, kwargs, lambda x: x, True) 

1648 

1649 

1650@pass_context 

1651def sync_do_rejectattr( 

1652 context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any 

1653) -> "t.Iterator[V]": 

1654 """Filters a sequence of objects by applying a test to the specified 

1655 attribute of each object, and rejecting the objects with the test 

1656 succeeding. 

1657 

1658 If no test is specified, the attribute's value will be evaluated as 

1659 a boolean. 

1660 

1661 .. sourcecode:: jinja 

1662 

1663 {{ users|rejectattr("is_active") }} 

1664 {{ users|rejectattr("email", "none") }} 

1665 

1666 Similar to a generator comprehension such as: 

1667 

1668 .. code-block:: python 

1669 

1670 (u for user in users if not user.is_active) 

1671 (u for user in users if not test_none(user.email)) 

1672 

1673 .. versionadded:: 2.7 

1674 """ 

1675 return select_or_reject(context, value, args, kwargs, lambda x: not x, True) 

1676 

1677 

1678@async_variant(sync_do_rejectattr) # type: ignore 

1679async def do_rejectattr( 

1680 context: "Context", 

1681 value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", 

1682 *args: t.Any, 

1683 **kwargs: t.Any, 

1684) -> "t.AsyncIterator[V]": 

1685 return async_select_or_reject(context, value, args, kwargs, lambda x: not x, True) 

1686 

1687 

1688@pass_eval_context 

1689def do_tojson( 

1690 eval_ctx: "EvalContext", value: t.Any, indent: t.Optional[int] = None 

1691) -> Markup: 

1692 """Serialize an object to a string of JSON, and mark it safe to 

1693 render in HTML. This filter is only for use in HTML documents. 

1694 

1695 The returned string is safe to render in HTML documents and 

1696 ``<script>`` tags. The exception is in HTML attributes that are 

1697 double quoted; either use single quotes or the ``|forceescape`` 

1698 filter. 

1699 

1700 :param value: The object to serialize to JSON. 

1701 :param indent: The ``indent`` parameter passed to ``dumps``, for 

1702 pretty-printing the value. 

1703 

1704 .. versionadded:: 2.9 

1705 """ 

1706 policies = eval_ctx.environment.policies 

1707 dumps = policies["json.dumps_function"] 

1708 kwargs = policies["json.dumps_kwargs"] 

1709 

1710 if indent is not None: 

1711 kwargs = kwargs.copy() 

1712 kwargs["indent"] = indent 

1713 

1714 return htmlsafe_json_dumps(value, dumps=dumps, **kwargs) 

1715 

1716 

1717def prepare_map( 

1718 context: "Context", args: t.Tuple[t.Any, ...], kwargs: t.Dict[str, t.Any] 

1719) -> t.Callable[[t.Any], t.Any]: 

1720 if not args and "attribute" in kwargs: 

1721 attribute = kwargs.pop("attribute") 

1722 default = kwargs.pop("default", None) 

1723 

1724 if kwargs: 

1725 raise FilterArgumentError( 

1726 f"Unexpected keyword argument {next(iter(kwargs))!r}" 

1727 ) 

1728 

1729 func = make_attrgetter(context.environment, attribute, default=default) 

1730 else: 

1731 try: 

1732 name = args[0] 

1733 args = args[1:] 

1734 except LookupError: 

1735 raise FilterArgumentError("map requires a filter argument") from None 

1736 

1737 def func(item: t.Any) -> t.Any: 

1738 return context.environment.call_filter( 

1739 name, item, args, kwargs, context=context 

1740 ) 

1741 

1742 return func 

1743 

1744 

1745def prepare_select_or_reject( 

1746 context: "Context", 

1747 args: t.Tuple[t.Any, ...], 

1748 kwargs: t.Dict[str, t.Any], 

1749 modfunc: t.Callable[[t.Any], t.Any], 

1750 lookup_attr: bool, 

1751) -> t.Callable[[t.Any], t.Any]: 

1752 if lookup_attr: 

1753 try: 

1754 attr = args[0] 

1755 except LookupError: 

1756 raise FilterArgumentError("Missing parameter for attribute name") from None 

1757 

1758 transfunc = make_attrgetter(context.environment, attr) 

1759 off = 1 

1760 else: 

1761 off = 0 

1762 

1763 def transfunc(x: V) -> V: 

1764 return x 

1765 

1766 try: 

1767 name = args[off] 

1768 args = args[1 + off :] 

1769 

1770 def func(item: t.Any) -> t.Any: 

1771 return context.environment.call_test(name, item, args, kwargs) 

1772 

1773 except LookupError: 

1774 func = bool # type: ignore 

1775 

1776 return lambda item: modfunc(func(transfunc(item))) 

1777 

1778 

1779def select_or_reject( 

1780 context: "Context", 

1781 value: "t.Iterable[V]", 

1782 args: t.Tuple[t.Any, ...], 

1783 kwargs: t.Dict[str, t.Any], 

1784 modfunc: t.Callable[[t.Any], t.Any], 

1785 lookup_attr: bool, 

1786) -> "t.Iterator[V]": 

1787 if value: 

1788 func = prepare_select_or_reject(context, args, kwargs, modfunc, lookup_attr) 

1789 

1790 for item in value: 

1791 if func(item): 

1792 yield item 

1793 

1794 

1795async def async_select_or_reject( 

1796 context: "Context", 

1797 value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", 

1798 args: t.Tuple[t.Any, ...], 

1799 kwargs: t.Dict[str, t.Any], 

1800 modfunc: t.Callable[[t.Any], t.Any], 

1801 lookup_attr: bool, 

1802) -> "t.AsyncIterator[V]": 

1803 if value: 

1804 func = prepare_select_or_reject(context, args, kwargs, modfunc, lookup_attr) 

1805 

1806 async for item in auto_aiter(value): 

1807 if func(item): 

1808 yield item 

1809 

1810 

1811FILTERS = { 

1812 "abs": abs, 

1813 "attr": do_attr, 

1814 "batch": do_batch, 

1815 "capitalize": do_capitalize, 

1816 "center": do_center, 

1817 "count": len, 

1818 "d": do_default, 

1819 "default": do_default, 

1820 "dictsort": do_dictsort, 

1821 "e": escape, 

1822 "escape": escape, 

1823 "filesizeformat": do_filesizeformat, 

1824 "first": do_first, 

1825 "float": do_float, 

1826 "forceescape": do_forceescape, 

1827 "format": do_format, 

1828 "groupby": do_groupby, 

1829 "indent": do_indent, 

1830 "int": do_int, 

1831 "join": do_join, 

1832 "last": do_last, 

1833 "length": len, 

1834 "list": do_list, 

1835 "lower": do_lower, 

1836 "items": do_items, 

1837 "map": do_map, 

1838 "min": do_min, 

1839 "max": do_max, 

1840 "pprint": do_pprint, 

1841 "random": do_random, 

1842 "reject": do_reject, 

1843 "rejectattr": do_rejectattr, 

1844 "replace": do_replace, 

1845 "reverse": do_reverse, 

1846 "round": do_round, 

1847 "safe": do_mark_safe, 

1848 "select": do_select, 

1849 "selectattr": do_selectattr, 

1850 "slice": do_slice, 

1851 "sort": do_sort, 

1852 "string": soft_str, 

1853 "striptags": do_striptags, 

1854 "sum": do_sum, 

1855 "title": do_title, 

1856 "trim": do_trim, 

1857 "truncate": do_truncate, 

1858 "unique": do_unique, 

1859 "upper": do_upper, 

1860 "urlencode": do_urlencode, 

1861 "urlize": do_urlize, 

1862 "wordcount": do_wordcount, 

1863 "wordwrap": do_wordwrap, 

1864 "xmlattr": do_xmlattr, 

1865 "tojson": do_tojson, 

1866}