Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/jinja2/utils.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler |
|---|---|
| date | Fri, 31 Jul 2020 00:32:28 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:d30785e31577 | 1:56ad4e20f292 |
|---|---|
| 1 # -*- coding: utf-8 -*- | |
| 2 import json | |
| 3 import os | |
| 4 import re | |
| 5 import warnings | |
| 6 from collections import deque | |
| 7 from random import choice | |
| 8 from random import randrange | |
| 9 from threading import Lock | |
| 10 | |
| 11 from markupsafe import escape | |
| 12 from markupsafe import Markup | |
| 13 | |
| 14 from ._compat import abc | |
| 15 from ._compat import string_types | |
| 16 from ._compat import text_type | |
| 17 from ._compat import url_quote | |
| 18 | |
| 19 _word_split_re = re.compile(r"(\s+)") | |
| 20 _punctuation_re = re.compile( | |
| 21 "^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$" | |
| 22 % ( | |
| 23 "|".join(map(re.escape, ("(", "<", "<"))), | |
| 24 "|".join(map(re.escape, (".", ",", ")", ">", "\n", ">"))), | |
| 25 ) | |
| 26 ) | |
| 27 _simple_email_re = re.compile(r"^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$") | |
| 28 _striptags_re = re.compile(r"(<!--.*?-->|<[^>]*>)") | |
| 29 _entity_re = re.compile(r"&([^;]+);") | |
| 30 _letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | |
| 31 _digits = "0123456789" | |
| 32 | |
| 33 # special singleton representing missing values for the runtime | |
| 34 missing = type("MissingType", (), {"__repr__": lambda x: "missing"})() | |
| 35 | |
| 36 # internal code | |
| 37 internal_code = set() | |
| 38 | |
| 39 concat = u"".join | |
| 40 | |
| 41 _slash_escape = "\\/" not in json.dumps("/") | |
| 42 | |
| 43 | |
| 44 def contextfunction(f): | |
| 45 """This decorator can be used to mark a function or method context callable. | |
| 46 A context callable is passed the active :class:`Context` as first argument when | |
| 47 called from the template. This is useful if a function wants to get access | |
| 48 to the context or functions provided on the context object. For example | |
| 49 a function that returns a sorted list of template variables the current | |
| 50 template exports could look like this:: | |
| 51 | |
| 52 @contextfunction | |
| 53 def get_exported_names(context): | |
| 54 return sorted(context.exported_vars) | |
| 55 """ | |
| 56 f.contextfunction = True | |
| 57 return f | |
| 58 | |
| 59 | |
| 60 def evalcontextfunction(f): | |
| 61 """This decorator can be used to mark a function or method as an eval | |
| 62 context callable. This is similar to the :func:`contextfunction` | |
| 63 but instead of passing the context, an evaluation context object is | |
| 64 passed. For more information about the eval context, see | |
| 65 :ref:`eval-context`. | |
| 66 | |
| 67 .. versionadded:: 2.4 | |
| 68 """ | |
| 69 f.evalcontextfunction = True | |
| 70 return f | |
| 71 | |
| 72 | |
| 73 def environmentfunction(f): | |
| 74 """This decorator can be used to mark a function or method as environment | |
| 75 callable. This decorator works exactly like the :func:`contextfunction` | |
| 76 decorator just that the first argument is the active :class:`Environment` | |
| 77 and not context. | |
| 78 """ | |
| 79 f.environmentfunction = True | |
| 80 return f | |
| 81 | |
| 82 | |
| 83 def internalcode(f): | |
| 84 """Marks the function as internally used""" | |
| 85 internal_code.add(f.__code__) | |
| 86 return f | |
| 87 | |
| 88 | |
| 89 def is_undefined(obj): | |
| 90 """Check if the object passed is undefined. This does nothing more than | |
| 91 performing an instance check against :class:`Undefined` but looks nicer. | |
| 92 This can be used for custom filters or tests that want to react to | |
| 93 undefined variables. For example a custom default filter can look like | |
| 94 this:: | |
| 95 | |
| 96 def default(var, default=''): | |
| 97 if is_undefined(var): | |
| 98 return default | |
| 99 return var | |
| 100 """ | |
| 101 from .runtime import Undefined | |
| 102 | |
| 103 return isinstance(obj, Undefined) | |
| 104 | |
| 105 | |
| 106 def consume(iterable): | |
| 107 """Consumes an iterable without doing anything with it.""" | |
| 108 for _ in iterable: | |
| 109 pass | |
| 110 | |
| 111 | |
| 112 def clear_caches(): | |
| 113 """Jinja keeps internal caches for environments and lexers. These are | |
| 114 used so that Jinja doesn't have to recreate environments and lexers all | |
| 115 the time. Normally you don't have to care about that but if you are | |
| 116 measuring memory consumption you may want to clean the caches. | |
| 117 """ | |
| 118 from .environment import _spontaneous_environments | |
| 119 from .lexer import _lexer_cache | |
| 120 | |
| 121 _spontaneous_environments.clear() | |
| 122 _lexer_cache.clear() | |
| 123 | |
| 124 | |
| 125 def import_string(import_name, silent=False): | |
| 126 """Imports an object based on a string. This is useful if you want to | |
| 127 use import paths as endpoints or something similar. An import path can | |
| 128 be specified either in dotted notation (``xml.sax.saxutils.escape``) | |
| 129 or with a colon as object delimiter (``xml.sax.saxutils:escape``). | |
| 130 | |
| 131 If the `silent` is True the return value will be `None` if the import | |
| 132 fails. | |
| 133 | |
| 134 :return: imported object | |
| 135 """ | |
| 136 try: | |
| 137 if ":" in import_name: | |
| 138 module, obj = import_name.split(":", 1) | |
| 139 elif "." in import_name: | |
| 140 module, _, obj = import_name.rpartition(".") | |
| 141 else: | |
| 142 return __import__(import_name) | |
| 143 return getattr(__import__(module, None, None, [obj]), obj) | |
| 144 except (ImportError, AttributeError): | |
| 145 if not silent: | |
| 146 raise | |
| 147 | |
| 148 | |
| 149 def open_if_exists(filename, mode="rb"): | |
| 150 """Returns a file descriptor for the filename if that file exists, | |
| 151 otherwise ``None``. | |
| 152 """ | |
| 153 if not os.path.isfile(filename): | |
| 154 return None | |
| 155 | |
| 156 return open(filename, mode) | |
| 157 | |
| 158 | |
| 159 def object_type_repr(obj): | |
| 160 """Returns the name of the object's type. For some recognized | |
| 161 singletons the name of the object is returned instead. (For | |
| 162 example for `None` and `Ellipsis`). | |
| 163 """ | |
| 164 if obj is None: | |
| 165 return "None" | |
| 166 elif obj is Ellipsis: | |
| 167 return "Ellipsis" | |
| 168 | |
| 169 cls = type(obj) | |
| 170 | |
| 171 # __builtin__ in 2.x, builtins in 3.x | |
| 172 if cls.__module__ in ("__builtin__", "builtins"): | |
| 173 name = cls.__name__ | |
| 174 else: | |
| 175 name = cls.__module__ + "." + cls.__name__ | |
| 176 | |
| 177 return "%s object" % name | |
| 178 | |
| 179 | |
| 180 def pformat(obj, verbose=False): | |
| 181 """Prettyprint an object. Either use the `pretty` library or the | |
| 182 builtin `pprint`. | |
| 183 """ | |
| 184 try: | |
| 185 from pretty import pretty | |
| 186 | |
| 187 return pretty(obj, verbose=verbose) | |
| 188 except ImportError: | |
| 189 from pprint import pformat | |
| 190 | |
| 191 return pformat(obj) | |
| 192 | |
| 193 | |
| 194 def urlize(text, trim_url_limit=None, rel=None, target=None): | |
| 195 """Converts any URLs in text into clickable links. Works on http://, | |
| 196 https:// and www. links. Links can have trailing punctuation (periods, | |
| 197 commas, close-parens) and leading punctuation (opening parens) and | |
| 198 it'll still do the right thing. | |
| 199 | |
| 200 If trim_url_limit is not None, the URLs in link text will be limited | |
| 201 to trim_url_limit characters. | |
| 202 | |
| 203 If nofollow is True, the URLs in link text will get a rel="nofollow" | |
| 204 attribute. | |
| 205 | |
| 206 If target is not None, a target attribute will be added to the link. | |
| 207 """ | |
| 208 trim_url = ( | |
| 209 lambda x, limit=trim_url_limit: limit is not None | |
| 210 and (x[:limit] + (len(x) >= limit and "..." or "")) | |
| 211 or x | |
| 212 ) | |
| 213 words = _word_split_re.split(text_type(escape(text))) | |
| 214 rel_attr = rel and ' rel="%s"' % text_type(escape(rel)) or "" | |
| 215 target_attr = target and ' target="%s"' % escape(target) or "" | |
| 216 | |
| 217 for i, word in enumerate(words): | |
| 218 match = _punctuation_re.match(word) | |
| 219 if match: | |
| 220 lead, middle, trail = match.groups() | |
| 221 if middle.startswith("www.") or ( | |
| 222 "@" not in middle | |
| 223 and not middle.startswith("http://") | |
| 224 and not middle.startswith("https://") | |
| 225 and len(middle) > 0 | |
| 226 and middle[0] in _letters + _digits | |
| 227 and ( | |
| 228 middle.endswith(".org") | |
| 229 or middle.endswith(".net") | |
| 230 or middle.endswith(".com") | |
| 231 ) | |
| 232 ): | |
| 233 middle = '<a href="http://%s"%s%s>%s</a>' % ( | |
| 234 middle, | |
| 235 rel_attr, | |
| 236 target_attr, | |
| 237 trim_url(middle), | |
| 238 ) | |
| 239 if middle.startswith("http://") or middle.startswith("https://"): | |
| 240 middle = '<a href="%s"%s%s>%s</a>' % ( | |
| 241 middle, | |
| 242 rel_attr, | |
| 243 target_attr, | |
| 244 trim_url(middle), | |
| 245 ) | |
| 246 if ( | |
| 247 "@" in middle | |
| 248 and not middle.startswith("www.") | |
| 249 and ":" not in middle | |
| 250 and _simple_email_re.match(middle) | |
| 251 ): | |
| 252 middle = '<a href="mailto:%s">%s</a>' % (middle, middle) | |
| 253 if lead + middle + trail != word: | |
| 254 words[i] = lead + middle + trail | |
| 255 return u"".join(words) | |
| 256 | |
| 257 | |
| 258 def generate_lorem_ipsum(n=5, html=True, min=20, max=100): | |
| 259 """Generate some lorem ipsum for the template.""" | |
| 260 from .constants import LOREM_IPSUM_WORDS | |
| 261 | |
| 262 words = LOREM_IPSUM_WORDS.split() | |
| 263 result = [] | |
| 264 | |
| 265 for _ in range(n): | |
| 266 next_capitalized = True | |
| 267 last_comma = last_fullstop = 0 | |
| 268 word = None | |
| 269 last = None | |
| 270 p = [] | |
| 271 | |
| 272 # each paragraph contains out of 20 to 100 words. | |
| 273 for idx, _ in enumerate(range(randrange(min, max))): | |
| 274 while True: | |
| 275 word = choice(words) | |
| 276 if word != last: | |
| 277 last = word | |
| 278 break | |
| 279 if next_capitalized: | |
| 280 word = word.capitalize() | |
| 281 next_capitalized = False | |
| 282 # add commas | |
| 283 if idx - randrange(3, 8) > last_comma: | |
| 284 last_comma = idx | |
| 285 last_fullstop += 2 | |
| 286 word += "," | |
| 287 # add end of sentences | |
| 288 if idx - randrange(10, 20) > last_fullstop: | |
| 289 last_comma = last_fullstop = idx | |
| 290 word += "." | |
| 291 next_capitalized = True | |
| 292 p.append(word) | |
| 293 | |
| 294 # ensure that the paragraph ends with a dot. | |
| 295 p = u" ".join(p) | |
| 296 if p.endswith(","): | |
| 297 p = p[:-1] + "." | |
| 298 elif not p.endswith("."): | |
| 299 p += "." | |
| 300 result.append(p) | |
| 301 | |
| 302 if not html: | |
| 303 return u"\n\n".join(result) | |
| 304 return Markup(u"\n".join(u"<p>%s</p>" % escape(x) for x in result)) | |
| 305 | |
| 306 | |
| 307 def unicode_urlencode(obj, charset="utf-8", for_qs=False): | |
| 308 """Quote a string for use in a URL using the given charset. | |
| 309 | |
| 310 This function is misnamed, it is a wrapper around | |
| 311 :func:`urllib.parse.quote`. | |
| 312 | |
| 313 :param obj: String or bytes to quote. Other types are converted to | |
| 314 string then encoded to bytes using the given charset. | |
| 315 :param charset: Encode text to bytes using this charset. | |
| 316 :param for_qs: Quote "/" and use "+" for spaces. | |
| 317 """ | |
| 318 if not isinstance(obj, string_types): | |
| 319 obj = text_type(obj) | |
| 320 | |
| 321 if isinstance(obj, text_type): | |
| 322 obj = obj.encode(charset) | |
| 323 | |
| 324 safe = b"" if for_qs else b"/" | |
| 325 rv = url_quote(obj, safe) | |
| 326 | |
| 327 if not isinstance(rv, text_type): | |
| 328 rv = rv.decode("utf-8") | |
| 329 | |
| 330 if for_qs: | |
| 331 rv = rv.replace("%20", "+") | |
| 332 | |
| 333 return rv | |
| 334 | |
| 335 | |
| 336 class LRUCache(object): | |
| 337 """A simple LRU Cache implementation.""" | |
| 338 | |
| 339 # this is fast for small capacities (something below 1000) but doesn't | |
| 340 # scale. But as long as it's only used as storage for templates this | |
| 341 # won't do any harm. | |
| 342 | |
| 343 def __init__(self, capacity): | |
| 344 self.capacity = capacity | |
| 345 self._mapping = {} | |
| 346 self._queue = deque() | |
| 347 self._postinit() | |
| 348 | |
| 349 def _postinit(self): | |
| 350 # alias all queue methods for faster lookup | |
| 351 self._popleft = self._queue.popleft | |
| 352 self._pop = self._queue.pop | |
| 353 self._remove = self._queue.remove | |
| 354 self._wlock = Lock() | |
| 355 self._append = self._queue.append | |
| 356 | |
| 357 def __getstate__(self): | |
| 358 return { | |
| 359 "capacity": self.capacity, | |
| 360 "_mapping": self._mapping, | |
| 361 "_queue": self._queue, | |
| 362 } | |
| 363 | |
| 364 def __setstate__(self, d): | |
| 365 self.__dict__.update(d) | |
| 366 self._postinit() | |
| 367 | |
| 368 def __getnewargs__(self): | |
| 369 return (self.capacity,) | |
| 370 | |
| 371 def copy(self): | |
| 372 """Return a shallow copy of the instance.""" | |
| 373 rv = self.__class__(self.capacity) | |
| 374 rv._mapping.update(self._mapping) | |
| 375 rv._queue.extend(self._queue) | |
| 376 return rv | |
| 377 | |
| 378 def get(self, key, default=None): | |
| 379 """Return an item from the cache dict or `default`""" | |
| 380 try: | |
| 381 return self[key] | |
| 382 except KeyError: | |
| 383 return default | |
| 384 | |
| 385 def setdefault(self, key, default=None): | |
| 386 """Set `default` if the key is not in the cache otherwise | |
| 387 leave unchanged. Return the value of this key. | |
| 388 """ | |
| 389 try: | |
| 390 return self[key] | |
| 391 except KeyError: | |
| 392 self[key] = default | |
| 393 return default | |
| 394 | |
| 395 def clear(self): | |
| 396 """Clear the cache.""" | |
| 397 self._wlock.acquire() | |
| 398 try: | |
| 399 self._mapping.clear() | |
| 400 self._queue.clear() | |
| 401 finally: | |
| 402 self._wlock.release() | |
| 403 | |
| 404 def __contains__(self, key): | |
| 405 """Check if a key exists in this cache.""" | |
| 406 return key in self._mapping | |
| 407 | |
| 408 def __len__(self): | |
| 409 """Return the current size of the cache.""" | |
| 410 return len(self._mapping) | |
| 411 | |
| 412 def __repr__(self): | |
| 413 return "<%s %r>" % (self.__class__.__name__, self._mapping) | |
| 414 | |
| 415 def __getitem__(self, key): | |
| 416 """Get an item from the cache. Moves the item up so that it has the | |
| 417 highest priority then. | |
| 418 | |
| 419 Raise a `KeyError` if it does not exist. | |
| 420 """ | |
| 421 self._wlock.acquire() | |
| 422 try: | |
| 423 rv = self._mapping[key] | |
| 424 if self._queue[-1] != key: | |
| 425 try: | |
| 426 self._remove(key) | |
| 427 except ValueError: | |
| 428 # if something removed the key from the container | |
| 429 # when we read, ignore the ValueError that we would | |
| 430 # get otherwise. | |
| 431 pass | |
| 432 self._append(key) | |
| 433 return rv | |
| 434 finally: | |
| 435 self._wlock.release() | |
| 436 | |
| 437 def __setitem__(self, key, value): | |
| 438 """Sets the value for an item. Moves the item up so that it | |
| 439 has the highest priority then. | |
| 440 """ | |
| 441 self._wlock.acquire() | |
| 442 try: | |
| 443 if key in self._mapping: | |
| 444 self._remove(key) | |
| 445 elif len(self._mapping) == self.capacity: | |
| 446 del self._mapping[self._popleft()] | |
| 447 self._append(key) | |
| 448 self._mapping[key] = value | |
| 449 finally: | |
| 450 self._wlock.release() | |
| 451 | |
| 452 def __delitem__(self, key): | |
| 453 """Remove an item from the cache dict. | |
| 454 Raise a `KeyError` if it does not exist. | |
| 455 """ | |
| 456 self._wlock.acquire() | |
| 457 try: | |
| 458 del self._mapping[key] | |
| 459 try: | |
| 460 self._remove(key) | |
| 461 except ValueError: | |
| 462 pass | |
| 463 finally: | |
| 464 self._wlock.release() | |
| 465 | |
| 466 def items(self): | |
| 467 """Return a list of items.""" | |
| 468 result = [(key, self._mapping[key]) for key in list(self._queue)] | |
| 469 result.reverse() | |
| 470 return result | |
| 471 | |
| 472 def iteritems(self): | |
| 473 """Iterate over all items.""" | |
| 474 warnings.warn( | |
| 475 "'iteritems()' will be removed in version 3.0. Use" | |
| 476 " 'iter(cache.items())' instead.", | |
| 477 DeprecationWarning, | |
| 478 stacklevel=2, | |
| 479 ) | |
| 480 return iter(self.items()) | |
| 481 | |
| 482 def values(self): | |
| 483 """Return a list of all values.""" | |
| 484 return [x[1] for x in self.items()] | |
| 485 | |
| 486 def itervalue(self): | |
| 487 """Iterate over all values.""" | |
| 488 warnings.warn( | |
| 489 "'itervalue()' will be removed in version 3.0. Use" | |
| 490 " 'iter(cache.values())' instead.", | |
| 491 DeprecationWarning, | |
| 492 stacklevel=2, | |
| 493 ) | |
| 494 return iter(self.values()) | |
| 495 | |
| 496 def itervalues(self): | |
| 497 """Iterate over all values.""" | |
| 498 warnings.warn( | |
| 499 "'itervalues()' will be removed in version 3.0. Use" | |
| 500 " 'iter(cache.values())' instead.", | |
| 501 DeprecationWarning, | |
| 502 stacklevel=2, | |
| 503 ) | |
| 504 return iter(self.values()) | |
| 505 | |
| 506 def keys(self): | |
| 507 """Return a list of all keys ordered by most recent usage.""" | |
| 508 return list(self) | |
| 509 | |
| 510 def iterkeys(self): | |
| 511 """Iterate over all keys in the cache dict, ordered by | |
| 512 the most recent usage. | |
| 513 """ | |
| 514 warnings.warn( | |
| 515 "'iterkeys()' will be removed in version 3.0. Use" | |
| 516 " 'iter(cache.keys())' instead.", | |
| 517 DeprecationWarning, | |
| 518 stacklevel=2, | |
| 519 ) | |
| 520 return iter(self) | |
| 521 | |
| 522 def __iter__(self): | |
| 523 return reversed(tuple(self._queue)) | |
| 524 | |
| 525 def __reversed__(self): | |
| 526 """Iterate over the keys in the cache dict, oldest items | |
| 527 coming first. | |
| 528 """ | |
| 529 return iter(tuple(self._queue)) | |
| 530 | |
| 531 __copy__ = copy | |
| 532 | |
| 533 | |
| 534 abc.MutableMapping.register(LRUCache) | |
| 535 | |
| 536 | |
| 537 def select_autoescape( | |
| 538 enabled_extensions=("html", "htm", "xml"), | |
| 539 disabled_extensions=(), | |
| 540 default_for_string=True, | |
| 541 default=False, | |
| 542 ): | |
| 543 """Intelligently sets the initial value of autoescaping based on the | |
| 544 filename of the template. This is the recommended way to configure | |
| 545 autoescaping if you do not want to write a custom function yourself. | |
| 546 | |
| 547 If you want to enable it for all templates created from strings or | |
| 548 for all templates with `.html` and `.xml` extensions:: | |
| 549 | |
| 550 from jinja2 import Environment, select_autoescape | |
| 551 env = Environment(autoescape=select_autoescape( | |
| 552 enabled_extensions=('html', 'xml'), | |
| 553 default_for_string=True, | |
| 554 )) | |
| 555 | |
| 556 Example configuration to turn it on at all times except if the template | |
| 557 ends with `.txt`:: | |
| 558 | |
| 559 from jinja2 import Environment, select_autoescape | |
| 560 env = Environment(autoescape=select_autoescape( | |
| 561 disabled_extensions=('txt',), | |
| 562 default_for_string=True, | |
| 563 default=True, | |
| 564 )) | |
| 565 | |
| 566 The `enabled_extensions` is an iterable of all the extensions that | |
| 567 autoescaping should be enabled for. Likewise `disabled_extensions` is | |
| 568 a list of all templates it should be disabled for. If a template is | |
| 569 loaded from a string then the default from `default_for_string` is used. | |
| 570 If nothing matches then the initial value of autoescaping is set to the | |
| 571 value of `default`. | |
| 572 | |
| 573 For security reasons this function operates case insensitive. | |
| 574 | |
| 575 .. versionadded:: 2.9 | |
| 576 """ | |
| 577 enabled_patterns = tuple("." + x.lstrip(".").lower() for x in enabled_extensions) | |
| 578 disabled_patterns = tuple("." + x.lstrip(".").lower() for x in disabled_extensions) | |
| 579 | |
| 580 def autoescape(template_name): | |
| 581 if template_name is None: | |
| 582 return default_for_string | |
| 583 template_name = template_name.lower() | |
| 584 if template_name.endswith(enabled_patterns): | |
| 585 return True | |
| 586 if template_name.endswith(disabled_patterns): | |
| 587 return False | |
| 588 return default | |
| 589 | |
| 590 return autoescape | |
| 591 | |
| 592 | |
| 593 def htmlsafe_json_dumps(obj, dumper=None, **kwargs): | |
| 594 """Works exactly like :func:`dumps` but is safe for use in ``<script>`` | |
| 595 tags. It accepts the same arguments and returns a JSON string. Note that | |
| 596 this is available in templates through the ``|tojson`` filter which will | |
| 597 also mark the result as safe. Due to how this function escapes certain | |
| 598 characters this is safe even if used outside of ``<script>`` tags. | |
| 599 | |
| 600 The following characters are escaped in strings: | |
| 601 | |
| 602 - ``<`` | |
| 603 - ``>`` | |
| 604 - ``&`` | |
| 605 - ``'`` | |
| 606 | |
| 607 This makes it safe to embed such strings in any place in HTML with the | |
| 608 notable exception of double quoted attributes. In that case single | |
| 609 quote your attributes or HTML escape it in addition. | |
| 610 """ | |
| 611 if dumper is None: | |
| 612 dumper = json.dumps | |
| 613 rv = ( | |
| 614 dumper(obj, **kwargs) | |
| 615 .replace(u"<", u"\\u003c") | |
| 616 .replace(u">", u"\\u003e") | |
| 617 .replace(u"&", u"\\u0026") | |
| 618 .replace(u"'", u"\\u0027") | |
| 619 ) | |
| 620 return Markup(rv) | |
| 621 | |
| 622 | |
| 623 class Cycler(object): | |
| 624 """Cycle through values by yield them one at a time, then restarting | |
| 625 once the end is reached. Available as ``cycler`` in templates. | |
| 626 | |
| 627 Similar to ``loop.cycle``, but can be used outside loops or across | |
| 628 multiple loops. For example, render a list of folders and files in a | |
| 629 list, alternating giving them "odd" and "even" classes. | |
| 630 | |
| 631 .. code-block:: html+jinja | |
| 632 | |
| 633 {% set row_class = cycler("odd", "even") %} | |
| 634 <ul class="browser"> | |
| 635 {% for folder in folders %} | |
| 636 <li class="folder {{ row_class.next() }}">{{ folder }} | |
| 637 {% endfor %} | |
| 638 {% for file in files %} | |
| 639 <li class="file {{ row_class.next() }}">{{ file }} | |
| 640 {% endfor %} | |
| 641 </ul> | |
| 642 | |
| 643 :param items: Each positional argument will be yielded in the order | |
| 644 given for each cycle. | |
| 645 | |
| 646 .. versionadded:: 2.1 | |
| 647 """ | |
| 648 | |
| 649 def __init__(self, *items): | |
| 650 if not items: | |
| 651 raise RuntimeError("at least one item has to be provided") | |
| 652 self.items = items | |
| 653 self.pos = 0 | |
| 654 | |
| 655 def reset(self): | |
| 656 """Resets the current item to the first item.""" | |
| 657 self.pos = 0 | |
| 658 | |
| 659 @property | |
| 660 def current(self): | |
| 661 """Return the current item. Equivalent to the item that will be | |
| 662 returned next time :meth:`next` is called. | |
| 663 """ | |
| 664 return self.items[self.pos] | |
| 665 | |
| 666 def next(self): | |
| 667 """Return the current item, then advance :attr:`current` to the | |
| 668 next item. | |
| 669 """ | |
| 670 rv = self.current | |
| 671 self.pos = (self.pos + 1) % len(self.items) | |
| 672 return rv | |
| 673 | |
| 674 __next__ = next | |
| 675 | |
| 676 | |
| 677 class Joiner(object): | |
| 678 """A joining helper for templates.""" | |
| 679 | |
| 680 def __init__(self, sep=u", "): | |
| 681 self.sep = sep | |
| 682 self.used = False | |
| 683 | |
| 684 def __call__(self): | |
| 685 if not self.used: | |
| 686 self.used = True | |
| 687 return u"" | |
| 688 return self.sep | |
| 689 | |
| 690 | |
| 691 class Namespace(object): | |
| 692 """A namespace object that can hold arbitrary attributes. It may be | |
| 693 initialized from a dictionary or with keyword arguments.""" | |
| 694 | |
| 695 def __init__(*args, **kwargs): # noqa: B902 | |
| 696 self, args = args[0], args[1:] | |
| 697 self.__attrs = dict(*args, **kwargs) | |
| 698 | |
| 699 def __getattribute__(self, name): | |
| 700 # __class__ is needed for the awaitable check in async mode | |
| 701 if name in {"_Namespace__attrs", "__class__"}: | |
| 702 return object.__getattribute__(self, name) | |
| 703 try: | |
| 704 return self.__attrs[name] | |
| 705 except KeyError: | |
| 706 raise AttributeError(name) | |
| 707 | |
| 708 def __setitem__(self, name, value): | |
| 709 self.__attrs[name] = value | |
| 710 | |
| 711 def __repr__(self): | |
| 712 return "<Namespace %r>" % self.__attrs | |
| 713 | |
| 714 | |
| 715 # does this python version support async for in and async generators? | |
| 716 try: | |
| 717 exec("async def _():\n async for _ in ():\n yield _") | |
| 718 have_async_gen = True | |
| 719 except SyntaxError: | |
| 720 have_async_gen = False | |
| 721 | |
| 722 | |
| 723 def soft_unicode(s): | |
| 724 from markupsafe import soft_unicode | |
| 725 | |
| 726 warnings.warn( | |
| 727 "'jinja2.utils.soft_unicode' will be removed in version 3.0." | |
| 728 " Use 'markupsafe.soft_unicode' instead.", | |
| 729 DeprecationWarning, | |
| 730 stacklevel=2, | |
| 731 ) | |
| 732 return soft_unicode(s) |
