Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
utils.py767 linesDownload Raw Back to jinja2
1import enum2import json3import os4import re5import typing as t6from collections import abc7from collections import deque8from random import choice9from random import randrange10from threading import Lock11from types import CodeType12from urllib.parse import quote_from_bytes13 14import markupsafe15 16if t.TYPE_CHECKING:17    import typing_extensions as te18 19F = t.TypeVar("F", bound=t.Callable[..., t.Any])20 21 22class _MissingType:23    def __repr__(self) -> str:24        return "missing"25 26    def __reduce__(self) -> str:27        return "missing"28 29 30missing: t.Any = _MissingType()31"""Special singleton representing missing values for the runtime."""32 33internal_code: t.MutableSet[CodeType] = set()34 35concat = "".join36 37 38def pass_context(f: F) -> F:39    """Pass the :class:`~jinja2.runtime.Context` as the first argument40    to the decorated function when called while rendering a template.41 42    Can be used on functions, filters, and tests.43 44    If only ``Context.eval_context`` is needed, use45    :func:`pass_eval_context`. If only ``Context.environment`` is46    needed, use :func:`pass_environment`.47 48    .. versionadded:: 3.0.049        Replaces ``contextfunction`` and ``contextfilter``.50    """51    f.jinja_pass_arg = _PassArg.context  # type: ignore52    return f53 54 55def pass_eval_context(f: F) -> F:56    """Pass the :class:`~jinja2.nodes.EvalContext` as the first argument57    to the decorated function when called while rendering a template.58    See :ref:`eval-context`.59 60    Can be used on functions, filters, and tests.61 62    If only ``EvalContext.environment`` is needed, use63    :func:`pass_environment`.64 65    .. versionadded:: 3.0.066        Replaces ``evalcontextfunction`` and ``evalcontextfilter``.67    """68    f.jinja_pass_arg = _PassArg.eval_context  # type: ignore69    return f70 71 72def pass_environment(f: F) -> F:73    """Pass the :class:`~jinja2.Environment` as the first argument to74    the decorated function when called while rendering a template.75 76    Can be used on functions, filters, and tests.77 78    .. versionadded:: 3.0.079        Replaces ``environmentfunction`` and ``environmentfilter``.80    """81    f.jinja_pass_arg = _PassArg.environment  # type: ignore82    return f83 84 85class _PassArg(enum.Enum):86    context = enum.auto()87    eval_context = enum.auto()88    environment = enum.auto()89 90    @classmethod91    def from_obj(cls, obj: F) -> t.Optional["_PassArg"]:92        if hasattr(obj, "jinja_pass_arg"):93            return obj.jinja_pass_arg  # type: ignore94 95        return None96 97 98def internalcode(f: F) -> F:99    """Marks the function as internally used"""100    internal_code.add(f.__code__)101    return f102 103 104def is_undefined(obj: t.Any) -> bool:105    """Check if the object passed is undefined.  This does nothing more than106    performing an instance check against :class:`Undefined` but looks nicer.107    This can be used for custom filters or tests that want to react to108    undefined variables.  For example a custom default filter can look like109    this::110 111        def default(var, default=''):112            if is_undefined(var):113                return default114            return var115    """116    from .runtime import Undefined117 118    return isinstance(obj, Undefined)119 120 121def consume(iterable: t.Iterable[t.Any]) -> None:122    """Consumes an iterable without doing anything with it."""123    for _ in iterable:124        pass125 126 127def clear_caches() -> None:128    """Jinja keeps internal caches for environments and lexers.  These are129    used so that Jinja doesn't have to recreate environments and lexers all130    the time.  Normally you don't have to care about that but if you are131    measuring memory consumption you may want to clean the caches.132    """133    from .environment import get_spontaneous_environment134    from .lexer import _lexer_cache135 136    get_spontaneous_environment.cache_clear()137    _lexer_cache.clear()138 139 140def import_string(import_name: str, silent: bool = False) -> t.Any:141    """Imports an object based on a string.  This is useful if you want to142    use import paths as endpoints or something similar.  An import path can143    be specified either in dotted notation (``xml.sax.saxutils.escape``)144    or with a colon as object delimiter (``xml.sax.saxutils:escape``).145 146    If the `silent` is True the return value will be `None` if the import147    fails.148 149    :return: imported object150    """151    try:152        if ":" in import_name:153            module, obj = import_name.split(":", 1)154        elif "." in import_name:155            module, _, obj = import_name.rpartition(".")156        else:157            return __import__(import_name)158        return getattr(__import__(module, None, None, [obj]), obj)159    except (ImportError, AttributeError):160        if not silent:161            raise162 163 164def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO[t.Any]]:165    """Returns a file descriptor for the filename if that file exists,166    otherwise ``None``.167    """168    if not os.path.isfile(filename):169        return None170 171    return open(filename, mode)172 173 174def object_type_repr(obj: t.Any) -> str:175    """Returns the name of the object's type.  For some recognized176    singletons the name of the object is returned instead. (For177    example for `None` and `Ellipsis`).178    """179    if obj is None:180        return "None"181    elif obj is Ellipsis:182        return "Ellipsis"183 184    cls = type(obj)185 186    if cls.__module__ == "builtins":187        return f"{cls.__name__} object"188 189    return f"{cls.__module__}.{cls.__name__} object"190 191 192def pformat(obj: t.Any) -> str:193    """Format an object using :func:`pprint.pformat`."""194    from pprint import pformat195 196    return pformat(obj)197 198 199_http_re = re.compile(200    r"""201    ^202    (203        (https?://|www\.)  # scheme or www204        (([\w%-]+\.)+)?  # subdomain205        (206            [a-z]{2,63}  # basic tld207        |208            xn--[\w%]{2,59}  # idna tld209        )210    |211        ([\w%-]{2,63}\.)+  # basic domain212        (com|net|int|edu|gov|org|info|mil)  # basic tld213    |214        (https?://)  # scheme215        (216            (([\d]{1,3})(\.[\d]{1,3}){3})  # IPv4217        |218            (\[([\da-f]{0,4}:){2}([\da-f]{0,4}:?){1,6}])  # IPv6219        )220    )221    (?::[\d]{1,5})?  # port222    (?:[/?#]\S*)?  # path, query, and fragment223    $224    """,225    re.IGNORECASE | re.VERBOSE,226)227_email_re = re.compile(r"^\S+@\w[\w.-]*\.\w+$")228 229 230def urlize(231    text: str,232    trim_url_limit: t.Optional[int] = None,233    rel: t.Optional[str] = None,234    target: t.Optional[str] = None,235    extra_schemes: t.Optional[t.Iterable[str]] = None,236) -> str:237    """Convert URLs in text into clickable links.238 239    This may not recognize links in some situations. Usually, a more240    comprehensive formatter, such as a Markdown library, is a better241    choice.242 243    Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email244    addresses. Links with trailing punctuation (periods, commas, closing245    parentheses) and leading punctuation (opening parentheses) are246    recognized excluding the punctuation. Email addresses that include247    header fields are not recognized (for example,248    ``mailto:address@example.com?cc=copy@example.com``).249 250    :param text: Original text containing URLs to link.251    :param trim_url_limit: Shorten displayed URL values to this length.252    :param target: Add the ``target`` attribute to links.253    :param rel: Add the ``rel`` attribute to links.254    :param extra_schemes: Recognize URLs that start with these schemes255        in addition to the default behavior.256 257    .. versionchanged:: 3.0258        The ``extra_schemes`` parameter was added.259 260    .. versionchanged:: 3.0261        Generate ``https://`` links for URLs without a scheme.262 263    .. versionchanged:: 3.0264        The parsing rules were updated. Recognize email addresses with265        or without the ``mailto:`` scheme. Validate IP addresses. Ignore266        parentheses and brackets in more cases.267    """268    if trim_url_limit is not None:269 270        def trim_url(x: str) -> str:271            if len(x) > trim_url_limit:272                return f"{x[:trim_url_limit]}..."273 274            return x275 276    else:277 278        def trim_url(x: str) -> str:279            return x280 281    words = re.split(r"(\s+)", str(markupsafe.escape(text)))282    rel_attr = f' rel="{markupsafe.escape(rel)}"' if rel else ""283    target_attr = f' target="{markupsafe.escape(target)}"' if target else ""284 285    for i, word in enumerate(words):286        head, middle, tail = "", word, ""287        match = re.match(r"^([(<]|&lt;)+", middle)288 289        if match:290            head = match.group()291            middle = middle[match.end() :]292 293        # Unlike lead, which is anchored to the start of the string,294        # need to check that the string ends with any of the characters295        # before trying to match all of them, to avoid backtracking.296        if middle.endswith((")", ">", ".", ",", "\n", "&gt;")):297            match = re.search(r"([)>.,\n]|&gt;)+$", middle)298 299            if match:300                tail = match.group()301                middle = middle[: match.start()]302 303        # Prefer balancing parentheses in URLs instead of ignoring a304        # trailing character.305        for start_char, end_char in ("(", ")"), ("<", ">"), ("&lt;", "&gt;"):306            start_count = middle.count(start_char)307 308            if start_count <= middle.count(end_char):309                # Balanced, or lighter on the left310                continue311 312            # Move as many as possible from the tail to balance313            for _ in range(min(start_count, tail.count(end_char))):314                end_index = tail.index(end_char) + len(end_char)315                # Move anything in the tail before the end char too316                middle += tail[:end_index]317                tail = tail[end_index:]318 319        if _http_re.match(middle):320            if middle.startswith("https://") or middle.startswith("http://"):321                middle = (322                    f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>'323                )324            else:325                middle = (326                    f'<a href="https://{middle}"{rel_attr}{target_attr}>'327                    f"{trim_url(middle)}</a>"328                )329 330        elif middle.startswith("mailto:") and _email_re.match(middle[7:]):331            middle = f'<a href="{middle}">{middle[7:]}</a>'332 333        elif (334            "@" in middle335            and not middle.startswith("www.")336            # ignore values like `@a@b`337            and not middle.startswith("@")338            and ":" not in middle339            and _email_re.match(middle)340        ):341            middle = f'<a href="mailto:{middle}">{middle}</a>'342 343        elif extra_schemes is not None:344            for scheme in extra_schemes:345                if middle != scheme and middle.startswith(scheme):346                    middle = f'<a href="{middle}"{rel_attr}{target_attr}>{middle}</a>'347 348        words[i] = f"{head}{middle}{tail}"349 350    return "".join(words)351 352 353def generate_lorem_ipsum(354    n: int = 5, html: bool = True, min: int = 20, max: int = 100355) -> str:356    """Generate some lorem ipsum for the template."""357    from .constants import LOREM_IPSUM_WORDS358 359    words = LOREM_IPSUM_WORDS.split()360    result = []361 362    for _ in range(n):363        next_capitalized = True364        last_comma = last_fullstop = 0365        word = None366        last = None367        p = []368 369        # each paragraph contains out of 20 to 100 words.370        for idx, _ in enumerate(range(randrange(min, max))):371            while True:372                word = choice(words)373                if word != last:374                    last = word375                    break376            if next_capitalized:377                word = word.capitalize()378                next_capitalized = False379            # add commas380            if idx - randrange(3, 8) > last_comma:381                last_comma = idx382                last_fullstop += 2383                word += ","384            # add end of sentences385            if idx - randrange(10, 20) > last_fullstop:386                last_comma = last_fullstop = idx387                word += "."388                next_capitalized = True389            p.append(word)390 391        # ensure that the paragraph ends with a dot.392        p_str = " ".join(p)393 394        if p_str.endswith(","):395            p_str = p_str[:-1] + "."396        elif not p_str.endswith("."):397            p_str += "."398 399        result.append(p_str)400 401    if not html:402        return "\n\n".join(result)403    return markupsafe.Markup(404        "\n".join(f"<p>{markupsafe.escape(x)}</p>" for x in result)405    )406 407 408def url_quote(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str:409    """Quote a string for use in a URL using the given charset.410 411    :param obj: String or bytes to quote. Other types are converted to412        string then encoded to bytes using the given charset.413    :param charset: Encode text to bytes using this charset.414    :param for_qs: Quote "/" and use "+" for spaces.415    """416    if not isinstance(obj, bytes):417        if not isinstance(obj, str):418            obj = str(obj)419 420        obj = obj.encode(charset)421 422    safe = b"" if for_qs else b"/"423    rv = quote_from_bytes(obj, safe)424 425    if for_qs:426        rv = rv.replace("%20", "+")427 428    return rv429 430 431@abc.MutableMapping.register432class LRUCache:433    """A simple LRU Cache implementation."""434 435    # this is fast for small capacities (something below 1000) but doesn't436    # scale.  But as long as it's only used as storage for templates this437    # won't do any harm.438 439    def __init__(self, capacity: int) -> None:440        self.capacity = capacity441        self._mapping: t.Dict[t.Any, t.Any] = {}442        self._queue: te.Deque[t.Any] = deque()443        self._postinit()444 445    def _postinit(self) -> None:446        # alias all queue methods for faster lookup447        self._popleft = self._queue.popleft448        self._pop = self._queue.pop449        self._remove = self._queue.remove450        self._wlock = Lock()451        self._append = self._queue.append452 453    def __getstate__(self) -> t.Mapping[str, t.Any]:454        return {455            "capacity": self.capacity,456            "_mapping": self._mapping,457            "_queue": self._queue,458        }459 460    def __setstate__(self, d: t.Mapping[str, t.Any]) -> None:461        self.__dict__.update(d)462        self._postinit()463 464    def __getnewargs__(self) -> t.Tuple[t.Any, ...]:465        return (self.capacity,)466 467    def copy(self) -> "te.Self":468        """Return a shallow copy of the instance."""469        rv = self.__class__(self.capacity)470        rv._mapping.update(self._mapping)471        rv._queue.extend(self._queue)472        return rv473 474    def get(self, key: t.Any, default: t.Any = None) -> t.Any:475        """Return an item from the cache dict or `default`"""476        try:477            return self[key]478        except KeyError:479            return default480 481    def setdefault(self, key: t.Any, default: t.Any = None) -> t.Any:482        """Set `default` if the key is not in the cache otherwise483        leave unchanged. Return the value of this key.484        """485        try:486            return self[key]487        except KeyError:488            self[key] = default489            return default490 491    def clear(self) -> None:492        """Clear the cache."""493        with self._wlock:494            self._mapping.clear()495            self._queue.clear()496 497    def __contains__(self, key: t.Any) -> bool:498        """Check if a key exists in this cache."""499        return key in self._mapping500 501    def __len__(self) -> int:502        """Return the current size of the cache."""503        return len(self._mapping)504 505    def __repr__(self) -> str:506        return f"<{type(self).__name__} {self._mapping!r}>"507 508    def __getitem__(self, key: t.Any) -> t.Any:509        """Get an item from the cache. Moves the item up so that it has the510        highest priority then.511 512        Raise a `KeyError` if it does not exist.513        """514        with self._wlock:515            rv = self._mapping[key]516 517            if self._queue[-1] != key:518                try:519                    self._remove(key)520                except ValueError:521                    # if something removed the key from the container522                    # when we read, ignore the ValueError that we would523                    # get otherwise.524                    pass525 526                self._append(key)527 528            return rv529 530    def __setitem__(self, key: t.Any, value: t.Any) -> None:531        """Sets the value for an item. Moves the item up so that it532        has the highest priority then.533        """534        with self._wlock:535            if key in self._mapping:536                self._remove(key)537            elif len(self._mapping) == self.capacity:538                del self._mapping[self._popleft()]539 540            self._append(key)541            self._mapping[key] = value542 543    def __delitem__(self, key: t.Any) -> None:544        """Remove an item from the cache dict.545        Raise a `KeyError` if it does not exist.546        """547        with self._wlock:548            del self._mapping[key]549 550            try:551                self._remove(key)552            except ValueError:553                pass554 555    def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]:556        """Return a list of items."""557        result = [(key, self._mapping[key]) for key in list(self._queue)]558        result.reverse()559        return result560 561    def values(self) -> t.Iterable[t.Any]:562        """Return a list of all values."""563        return [x[1] for x in self.items()]564 565    def keys(self) -> t.Iterable[t.Any]:566        """Return a list of all keys ordered by most recent usage."""567        return list(self)568 569    def __iter__(self) -> t.Iterator[t.Any]:570        return reversed(tuple(self._queue))571 572    def __reversed__(self) -> t.Iterator[t.Any]:573        """Iterate over the keys in the cache dict, oldest items574        coming first.575        """576        return iter(tuple(self._queue))577 578    __copy__ = copy579 580 581def select_autoescape(582    enabled_extensions: t.Collection[str] = ("html", "htm", "xml"),583    disabled_extensions: t.Collection[str] = (),584    default_for_string: bool = True,585    default: bool = False,586) -> t.Callable[[t.Optional[str]], bool]:587    """Intelligently sets the initial value of autoescaping based on the588    filename of the template.  This is the recommended way to configure589    autoescaping if you do not want to write a custom function yourself.590 591    If you want to enable it for all templates created from strings or592    for all templates with `.html` and `.xml` extensions::593 594        from jinja2 import Environment, select_autoescape595        env = Environment(autoescape=select_autoescape(596            enabled_extensions=('html', 'xml'),597            default_for_string=True,598        ))599 600    Example configuration to turn it on at all times except if the template601    ends with `.txt`::602 603        from jinja2 import Environment, select_autoescape604        env = Environment(autoescape=select_autoescape(605            disabled_extensions=('txt',),606            default_for_string=True,607            default=True,608        ))609 610    The `enabled_extensions` is an iterable of all the extensions that611    autoescaping should be enabled for.  Likewise `disabled_extensions` is612    a list of all templates it should be disabled for.  If a template is613    loaded from a string then the default from `default_for_string` is used.614    If nothing matches then the initial value of autoescaping is set to the615    value of `default`.616 617    For security reasons this function operates case insensitive.618 619    .. versionadded:: 2.9620    """621    enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions)622    disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions)623 624    def autoescape(template_name: t.Optional[str]) -> bool:625        if template_name is None:626            return default_for_string627        template_name = template_name.lower()628        if template_name.endswith(enabled_patterns):629            return True630        if template_name.endswith(disabled_patterns):631            return False632        return default633 634    return autoescape635 636 637def htmlsafe_json_dumps(638    obj: t.Any, dumps: t.Optional[t.Callable[..., str]] = None, **kwargs: t.Any639) -> markupsafe.Markup:640    """Serialize an object to a string of JSON with :func:`json.dumps`,641    then replace HTML-unsafe characters with Unicode escapes and mark642    the result safe with :class:`~markupsafe.Markup`.643 644    This is available in templates as the ``|tojson`` filter.645 646    The following characters are escaped: ``<``, ``>``, ``&``, ``'``.647 648    The returned string is safe to render in HTML documents and649    ``<script>`` tags. The exception is in HTML attributes that are650    double quoted; either use single quotes or the ``|forceescape``651    filter.652 653    :param obj: The object to serialize to JSON.654    :param dumps: The ``dumps`` function to use. Defaults to655        ``env.policies["json.dumps_function"]``, which defaults to656        :func:`json.dumps`.657    :param kwargs: Extra arguments to pass to ``dumps``. Merged onto658        ``env.policies["json.dumps_kwargs"]``.659 660    .. versionchanged:: 3.0661        The ``dumper`` parameter is renamed to ``dumps``.662 663    .. versionadded:: 2.9664    """665    if dumps is None:666        dumps = json.dumps667 668    return markupsafe.Markup(669        dumps(obj, **kwargs)670        .replace("<", "\\u003c")671        .replace(">", "\\u003e")672        .replace("&", "\\u0026")673        .replace("'", "\\u0027")674    )675 676 677class Cycler:678    """Cycle through values by yield them one at a time, then restarting679    once the end is reached. Available as ``cycler`` in templates.680 681    Similar to ``loop.cycle``, but can be used outside loops or across682    multiple loops. For example, render a list of folders and files in a683    list, alternating giving them "odd" and "even" classes.684 685    .. code-block:: html+jinja686 687        {% set row_class = cycler("odd", "even") %}688        <ul class="browser">689        {% for folder in folders %}690          <li class="folder {{ row_class.next() }}">{{ folder }}691        {% endfor %}692        {% for file in files %}693          <li class="file {{ row_class.next() }}">{{ file }}694        {% endfor %}695        </ul>696 697    :param items: Each positional argument will be yielded in the order698        given for each cycle.699 700    .. versionadded:: 2.1701    """702 703    def __init__(self, *items: t.Any) -> None:704        if not items:705            raise RuntimeError("at least one item has to be provided")706        self.items = items707        self.pos = 0708 709    def reset(self) -> None:710        """Resets the current item to the first item."""711        self.pos = 0712 713    @property714    def current(self) -> t.Any:715        """Return the current item. Equivalent to the item that will be716        returned next time :meth:`next` is called.717        """718        return self.items[self.pos]719 720    def next(self) -> t.Any:721        """Return the current item, then advance :attr:`current` to the722        next item.723        """724        rv = self.current725        self.pos = (self.pos + 1) % len(self.items)726        return rv727 728    __next__ = next729 730 731class Joiner:732    """A joining helper for templates."""733 734    def __init__(self, sep: str = ", ") -> None:735        self.sep = sep736        self.used = False737 738    def __call__(self) -> str:739        if not self.used:740            self.used = True741            return ""742        return self.sep743 744 745class Namespace:746    """A namespace object that can hold arbitrary attributes.  It may be747    initialized from a dictionary or with keyword arguments."""748 749    def __init__(*args: t.Any, **kwargs: t.Any) -> None:  # noqa: B902750        self, args = args[0], args[1:]751        self.__attrs = dict(*args, **kwargs)752 753    def __getattribute__(self, name: str) -> t.Any:754        # __class__ is needed for the awaitable check in async mode755        if name in {"_Namespace__attrs", "__class__"}:756            return object.__getattribute__(self, name)757        try:758            return self.__attrs[name]759        except KeyError:760            raise AttributeError(name) from None761 762    def __setitem__(self, name: str, value: t.Any) -> None:763        self.__attrs[name] = value764 765    def __repr__(self) -> str:766        return f"<Namespace {self.__attrs!r}>"767 
codekingpro/portable-devtools · Team Ai