mirror of
https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server.git
synced 2025-11-14 17:46:03 +00:00
Update python site-packages.
Added MarkupSafe, Jinja2, itsdangerous, Werkzeug, click, flask.
This commit is contained in:
parent
8d9a94b5d6
commit
87717aad33
67 changed files with 25256 additions and 162 deletions
75
dist/ba_data/python-site-packages/click/__init__.py
vendored
Normal file
75
dist/ba_data/python-site-packages/click/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
"""
|
||||||
|
Click is a simple Python module inspired by the stdlib optparse to make
|
||||||
|
writing command line scripts fun. Unlike other modules, it's based
|
||||||
|
around a simple API that does not come with too much magic and is
|
||||||
|
composable.
|
||||||
|
"""
|
||||||
|
from .core import Argument as Argument
|
||||||
|
from .core import BaseCommand as BaseCommand
|
||||||
|
from .core import Command as Command
|
||||||
|
from .core import CommandCollection as CommandCollection
|
||||||
|
from .core import Context as Context
|
||||||
|
from .core import Group as Group
|
||||||
|
from .core import MultiCommand as MultiCommand
|
||||||
|
from .core import Option as Option
|
||||||
|
from .core import Parameter as Parameter
|
||||||
|
from .decorators import argument as argument
|
||||||
|
from .decorators import command as command
|
||||||
|
from .decorators import confirmation_option as confirmation_option
|
||||||
|
from .decorators import group as group
|
||||||
|
from .decorators import help_option as help_option
|
||||||
|
from .decorators import make_pass_decorator as make_pass_decorator
|
||||||
|
from .decorators import option as option
|
||||||
|
from .decorators import pass_context as pass_context
|
||||||
|
from .decorators import pass_obj as pass_obj
|
||||||
|
from .decorators import password_option as password_option
|
||||||
|
from .decorators import version_option as version_option
|
||||||
|
from .exceptions import Abort as Abort
|
||||||
|
from .exceptions import BadArgumentUsage as BadArgumentUsage
|
||||||
|
from .exceptions import BadOptionUsage as BadOptionUsage
|
||||||
|
from .exceptions import BadParameter as BadParameter
|
||||||
|
from .exceptions import ClickException as ClickException
|
||||||
|
from .exceptions import FileError as FileError
|
||||||
|
from .exceptions import MissingParameter as MissingParameter
|
||||||
|
from .exceptions import NoSuchOption as NoSuchOption
|
||||||
|
from .exceptions import UsageError as UsageError
|
||||||
|
from .formatting import HelpFormatter as HelpFormatter
|
||||||
|
from .formatting import wrap_text as wrap_text
|
||||||
|
from .globals import get_current_context as get_current_context
|
||||||
|
from .parser import OptionParser as OptionParser
|
||||||
|
from .termui import clear as clear
|
||||||
|
from .termui import confirm as confirm
|
||||||
|
from .termui import echo_via_pager as echo_via_pager
|
||||||
|
from .termui import edit as edit
|
||||||
|
from .termui import get_terminal_size as get_terminal_size
|
||||||
|
from .termui import getchar as getchar
|
||||||
|
from .termui import launch as launch
|
||||||
|
from .termui import pause as pause
|
||||||
|
from .termui import progressbar as progressbar
|
||||||
|
from .termui import prompt as prompt
|
||||||
|
from .termui import secho as secho
|
||||||
|
from .termui import style as style
|
||||||
|
from .termui import unstyle as unstyle
|
||||||
|
from .types import BOOL as BOOL
|
||||||
|
from .types import Choice as Choice
|
||||||
|
from .types import DateTime as DateTime
|
||||||
|
from .types import File as File
|
||||||
|
from .types import FLOAT as FLOAT
|
||||||
|
from .types import FloatRange as FloatRange
|
||||||
|
from .types import INT as INT
|
||||||
|
from .types import IntRange as IntRange
|
||||||
|
from .types import ParamType as ParamType
|
||||||
|
from .types import Path as Path
|
||||||
|
from .types import STRING as STRING
|
||||||
|
from .types import Tuple as Tuple
|
||||||
|
from .types import UNPROCESSED as UNPROCESSED
|
||||||
|
from .types import UUID as UUID
|
||||||
|
from .utils import echo as echo
|
||||||
|
from .utils import format_filename as format_filename
|
||||||
|
from .utils import get_app_dir as get_app_dir
|
||||||
|
from .utils import get_binary_stream as get_binary_stream
|
||||||
|
from .utils import get_os_args as get_os_args
|
||||||
|
from .utils import get_text_stream as get_text_stream
|
||||||
|
from .utils import open_file as open_file
|
||||||
|
|
||||||
|
__version__ = "8.0.4"
|
||||||
626
dist/ba_data/python-site-packages/click/_compat.py
vendored
Normal file
626
dist/ba_data/python-site-packages/click/_compat.py
vendored
Normal file
|
|
@ -0,0 +1,626 @@
|
||||||
|
import codecs
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import typing as t
|
||||||
|
from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
|
CYGWIN = sys.platform.startswith("cygwin")
|
||||||
|
MSYS2 = sys.platform.startswith("win") and ("GCC" in sys.version)
|
||||||
|
# Determine local App Engine environment, per Google's own suggestion
|
||||||
|
APP_ENGINE = "APPENGINE_RUNTIME" in os.environ and "Development/" in os.environ.get(
|
||||||
|
"SERVER_SOFTWARE", ""
|
||||||
|
)
|
||||||
|
WIN = sys.platform.startswith("win") and not APP_ENGINE and not MSYS2
|
||||||
|
auto_wrap_for_ansi: t.Optional[t.Callable[[t.TextIO], t.TextIO]] = None
|
||||||
|
_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]")
|
||||||
|
|
||||||
|
|
||||||
|
def get_filesystem_encoding() -> str:
|
||||||
|
return sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||||
|
|
||||||
|
|
||||||
|
def _make_text_stream(
|
||||||
|
stream: t.BinaryIO,
|
||||||
|
encoding: t.Optional[str],
|
||||||
|
errors: t.Optional[str],
|
||||||
|
force_readable: bool = False,
|
||||||
|
force_writable: bool = False,
|
||||||
|
) -> t.TextIO:
|
||||||
|
if encoding is None:
|
||||||
|
encoding = get_best_encoding(stream)
|
||||||
|
if errors is None:
|
||||||
|
errors = "replace"
|
||||||
|
return _NonClosingTextIOWrapper(
|
||||||
|
stream,
|
||||||
|
encoding,
|
||||||
|
errors,
|
||||||
|
line_buffering=True,
|
||||||
|
force_readable=force_readable,
|
||||||
|
force_writable=force_writable,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_ascii_encoding(encoding: str) -> bool:
|
||||||
|
"""Checks if a given encoding is ascii."""
|
||||||
|
try:
|
||||||
|
return codecs.lookup(encoding).name == "ascii"
|
||||||
|
except LookupError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_best_encoding(stream: t.IO) -> str:
|
||||||
|
"""Returns the default stream encoding if not found."""
|
||||||
|
rv = getattr(stream, "encoding", None) or sys.getdefaultencoding()
|
||||||
|
if is_ascii_encoding(rv):
|
||||||
|
return "utf-8"
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class _NonClosingTextIOWrapper(io.TextIOWrapper):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
stream: t.BinaryIO,
|
||||||
|
encoding: t.Optional[str],
|
||||||
|
errors: t.Optional[str],
|
||||||
|
force_readable: bool = False,
|
||||||
|
force_writable: bool = False,
|
||||||
|
**extra: t.Any,
|
||||||
|
) -> None:
|
||||||
|
self._stream = stream = t.cast(
|
||||||
|
t.BinaryIO, _FixupStream(stream, force_readable, force_writable)
|
||||||
|
)
|
||||||
|
super().__init__(stream, encoding, errors, **extra)
|
||||||
|
|
||||||
|
def __del__(self) -> None:
|
||||||
|
try:
|
||||||
|
self.detach()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def isatty(self) -> bool:
|
||||||
|
# https://bitbucket.org/pypy/pypy/issue/1803
|
||||||
|
return self._stream.isatty()
|
||||||
|
|
||||||
|
|
||||||
|
class _FixupStream:
|
||||||
|
"""The new io interface needs more from streams than streams
|
||||||
|
traditionally implement. As such, this fix-up code is necessary in
|
||||||
|
some circumstances.
|
||||||
|
|
||||||
|
The forcing of readable and writable flags are there because some tools
|
||||||
|
put badly patched objects on sys (one such offender are certain version
|
||||||
|
of jupyter notebook).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
stream: t.BinaryIO,
|
||||||
|
force_readable: bool = False,
|
||||||
|
force_writable: bool = False,
|
||||||
|
):
|
||||||
|
self._stream = stream
|
||||||
|
self._force_readable = force_readable
|
||||||
|
self._force_writable = force_writable
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> t.Any:
|
||||||
|
return getattr(self._stream, name)
|
||||||
|
|
||||||
|
def read1(self, size: int) -> bytes:
|
||||||
|
f = getattr(self._stream, "read1", None)
|
||||||
|
|
||||||
|
if f is not None:
|
||||||
|
return t.cast(bytes, f(size))
|
||||||
|
|
||||||
|
return self._stream.read(size)
|
||||||
|
|
||||||
|
def readable(self) -> bool:
|
||||||
|
if self._force_readable:
|
||||||
|
return True
|
||||||
|
x = getattr(self._stream, "readable", None)
|
||||||
|
if x is not None:
|
||||||
|
return t.cast(bool, x())
|
||||||
|
try:
|
||||||
|
self._stream.read(0)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def writable(self) -> bool:
|
||||||
|
if self._force_writable:
|
||||||
|
return True
|
||||||
|
x = getattr(self._stream, "writable", None)
|
||||||
|
if x is not None:
|
||||||
|
return t.cast(bool, x())
|
||||||
|
try:
|
||||||
|
self._stream.write("") # type: ignore
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
self._stream.write(b"")
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def seekable(self) -> bool:
|
||||||
|
x = getattr(self._stream, "seekable", None)
|
||||||
|
if x is not None:
|
||||||
|
return t.cast(bool, x())
|
||||||
|
try:
|
||||||
|
self._stream.seek(self._stream.tell())
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _is_binary_reader(stream: t.IO, default: bool = False) -> bool:
|
||||||
|
try:
|
||||||
|
return isinstance(stream.read(0), bytes)
|
||||||
|
except Exception:
|
||||||
|
return default
|
||||||
|
# This happens in some cases where the stream was already
|
||||||
|
# closed. In this case, we assume the default.
|
||||||
|
|
||||||
|
|
||||||
|
def _is_binary_writer(stream: t.IO, default: bool = False) -> bool:
|
||||||
|
try:
|
||||||
|
stream.write(b"")
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
stream.write("")
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return default
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _find_binary_reader(stream: t.IO) -> t.Optional[t.BinaryIO]:
|
||||||
|
# We need to figure out if the given stream is already binary.
|
||||||
|
# This can happen because the official docs recommend detaching
|
||||||
|
# the streams to get binary streams. Some code might do this, so
|
||||||
|
# we need to deal with this case explicitly.
|
||||||
|
if _is_binary_reader(stream, False):
|
||||||
|
return t.cast(t.BinaryIO, stream)
|
||||||
|
|
||||||
|
buf = getattr(stream, "buffer", None)
|
||||||
|
|
||||||
|
# Same situation here; this time we assume that the buffer is
|
||||||
|
# actually binary in case it's closed.
|
||||||
|
if buf is not None and _is_binary_reader(buf, True):
|
||||||
|
return t.cast(t.BinaryIO, buf)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _find_binary_writer(stream: t.IO) -> t.Optional[t.BinaryIO]:
|
||||||
|
# We need to figure out if the given stream is already binary.
|
||||||
|
# This can happen because the official docs recommend detaching
|
||||||
|
# the streams to get binary streams. Some code might do this, so
|
||||||
|
# we need to deal with this case explicitly.
|
||||||
|
if _is_binary_writer(stream, False):
|
||||||
|
return t.cast(t.BinaryIO, stream)
|
||||||
|
|
||||||
|
buf = getattr(stream, "buffer", None)
|
||||||
|
|
||||||
|
# Same situation here; this time we assume that the buffer is
|
||||||
|
# actually binary in case it's closed.
|
||||||
|
if buf is not None and _is_binary_writer(buf, True):
|
||||||
|
return t.cast(t.BinaryIO, buf)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _stream_is_misconfigured(stream: t.TextIO) -> bool:
|
||||||
|
"""A stream is misconfigured if its encoding is ASCII."""
|
||||||
|
# If the stream does not have an encoding set, we assume it's set
|
||||||
|
# to ASCII. This appears to happen in certain unittest
|
||||||
|
# environments. It's not quite clear what the correct behavior is
|
||||||
|
# but this at least will force Click to recover somehow.
|
||||||
|
return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii")
|
||||||
|
|
||||||
|
|
||||||
|
def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: t.Optional[str]) -> bool:
|
||||||
|
"""A stream attribute is compatible if it is equal to the
|
||||||
|
desired value or the desired value is unset and the attribute
|
||||||
|
has a value.
|
||||||
|
"""
|
||||||
|
stream_value = getattr(stream, attr, None)
|
||||||
|
return stream_value == value or (value is None and stream_value is not None)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_compatible_text_stream(
|
||||||
|
stream: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str]
|
||||||
|
) -> bool:
|
||||||
|
"""Check if a stream's encoding and errors attributes are
|
||||||
|
compatible with the desired values.
|
||||||
|
"""
|
||||||
|
return _is_compat_stream_attr(
|
||||||
|
stream, "encoding", encoding
|
||||||
|
) and _is_compat_stream_attr(stream, "errors", errors)
|
||||||
|
|
||||||
|
|
||||||
|
def _force_correct_text_stream(
|
||||||
|
text_stream: t.IO,
|
||||||
|
encoding: t.Optional[str],
|
||||||
|
errors: t.Optional[str],
|
||||||
|
is_binary: t.Callable[[t.IO, bool], bool],
|
||||||
|
find_binary: t.Callable[[t.IO], t.Optional[t.BinaryIO]],
|
||||||
|
force_readable: bool = False,
|
||||||
|
force_writable: bool = False,
|
||||||
|
) -> t.TextIO:
|
||||||
|
if is_binary(text_stream, False):
|
||||||
|
binary_reader = t.cast(t.BinaryIO, text_stream)
|
||||||
|
else:
|
||||||
|
text_stream = t.cast(t.TextIO, text_stream)
|
||||||
|
# If the stream looks compatible, and won't default to a
|
||||||
|
# misconfigured ascii encoding, return it as-is.
|
||||||
|
if _is_compatible_text_stream(text_stream, encoding, errors) and not (
|
||||||
|
encoding is None and _stream_is_misconfigured(text_stream)
|
||||||
|
):
|
||||||
|
return text_stream
|
||||||
|
|
||||||
|
# Otherwise, get the underlying binary reader.
|
||||||
|
possible_binary_reader = find_binary(text_stream)
|
||||||
|
|
||||||
|
# If that's not possible, silently use the original reader
|
||||||
|
# and get mojibake instead of exceptions.
|
||||||
|
if possible_binary_reader is None:
|
||||||
|
return text_stream
|
||||||
|
|
||||||
|
binary_reader = possible_binary_reader
|
||||||
|
|
||||||
|
# Default errors to replace instead of strict in order to get
|
||||||
|
# something that works.
|
||||||
|
if errors is None:
|
||||||
|
errors = "replace"
|
||||||
|
|
||||||
|
# Wrap the binary stream in a text stream with the correct
|
||||||
|
# encoding parameters.
|
||||||
|
return _make_text_stream(
|
||||||
|
binary_reader,
|
||||||
|
encoding,
|
||||||
|
errors,
|
||||||
|
force_readable=force_readable,
|
||||||
|
force_writable=force_writable,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _force_correct_text_reader(
|
||||||
|
text_reader: t.IO,
|
||||||
|
encoding: t.Optional[str],
|
||||||
|
errors: t.Optional[str],
|
||||||
|
force_readable: bool = False,
|
||||||
|
) -> t.TextIO:
|
||||||
|
return _force_correct_text_stream(
|
||||||
|
text_reader,
|
||||||
|
encoding,
|
||||||
|
errors,
|
||||||
|
_is_binary_reader,
|
||||||
|
_find_binary_reader,
|
||||||
|
force_readable=force_readable,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _force_correct_text_writer(
|
||||||
|
text_writer: t.IO,
|
||||||
|
encoding: t.Optional[str],
|
||||||
|
errors: t.Optional[str],
|
||||||
|
force_writable: bool = False,
|
||||||
|
) -> t.TextIO:
|
||||||
|
return _force_correct_text_stream(
|
||||||
|
text_writer,
|
||||||
|
encoding,
|
||||||
|
errors,
|
||||||
|
_is_binary_writer,
|
||||||
|
_find_binary_writer,
|
||||||
|
force_writable=force_writable,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_binary_stdin() -> t.BinaryIO:
|
||||||
|
reader = _find_binary_reader(sys.stdin)
|
||||||
|
if reader is None:
|
||||||
|
raise RuntimeError("Was not able to determine binary stream for sys.stdin.")
|
||||||
|
return reader
|
||||||
|
|
||||||
|
|
||||||
|
def get_binary_stdout() -> t.BinaryIO:
|
||||||
|
writer = _find_binary_writer(sys.stdout)
|
||||||
|
if writer is None:
|
||||||
|
raise RuntimeError("Was not able to determine binary stream for sys.stdout.")
|
||||||
|
return writer
|
||||||
|
|
||||||
|
|
||||||
|
def get_binary_stderr() -> t.BinaryIO:
|
||||||
|
writer = _find_binary_writer(sys.stderr)
|
||||||
|
if writer is None:
|
||||||
|
raise RuntimeError("Was not able to determine binary stream for sys.stderr.")
|
||||||
|
return writer
|
||||||
|
|
||||||
|
|
||||||
|
def get_text_stdin(
|
||||||
|
encoding: t.Optional[str] = None, errors: t.Optional[str] = None
|
||||||
|
) -> t.TextIO:
|
||||||
|
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_text_stdout(
|
||||||
|
encoding: t.Optional[str] = None, errors: t.Optional[str] = None
|
||||||
|
) -> t.TextIO:
|
||||||
|
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_text_stderr(
|
||||||
|
encoding: t.Optional[str] = None, errors: t.Optional[str] = None
|
||||||
|
) -> t.TextIO:
|
||||||
|
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _wrap_io_open(
|
||||||
|
file: t.Union[str, os.PathLike, int],
|
||||||
|
mode: str,
|
||||||
|
encoding: t.Optional[str],
|
||||||
|
errors: t.Optional[str],
|
||||||
|
) -> t.IO:
|
||||||
|
"""Handles not passing ``encoding`` and ``errors`` in binary mode."""
|
||||||
|
if "b" in mode:
|
||||||
|
return open(file, mode)
|
||||||
|
|
||||||
|
return open(file, mode, encoding=encoding, errors=errors)
|
||||||
|
|
||||||
|
|
||||||
|
def open_stream(
|
||||||
|
filename: str,
|
||||||
|
mode: str = "r",
|
||||||
|
encoding: t.Optional[str] = None,
|
||||||
|
errors: t.Optional[str] = "strict",
|
||||||
|
atomic: bool = False,
|
||||||
|
) -> t.Tuple[t.IO, bool]:
|
||||||
|
binary = "b" in mode
|
||||||
|
|
||||||
|
# Standard streams first. These are simple because they ignore the
|
||||||
|
# atomic flag. Use fsdecode to handle Path("-").
|
||||||
|
if os.fsdecode(filename) == "-":
|
||||||
|
if any(m in mode for m in ["w", "a", "x"]):
|
||||||
|
if binary:
|
||||||
|
return get_binary_stdout(), False
|
||||||
|
return get_text_stdout(encoding=encoding, errors=errors), False
|
||||||
|
if binary:
|
||||||
|
return get_binary_stdin(), False
|
||||||
|
return get_text_stdin(encoding=encoding, errors=errors), False
|
||||||
|
|
||||||
|
# Non-atomic writes directly go out through the regular open functions.
|
||||||
|
if not atomic:
|
||||||
|
return _wrap_io_open(filename, mode, encoding, errors), True
|
||||||
|
|
||||||
|
# Some usability stuff for atomic writes
|
||||||
|
if "a" in mode:
|
||||||
|
raise ValueError(
|
||||||
|
"Appending to an existing file is not supported, because that"
|
||||||
|
" would involve an expensive `copy`-operation to a temporary"
|
||||||
|
" file. Open the file in normal `w`-mode and copy explicitly"
|
||||||
|
" if that's what you're after."
|
||||||
|
)
|
||||||
|
if "x" in mode:
|
||||||
|
raise ValueError("Use the `overwrite`-parameter instead.")
|
||||||
|
if "w" not in mode:
|
||||||
|
raise ValueError("Atomic writes only make sense with `w`-mode.")
|
||||||
|
|
||||||
|
# Atomic writes are more complicated. They work by opening a file
|
||||||
|
# as a proxy in the same folder and then using the fdopen
|
||||||
|
# functionality to wrap it in a Python file. Then we wrap it in an
|
||||||
|
# atomic file that moves the file over on close.
|
||||||
|
import errno
|
||||||
|
import random
|
||||||
|
|
||||||
|
try:
|
||||||
|
perm: t.Optional[int] = os.stat(filename).st_mode
|
||||||
|
except OSError:
|
||||||
|
perm = None
|
||||||
|
|
||||||
|
flags = os.O_RDWR | os.O_CREAT | os.O_EXCL
|
||||||
|
|
||||||
|
if binary:
|
||||||
|
flags |= getattr(os, "O_BINARY", 0)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
tmp_filename = os.path.join(
|
||||||
|
os.path.dirname(filename),
|
||||||
|
f".__atomic-write{random.randrange(1 << 32):08x}",
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm)
|
||||||
|
break
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.EEXIST or (
|
||||||
|
os.name == "nt"
|
||||||
|
and e.errno == errno.EACCES
|
||||||
|
and os.path.isdir(e.filename)
|
||||||
|
and os.access(e.filename, os.W_OK)
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
raise
|
||||||
|
|
||||||
|
if perm is not None:
|
||||||
|
os.chmod(tmp_filename, perm) # in case perm includes bits in umask
|
||||||
|
|
||||||
|
f = _wrap_io_open(fd, mode, encoding, errors)
|
||||||
|
af = _AtomicFile(f, tmp_filename, os.path.realpath(filename))
|
||||||
|
return t.cast(t.IO, af), True
|
||||||
|
|
||||||
|
|
||||||
|
class _AtomicFile:
|
||||||
|
def __init__(self, f: t.IO, tmp_filename: str, real_filename: str) -> None:
|
||||||
|
self._f = f
|
||||||
|
self._tmp_filename = tmp_filename
|
||||||
|
self._real_filename = real_filename
|
||||||
|
self.closed = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._real_filename
|
||||||
|
|
||||||
|
def close(self, delete: bool = False) -> None:
|
||||||
|
if self.closed:
|
||||||
|
return
|
||||||
|
self._f.close()
|
||||||
|
os.replace(self._tmp_filename, self._real_filename)
|
||||||
|
self.closed = True
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> t.Any:
|
||||||
|
return getattr(self._f, name)
|
||||||
|
|
||||||
|
def __enter__(self) -> "_AtomicFile":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, tb): # type: ignore
|
||||||
|
self.close(delete=exc_type is not None)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return repr(self._f)
|
||||||
|
|
||||||
|
|
||||||
|
def strip_ansi(value: str) -> str:
|
||||||
|
return _ansi_re.sub("", value)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_jupyter_kernel_output(stream: t.IO) -> bool:
|
||||||
|
while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)):
|
||||||
|
stream = stream._stream
|
||||||
|
|
||||||
|
return stream.__class__.__module__.startswith("ipykernel.")
|
||||||
|
|
||||||
|
|
||||||
|
def should_strip_ansi(
|
||||||
|
stream: t.Optional[t.IO] = None, color: t.Optional[bool] = None
|
||||||
|
) -> bool:
|
||||||
|
if color is None:
|
||||||
|
if stream is None:
|
||||||
|
stream = sys.stdin
|
||||||
|
return not isatty(stream) and not _is_jupyter_kernel_output(stream)
|
||||||
|
return not color
|
||||||
|
|
||||||
|
|
||||||
|
# On Windows, wrap the output streams with colorama to support ANSI
|
||||||
|
# color codes.
|
||||||
|
# NOTE: double check is needed so mypy does not analyze this on Linux
|
||||||
|
if sys.platform.startswith("win") and WIN:
|
||||||
|
from ._winconsole import _get_windows_console_stream
|
||||||
|
|
||||||
|
def _get_argv_encoding() -> str:
|
||||||
|
import locale
|
||||||
|
|
||||||
|
return locale.getpreferredencoding()
|
||||||
|
|
||||||
|
_ansi_stream_wrappers: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
|
||||||
|
|
||||||
|
def auto_wrap_for_ansi(
|
||||||
|
stream: t.TextIO, color: t.Optional[bool] = None
|
||||||
|
) -> t.TextIO:
|
||||||
|
"""Support ANSI color and style codes on Windows by wrapping a
|
||||||
|
stream with colorama.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cached = _ansi_stream_wrappers.get(stream)
|
||||||
|
except Exception:
|
||||||
|
cached = None
|
||||||
|
|
||||||
|
if cached is not None:
|
||||||
|
return cached
|
||||||
|
|
||||||
|
import colorama
|
||||||
|
|
||||||
|
strip = should_strip_ansi(stream, color)
|
||||||
|
ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
|
||||||
|
rv = t.cast(t.TextIO, ansi_wrapper.stream)
|
||||||
|
_write = rv.write
|
||||||
|
|
||||||
|
def _safe_write(s):
|
||||||
|
try:
|
||||||
|
return _write(s)
|
||||||
|
except BaseException:
|
||||||
|
ansi_wrapper.reset_all()
|
||||||
|
raise
|
||||||
|
|
||||||
|
rv.write = _safe_write
|
||||||
|
|
||||||
|
try:
|
||||||
|
_ansi_stream_wrappers[stream] = rv
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def _get_argv_encoding() -> str:
|
||||||
|
return getattr(sys.stdin, "encoding", None) or get_filesystem_encoding()
|
||||||
|
|
||||||
|
def _get_windows_console_stream(
|
||||||
|
f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str]
|
||||||
|
) -> t.Optional[t.TextIO]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def term_len(x: str) -> int:
|
||||||
|
return len(strip_ansi(x))
|
||||||
|
|
||||||
|
|
||||||
|
def isatty(stream: t.IO) -> bool:
|
||||||
|
try:
|
||||||
|
return stream.isatty()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _make_cached_stream_func(
|
||||||
|
src_func: t.Callable[[], t.TextIO], wrapper_func: t.Callable[[], t.TextIO]
|
||||||
|
) -> t.Callable[[], t.TextIO]:
|
||||||
|
cache: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
|
||||||
|
|
||||||
|
def func() -> t.TextIO:
|
||||||
|
stream = src_func()
|
||||||
|
try:
|
||||||
|
rv = cache.get(stream)
|
||||||
|
except Exception:
|
||||||
|
rv = None
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
rv = wrapper_func()
|
||||||
|
try:
|
||||||
|
cache[stream] = rv
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return rv
|
||||||
|
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin)
|
||||||
|
_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout)
|
||||||
|
_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr)
|
||||||
|
|
||||||
|
|
||||||
|
binary_streams: t.Mapping[str, t.Callable[[], t.BinaryIO]] = {
|
||||||
|
"stdin": get_binary_stdin,
|
||||||
|
"stdout": get_binary_stdout,
|
||||||
|
"stderr": get_binary_stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
text_streams: t.Mapping[
|
||||||
|
str, t.Callable[[t.Optional[str], t.Optional[str]], t.TextIO]
|
||||||
|
] = {
|
||||||
|
"stdin": get_text_stdin,
|
||||||
|
"stdout": get_text_stdout,
|
||||||
|
"stderr": get_text_stderr,
|
||||||
|
}
|
||||||
717
dist/ba_data/python-site-packages/click/_termui_impl.py
vendored
Normal file
717
dist/ba_data/python-site-packages/click/_termui_impl.py
vendored
Normal file
|
|
@ -0,0 +1,717 @@
|
||||||
|
"""
|
||||||
|
This module contains implementations for the termui module. To keep the
|
||||||
|
import time of Click down, some infrequently used functionality is
|
||||||
|
placed in this module and only imported as needed.
|
||||||
|
"""
|
||||||
|
import contextlib
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import typing as t
|
||||||
|
from gettext import gettext as _
|
||||||
|
|
||||||
|
from ._compat import _default_text_stdout
|
||||||
|
from ._compat import CYGWIN
|
||||||
|
from ._compat import get_best_encoding
|
||||||
|
from ._compat import isatty
|
||||||
|
from ._compat import open_stream
|
||||||
|
from ._compat import strip_ansi
|
||||||
|
from ._compat import term_len
|
||||||
|
from ._compat import WIN
|
||||||
|
from .exceptions import ClickException
|
||||||
|
from .utils import echo
|
||||||
|
|
||||||
|
V = t.TypeVar("V")
|
||||||
|
|
||||||
|
if os.name == "nt":
|
||||||
|
BEFORE_BAR = "\r"
|
||||||
|
AFTER_BAR = "\n"
|
||||||
|
else:
|
||||||
|
BEFORE_BAR = "\r\033[?25l"
|
||||||
|
AFTER_BAR = "\033[?25h\n"
|
||||||
|
|
||||||
|
|
||||||
|
class ProgressBar(t.Generic[V]):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
iterable: t.Optional[t.Iterable[V]],
|
||||||
|
length: t.Optional[int] = None,
|
||||||
|
fill_char: str = "#",
|
||||||
|
empty_char: str = " ",
|
||||||
|
bar_template: str = "%(bar)s",
|
||||||
|
info_sep: str = " ",
|
||||||
|
show_eta: bool = True,
|
||||||
|
show_percent: t.Optional[bool] = None,
|
||||||
|
show_pos: bool = False,
|
||||||
|
item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None,
|
||||||
|
label: t.Optional[str] = None,
|
||||||
|
file: t.Optional[t.TextIO] = None,
|
||||||
|
color: t.Optional[bool] = None,
|
||||||
|
update_min_steps: int = 1,
|
||||||
|
width: int = 30,
|
||||||
|
) -> None:
|
||||||
|
self.fill_char = fill_char
|
||||||
|
self.empty_char = empty_char
|
||||||
|
self.bar_template = bar_template
|
||||||
|
self.info_sep = info_sep
|
||||||
|
self.show_eta = show_eta
|
||||||
|
self.show_percent = show_percent
|
||||||
|
self.show_pos = show_pos
|
||||||
|
self.item_show_func = item_show_func
|
||||||
|
self.label = label or ""
|
||||||
|
if file is None:
|
||||||
|
file = _default_text_stdout()
|
||||||
|
self.file = file
|
||||||
|
self.color = color
|
||||||
|
self.update_min_steps = update_min_steps
|
||||||
|
self._completed_intervals = 0
|
||||||
|
self.width = width
|
||||||
|
self.autowidth = width == 0
|
||||||
|
|
||||||
|
if length is None:
|
||||||
|
from operator import length_hint
|
||||||
|
|
||||||
|
length = length_hint(iterable, -1)
|
||||||
|
|
||||||
|
if length == -1:
|
||||||
|
length = None
|
||||||
|
if iterable is None:
|
||||||
|
if length is None:
|
||||||
|
raise TypeError("iterable or length is required")
|
||||||
|
iterable = t.cast(t.Iterable[V], range(length))
|
||||||
|
self.iter = iter(iterable)
|
||||||
|
self.length = length
|
||||||
|
self.pos = 0
|
||||||
|
self.avg: t.List[float] = []
|
||||||
|
self.start = self.last_eta = time.time()
|
||||||
|
self.eta_known = False
|
||||||
|
self.finished = False
|
||||||
|
self.max_width: t.Optional[int] = None
|
||||||
|
self.entered = False
|
||||||
|
self.current_item: t.Optional[V] = None
|
||||||
|
self.is_hidden = not isatty(self.file)
|
||||||
|
self._last_line: t.Optional[str] = None
|
||||||
|
|
||||||
|
def __enter__(self) -> "ProgressBar":
|
||||||
|
self.entered = True
|
||||||
|
self.render_progress()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, tb): # type: ignore
|
||||||
|
self.render_finish()
|
||||||
|
|
||||||
|
def __iter__(self) -> t.Iterator[V]:
|
||||||
|
if not self.entered:
|
||||||
|
raise RuntimeError("You need to use progress bars in a with block.")
|
||||||
|
self.render_progress()
|
||||||
|
return self.generator()
|
||||||
|
|
||||||
|
def __next__(self) -> V:
|
||||||
|
# Iteration is defined in terms of a generator function,
|
||||||
|
# returned by iter(self); use that to define next(). This works
|
||||||
|
# because `self.iter` is an iterable consumed by that generator,
|
||||||
|
# so it is re-entry safe. Calling `next(self.generator())`
|
||||||
|
# twice works and does "what you want".
|
||||||
|
return next(iter(self))
|
||||||
|
|
||||||
|
def render_finish(self) -> None:
|
||||||
|
if self.is_hidden:
|
||||||
|
return
|
||||||
|
self.file.write(AFTER_BAR)
|
||||||
|
self.file.flush()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pct(self) -> float:
|
||||||
|
if self.finished:
|
||||||
|
return 1.0
|
||||||
|
return min(self.pos / (float(self.length or 1) or 1), 1.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def time_per_iteration(self) -> float:
|
||||||
|
if not self.avg:
|
||||||
|
return 0.0
|
||||||
|
return sum(self.avg) / float(len(self.avg))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def eta(self) -> float:
|
||||||
|
if self.length is not None and not self.finished:
|
||||||
|
return self.time_per_iteration * (self.length - self.pos)
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def format_eta(self) -> str:
|
||||||
|
if self.eta_known:
|
||||||
|
t = int(self.eta)
|
||||||
|
seconds = t % 60
|
||||||
|
t //= 60
|
||||||
|
minutes = t % 60
|
||||||
|
t //= 60
|
||||||
|
hours = t % 24
|
||||||
|
t //= 24
|
||||||
|
if t > 0:
|
||||||
|
return f"{t}d {hours:02}:{minutes:02}:{seconds:02}"
|
||||||
|
else:
|
||||||
|
return f"{hours:02}:{minutes:02}:{seconds:02}"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def format_pos(self) -> str:
|
||||||
|
pos = str(self.pos)
|
||||||
|
if self.length is not None:
|
||||||
|
pos += f"/{self.length}"
|
||||||
|
return pos
|
||||||
|
|
||||||
|
def format_pct(self) -> str:
|
||||||
|
return f"{int(self.pct * 100): 4}%"[1:]
|
||||||
|
|
||||||
|
def format_bar(self) -> str:
|
||||||
|
if self.length is not None:
|
||||||
|
bar_length = int(self.pct * self.width)
|
||||||
|
bar = self.fill_char * bar_length
|
||||||
|
bar += self.empty_char * (self.width - bar_length)
|
||||||
|
elif self.finished:
|
||||||
|
bar = self.fill_char * self.width
|
||||||
|
else:
|
||||||
|
chars = list(self.empty_char * (self.width or 1))
|
||||||
|
if self.time_per_iteration != 0:
|
||||||
|
chars[
|
||||||
|
int(
|
||||||
|
(math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5)
|
||||||
|
* self.width
|
||||||
|
)
|
||||||
|
] = self.fill_char
|
||||||
|
bar = "".join(chars)
|
||||||
|
return bar
|
||||||
|
|
||||||
|
def format_progress_line(self) -> str:
|
||||||
|
show_percent = self.show_percent
|
||||||
|
|
||||||
|
info_bits = []
|
||||||
|
if self.length is not None and show_percent is None:
|
||||||
|
show_percent = not self.show_pos
|
||||||
|
|
||||||
|
if self.show_pos:
|
||||||
|
info_bits.append(self.format_pos())
|
||||||
|
if show_percent:
|
||||||
|
info_bits.append(self.format_pct())
|
||||||
|
if self.show_eta and self.eta_known and not self.finished:
|
||||||
|
info_bits.append(self.format_eta())
|
||||||
|
if self.item_show_func is not None:
|
||||||
|
item_info = self.item_show_func(self.current_item)
|
||||||
|
if item_info is not None:
|
||||||
|
info_bits.append(item_info)
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.bar_template
|
||||||
|
% {
|
||||||
|
"label": self.label,
|
||||||
|
"bar": self.format_bar(),
|
||||||
|
"info": self.info_sep.join(info_bits),
|
||||||
|
}
|
||||||
|
).rstrip()
|
||||||
|
|
||||||
|
def render_progress(self) -> None:
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
if self.is_hidden:
|
||||||
|
# Only output the label as it changes if the output is not a
|
||||||
|
# TTY. Use file=stderr if you expect to be piping stdout.
|
||||||
|
if self._last_line != self.label:
|
||||||
|
self._last_line = self.label
|
||||||
|
echo(self.label, file=self.file, color=self.color)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
buf = []
|
||||||
|
# Update width in case the terminal has been resized
|
||||||
|
if self.autowidth:
|
||||||
|
old_width = self.width
|
||||||
|
self.width = 0
|
||||||
|
clutter_length = term_len(self.format_progress_line())
|
||||||
|
new_width = max(0, shutil.get_terminal_size().columns - clutter_length)
|
||||||
|
if new_width < old_width:
|
||||||
|
buf.append(BEFORE_BAR)
|
||||||
|
buf.append(" " * self.max_width) # type: ignore
|
||||||
|
self.max_width = new_width
|
||||||
|
self.width = new_width
|
||||||
|
|
||||||
|
clear_width = self.width
|
||||||
|
if self.max_width is not None:
|
||||||
|
clear_width = self.max_width
|
||||||
|
|
||||||
|
buf.append(BEFORE_BAR)
|
||||||
|
line = self.format_progress_line()
|
||||||
|
line_len = term_len(line)
|
||||||
|
if self.max_width is None or self.max_width < line_len:
|
||||||
|
self.max_width = line_len
|
||||||
|
|
||||||
|
buf.append(line)
|
||||||
|
buf.append(" " * (clear_width - line_len))
|
||||||
|
line = "".join(buf)
|
||||||
|
# Render the line only if it changed.
|
||||||
|
|
||||||
|
if line != self._last_line:
|
||||||
|
self._last_line = line
|
||||||
|
echo(line, file=self.file, color=self.color, nl=False)
|
||||||
|
self.file.flush()
|
||||||
|
|
||||||
|
def make_step(self, n_steps: int) -> None:
|
||||||
|
self.pos += n_steps
|
||||||
|
if self.length is not None and self.pos >= self.length:
|
||||||
|
self.finished = True
|
||||||
|
|
||||||
|
if (time.time() - self.last_eta) < 1.0:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.last_eta = time.time()
|
||||||
|
|
||||||
|
# self.avg is a rolling list of length <= 7 of steps where steps are
|
||||||
|
# defined as time elapsed divided by the total progress through
|
||||||
|
# self.length.
|
||||||
|
if self.pos:
|
||||||
|
step = (time.time() - self.start) / self.pos
|
||||||
|
else:
|
||||||
|
step = time.time() - self.start
|
||||||
|
|
||||||
|
self.avg = self.avg[-6:] + [step]
|
||||||
|
|
||||||
|
self.eta_known = self.length is not None
|
||||||
|
|
||||||
|
def update(self, n_steps: int, current_item: t.Optional[V] = None) -> None:
|
||||||
|
"""Update the progress bar by advancing a specified number of
|
||||||
|
steps, and optionally set the ``current_item`` for this new
|
||||||
|
position.
|
||||||
|
|
||||||
|
:param n_steps: Number of steps to advance.
|
||||||
|
:param current_item: Optional item to set as ``current_item``
|
||||||
|
for the updated position.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
Added the ``current_item`` optional parameter.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
Only render when the number of steps meets the
|
||||||
|
``update_min_steps`` threshold.
|
||||||
|
"""
|
||||||
|
if current_item is not None:
|
||||||
|
self.current_item = current_item
|
||||||
|
|
||||||
|
self._completed_intervals += n_steps
|
||||||
|
|
||||||
|
if self._completed_intervals >= self.update_min_steps:
|
||||||
|
self.make_step(self._completed_intervals)
|
||||||
|
self.render_progress()
|
||||||
|
self._completed_intervals = 0
|
||||||
|
|
||||||
|
def finish(self) -> None:
|
||||||
|
self.eta_known = False
|
||||||
|
self.current_item = None
|
||||||
|
self.finished = True
|
||||||
|
|
||||||
|
def generator(self) -> t.Iterator[V]:
|
||||||
|
"""Return a generator which yields the items added to the bar
|
||||||
|
during construction, and updates the progress bar *after* the
|
||||||
|
yielded block returns.
|
||||||
|
"""
|
||||||
|
# WARNING: the iterator interface for `ProgressBar` relies on
|
||||||
|
# this and only works because this is a simple generator which
|
||||||
|
# doesn't create or manage additional state. If this function
|
||||||
|
# changes, the impact should be evaluated both against
|
||||||
|
# `iter(bar)` and `next(bar)`. `next()` in particular may call
|
||||||
|
# `self.generator()` repeatedly, and this must remain safe in
|
||||||
|
# order for that interface to work.
|
||||||
|
if not self.entered:
|
||||||
|
raise RuntimeError("You need to use progress bars in a with block.")
|
||||||
|
|
||||||
|
if self.is_hidden:
|
||||||
|
yield from self.iter
|
||||||
|
else:
|
||||||
|
for rv in self.iter:
|
||||||
|
self.current_item = rv
|
||||||
|
|
||||||
|
# This allows show_item_func to be updated before the
|
||||||
|
# item is processed. Only trigger at the beginning of
|
||||||
|
# the update interval.
|
||||||
|
if self._completed_intervals == 0:
|
||||||
|
self.render_progress()
|
||||||
|
|
||||||
|
yield rv
|
||||||
|
self.update(1)
|
||||||
|
|
||||||
|
self.finish()
|
||||||
|
self.render_progress()
|
||||||
|
|
||||||
|
|
||||||
|
def pager(generator: t.Iterable[str], color: t.Optional[bool] = None) -> None:
|
||||||
|
"""Decide what method to use for paging through text."""
|
||||||
|
stdout = _default_text_stdout()
|
||||||
|
if not isatty(sys.stdin) or not isatty(stdout):
|
||||||
|
return _nullpager(stdout, generator, color)
|
||||||
|
pager_cmd = (os.environ.get("PAGER", None) or "").strip()
|
||||||
|
if pager_cmd:
|
||||||
|
if WIN:
|
||||||
|
return _tempfilepager(generator, pager_cmd, color)
|
||||||
|
return _pipepager(generator, pager_cmd, color)
|
||||||
|
if os.environ.get("TERM") in ("dumb", "emacs"):
|
||||||
|
return _nullpager(stdout, generator, color)
|
||||||
|
if WIN or sys.platform.startswith("os2"):
|
||||||
|
return _tempfilepager(generator, "more <", color)
|
||||||
|
if hasattr(os, "system") and os.system("(less) 2>/dev/null") == 0:
|
||||||
|
return _pipepager(generator, "less", color)
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
fd, filename = tempfile.mkstemp()
|
||||||
|
os.close(fd)
|
||||||
|
try:
|
||||||
|
if hasattr(os, "system") and os.system(f'more "{filename}"') == 0:
|
||||||
|
return _pipepager(generator, "more", color)
|
||||||
|
return _nullpager(stdout, generator, color)
|
||||||
|
finally:
|
||||||
|
os.unlink(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> None:
|
||||||
|
"""Page through text by feeding it to another program. Invoking a
|
||||||
|
pager through this might support colors.
|
||||||
|
"""
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
env = dict(os.environ)
|
||||||
|
|
||||||
|
# If we're piping to less we might support colors under the
|
||||||
|
# condition that
|
||||||
|
cmd_detail = cmd.rsplit("/", 1)[-1].split()
|
||||||
|
if color is None and cmd_detail[0] == "less":
|
||||||
|
less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_detail[1:])}"
|
||||||
|
if not less_flags:
|
||||||
|
env["LESS"] = "-R"
|
||||||
|
color = True
|
||||||
|
elif "r" in less_flags or "R" in less_flags:
|
||||||
|
color = True
|
||||||
|
|
||||||
|
c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env)
|
||||||
|
stdin = t.cast(t.BinaryIO, c.stdin)
|
||||||
|
encoding = get_best_encoding(stdin)
|
||||||
|
try:
|
||||||
|
for text in generator:
|
||||||
|
if not color:
|
||||||
|
text = strip_ansi(text)
|
||||||
|
|
||||||
|
stdin.write(text.encode(encoding, "replace"))
|
||||||
|
except (OSError, KeyboardInterrupt):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
stdin.close()
|
||||||
|
|
||||||
|
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting
|
||||||
|
# search or other commands inside less).
|
||||||
|
#
|
||||||
|
# That means when the user hits ^C, the parent process (click) terminates,
|
||||||
|
# but less is still alive, paging the output and messing up the terminal.
|
||||||
|
#
|
||||||
|
# If the user wants to make the pager exit on ^C, they should set
|
||||||
|
# `LESS='-K'`. It's not our decision to make.
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
c.wait()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def _tempfilepager(
|
||||||
|
generator: t.Iterable[str], cmd: str, color: t.Optional[bool]
|
||||||
|
) -> None:
|
||||||
|
"""Page through text by invoking a program on a temporary file."""
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
fd, filename = tempfile.mkstemp()
|
||||||
|
# TODO: This never terminates if the passed generator never terminates.
|
||||||
|
text = "".join(generator)
|
||||||
|
if not color:
|
||||||
|
text = strip_ansi(text)
|
||||||
|
encoding = get_best_encoding(sys.stdout)
|
||||||
|
with open_stream(filename, "wb")[0] as f:
|
||||||
|
f.write(text.encode(encoding))
|
||||||
|
try:
|
||||||
|
os.system(f'{cmd} "{filename}"')
|
||||||
|
finally:
|
||||||
|
os.close(fd)
|
||||||
|
os.unlink(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def _nullpager(
|
||||||
|
stream: t.TextIO, generator: t.Iterable[str], color: t.Optional[bool]
|
||||||
|
) -> None:
|
||||||
|
"""Simply print unformatted text. This is the ultimate fallback."""
|
||||||
|
for text in generator:
|
||||||
|
if not color:
|
||||||
|
text = strip_ansi(text)
|
||||||
|
stream.write(text)
|
||||||
|
|
||||||
|
|
||||||
|
class Editor:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
editor: t.Optional[str] = None,
|
||||||
|
env: t.Optional[t.Mapping[str, str]] = None,
|
||||||
|
require_save: bool = True,
|
||||||
|
extension: str = ".txt",
|
||||||
|
) -> None:
|
||||||
|
self.editor = editor
|
||||||
|
self.env = env
|
||||||
|
self.require_save = require_save
|
||||||
|
self.extension = extension
|
||||||
|
|
||||||
|
def get_editor(self) -> str:
|
||||||
|
if self.editor is not None:
|
||||||
|
return self.editor
|
||||||
|
for key in "VISUAL", "EDITOR":
|
||||||
|
rv = os.environ.get(key)
|
||||||
|
if rv:
|
||||||
|
return rv
|
||||||
|
if WIN:
|
||||||
|
return "notepad"
|
||||||
|
for editor in "sensible-editor", "vim", "nano":
|
||||||
|
if os.system(f"which {editor} >/dev/null 2>&1") == 0:
|
||||||
|
return editor
|
||||||
|
return "vi"
|
||||||
|
|
||||||
|
def edit_file(self, filename: str) -> None:
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
editor = self.get_editor()
|
||||||
|
environ: t.Optional[t.Dict[str, str]] = None
|
||||||
|
|
||||||
|
if self.env:
|
||||||
|
environ = os.environ.copy()
|
||||||
|
environ.update(self.env)
|
||||||
|
|
||||||
|
try:
|
||||||
|
c = subprocess.Popen(f'{editor} "{filename}"', env=environ, shell=True)
|
||||||
|
exit_code = c.wait()
|
||||||
|
if exit_code != 0:
|
||||||
|
raise ClickException(
|
||||||
|
_("{editor}: Editing failed").format(editor=editor)
|
||||||
|
)
|
||||||
|
except OSError as e:
|
||||||
|
raise ClickException(
|
||||||
|
_("{editor}: Editing failed: {e}").format(editor=editor, e=e)
|
||||||
|
) from e
|
||||||
|
|
||||||
|
def edit(self, text: t.Optional[t.AnyStr]) -> t.Optional[t.AnyStr]:
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
if not text:
|
||||||
|
data = b""
|
||||||
|
elif isinstance(text, (bytes, bytearray)):
|
||||||
|
data = text
|
||||||
|
else:
|
||||||
|
if text and not text.endswith("\n"):
|
||||||
|
text += "\n"
|
||||||
|
|
||||||
|
if WIN:
|
||||||
|
data = text.replace("\n", "\r\n").encode("utf-8-sig")
|
||||||
|
else:
|
||||||
|
data = text.encode("utf-8")
|
||||||
|
|
||||||
|
fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension)
|
||||||
|
f: t.BinaryIO
|
||||||
|
|
||||||
|
try:
|
||||||
|
with os.fdopen(fd, "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
# If the filesystem resolution is 1 second, like Mac OS
|
||||||
|
# 10.12 Extended, or 2 seconds, like FAT32, and the editor
|
||||||
|
# closes very fast, require_save can fail. Set the modified
|
||||||
|
# time to be 2 seconds in the past to work around this.
|
||||||
|
os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2))
|
||||||
|
# Depending on the resolution, the exact value might not be
|
||||||
|
# recorded, so get the new recorded value.
|
||||||
|
timestamp = os.path.getmtime(name)
|
||||||
|
|
||||||
|
self.edit_file(name)
|
||||||
|
|
||||||
|
if self.require_save and os.path.getmtime(name) == timestamp:
|
||||||
|
return None
|
||||||
|
|
||||||
|
with open(name, "rb") as f:
|
||||||
|
rv = f.read()
|
||||||
|
|
||||||
|
if isinstance(text, (bytes, bytearray)):
|
||||||
|
return rv
|
||||||
|
|
||||||
|
return rv.decode("utf-8-sig").replace("\r\n", "\n") # type: ignore
|
||||||
|
finally:
|
||||||
|
os.unlink(name)
|
||||||
|
|
||||||
|
|
||||||
|
def open_url(url: str, wait: bool = False, locate: bool = False) -> int:
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def _unquote_file(url: str) -> str:
|
||||||
|
from urllib.parse import unquote
|
||||||
|
|
||||||
|
if url.startswith("file://"):
|
||||||
|
url = unquote(url[7:])
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
args = ["open"]
|
||||||
|
if wait:
|
||||||
|
args.append("-W")
|
||||||
|
if locate:
|
||||||
|
args.append("-R")
|
||||||
|
args.append(_unquote_file(url))
|
||||||
|
null = open("/dev/null", "w")
|
||||||
|
try:
|
||||||
|
return subprocess.Popen(args, stderr=null).wait()
|
||||||
|
finally:
|
||||||
|
null.close()
|
||||||
|
elif WIN:
|
||||||
|
if locate:
|
||||||
|
url = _unquote_file(url.replace('"', ""))
|
||||||
|
args = f'explorer /select,"{url}"'
|
||||||
|
else:
|
||||||
|
url = url.replace('"', "")
|
||||||
|
wait_str = "/WAIT" if wait else ""
|
||||||
|
args = f'start {wait_str} "" "{url}"'
|
||||||
|
return os.system(args)
|
||||||
|
elif CYGWIN:
|
||||||
|
if locate:
|
||||||
|
url = os.path.dirname(_unquote_file(url).replace('"', ""))
|
||||||
|
args = f'cygstart "{url}"'
|
||||||
|
else:
|
||||||
|
url = url.replace('"', "")
|
||||||
|
wait_str = "-w" if wait else ""
|
||||||
|
args = f'cygstart {wait_str} "{url}"'
|
||||||
|
return os.system(args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if locate:
|
||||||
|
url = os.path.dirname(_unquote_file(url)) or "."
|
||||||
|
else:
|
||||||
|
url = _unquote_file(url)
|
||||||
|
c = subprocess.Popen(["xdg-open", url])
|
||||||
|
if wait:
|
||||||
|
return c.wait()
|
||||||
|
return 0
|
||||||
|
except OSError:
|
||||||
|
if url.startswith(("http://", "https://")) and not locate and not wait:
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
|
webbrowser.open(url)
|
||||||
|
return 0
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def _translate_ch_to_exc(ch: str) -> t.Optional[BaseException]:
|
||||||
|
if ch == "\x03":
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
|
||||||
|
if ch == "\x04" and not WIN: # Unix-like, Ctrl+D
|
||||||
|
raise EOFError()
|
||||||
|
|
||||||
|
if ch == "\x1a" and WIN: # Windows, Ctrl+Z
|
||||||
|
raise EOFError()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
if WIN:
|
||||||
|
import msvcrt
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def raw_terminal() -> t.Iterator[int]:
|
||||||
|
yield -1
|
||||||
|
|
||||||
|
def getchar(echo: bool) -> str:
|
||||||
|
# The function `getch` will return a bytes object corresponding to
|
||||||
|
# the pressed character. Since Windows 10 build 1803, it will also
|
||||||
|
# return \x00 when called a second time after pressing a regular key.
|
||||||
|
#
|
||||||
|
# `getwch` does not share this probably-bugged behavior. Moreover, it
|
||||||
|
# returns a Unicode object by default, which is what we want.
|
||||||
|
#
|
||||||
|
# Either of these functions will return \x00 or \xe0 to indicate
|
||||||
|
# a special key, and you need to call the same function again to get
|
||||||
|
# the "rest" of the code. The fun part is that \u00e0 is
|
||||||
|
# "latin small letter a with grave", so if you type that on a French
|
||||||
|
# keyboard, you _also_ get a \xe0.
|
||||||
|
# E.g., consider the Up arrow. This returns \xe0 and then \x48. The
|
||||||
|
# resulting Unicode string reads as "a with grave" + "capital H".
|
||||||
|
# This is indistinguishable from when the user actually types
|
||||||
|
# "a with grave" and then "capital H".
|
||||||
|
#
|
||||||
|
# When \xe0 is returned, we assume it's part of a special-key sequence
|
||||||
|
# and call `getwch` again, but that means that when the user types
|
||||||
|
# the \u00e0 character, `getchar` doesn't return until a second
|
||||||
|
# character is typed.
|
||||||
|
# The alternative is returning immediately, but that would mess up
|
||||||
|
# cross-platform handling of arrow keys and others that start with
|
||||||
|
# \xe0. Another option is using `getch`, but then we can't reliably
|
||||||
|
# read non-ASCII characters, because return values of `getch` are
|
||||||
|
# limited to the current 8-bit codepage.
|
||||||
|
#
|
||||||
|
# Anyway, Click doesn't claim to do this Right(tm), and using `getwch`
|
||||||
|
# is doing the right thing in more situations than with `getch`.
|
||||||
|
func: t.Callable[[], str]
|
||||||
|
|
||||||
|
if echo:
|
||||||
|
func = msvcrt.getwche # type: ignore
|
||||||
|
else:
|
||||||
|
func = msvcrt.getwch # type: ignore
|
||||||
|
|
||||||
|
rv = func()
|
||||||
|
|
||||||
|
if rv in ("\x00", "\xe0"):
|
||||||
|
# \x00 and \xe0 are control characters that indicate special key,
|
||||||
|
# see above.
|
||||||
|
rv += func()
|
||||||
|
|
||||||
|
_translate_ch_to_exc(rv)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
else:
|
||||||
|
import tty
|
||||||
|
import termios
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def raw_terminal() -> t.Iterator[int]:
|
||||||
|
f: t.Optional[t.TextIO]
|
||||||
|
fd: int
|
||||||
|
|
||||||
|
if not isatty(sys.stdin):
|
||||||
|
f = open("/dev/tty")
|
||||||
|
fd = f.fileno()
|
||||||
|
else:
|
||||||
|
fd = sys.stdin.fileno()
|
||||||
|
f = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
old_settings = termios.tcgetattr(fd)
|
||||||
|
|
||||||
|
try:
|
||||||
|
tty.setraw(fd)
|
||||||
|
yield fd
|
||||||
|
finally:
|
||||||
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
if f is not None:
|
||||||
|
f.close()
|
||||||
|
except termios.error:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def getchar(echo: bool) -> str:
|
||||||
|
with raw_terminal() as fd:
|
||||||
|
ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace")
|
||||||
|
|
||||||
|
if echo and isatty(sys.stdout):
|
||||||
|
sys.stdout.write(ch)
|
||||||
|
|
||||||
|
_translate_ch_to_exc(ch)
|
||||||
|
return ch
|
||||||
49
dist/ba_data/python-site-packages/click/_textwrap.py
vendored
Normal file
49
dist/ba_data/python-site-packages/click/_textwrap.py
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import textwrap
|
||||||
|
import typing as t
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
|
||||||
|
class TextWrapper(textwrap.TextWrapper):
|
||||||
|
def _handle_long_word(
|
||||||
|
self,
|
||||||
|
reversed_chunks: t.List[str],
|
||||||
|
cur_line: t.List[str],
|
||||||
|
cur_len: int,
|
||||||
|
width: int,
|
||||||
|
) -> None:
|
||||||
|
space_left = max(width - cur_len, 1)
|
||||||
|
|
||||||
|
if self.break_long_words:
|
||||||
|
last = reversed_chunks[-1]
|
||||||
|
cut = last[:space_left]
|
||||||
|
res = last[space_left:]
|
||||||
|
cur_line.append(cut)
|
||||||
|
reversed_chunks[-1] = res
|
||||||
|
elif not cur_line:
|
||||||
|
cur_line.append(reversed_chunks.pop())
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def extra_indent(self, indent: str) -> t.Iterator[None]:
|
||||||
|
old_initial_indent = self.initial_indent
|
||||||
|
old_subsequent_indent = self.subsequent_indent
|
||||||
|
self.initial_indent += indent
|
||||||
|
self.subsequent_indent += indent
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self.initial_indent = old_initial_indent
|
||||||
|
self.subsequent_indent = old_subsequent_indent
|
||||||
|
|
||||||
|
def indent_only(self, text: str) -> str:
|
||||||
|
rv = []
|
||||||
|
|
||||||
|
for idx, line in enumerate(text.splitlines()):
|
||||||
|
indent = self.initial_indent
|
||||||
|
|
||||||
|
if idx > 0:
|
||||||
|
indent = self.subsequent_indent
|
||||||
|
|
||||||
|
rv.append(f"{indent}{line}")
|
||||||
|
|
||||||
|
return "\n".join(rv)
|
||||||
100
dist/ba_data/python-site-packages/click/_unicodefun.py
vendored
Normal file
100
dist/ba_data/python-site-packages/click/_unicodefun.py
vendored
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
import codecs
|
||||||
|
import os
|
||||||
|
from gettext import gettext as _
|
||||||
|
|
||||||
|
|
||||||
|
def _verify_python_env() -> None:
|
||||||
|
"""Ensures that the environment is good for Unicode."""
|
||||||
|
try:
|
||||||
|
from locale import getpreferredencoding
|
||||||
|
|
||||||
|
fs_enc = codecs.lookup(getpreferredencoding()).name
|
||||||
|
except Exception:
|
||||||
|
fs_enc = "ascii"
|
||||||
|
|
||||||
|
if fs_enc != "ascii":
|
||||||
|
return
|
||||||
|
|
||||||
|
extra = [
|
||||||
|
_(
|
||||||
|
"Click will abort further execution because Python was"
|
||||||
|
" configured to use ASCII as encoding for the environment."
|
||||||
|
" Consult https://click.palletsprojects.com/unicode-support/"
|
||||||
|
" for mitigation steps."
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
if os.name == "posix":
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
try:
|
||||||
|
rv = subprocess.Popen(
|
||||||
|
["locale", "-a"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
encoding="ascii",
|
||||||
|
errors="replace",
|
||||||
|
).communicate()[0]
|
||||||
|
except OSError:
|
||||||
|
rv = ""
|
||||||
|
|
||||||
|
good_locales = set()
|
||||||
|
has_c_utf8 = False
|
||||||
|
|
||||||
|
for line in rv.splitlines():
|
||||||
|
locale = line.strip()
|
||||||
|
|
||||||
|
if locale.lower().endswith((".utf-8", ".utf8")):
|
||||||
|
good_locales.add(locale)
|
||||||
|
|
||||||
|
if locale.lower() in ("c.utf8", "c.utf-8"):
|
||||||
|
has_c_utf8 = True
|
||||||
|
|
||||||
|
if not good_locales:
|
||||||
|
extra.append(
|
||||||
|
_(
|
||||||
|
"Additional information: on this system no suitable"
|
||||||
|
" UTF-8 locales were discovered. This most likely"
|
||||||
|
" requires resolving by reconfiguring the locale"
|
||||||
|
" system."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif has_c_utf8:
|
||||||
|
extra.append(
|
||||||
|
_(
|
||||||
|
"This system supports the C.UTF-8 locale which is"
|
||||||
|
" recommended. You might be able to resolve your"
|
||||||
|
" issue by exporting the following environment"
|
||||||
|
" variables:"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
extra.append(" export LC_ALL=C.UTF-8\n export LANG=C.UTF-8")
|
||||||
|
else:
|
||||||
|
extra.append(
|
||||||
|
_(
|
||||||
|
"This system lists some UTF-8 supporting locales"
|
||||||
|
" that you can pick from. The following suitable"
|
||||||
|
" locales were discovered: {locales}"
|
||||||
|
).format(locales=", ".join(sorted(good_locales)))
|
||||||
|
)
|
||||||
|
|
||||||
|
bad_locale = None
|
||||||
|
|
||||||
|
for env_locale in os.environ.get("LC_ALL"), os.environ.get("LANG"):
|
||||||
|
if env_locale and env_locale.lower().endswith((".utf-8", ".utf8")):
|
||||||
|
bad_locale = env_locale
|
||||||
|
|
||||||
|
if env_locale is not None:
|
||||||
|
break
|
||||||
|
|
||||||
|
if bad_locale is not None:
|
||||||
|
extra.append(
|
||||||
|
_(
|
||||||
|
"Click discovered that you exported a UTF-8 locale"
|
||||||
|
" but the locale system could not pick up from it"
|
||||||
|
" because it does not exist. The exported locale is"
|
||||||
|
" {locale!r} but it is not supported."
|
||||||
|
).format(locale=bad_locale)
|
||||||
|
)
|
||||||
|
|
||||||
|
raise RuntimeError("\n\n".join(extra))
|
||||||
279
dist/ba_data/python-site-packages/click/_winconsole.py
vendored
Normal file
279
dist/ba_data/python-site-packages/click/_winconsole.py
vendored
Normal file
|
|
@ -0,0 +1,279 @@
|
||||||
|
# This module is based on the excellent work by Adam Bartoš who
|
||||||
|
# provided a lot of what went into the implementation here in
|
||||||
|
# the discussion to issue1602 in the Python bug tracker.
|
||||||
|
#
|
||||||
|
# There are some general differences in regards to how this works
|
||||||
|
# compared to the original patches as we do not need to patch
|
||||||
|
# the entire interpreter but just work in our little world of
|
||||||
|
# echo and prompt.
|
||||||
|
import io
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import typing as t
|
||||||
|
from ctypes import byref
|
||||||
|
from ctypes import c_char
|
||||||
|
from ctypes import c_char_p
|
||||||
|
from ctypes import c_int
|
||||||
|
from ctypes import c_ssize_t
|
||||||
|
from ctypes import c_ulong
|
||||||
|
from ctypes import c_void_p
|
||||||
|
from ctypes import POINTER
|
||||||
|
from ctypes import py_object
|
||||||
|
from ctypes import Structure
|
||||||
|
from ctypes.wintypes import DWORD
|
||||||
|
from ctypes.wintypes import HANDLE
|
||||||
|
from ctypes.wintypes import LPCWSTR
|
||||||
|
from ctypes.wintypes import LPWSTR
|
||||||
|
|
||||||
|
from ._compat import _NonClosingTextIOWrapper
|
||||||
|
|
||||||
|
assert sys.platform == "win32"
|
||||||
|
import msvcrt # noqa: E402
|
||||||
|
from ctypes import windll # noqa: E402
|
||||||
|
from ctypes import WINFUNCTYPE # noqa: E402
|
||||||
|
|
||||||
|
c_ssize_p = POINTER(c_ssize_t)
|
||||||
|
|
||||||
|
kernel32 = windll.kernel32
|
||||||
|
GetStdHandle = kernel32.GetStdHandle
|
||||||
|
ReadConsoleW = kernel32.ReadConsoleW
|
||||||
|
WriteConsoleW = kernel32.WriteConsoleW
|
||||||
|
GetConsoleMode = kernel32.GetConsoleMode
|
||||||
|
GetLastError = kernel32.GetLastError
|
||||||
|
GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32))
|
||||||
|
CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
|
||||||
|
("CommandLineToArgvW", windll.shell32)
|
||||||
|
)
|
||||||
|
LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32))
|
||||||
|
|
||||||
|
STDIN_HANDLE = GetStdHandle(-10)
|
||||||
|
STDOUT_HANDLE = GetStdHandle(-11)
|
||||||
|
STDERR_HANDLE = GetStdHandle(-12)
|
||||||
|
|
||||||
|
PyBUF_SIMPLE = 0
|
||||||
|
PyBUF_WRITABLE = 1
|
||||||
|
|
||||||
|
ERROR_SUCCESS = 0
|
||||||
|
ERROR_NOT_ENOUGH_MEMORY = 8
|
||||||
|
ERROR_OPERATION_ABORTED = 995
|
||||||
|
|
||||||
|
STDIN_FILENO = 0
|
||||||
|
STDOUT_FILENO = 1
|
||||||
|
STDERR_FILENO = 2
|
||||||
|
|
||||||
|
EOF = b"\x1a"
|
||||||
|
MAX_BYTES_WRITTEN = 32767
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ctypes import pythonapi
|
||||||
|
except ImportError:
|
||||||
|
# On PyPy we cannot get buffers so our ability to operate here is
|
||||||
|
# severely limited.
|
||||||
|
get_buffer = None
|
||||||
|
else:
|
||||||
|
|
||||||
|
class Py_buffer(Structure):
|
||||||
|
_fields_ = [
|
||||||
|
("buf", c_void_p),
|
||||||
|
("obj", py_object),
|
||||||
|
("len", c_ssize_t),
|
||||||
|
("itemsize", c_ssize_t),
|
||||||
|
("readonly", c_int),
|
||||||
|
("ndim", c_int),
|
||||||
|
("format", c_char_p),
|
||||||
|
("shape", c_ssize_p),
|
||||||
|
("strides", c_ssize_p),
|
||||||
|
("suboffsets", c_ssize_p),
|
||||||
|
("internal", c_void_p),
|
||||||
|
]
|
||||||
|
|
||||||
|
PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
|
||||||
|
PyBuffer_Release = pythonapi.PyBuffer_Release
|
||||||
|
|
||||||
|
def get_buffer(obj, writable=False):
|
||||||
|
buf = Py_buffer()
|
||||||
|
flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE
|
||||||
|
PyObject_GetBuffer(py_object(obj), byref(buf), flags)
|
||||||
|
|
||||||
|
try:
|
||||||
|
buffer_type = c_char * buf.len
|
||||||
|
return buffer_type.from_address(buf.buf)
|
||||||
|
finally:
|
||||||
|
PyBuffer_Release(byref(buf))
|
||||||
|
|
||||||
|
|
||||||
|
class _WindowsConsoleRawIOBase(io.RawIOBase):
|
||||||
|
def __init__(self, handle):
|
||||||
|
self.handle = handle
|
||||||
|
|
||||||
|
def isatty(self):
|
||||||
|
super().isatty()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
|
||||||
|
def readable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def readinto(self, b):
|
||||||
|
bytes_to_be_read = len(b)
|
||||||
|
if not bytes_to_be_read:
|
||||||
|
return 0
|
||||||
|
elif bytes_to_be_read % 2:
|
||||||
|
raise ValueError(
|
||||||
|
"cannot read odd number of bytes from UTF-16-LE encoded console"
|
||||||
|
)
|
||||||
|
|
||||||
|
buffer = get_buffer(b, writable=True)
|
||||||
|
code_units_to_be_read = bytes_to_be_read // 2
|
||||||
|
code_units_read = c_ulong()
|
||||||
|
|
||||||
|
rv = ReadConsoleW(
|
||||||
|
HANDLE(self.handle),
|
||||||
|
buffer,
|
||||||
|
code_units_to_be_read,
|
||||||
|
byref(code_units_read),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if GetLastError() == ERROR_OPERATION_ABORTED:
|
||||||
|
# wait for KeyboardInterrupt
|
||||||
|
time.sleep(0.1)
|
||||||
|
if not rv:
|
||||||
|
raise OSError(f"Windows error: {GetLastError()}")
|
||||||
|
|
||||||
|
if buffer[0] == EOF:
|
||||||
|
return 0
|
||||||
|
return 2 * code_units_read.value
|
||||||
|
|
||||||
|
|
||||||
|
class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
|
||||||
|
def writable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_error_message(errno):
|
||||||
|
if errno == ERROR_SUCCESS:
|
||||||
|
return "ERROR_SUCCESS"
|
||||||
|
elif errno == ERROR_NOT_ENOUGH_MEMORY:
|
||||||
|
return "ERROR_NOT_ENOUGH_MEMORY"
|
||||||
|
return f"Windows error {errno}"
|
||||||
|
|
||||||
|
def write(self, b):
|
||||||
|
bytes_to_be_written = len(b)
|
||||||
|
buf = get_buffer(b)
|
||||||
|
code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2
|
||||||
|
code_units_written = c_ulong()
|
||||||
|
|
||||||
|
WriteConsoleW(
|
||||||
|
HANDLE(self.handle),
|
||||||
|
buf,
|
||||||
|
code_units_to_be_written,
|
||||||
|
byref(code_units_written),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
bytes_written = 2 * code_units_written.value
|
||||||
|
|
||||||
|
if bytes_written == 0 and bytes_to_be_written > 0:
|
||||||
|
raise OSError(self._get_error_message(GetLastError()))
|
||||||
|
return bytes_written
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleStream:
|
||||||
|
def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None:
|
||||||
|
self._text_stream = text_stream
|
||||||
|
self.buffer = byte_stream
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self.buffer.name
|
||||||
|
|
||||||
|
def write(self, x: t.AnyStr) -> int:
|
||||||
|
if isinstance(x, str):
|
||||||
|
return self._text_stream.write(x)
|
||||||
|
try:
|
||||||
|
self.flush()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return self.buffer.write(x)
|
||||||
|
|
||||||
|
def writelines(self, lines: t.Iterable[t.AnyStr]) -> None:
|
||||||
|
for line in lines:
|
||||||
|
self.write(line)
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> t.Any:
|
||||||
|
return getattr(self._text_stream, name)
|
||||||
|
|
||||||
|
def isatty(self) -> bool:
|
||||||
|
return self.buffer.isatty()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<ConsoleStream name={self.name!r} encoding={self.encoding!r}>"
|
||||||
|
|
||||||
|
|
||||||
|
def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO:
|
||||||
|
text_stream = _NonClosingTextIOWrapper(
|
||||||
|
io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
|
||||||
|
"utf-16-le",
|
||||||
|
"strict",
|
||||||
|
line_buffering=True,
|
||||||
|
)
|
||||||
|
return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO:
|
||||||
|
text_stream = _NonClosingTextIOWrapper(
|
||||||
|
io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)),
|
||||||
|
"utf-16-le",
|
||||||
|
"strict",
|
||||||
|
line_buffering=True,
|
||||||
|
)
|
||||||
|
return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO:
|
||||||
|
text_stream = _NonClosingTextIOWrapper(
|
||||||
|
io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)),
|
||||||
|
"utf-16-le",
|
||||||
|
"strict",
|
||||||
|
line_buffering=True,
|
||||||
|
)
|
||||||
|
return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
|
||||||
|
|
||||||
|
|
||||||
|
_stream_factories: t.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = {
|
||||||
|
0: _get_text_stdin,
|
||||||
|
1: _get_text_stdout,
|
||||||
|
2: _get_text_stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _is_console(f: t.TextIO) -> bool:
|
||||||
|
if not hasattr(f, "fileno"):
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
fileno = f.fileno()
|
||||||
|
except (OSError, io.UnsupportedOperation):
|
||||||
|
return False
|
||||||
|
|
||||||
|
handle = msvcrt.get_osfhandle(fileno)
|
||||||
|
return bool(GetConsoleMode(handle, byref(DWORD())))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_windows_console_stream(
|
||||||
|
f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str]
|
||||||
|
) -> t.Optional[t.TextIO]:
|
||||||
|
if (
|
||||||
|
get_buffer is not None
|
||||||
|
and encoding in {"utf-16-le", None}
|
||||||
|
and errors in {"strict", None}
|
||||||
|
and _is_console(f)
|
||||||
|
):
|
||||||
|
func = _stream_factories.get(f.fileno())
|
||||||
|
if func is not None:
|
||||||
|
b = getattr(f, "buffer", None)
|
||||||
|
|
||||||
|
if b is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return func(b)
|
||||||
2953
dist/ba_data/python-site-packages/click/core.py
vendored
Normal file
2953
dist/ba_data/python-site-packages/click/core.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
436
dist/ba_data/python-site-packages/click/decorators.py
vendored
Normal file
436
dist/ba_data/python-site-packages/click/decorators.py
vendored
Normal file
|
|
@ -0,0 +1,436 @@
|
||||||
|
import inspect
|
||||||
|
import types
|
||||||
|
import typing as t
|
||||||
|
from functools import update_wrapper
|
||||||
|
from gettext import gettext as _
|
||||||
|
|
||||||
|
from .core import Argument
|
||||||
|
from .core import Command
|
||||||
|
from .core import Context
|
||||||
|
from .core import Group
|
||||||
|
from .core import Option
|
||||||
|
from .core import Parameter
|
||||||
|
from .globals import get_current_context
|
||||||
|
from .utils import echo
|
||||||
|
|
||||||
|
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||||
|
FC = t.TypeVar("FC", bound=t.Union[t.Callable[..., t.Any], Command])
|
||||||
|
|
||||||
|
|
||||||
|
def pass_context(f: F) -> F:
|
||||||
|
"""Marks a callback as wanting to receive the current context
|
||||||
|
object as first argument.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def new_func(*args, **kwargs): # type: ignore
|
||||||
|
return f(get_current_context(), *args, **kwargs)
|
||||||
|
|
||||||
|
return update_wrapper(t.cast(F, new_func), f)
|
||||||
|
|
||||||
|
|
||||||
|
def pass_obj(f: F) -> F:
|
||||||
|
"""Similar to :func:`pass_context`, but only pass the object on the
|
||||||
|
context onwards (:attr:`Context.obj`). This is useful if that object
|
||||||
|
represents the state of a nested system.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def new_func(*args, **kwargs): # type: ignore
|
||||||
|
return f(get_current_context().obj, *args, **kwargs)
|
||||||
|
|
||||||
|
return update_wrapper(t.cast(F, new_func), f)
|
||||||
|
|
||||||
|
|
||||||
|
def make_pass_decorator(
|
||||||
|
object_type: t.Type, ensure: bool = False
|
||||||
|
) -> "t.Callable[[F], F]":
|
||||||
|
"""Given an object type this creates a decorator that will work
|
||||||
|
similar to :func:`pass_obj` but instead of passing the object of the
|
||||||
|
current context, it will find the innermost context of type
|
||||||
|
:func:`object_type`.
|
||||||
|
|
||||||
|
This generates a decorator that works roughly like this::
|
||||||
|
|
||||||
|
from functools import update_wrapper
|
||||||
|
|
||||||
|
def decorator(f):
|
||||||
|
@pass_context
|
||||||
|
def new_func(ctx, *args, **kwargs):
|
||||||
|
obj = ctx.find_object(object_type)
|
||||||
|
return ctx.invoke(f, obj, *args, **kwargs)
|
||||||
|
return update_wrapper(new_func, f)
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
:param object_type: the type of the object to pass.
|
||||||
|
:param ensure: if set to `True`, a new object will be created and
|
||||||
|
remembered on the context if it's not there yet.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f: F) -> F:
|
||||||
|
def new_func(*args, **kwargs): # type: ignore
|
||||||
|
ctx = get_current_context()
|
||||||
|
|
||||||
|
if ensure:
|
||||||
|
obj = ctx.ensure_object(object_type)
|
||||||
|
else:
|
||||||
|
obj = ctx.find_object(object_type)
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Managed to invoke callback without a context"
|
||||||
|
f" object of type {object_type.__name__!r}"
|
||||||
|
" existing."
|
||||||
|
)
|
||||||
|
|
||||||
|
return ctx.invoke(f, obj, *args, **kwargs)
|
||||||
|
|
||||||
|
return update_wrapper(t.cast(F, new_func), f)
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def pass_meta_key(
|
||||||
|
key: str, *, doc_description: t.Optional[str] = None
|
||||||
|
) -> "t.Callable[[F], F]":
|
||||||
|
"""Create a decorator that passes a key from
|
||||||
|
:attr:`click.Context.meta` as the first argument to the decorated
|
||||||
|
function.
|
||||||
|
|
||||||
|
:param key: Key in ``Context.meta`` to pass.
|
||||||
|
:param doc_description: Description of the object being passed,
|
||||||
|
inserted into the decorator's docstring. Defaults to "the 'key'
|
||||||
|
key from Context.meta".
|
||||||
|
|
||||||
|
.. versionadded:: 8.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f: F) -> F:
|
||||||
|
def new_func(*args, **kwargs): # type: ignore
|
||||||
|
ctx = get_current_context()
|
||||||
|
obj = ctx.meta[key]
|
||||||
|
return ctx.invoke(f, obj, *args, **kwargs)
|
||||||
|
|
||||||
|
return update_wrapper(t.cast(F, new_func), f)
|
||||||
|
|
||||||
|
if doc_description is None:
|
||||||
|
doc_description = f"the {key!r} key from :attr:`click.Context.meta`"
|
||||||
|
|
||||||
|
decorator.__doc__ = (
|
||||||
|
f"Decorator that passes {doc_description} as the first argument"
|
||||||
|
" to the decorated function."
|
||||||
|
)
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def _make_command(
|
||||||
|
f: F,
|
||||||
|
name: t.Optional[str],
|
||||||
|
attrs: t.MutableMapping[str, t.Any],
|
||||||
|
cls: t.Type[Command],
|
||||||
|
) -> Command:
|
||||||
|
if isinstance(f, Command):
|
||||||
|
raise TypeError("Attempted to convert a callback into a command twice.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
params = f.__click_params__ # type: ignore
|
||||||
|
params.reverse()
|
||||||
|
del f.__click_params__ # type: ignore
|
||||||
|
except AttributeError:
|
||||||
|
params = []
|
||||||
|
|
||||||
|
help = attrs.get("help")
|
||||||
|
|
||||||
|
if help is None:
|
||||||
|
help = inspect.getdoc(f)
|
||||||
|
else:
|
||||||
|
help = inspect.cleandoc(help)
|
||||||
|
|
||||||
|
attrs["help"] = help
|
||||||
|
return cls(
|
||||||
|
name=name or f.__name__.lower().replace("_", "-"),
|
||||||
|
callback=f,
|
||||||
|
params=params,
|
||||||
|
**attrs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def command(
|
||||||
|
name: t.Optional[str] = None,
|
||||||
|
cls: t.Optional[t.Type[Command]] = None,
|
||||||
|
**attrs: t.Any,
|
||||||
|
) -> t.Callable[[F], Command]:
|
||||||
|
r"""Creates a new :class:`Command` and uses the decorated function as
|
||||||
|
callback. This will also automatically attach all decorated
|
||||||
|
:func:`option`\s and :func:`argument`\s as parameters to the command.
|
||||||
|
|
||||||
|
The name of the command defaults to the name of the function with
|
||||||
|
underscores replaced by dashes. If you want to change that, you can
|
||||||
|
pass the intended name as the first argument.
|
||||||
|
|
||||||
|
All keyword arguments are forwarded to the underlying command class.
|
||||||
|
|
||||||
|
Once decorated the function turns into a :class:`Command` instance
|
||||||
|
that can be invoked as a command line utility or be attached to a
|
||||||
|
command :class:`Group`.
|
||||||
|
|
||||||
|
:param name: the name of the command. This defaults to the function
|
||||||
|
name with underscores replaced by dashes.
|
||||||
|
:param cls: the command class to instantiate. This defaults to
|
||||||
|
:class:`Command`.
|
||||||
|
"""
|
||||||
|
if cls is None:
|
||||||
|
cls = Command
|
||||||
|
|
||||||
|
def decorator(f: t.Callable[..., t.Any]) -> Command:
|
||||||
|
cmd = _make_command(f, name, attrs, cls) # type: ignore
|
||||||
|
cmd.__doc__ = f.__doc__
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def group(name: t.Optional[str] = None, **attrs: t.Any) -> t.Callable[[F], Group]:
|
||||||
|
"""Creates a new :class:`Group` with a function as callback. This
|
||||||
|
works otherwise the same as :func:`command` just that the `cls`
|
||||||
|
parameter is set to :class:`Group`.
|
||||||
|
"""
|
||||||
|
attrs.setdefault("cls", Group)
|
||||||
|
return t.cast(Group, command(name, **attrs))
|
||||||
|
|
||||||
|
|
||||||
|
def _param_memo(f: FC, param: Parameter) -> None:
|
||||||
|
if isinstance(f, Command):
|
||||||
|
f.params.append(param)
|
||||||
|
else:
|
||||||
|
if not hasattr(f, "__click_params__"):
|
||||||
|
f.__click_params__ = [] # type: ignore
|
||||||
|
|
||||||
|
f.__click_params__.append(param) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def argument(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]:
|
||||||
|
"""Attaches an argument to the command. All positional arguments are
|
||||||
|
passed as parameter declarations to :class:`Argument`; all keyword
|
||||||
|
arguments are forwarded unchanged (except ``cls``).
|
||||||
|
This is equivalent to creating an :class:`Argument` instance manually
|
||||||
|
and attaching it to the :attr:`Command.params` list.
|
||||||
|
|
||||||
|
:param cls: the argument class to instantiate. This defaults to
|
||||||
|
:class:`Argument`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f: FC) -> FC:
|
||||||
|
ArgumentClass = attrs.pop("cls", Argument)
|
||||||
|
_param_memo(f, ArgumentClass(param_decls, **attrs))
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def option(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]:
|
||||||
|
"""Attaches an option to the command. All positional arguments are
|
||||||
|
passed as parameter declarations to :class:`Option`; all keyword
|
||||||
|
arguments are forwarded unchanged (except ``cls``).
|
||||||
|
This is equivalent to creating an :class:`Option` instance manually
|
||||||
|
and attaching it to the :attr:`Command.params` list.
|
||||||
|
|
||||||
|
:param cls: the option class to instantiate. This defaults to
|
||||||
|
:class:`Option`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(f: FC) -> FC:
|
||||||
|
# Issue 926, copy attrs, so pre-defined options can re-use the same cls=
|
||||||
|
option_attrs = attrs.copy()
|
||||||
|
|
||||||
|
if "help" in option_attrs:
|
||||||
|
option_attrs["help"] = inspect.cleandoc(option_attrs["help"])
|
||||||
|
OptionClass = option_attrs.pop("cls", Option)
|
||||||
|
_param_memo(f, OptionClass(param_decls, **option_attrs))
|
||||||
|
return f
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
|
||||||
|
"""Add a ``--yes`` option which shows a prompt before continuing if
|
||||||
|
not passed. If the prompt is declined, the program will exit.
|
||||||
|
|
||||||
|
:param param_decls: One or more option names. Defaults to the single
|
||||||
|
value ``"--yes"``.
|
||||||
|
:param kwargs: Extra arguments are passed to :func:`option`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
||||||
|
if not value:
|
||||||
|
ctx.abort()
|
||||||
|
|
||||||
|
if not param_decls:
|
||||||
|
param_decls = ("--yes",)
|
||||||
|
|
||||||
|
kwargs.setdefault("is_flag", True)
|
||||||
|
kwargs.setdefault("callback", callback)
|
||||||
|
kwargs.setdefault("expose_value", False)
|
||||||
|
kwargs.setdefault("prompt", "Do you want to continue?")
|
||||||
|
kwargs.setdefault("help", "Confirm the action without prompting.")
|
||||||
|
return option(*param_decls, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
|
||||||
|
"""Add a ``--password`` option which prompts for a password, hiding
|
||||||
|
input and asking to enter the value again for confirmation.
|
||||||
|
|
||||||
|
:param param_decls: One or more option names. Defaults to the single
|
||||||
|
value ``"--password"``.
|
||||||
|
:param kwargs: Extra arguments are passed to :func:`option`.
|
||||||
|
"""
|
||||||
|
if not param_decls:
|
||||||
|
param_decls = ("--password",)
|
||||||
|
|
||||||
|
kwargs.setdefault("prompt", True)
|
||||||
|
kwargs.setdefault("confirmation_prompt", True)
|
||||||
|
kwargs.setdefault("hide_input", True)
|
||||||
|
return option(*param_decls, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def version_option(
|
||||||
|
version: t.Optional[str] = None,
|
||||||
|
*param_decls: str,
|
||||||
|
package_name: t.Optional[str] = None,
|
||||||
|
prog_name: t.Optional[str] = None,
|
||||||
|
message: t.Optional[str] = None,
|
||||||
|
**kwargs: t.Any,
|
||||||
|
) -> t.Callable[[FC], FC]:
|
||||||
|
"""Add a ``--version`` option which immediately prints the version
|
||||||
|
number and exits the program.
|
||||||
|
|
||||||
|
If ``version`` is not provided, Click will try to detect it using
|
||||||
|
:func:`importlib.metadata.version` to get the version for the
|
||||||
|
``package_name``. On Python < 3.8, the ``importlib_metadata``
|
||||||
|
backport must be installed.
|
||||||
|
|
||||||
|
If ``package_name`` is not provided, Click will try to detect it by
|
||||||
|
inspecting the stack frames. This will be used to detect the
|
||||||
|
version, so it must match the name of the installed package.
|
||||||
|
|
||||||
|
:param version: The version number to show. If not provided, Click
|
||||||
|
will try to detect it.
|
||||||
|
:param param_decls: One or more option names. Defaults to the single
|
||||||
|
value ``"--version"``.
|
||||||
|
:param package_name: The package name to detect the version from. If
|
||||||
|
not provided, Click will try to detect it.
|
||||||
|
:param prog_name: The name of the CLI to show in the message. If not
|
||||||
|
provided, it will be detected from the command.
|
||||||
|
:param message: The message to show. The values ``%(prog)s``,
|
||||||
|
``%(package)s``, and ``%(version)s`` are available. Defaults to
|
||||||
|
``"%(prog)s, version %(version)s"``.
|
||||||
|
:param kwargs: Extra arguments are passed to :func:`option`.
|
||||||
|
:raise RuntimeError: ``version`` could not be detected.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
Add the ``package_name`` parameter, and the ``%(package)s``
|
||||||
|
value for messages.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
Use :mod:`importlib.metadata` instead of ``pkg_resources``. The
|
||||||
|
version is detected based on the package name, not the entry
|
||||||
|
point name. The Python package name must match the installed
|
||||||
|
package name, or be passed with ``package_name=``.
|
||||||
|
"""
|
||||||
|
if message is None:
|
||||||
|
message = _("%(prog)s, version %(version)s")
|
||||||
|
|
||||||
|
if version is None and package_name is None:
|
||||||
|
frame = inspect.currentframe()
|
||||||
|
f_back = frame.f_back if frame is not None else None
|
||||||
|
f_globals = f_back.f_globals if f_back is not None else None
|
||||||
|
# break reference cycle
|
||||||
|
# https://docs.python.org/3/library/inspect.html#the-interpreter-stack
|
||||||
|
del frame
|
||||||
|
|
||||||
|
if f_globals is not None:
|
||||||
|
package_name = f_globals.get("__name__")
|
||||||
|
|
||||||
|
if package_name == "__main__":
|
||||||
|
package_name = f_globals.get("__package__")
|
||||||
|
|
||||||
|
if package_name:
|
||||||
|
package_name = package_name.partition(".")[0]
|
||||||
|
|
||||||
|
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
||||||
|
if not value or ctx.resilient_parsing:
|
||||||
|
return
|
||||||
|
|
||||||
|
nonlocal prog_name
|
||||||
|
nonlocal version
|
||||||
|
|
||||||
|
if prog_name is None:
|
||||||
|
prog_name = ctx.find_root().info_name
|
||||||
|
|
||||||
|
if version is None and package_name is not None:
|
||||||
|
metadata: t.Optional[types.ModuleType]
|
||||||
|
|
||||||
|
try:
|
||||||
|
from importlib import metadata # type: ignore
|
||||||
|
except ImportError:
|
||||||
|
# Python < 3.8
|
||||||
|
import importlib_metadata as metadata # type: ignore
|
||||||
|
|
||||||
|
try:
|
||||||
|
version = metadata.version(package_name) # type: ignore
|
||||||
|
except metadata.PackageNotFoundError: # type: ignore
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{package_name!r} is not installed. Try passing"
|
||||||
|
" 'package_name' instead."
|
||||||
|
) from None
|
||||||
|
|
||||||
|
if version is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Could not determine the version for {package_name!r} automatically."
|
||||||
|
)
|
||||||
|
|
||||||
|
echo(
|
||||||
|
t.cast(str, message)
|
||||||
|
% {"prog": prog_name, "package": package_name, "version": version},
|
||||||
|
color=ctx.color,
|
||||||
|
)
|
||||||
|
ctx.exit()
|
||||||
|
|
||||||
|
if not param_decls:
|
||||||
|
param_decls = ("--version",)
|
||||||
|
|
||||||
|
kwargs.setdefault("is_flag", True)
|
||||||
|
kwargs.setdefault("expose_value", False)
|
||||||
|
kwargs.setdefault("is_eager", True)
|
||||||
|
kwargs.setdefault("help", _("Show the version and exit."))
|
||||||
|
kwargs["callback"] = callback
|
||||||
|
return option(*param_decls, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
|
||||||
|
"""Add a ``--help`` option which immediately prints the help page
|
||||||
|
and exits the program.
|
||||||
|
|
||||||
|
This is usually unnecessary, as the ``--help`` option is added to
|
||||||
|
each command automatically unless ``add_help_option=False`` is
|
||||||
|
passed.
|
||||||
|
|
||||||
|
:param param_decls: One or more option names. Defaults to the single
|
||||||
|
value ``"--help"``.
|
||||||
|
:param kwargs: Extra arguments are passed to :func:`option`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
||||||
|
if not value or ctx.resilient_parsing:
|
||||||
|
return
|
||||||
|
|
||||||
|
echo(ctx.get_help(), color=ctx.color)
|
||||||
|
ctx.exit()
|
||||||
|
|
||||||
|
if not param_decls:
|
||||||
|
param_decls = ("--help",)
|
||||||
|
|
||||||
|
kwargs.setdefault("is_flag", True)
|
||||||
|
kwargs.setdefault("expose_value", False)
|
||||||
|
kwargs.setdefault("is_eager", True)
|
||||||
|
kwargs.setdefault("help", _("Show this message and exit."))
|
||||||
|
kwargs["callback"] = callback
|
||||||
|
return option(*param_decls, **kwargs)
|
||||||
287
dist/ba_data/python-site-packages/click/exceptions.py
vendored
Normal file
287
dist/ba_data/python-site-packages/click/exceptions.py
vendored
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
import os
|
||||||
|
import typing as t
|
||||||
|
from gettext import gettext as _
|
||||||
|
from gettext import ngettext
|
||||||
|
|
||||||
|
from ._compat import get_text_stderr
|
||||||
|
from .utils import echo
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
from .core import Context
|
||||||
|
from .core import Parameter
|
||||||
|
|
||||||
|
|
||||||
|
def _join_param_hints(
|
||||||
|
param_hint: t.Optional[t.Union[t.Sequence[str], str]]
|
||||||
|
) -> t.Optional[str]:
|
||||||
|
if param_hint is not None and not isinstance(param_hint, str):
|
||||||
|
return " / ".join(repr(x) for x in param_hint)
|
||||||
|
|
||||||
|
return param_hint
|
||||||
|
|
||||||
|
|
||||||
|
class ClickException(Exception):
|
||||||
|
"""An exception that Click can handle and show to the user."""
|
||||||
|
|
||||||
|
#: The exit code for this exception.
|
||||||
|
exit_code = 1
|
||||||
|
|
||||||
|
def __init__(self, message: str) -> None:
|
||||||
|
super().__init__(message)
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def format_message(self) -> str:
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
def show(self, file: t.Optional[t.IO] = None) -> None:
|
||||||
|
if file is None:
|
||||||
|
file = get_text_stderr()
|
||||||
|
|
||||||
|
echo(_("Error: {message}").format(message=self.format_message()), file=file)
|
||||||
|
|
||||||
|
|
||||||
|
class UsageError(ClickException):
|
||||||
|
"""An internal exception that signals a usage error. This typically
|
||||||
|
aborts any further handling.
|
||||||
|
|
||||||
|
:param message: the error message to display.
|
||||||
|
:param ctx: optionally the context that caused this error. Click will
|
||||||
|
fill in the context automatically in some situations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
exit_code = 2
|
||||||
|
|
||||||
|
def __init__(self, message: str, ctx: t.Optional["Context"] = None) -> None:
|
||||||
|
super().__init__(message)
|
||||||
|
self.ctx = ctx
|
||||||
|
self.cmd = self.ctx.command if self.ctx else None
|
||||||
|
|
||||||
|
def show(self, file: t.Optional[t.IO] = None) -> None:
|
||||||
|
if file is None:
|
||||||
|
file = get_text_stderr()
|
||||||
|
color = None
|
||||||
|
hint = ""
|
||||||
|
if (
|
||||||
|
self.ctx is not None
|
||||||
|
and self.ctx.command.get_help_option(self.ctx) is not None
|
||||||
|
):
|
||||||
|
hint = _("Try '{command} {option}' for help.").format(
|
||||||
|
command=self.ctx.command_path, option=self.ctx.help_option_names[0]
|
||||||
|
)
|
||||||
|
hint = f"{hint}\n"
|
||||||
|
if self.ctx is not None:
|
||||||
|
color = self.ctx.color
|
||||||
|
echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color)
|
||||||
|
echo(
|
||||||
|
_("Error: {message}").format(message=self.format_message()),
|
||||||
|
file=file,
|
||||||
|
color=color,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BadParameter(UsageError):
|
||||||
|
"""An exception that formats out a standardized error message for a
|
||||||
|
bad parameter. This is useful when thrown from a callback or type as
|
||||||
|
Click will attach contextual information to it (for instance, which
|
||||||
|
parameter it is).
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
:param param: the parameter object that caused this error. This can
|
||||||
|
be left out, and Click will attach this info itself
|
||||||
|
if possible.
|
||||||
|
:param param_hint: a string that shows up as parameter name. This
|
||||||
|
can be used as alternative to `param` in cases
|
||||||
|
where custom validation should happen. If it is
|
||||||
|
a string it's used as such, if it's a list then
|
||||||
|
each item is quoted and separated.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
message: str,
|
||||||
|
ctx: t.Optional["Context"] = None,
|
||||||
|
param: t.Optional["Parameter"] = None,
|
||||||
|
param_hint: t.Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(message, ctx)
|
||||||
|
self.param = param
|
||||||
|
self.param_hint = param_hint
|
||||||
|
|
||||||
|
def format_message(self) -> str:
|
||||||
|
if self.param_hint is not None:
|
||||||
|
param_hint = self.param_hint
|
||||||
|
elif self.param is not None:
|
||||||
|
param_hint = self.param.get_error_hint(self.ctx) # type: ignore
|
||||||
|
else:
|
||||||
|
return _("Invalid value: {message}").format(message=self.message)
|
||||||
|
|
||||||
|
return _("Invalid value for {param_hint}: {message}").format(
|
||||||
|
param_hint=_join_param_hints(param_hint), message=self.message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MissingParameter(BadParameter):
|
||||||
|
"""Raised if click required an option or argument but it was not
|
||||||
|
provided when invoking the script.
|
||||||
|
|
||||||
|
.. versionadded:: 4.0
|
||||||
|
|
||||||
|
:param param_type: a string that indicates the type of the parameter.
|
||||||
|
The default is to inherit the parameter type from
|
||||||
|
the given `param`. Valid values are ``'parameter'``,
|
||||||
|
``'option'`` or ``'argument'``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
message: t.Optional[str] = None,
|
||||||
|
ctx: t.Optional["Context"] = None,
|
||||||
|
param: t.Optional["Parameter"] = None,
|
||||||
|
param_hint: t.Optional[str] = None,
|
||||||
|
param_type: t.Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(message or "", ctx, param, param_hint)
|
||||||
|
self.param_type = param_type
|
||||||
|
|
||||||
|
def format_message(self) -> str:
|
||||||
|
if self.param_hint is not None:
|
||||||
|
param_hint: t.Optional[str] = self.param_hint
|
||||||
|
elif self.param is not None:
|
||||||
|
param_hint = self.param.get_error_hint(self.ctx) # type: ignore
|
||||||
|
else:
|
||||||
|
param_hint = None
|
||||||
|
|
||||||
|
param_hint = _join_param_hints(param_hint)
|
||||||
|
param_hint = f" {param_hint}" if param_hint else ""
|
||||||
|
|
||||||
|
param_type = self.param_type
|
||||||
|
if param_type is None and self.param is not None:
|
||||||
|
param_type = self.param.param_type_name
|
||||||
|
|
||||||
|
msg = self.message
|
||||||
|
if self.param is not None:
|
||||||
|
msg_extra = self.param.type.get_missing_message(self.param)
|
||||||
|
if msg_extra:
|
||||||
|
if msg:
|
||||||
|
msg += f". {msg_extra}"
|
||||||
|
else:
|
||||||
|
msg = msg_extra
|
||||||
|
|
||||||
|
msg = f" {msg}" if msg else ""
|
||||||
|
|
||||||
|
# Translate param_type for known types.
|
||||||
|
if param_type == "argument":
|
||||||
|
missing = _("Missing argument")
|
||||||
|
elif param_type == "option":
|
||||||
|
missing = _("Missing option")
|
||||||
|
elif param_type == "parameter":
|
||||||
|
missing = _("Missing parameter")
|
||||||
|
else:
|
||||||
|
missing = _("Missing {param_type}").format(param_type=param_type)
|
||||||
|
|
||||||
|
return f"{missing}{param_hint}.{msg}"
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
if not self.message:
|
||||||
|
param_name = self.param.name if self.param else None
|
||||||
|
return _("Missing parameter: {param_name}").format(param_name=param_name)
|
||||||
|
else:
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchOption(UsageError):
|
||||||
|
"""Raised if click attempted to handle an option that does not
|
||||||
|
exist.
|
||||||
|
|
||||||
|
.. versionadded:: 4.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
option_name: str,
|
||||||
|
message: t.Optional[str] = None,
|
||||||
|
possibilities: t.Optional[t.Sequence[str]] = None,
|
||||||
|
ctx: t.Optional["Context"] = None,
|
||||||
|
) -> None:
|
||||||
|
if message is None:
|
||||||
|
message = _("No such option: {name}").format(name=option_name)
|
||||||
|
|
||||||
|
super().__init__(message, ctx)
|
||||||
|
self.option_name = option_name
|
||||||
|
self.possibilities = possibilities
|
||||||
|
|
||||||
|
def format_message(self) -> str:
|
||||||
|
if not self.possibilities:
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
possibility_str = ", ".join(sorted(self.possibilities))
|
||||||
|
suggest = ngettext(
|
||||||
|
"Did you mean {possibility}?",
|
||||||
|
"(Possible options: {possibilities})",
|
||||||
|
len(self.possibilities),
|
||||||
|
).format(possibility=possibility_str, possibilities=possibility_str)
|
||||||
|
return f"{self.message} {suggest}"
|
||||||
|
|
||||||
|
|
||||||
|
class BadOptionUsage(UsageError):
|
||||||
|
"""Raised if an option is generally supplied but the use of the option
|
||||||
|
was incorrect. This is for instance raised if the number of arguments
|
||||||
|
for an option is not correct.
|
||||||
|
|
||||||
|
.. versionadded:: 4.0
|
||||||
|
|
||||||
|
:param option_name: the name of the option being used incorrectly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, option_name: str, message: str, ctx: t.Optional["Context"] = None
|
||||||
|
) -> None:
|
||||||
|
super().__init__(message, ctx)
|
||||||
|
self.option_name = option_name
|
||||||
|
|
||||||
|
|
||||||
|
class BadArgumentUsage(UsageError):
|
||||||
|
"""Raised if an argument is generally supplied but the use of the argument
|
||||||
|
was incorrect. This is for instance raised if the number of values
|
||||||
|
for an argument is not correct.
|
||||||
|
|
||||||
|
.. versionadded:: 6.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class FileError(ClickException):
|
||||||
|
"""Raised if a file cannot be opened."""
|
||||||
|
|
||||||
|
def __init__(self, filename: str, hint: t.Optional[str] = None) -> None:
|
||||||
|
if hint is None:
|
||||||
|
hint = _("unknown error")
|
||||||
|
|
||||||
|
super().__init__(hint)
|
||||||
|
self.ui_filename = os.fsdecode(filename)
|
||||||
|
self.filename = filename
|
||||||
|
|
||||||
|
def format_message(self) -> str:
|
||||||
|
return _("Could not open file {filename!r}: {message}").format(
|
||||||
|
filename=self.ui_filename, message=self.message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Abort(RuntimeError):
|
||||||
|
"""An internal signalling exception that signals Click to abort."""
|
||||||
|
|
||||||
|
|
||||||
|
class Exit(RuntimeError):
|
||||||
|
"""An exception that indicates that the application should exit with some
|
||||||
|
status code.
|
||||||
|
|
||||||
|
:param code: the status code to exit with.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ("exit_code",)
|
||||||
|
|
||||||
|
def __init__(self, code: int = 0) -> None:
|
||||||
|
self.exit_code = code
|
||||||
301
dist/ba_data/python-site-packages/click/formatting.py
vendored
Normal file
301
dist/ba_data/python-site-packages/click/formatting.py
vendored
Normal file
|
|
@ -0,0 +1,301 @@
|
||||||
|
import typing as t
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from gettext import gettext as _
|
||||||
|
|
||||||
|
from ._compat import term_len
|
||||||
|
from .parser import split_opt
|
||||||
|
|
||||||
|
# Can force a width. This is used by the test system
|
||||||
|
FORCED_WIDTH: t.Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
def measure_table(rows: t.Iterable[t.Tuple[str, str]]) -> t.Tuple[int, ...]:
|
||||||
|
widths: t.Dict[int, int] = {}
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
for idx, col in enumerate(row):
|
||||||
|
widths[idx] = max(widths.get(idx, 0), term_len(col))
|
||||||
|
|
||||||
|
return tuple(y for x, y in sorted(widths.items()))
|
||||||
|
|
||||||
|
|
||||||
|
def iter_rows(
|
||||||
|
rows: t.Iterable[t.Tuple[str, str]], col_count: int
|
||||||
|
) -> t.Iterator[t.Tuple[str, ...]]:
|
||||||
|
for row in rows:
|
||||||
|
yield row + ("",) * (col_count - len(row))
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_text(
|
||||||
|
text: str,
|
||||||
|
width: int = 78,
|
||||||
|
initial_indent: str = "",
|
||||||
|
subsequent_indent: str = "",
|
||||||
|
preserve_paragraphs: bool = False,
|
||||||
|
) -> str:
|
||||||
|
"""A helper function that intelligently wraps text. By default, it
|
||||||
|
assumes that it operates on a single paragraph of text but if the
|
||||||
|
`preserve_paragraphs` parameter is provided it will intelligently
|
||||||
|
handle paragraphs (defined by two empty lines).
|
||||||
|
|
||||||
|
If paragraphs are handled, a paragraph can be prefixed with an empty
|
||||||
|
line containing the ``\\b`` character (``\\x08``) to indicate that
|
||||||
|
no rewrapping should happen in that block.
|
||||||
|
|
||||||
|
:param text: the text that should be rewrapped.
|
||||||
|
:param width: the maximum width for the text.
|
||||||
|
:param initial_indent: the initial indent that should be placed on the
|
||||||
|
first line as a string.
|
||||||
|
:param subsequent_indent: the indent string that should be placed on
|
||||||
|
each consecutive line.
|
||||||
|
:param preserve_paragraphs: if this flag is set then the wrapping will
|
||||||
|
intelligently handle paragraphs.
|
||||||
|
"""
|
||||||
|
from ._textwrap import TextWrapper
|
||||||
|
|
||||||
|
text = text.expandtabs()
|
||||||
|
wrapper = TextWrapper(
|
||||||
|
width,
|
||||||
|
initial_indent=initial_indent,
|
||||||
|
subsequent_indent=subsequent_indent,
|
||||||
|
replace_whitespace=False,
|
||||||
|
)
|
||||||
|
if not preserve_paragraphs:
|
||||||
|
return wrapper.fill(text)
|
||||||
|
|
||||||
|
p: t.List[t.Tuple[int, bool, str]] = []
|
||||||
|
buf: t.List[str] = []
|
||||||
|
indent = None
|
||||||
|
|
||||||
|
def _flush_par() -> None:
|
||||||
|
if not buf:
|
||||||
|
return
|
||||||
|
if buf[0].strip() == "\b":
|
||||||
|
p.append((indent or 0, True, "\n".join(buf[1:])))
|
||||||
|
else:
|
||||||
|
p.append((indent or 0, False, " ".join(buf)))
|
||||||
|
del buf[:]
|
||||||
|
|
||||||
|
for line in text.splitlines():
|
||||||
|
if not line:
|
||||||
|
_flush_par()
|
||||||
|
indent = None
|
||||||
|
else:
|
||||||
|
if indent is None:
|
||||||
|
orig_len = term_len(line)
|
||||||
|
line = line.lstrip()
|
||||||
|
indent = orig_len - term_len(line)
|
||||||
|
buf.append(line)
|
||||||
|
_flush_par()
|
||||||
|
|
||||||
|
rv = []
|
||||||
|
for indent, raw, text in p:
|
||||||
|
with wrapper.extra_indent(" " * indent):
|
||||||
|
if raw:
|
||||||
|
rv.append(wrapper.indent_only(text))
|
||||||
|
else:
|
||||||
|
rv.append(wrapper.fill(text))
|
||||||
|
|
||||||
|
return "\n\n".join(rv)
|
||||||
|
|
||||||
|
|
||||||
|
class HelpFormatter:
|
||||||
|
"""This class helps with formatting text-based help pages. It's
|
||||||
|
usually just needed for very special internal cases, but it's also
|
||||||
|
exposed so that developers can write their own fancy outputs.
|
||||||
|
|
||||||
|
At present, it always writes into memory.
|
||||||
|
|
||||||
|
:param indent_increment: the additional increment for each level.
|
||||||
|
:param width: the width for the text. This defaults to the terminal
|
||||||
|
width clamped to a maximum of 78.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
indent_increment: int = 2,
|
||||||
|
width: t.Optional[int] = None,
|
||||||
|
max_width: t.Optional[int] = None,
|
||||||
|
) -> None:
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
self.indent_increment = indent_increment
|
||||||
|
if max_width is None:
|
||||||
|
max_width = 80
|
||||||
|
if width is None:
|
||||||
|
width = FORCED_WIDTH
|
||||||
|
if width is None:
|
||||||
|
width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50)
|
||||||
|
self.width = width
|
||||||
|
self.current_indent = 0
|
||||||
|
self.buffer: t.List[str] = []
|
||||||
|
|
||||||
|
def write(self, string: str) -> None:
|
||||||
|
"""Writes a unicode string into the internal buffer."""
|
||||||
|
self.buffer.append(string)
|
||||||
|
|
||||||
|
def indent(self) -> None:
|
||||||
|
"""Increases the indentation."""
|
||||||
|
self.current_indent += self.indent_increment
|
||||||
|
|
||||||
|
def dedent(self) -> None:
|
||||||
|
"""Decreases the indentation."""
|
||||||
|
self.current_indent -= self.indent_increment
|
||||||
|
|
||||||
|
def write_usage(
|
||||||
|
self, prog: str, args: str = "", prefix: t.Optional[str] = None
|
||||||
|
) -> None:
|
||||||
|
"""Writes a usage line into the buffer.
|
||||||
|
|
||||||
|
:param prog: the program name.
|
||||||
|
:param args: whitespace separated list of arguments.
|
||||||
|
:param prefix: The prefix for the first line. Defaults to
|
||||||
|
``"Usage: "``.
|
||||||
|
"""
|
||||||
|
if prefix is None:
|
||||||
|
prefix = f"{_('Usage:')} "
|
||||||
|
|
||||||
|
usage_prefix = f"{prefix:>{self.current_indent}}{prog} "
|
||||||
|
text_width = self.width - self.current_indent
|
||||||
|
|
||||||
|
if text_width >= (term_len(usage_prefix) + 20):
|
||||||
|
# The arguments will fit to the right of the prefix.
|
||||||
|
indent = " " * term_len(usage_prefix)
|
||||||
|
self.write(
|
||||||
|
wrap_text(
|
||||||
|
args,
|
||||||
|
text_width,
|
||||||
|
initial_indent=usage_prefix,
|
||||||
|
subsequent_indent=indent,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# The prefix is too long, put the arguments on the next line.
|
||||||
|
self.write(usage_prefix)
|
||||||
|
self.write("\n")
|
||||||
|
indent = " " * (max(self.current_indent, term_len(prefix)) + 4)
|
||||||
|
self.write(
|
||||||
|
wrap_text(
|
||||||
|
args, text_width, initial_indent=indent, subsequent_indent=indent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.write("\n")
|
||||||
|
|
||||||
|
def write_heading(self, heading: str) -> None:
|
||||||
|
"""Writes a heading into the buffer."""
|
||||||
|
self.write(f"{'':>{self.current_indent}}{heading}:\n")
|
||||||
|
|
||||||
|
def write_paragraph(self) -> None:
|
||||||
|
"""Writes a paragraph into the buffer."""
|
||||||
|
if self.buffer:
|
||||||
|
self.write("\n")
|
||||||
|
|
||||||
|
def write_text(self, text: str) -> None:
|
||||||
|
"""Writes re-indented text into the buffer. This rewraps and
|
||||||
|
preserves paragraphs.
|
||||||
|
"""
|
||||||
|
indent = " " * self.current_indent
|
||||||
|
self.write(
|
||||||
|
wrap_text(
|
||||||
|
text,
|
||||||
|
self.width,
|
||||||
|
initial_indent=indent,
|
||||||
|
subsequent_indent=indent,
|
||||||
|
preserve_paragraphs=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.write("\n")
|
||||||
|
|
||||||
|
def write_dl(
|
||||||
|
self,
|
||||||
|
rows: t.Sequence[t.Tuple[str, str]],
|
||||||
|
col_max: int = 30,
|
||||||
|
col_spacing: int = 2,
|
||||||
|
) -> None:
|
||||||
|
"""Writes a definition list into the buffer. This is how options
|
||||||
|
and commands are usually formatted.
|
||||||
|
|
||||||
|
:param rows: a list of two item tuples for the terms and values.
|
||||||
|
:param col_max: the maximum width of the first column.
|
||||||
|
:param col_spacing: the number of spaces between the first and
|
||||||
|
second column.
|
||||||
|
"""
|
||||||
|
rows = list(rows)
|
||||||
|
widths = measure_table(rows)
|
||||||
|
if len(widths) != 2:
|
||||||
|
raise TypeError("Expected two columns for definition list")
|
||||||
|
|
||||||
|
first_col = min(widths[0], col_max) + col_spacing
|
||||||
|
|
||||||
|
for first, second in iter_rows(rows, len(widths)):
|
||||||
|
self.write(f"{'':>{self.current_indent}}{first}")
|
||||||
|
if not second:
|
||||||
|
self.write("\n")
|
||||||
|
continue
|
||||||
|
if term_len(first) <= first_col - col_spacing:
|
||||||
|
self.write(" " * (first_col - term_len(first)))
|
||||||
|
else:
|
||||||
|
self.write("\n")
|
||||||
|
self.write(" " * (first_col + self.current_indent))
|
||||||
|
|
||||||
|
text_width = max(self.width - first_col - 2, 10)
|
||||||
|
wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True)
|
||||||
|
lines = wrapped_text.splitlines()
|
||||||
|
|
||||||
|
if lines:
|
||||||
|
self.write(f"{lines[0]}\n")
|
||||||
|
|
||||||
|
for line in lines[1:]:
|
||||||
|
self.write(f"{'':>{first_col + self.current_indent}}{line}\n")
|
||||||
|
else:
|
||||||
|
self.write("\n")
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def section(self, name: str) -> t.Iterator[None]:
|
||||||
|
"""Helpful context manager that writes a paragraph, a heading,
|
||||||
|
and the indents.
|
||||||
|
|
||||||
|
:param name: the section name that is written as heading.
|
||||||
|
"""
|
||||||
|
self.write_paragraph()
|
||||||
|
self.write_heading(name)
|
||||||
|
self.indent()
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self.dedent()
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def indentation(self) -> t.Iterator[None]:
|
||||||
|
"""A context manager that increases the indentation."""
|
||||||
|
self.indent()
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self.dedent()
|
||||||
|
|
||||||
|
def getvalue(self) -> str:
|
||||||
|
"""Returns the buffer contents."""
|
||||||
|
return "".join(self.buffer)
|
||||||
|
|
||||||
|
|
||||||
|
def join_options(options: t.Sequence[str]) -> t.Tuple[str, bool]:
|
||||||
|
"""Given a list of option strings this joins them in the most appropriate
|
||||||
|
way and returns them in the form ``(formatted_string,
|
||||||
|
any_prefix_is_slash)`` where the second item in the tuple is a flag that
|
||||||
|
indicates if any of the option prefixes was a slash.
|
||||||
|
"""
|
||||||
|
rv = []
|
||||||
|
any_prefix_is_slash = False
|
||||||
|
|
||||||
|
for opt in options:
|
||||||
|
prefix = split_opt(opt)[0]
|
||||||
|
|
||||||
|
if prefix == "/":
|
||||||
|
any_prefix_is_slash = True
|
||||||
|
|
||||||
|
rv.append((len(prefix), opt))
|
||||||
|
|
||||||
|
rv.sort(key=lambda x: x[0])
|
||||||
|
return ", ".join(x[1] for x in rv), any_prefix_is_slash
|
||||||
68
dist/ba_data/python-site-packages/click/globals.py
vendored
Normal file
68
dist/ba_data/python-site-packages/click/globals.py
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import typing as t
|
||||||
|
from threading import local
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
|
from .core import Context
|
||||||
|
|
||||||
|
_local = local()
|
||||||
|
|
||||||
|
|
||||||
|
@t.overload
|
||||||
|
def get_current_context(silent: "te.Literal[False]" = False) -> "Context":
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@t.overload
|
||||||
|
def get_current_context(silent: bool = ...) -> t.Optional["Context"]:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_context(silent: bool = False) -> t.Optional["Context"]:
|
||||||
|
"""Returns the current click context. This can be used as a way to
|
||||||
|
access the current context object from anywhere. This is a more implicit
|
||||||
|
alternative to the :func:`pass_context` decorator. This function is
|
||||||
|
primarily useful for helpers such as :func:`echo` which might be
|
||||||
|
interested in changing its behavior based on the current context.
|
||||||
|
|
||||||
|
To push the current context, :meth:`Context.scope` can be used.
|
||||||
|
|
||||||
|
.. versionadded:: 5.0
|
||||||
|
|
||||||
|
:param silent: if set to `True` the return value is `None` if no context
|
||||||
|
is available. The default behavior is to raise a
|
||||||
|
:exc:`RuntimeError`.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return t.cast("Context", _local.stack[-1])
|
||||||
|
except (AttributeError, IndexError) as e:
|
||||||
|
if not silent:
|
||||||
|
raise RuntimeError("There is no active click context.") from e
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def push_context(ctx: "Context") -> None:
|
||||||
|
"""Pushes a new context to the current stack."""
|
||||||
|
_local.__dict__.setdefault("stack", []).append(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
def pop_context() -> None:
|
||||||
|
"""Removes the top level from the stack."""
|
||||||
|
_local.stack.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_color_default(color: t.Optional[bool] = None) -> t.Optional[bool]:
|
||||||
|
"""Internal helper to get the default value of the color flag. If a
|
||||||
|
value is passed it's returned unchanged, otherwise it's looked up from
|
||||||
|
the current context.
|
||||||
|
"""
|
||||||
|
if color is not None:
|
||||||
|
return color
|
||||||
|
|
||||||
|
ctx = get_current_context(silent=True)
|
||||||
|
|
||||||
|
if ctx is not None:
|
||||||
|
return ctx.color
|
||||||
|
|
||||||
|
return None
|
||||||
529
dist/ba_data/python-site-packages/click/parser.py
vendored
Normal file
529
dist/ba_data/python-site-packages/click/parser.py
vendored
Normal file
|
|
@ -0,0 +1,529 @@
|
||||||
|
"""
|
||||||
|
This module started out as largely a copy paste from the stdlib's
|
||||||
|
optparse module with the features removed that we do not need from
|
||||||
|
optparse because we implement them in Click on a higher level (for
|
||||||
|
instance type handling, help formatting and a lot more).
|
||||||
|
|
||||||
|
The plan is to remove more and more from here over time.
|
||||||
|
|
||||||
|
The reason this is a different module and not optparse from the stdlib
|
||||||
|
is that there are differences in 2.x and 3.x about the error messages
|
||||||
|
generated and optparse in the stdlib uses gettext for no good reason
|
||||||
|
and might cause us issues.
|
||||||
|
|
||||||
|
Click uses parts of optparse written by Gregory P. Ward and maintained
|
||||||
|
by the Python Software Foundation. This is limited to code in parser.py.
|
||||||
|
|
||||||
|
Copyright 2001-2006 Gregory P. Ward. All rights reserved.
|
||||||
|
Copyright 2002-2006 Python Software Foundation. All rights reserved.
|
||||||
|
"""
|
||||||
|
# This code uses parts of optparse written by Gregory P. Ward and
|
||||||
|
# maintained by the Python Software Foundation.
|
||||||
|
# Copyright 2001-2006 Gregory P. Ward
|
||||||
|
# Copyright 2002-2006 Python Software Foundation
|
||||||
|
import typing as t
|
||||||
|
from collections import deque
|
||||||
|
from gettext import gettext as _
|
||||||
|
from gettext import ngettext
|
||||||
|
|
||||||
|
from .exceptions import BadArgumentUsage
|
||||||
|
from .exceptions import BadOptionUsage
|
||||||
|
from .exceptions import NoSuchOption
|
||||||
|
from .exceptions import UsageError
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
|
from .core import Argument as CoreArgument
|
||||||
|
from .core import Context
|
||||||
|
from .core import Option as CoreOption
|
||||||
|
from .core import Parameter as CoreParameter
|
||||||
|
|
||||||
|
V = t.TypeVar("V")
|
||||||
|
|
||||||
|
# Sentinel value that indicates an option was passed as a flag without a
|
||||||
|
# value but is not a flag option. Option.consume_value uses this to
|
||||||
|
# prompt or use the flag_value.
|
||||||
|
_flag_needs_value = object()
|
||||||
|
|
||||||
|
|
||||||
|
def _unpack_args(
|
||||||
|
args: t.Sequence[str], nargs_spec: t.Sequence[int]
|
||||||
|
) -> t.Tuple[t.Sequence[t.Union[str, t.Sequence[t.Optional[str]], None]], t.List[str]]:
|
||||||
|
"""Given an iterable of arguments and an iterable of nargs specifications,
|
||||||
|
it returns a tuple with all the unpacked arguments at the first index
|
||||||
|
and all remaining arguments as the second.
|
||||||
|
|
||||||
|
The nargs specification is the number of arguments that should be consumed
|
||||||
|
or `-1` to indicate that this position should eat up all the remainders.
|
||||||
|
|
||||||
|
Missing items are filled with `None`.
|
||||||
|
"""
|
||||||
|
args = deque(args)
|
||||||
|
nargs_spec = deque(nargs_spec)
|
||||||
|
rv: t.List[t.Union[str, t.Tuple[t.Optional[str], ...], None]] = []
|
||||||
|
spos: t.Optional[int] = None
|
||||||
|
|
||||||
|
def _fetch(c: "te.Deque[V]") -> t.Optional[V]:
|
||||||
|
try:
|
||||||
|
if spos is None:
|
||||||
|
return c.popleft()
|
||||||
|
else:
|
||||||
|
return c.pop()
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
while nargs_spec:
|
||||||
|
nargs = _fetch(nargs_spec)
|
||||||
|
|
||||||
|
if nargs is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if nargs == 1:
|
||||||
|
rv.append(_fetch(args))
|
||||||
|
elif nargs > 1:
|
||||||
|
x = [_fetch(args) for _ in range(nargs)]
|
||||||
|
|
||||||
|
# If we're reversed, we're pulling in the arguments in reverse,
|
||||||
|
# so we need to turn them around.
|
||||||
|
if spos is not None:
|
||||||
|
x.reverse()
|
||||||
|
|
||||||
|
rv.append(tuple(x))
|
||||||
|
elif nargs < 0:
|
||||||
|
if spos is not None:
|
||||||
|
raise TypeError("Cannot have two nargs < 0")
|
||||||
|
|
||||||
|
spos = len(rv)
|
||||||
|
rv.append(None)
|
||||||
|
|
||||||
|
# spos is the position of the wildcard (star). If it's not `None`,
|
||||||
|
# we fill it with the remainder.
|
||||||
|
if spos is not None:
|
||||||
|
rv[spos] = tuple(args)
|
||||||
|
args = []
|
||||||
|
rv[spos + 1 :] = reversed(rv[spos + 1 :])
|
||||||
|
|
||||||
|
return tuple(rv), list(args)
|
||||||
|
|
||||||
|
|
||||||
|
def split_opt(opt: str) -> t.Tuple[str, str]:
|
||||||
|
first = opt[:1]
|
||||||
|
if first.isalnum():
|
||||||
|
return "", opt
|
||||||
|
if opt[1:2] == first:
|
||||||
|
return opt[:2], opt[2:]
|
||||||
|
return first, opt[1:]
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_opt(opt: str, ctx: t.Optional["Context"]) -> str:
|
||||||
|
if ctx is None or ctx.token_normalize_func is None:
|
||||||
|
return opt
|
||||||
|
prefix, opt = split_opt(opt)
|
||||||
|
return f"{prefix}{ctx.token_normalize_func(opt)}"
|
||||||
|
|
||||||
|
|
||||||
|
def split_arg_string(string: str) -> t.List[str]:
|
||||||
|
"""Split an argument string as with :func:`shlex.split`, but don't
|
||||||
|
fail if the string is incomplete. Ignores a missing closing quote or
|
||||||
|
incomplete escape sequence and uses the partial token as-is.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
split_arg_string("example 'my file")
|
||||||
|
["example", "my file"]
|
||||||
|
|
||||||
|
split_arg_string("example my\\")
|
||||||
|
["example", "my"]
|
||||||
|
|
||||||
|
:param string: String to split.
|
||||||
|
"""
|
||||||
|
import shlex
|
||||||
|
|
||||||
|
lex = shlex.shlex(string, posix=True)
|
||||||
|
lex.whitespace_split = True
|
||||||
|
lex.commenters = ""
|
||||||
|
out = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
for token in lex:
|
||||||
|
out.append(token)
|
||||||
|
except ValueError:
|
||||||
|
# Raised when end-of-string is reached in an invalid state. Use
|
||||||
|
# the partial token as-is. The quote or escape character is in
|
||||||
|
# lex.state, not lex.token.
|
||||||
|
out.append(lex.token)
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class Option:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
obj: "CoreOption",
|
||||||
|
opts: t.Sequence[str],
|
||||||
|
dest: t.Optional[str],
|
||||||
|
action: t.Optional[str] = None,
|
||||||
|
nargs: int = 1,
|
||||||
|
const: t.Optional[t.Any] = None,
|
||||||
|
):
|
||||||
|
self._short_opts = []
|
||||||
|
self._long_opts = []
|
||||||
|
self.prefixes = set()
|
||||||
|
|
||||||
|
for opt in opts:
|
||||||
|
prefix, value = split_opt(opt)
|
||||||
|
if not prefix:
|
||||||
|
raise ValueError(f"Invalid start character for option ({opt})")
|
||||||
|
self.prefixes.add(prefix[0])
|
||||||
|
if len(prefix) == 1 and len(value) == 1:
|
||||||
|
self._short_opts.append(opt)
|
||||||
|
else:
|
||||||
|
self._long_opts.append(opt)
|
||||||
|
self.prefixes.add(prefix)
|
||||||
|
|
||||||
|
if action is None:
|
||||||
|
action = "store"
|
||||||
|
|
||||||
|
self.dest = dest
|
||||||
|
self.action = action
|
||||||
|
self.nargs = nargs
|
||||||
|
self.const = const
|
||||||
|
self.obj = obj
|
||||||
|
|
||||||
|
@property
|
||||||
|
def takes_value(self) -> bool:
|
||||||
|
return self.action in ("store", "append")
|
||||||
|
|
||||||
|
def process(self, value: str, state: "ParsingState") -> None:
|
||||||
|
if self.action == "store":
|
||||||
|
state.opts[self.dest] = value # type: ignore
|
||||||
|
elif self.action == "store_const":
|
||||||
|
state.opts[self.dest] = self.const # type: ignore
|
||||||
|
elif self.action == "append":
|
||||||
|
state.opts.setdefault(self.dest, []).append(value) # type: ignore
|
||||||
|
elif self.action == "append_const":
|
||||||
|
state.opts.setdefault(self.dest, []).append(self.const) # type: ignore
|
||||||
|
elif self.action == "count":
|
||||||
|
state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore
|
||||||
|
else:
|
||||||
|
raise ValueError(f"unknown action '{self.action}'")
|
||||||
|
state.order.append(self.obj)
|
||||||
|
|
||||||
|
|
||||||
|
class Argument:
|
||||||
|
def __init__(self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1):
|
||||||
|
self.dest = dest
|
||||||
|
self.nargs = nargs
|
||||||
|
self.obj = obj
|
||||||
|
|
||||||
|
def process(
|
||||||
|
self,
|
||||||
|
value: t.Union[t.Optional[str], t.Sequence[t.Optional[str]]],
|
||||||
|
state: "ParsingState",
|
||||||
|
) -> None:
|
||||||
|
if self.nargs > 1:
|
||||||
|
assert value is not None
|
||||||
|
holes = sum(1 for x in value if x is None)
|
||||||
|
if holes == len(value):
|
||||||
|
value = None
|
||||||
|
elif holes != 0:
|
||||||
|
raise BadArgumentUsage(
|
||||||
|
_("Argument {name!r} takes {nargs} values.").format(
|
||||||
|
name=self.dest, nargs=self.nargs
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.nargs == -1 and self.obj.envvar is not None and value == ():
|
||||||
|
# Replace empty tuple with None so that a value from the
|
||||||
|
# environment may be tried.
|
||||||
|
value = None
|
||||||
|
|
||||||
|
state.opts[self.dest] = value # type: ignore
|
||||||
|
state.order.append(self.obj)
|
||||||
|
|
||||||
|
|
||||||
|
class ParsingState:
|
||||||
|
def __init__(self, rargs: t.List[str]) -> None:
|
||||||
|
self.opts: t.Dict[str, t.Any] = {}
|
||||||
|
self.largs: t.List[str] = []
|
||||||
|
self.rargs = rargs
|
||||||
|
self.order: t.List["CoreParameter"] = []
|
||||||
|
|
||||||
|
|
||||||
|
class OptionParser:
|
||||||
|
"""The option parser is an internal class that is ultimately used to
|
||||||
|
parse options and arguments. It's modelled after optparse and brings
|
||||||
|
a similar but vastly simplified API. It should generally not be used
|
||||||
|
directly as the high level Click classes wrap it for you.
|
||||||
|
|
||||||
|
It's not nearly as extensible as optparse or argparse as it does not
|
||||||
|
implement features that are implemented on a higher level (such as
|
||||||
|
types or defaults).
|
||||||
|
|
||||||
|
:param ctx: optionally the :class:`~click.Context` where this parser
|
||||||
|
should go with.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, ctx: t.Optional["Context"] = None) -> None:
|
||||||
|
#: The :class:`~click.Context` for this parser. This might be
|
||||||
|
#: `None` for some advanced use cases.
|
||||||
|
self.ctx = ctx
|
||||||
|
#: This controls how the parser deals with interspersed arguments.
|
||||||
|
#: If this is set to `False`, the parser will stop on the first
|
||||||
|
#: non-option. Click uses this to implement nested subcommands
|
||||||
|
#: safely.
|
||||||
|
self.allow_interspersed_args = True
|
||||||
|
#: This tells the parser how to deal with unknown options. By
|
||||||
|
#: default it will error out (which is sensible), but there is a
|
||||||
|
#: second mode where it will ignore it and continue processing
|
||||||
|
#: after shifting all the unknown options into the resulting args.
|
||||||
|
self.ignore_unknown_options = False
|
||||||
|
|
||||||
|
if ctx is not None:
|
||||||
|
self.allow_interspersed_args = ctx.allow_interspersed_args
|
||||||
|
self.ignore_unknown_options = ctx.ignore_unknown_options
|
||||||
|
|
||||||
|
self._short_opt: t.Dict[str, Option] = {}
|
||||||
|
self._long_opt: t.Dict[str, Option] = {}
|
||||||
|
self._opt_prefixes = {"-", "--"}
|
||||||
|
self._args: t.List[Argument] = []
|
||||||
|
|
||||||
|
def add_option(
|
||||||
|
self,
|
||||||
|
obj: "CoreOption",
|
||||||
|
opts: t.Sequence[str],
|
||||||
|
dest: t.Optional[str],
|
||||||
|
action: t.Optional[str] = None,
|
||||||
|
nargs: int = 1,
|
||||||
|
const: t.Optional[t.Any] = None,
|
||||||
|
) -> None:
|
||||||
|
"""Adds a new option named `dest` to the parser. The destination
|
||||||
|
is not inferred (unlike with optparse) and needs to be explicitly
|
||||||
|
provided. Action can be any of ``store``, ``store_const``,
|
||||||
|
``append``, ``append_const`` or ``count``.
|
||||||
|
|
||||||
|
The `obj` can be used to identify the option in the order list
|
||||||
|
that is returned from the parser.
|
||||||
|
"""
|
||||||
|
opts = [normalize_opt(opt, self.ctx) for opt in opts]
|
||||||
|
option = Option(obj, opts, dest, action=action, nargs=nargs, const=const)
|
||||||
|
self._opt_prefixes.update(option.prefixes)
|
||||||
|
for opt in option._short_opts:
|
||||||
|
self._short_opt[opt] = option
|
||||||
|
for opt in option._long_opts:
|
||||||
|
self._long_opt[opt] = option
|
||||||
|
|
||||||
|
def add_argument(
|
||||||
|
self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1
|
||||||
|
) -> None:
|
||||||
|
"""Adds a positional argument named `dest` to the parser.
|
||||||
|
|
||||||
|
The `obj` can be used to identify the option in the order list
|
||||||
|
that is returned from the parser.
|
||||||
|
"""
|
||||||
|
self._args.append(Argument(obj, dest=dest, nargs=nargs))
|
||||||
|
|
||||||
|
def parse_args(
|
||||||
|
self, args: t.List[str]
|
||||||
|
) -> t.Tuple[t.Dict[str, t.Any], t.List[str], t.List["CoreParameter"]]:
|
||||||
|
"""Parses positional arguments and returns ``(values, args, order)``
|
||||||
|
for the parsed options and arguments as well as the leftover
|
||||||
|
arguments if there are any. The order is a list of objects as they
|
||||||
|
appear on the command line. If arguments appear multiple times they
|
||||||
|
will be memorized multiple times as well.
|
||||||
|
"""
|
||||||
|
state = ParsingState(args)
|
||||||
|
try:
|
||||||
|
self._process_args_for_options(state)
|
||||||
|
self._process_args_for_args(state)
|
||||||
|
except UsageError:
|
||||||
|
if self.ctx is None or not self.ctx.resilient_parsing:
|
||||||
|
raise
|
||||||
|
return state.opts, state.largs, state.order
|
||||||
|
|
||||||
|
def _process_args_for_args(self, state: ParsingState) -> None:
|
||||||
|
pargs, args = _unpack_args(
|
||||||
|
state.largs + state.rargs, [x.nargs for x in self._args]
|
||||||
|
)
|
||||||
|
|
||||||
|
for idx, arg in enumerate(self._args):
|
||||||
|
arg.process(pargs[idx], state)
|
||||||
|
|
||||||
|
state.largs = args
|
||||||
|
state.rargs = []
|
||||||
|
|
||||||
|
def _process_args_for_options(self, state: ParsingState) -> None:
|
||||||
|
while state.rargs:
|
||||||
|
arg = state.rargs.pop(0)
|
||||||
|
arglen = len(arg)
|
||||||
|
# Double dashes always handled explicitly regardless of what
|
||||||
|
# prefixes are valid.
|
||||||
|
if arg == "--":
|
||||||
|
return
|
||||||
|
elif arg[:1] in self._opt_prefixes and arglen > 1:
|
||||||
|
self._process_opts(arg, state)
|
||||||
|
elif self.allow_interspersed_args:
|
||||||
|
state.largs.append(arg)
|
||||||
|
else:
|
||||||
|
state.rargs.insert(0, arg)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Say this is the original argument list:
|
||||||
|
# [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
|
||||||
|
# ^
|
||||||
|
# (we are about to process arg(i)).
|
||||||
|
#
|
||||||
|
# Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
|
||||||
|
# [arg0, ..., arg(i-1)] (any options and their arguments will have
|
||||||
|
# been removed from largs).
|
||||||
|
#
|
||||||
|
# The while loop will usually consume 1 or more arguments per pass.
|
||||||
|
# If it consumes 1 (eg. arg is an option that takes no arguments),
|
||||||
|
# then after _process_arg() is done the situation is:
|
||||||
|
#
|
||||||
|
# largs = subset of [arg0, ..., arg(i)]
|
||||||
|
# rargs = [arg(i+1), ..., arg(N-1)]
|
||||||
|
#
|
||||||
|
# If allow_interspersed_args is false, largs will always be
|
||||||
|
# *empty* -- still a subset of [arg0, ..., arg(i-1)], but
|
||||||
|
# not a very interesting subset!
|
||||||
|
|
||||||
|
def _match_long_opt(
|
||||||
|
self, opt: str, explicit_value: t.Optional[str], state: ParsingState
|
||||||
|
) -> None:
|
||||||
|
if opt not in self._long_opt:
|
||||||
|
from difflib import get_close_matches
|
||||||
|
|
||||||
|
possibilities = get_close_matches(opt, self._long_opt)
|
||||||
|
raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx)
|
||||||
|
|
||||||
|
option = self._long_opt[opt]
|
||||||
|
if option.takes_value:
|
||||||
|
# At this point it's safe to modify rargs by injecting the
|
||||||
|
# explicit value, because no exception is raised in this
|
||||||
|
# branch. This means that the inserted value will be fully
|
||||||
|
# consumed.
|
||||||
|
if explicit_value is not None:
|
||||||
|
state.rargs.insert(0, explicit_value)
|
||||||
|
|
||||||
|
value = self._get_value_from_state(opt, option, state)
|
||||||
|
|
||||||
|
elif explicit_value is not None:
|
||||||
|
raise BadOptionUsage(
|
||||||
|
opt, _("Option {name!r} does not take a value.").format(name=opt)
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
value = None
|
||||||
|
|
||||||
|
option.process(value, state)
|
||||||
|
|
||||||
|
def _match_short_opt(self, arg: str, state: ParsingState) -> None:
|
||||||
|
stop = False
|
||||||
|
i = 1
|
||||||
|
prefix = arg[0]
|
||||||
|
unknown_options = []
|
||||||
|
|
||||||
|
for ch in arg[1:]:
|
||||||
|
opt = normalize_opt(f"{prefix}{ch}", self.ctx)
|
||||||
|
option = self._short_opt.get(opt)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if not option:
|
||||||
|
if self.ignore_unknown_options:
|
||||||
|
unknown_options.append(ch)
|
||||||
|
continue
|
||||||
|
raise NoSuchOption(opt, ctx=self.ctx)
|
||||||
|
if option.takes_value:
|
||||||
|
# Any characters left in arg? Pretend they're the
|
||||||
|
# next arg, and stop consuming characters of arg.
|
||||||
|
if i < len(arg):
|
||||||
|
state.rargs.insert(0, arg[i:])
|
||||||
|
stop = True
|
||||||
|
|
||||||
|
value = self._get_value_from_state(opt, option, state)
|
||||||
|
|
||||||
|
else:
|
||||||
|
value = None
|
||||||
|
|
||||||
|
option.process(value, state)
|
||||||
|
|
||||||
|
if stop:
|
||||||
|
break
|
||||||
|
|
||||||
|
# If we got any unknown options we re-combinate the string of the
|
||||||
|
# remaining options and re-attach the prefix, then report that
|
||||||
|
# to the state as new larg. This way there is basic combinatorics
|
||||||
|
# that can be achieved while still ignoring unknown arguments.
|
||||||
|
if self.ignore_unknown_options and unknown_options:
|
||||||
|
state.largs.append(f"{prefix}{''.join(unknown_options)}")
|
||||||
|
|
||||||
|
def _get_value_from_state(
|
||||||
|
self, option_name: str, option: Option, state: ParsingState
|
||||||
|
) -> t.Any:
|
||||||
|
nargs = option.nargs
|
||||||
|
|
||||||
|
if len(state.rargs) < nargs:
|
||||||
|
if option.obj._flag_needs_value:
|
||||||
|
# Option allows omitting the value.
|
||||||
|
value = _flag_needs_value
|
||||||
|
else:
|
||||||
|
raise BadOptionUsage(
|
||||||
|
option_name,
|
||||||
|
ngettext(
|
||||||
|
"Option {name!r} requires an argument.",
|
||||||
|
"Option {name!r} requires {nargs} arguments.",
|
||||||
|
nargs,
|
||||||
|
).format(name=option_name, nargs=nargs),
|
||||||
|
)
|
||||||
|
elif nargs == 1:
|
||||||
|
next_rarg = state.rargs[0]
|
||||||
|
|
||||||
|
if (
|
||||||
|
option.obj._flag_needs_value
|
||||||
|
and isinstance(next_rarg, str)
|
||||||
|
and next_rarg[:1] in self._opt_prefixes
|
||||||
|
and len(next_rarg) > 1
|
||||||
|
):
|
||||||
|
# The next arg looks like the start of an option, don't
|
||||||
|
# use it as the value if omitting the value is allowed.
|
||||||
|
value = _flag_needs_value
|
||||||
|
else:
|
||||||
|
value = state.rargs.pop(0)
|
||||||
|
else:
|
||||||
|
value = tuple(state.rargs[:nargs])
|
||||||
|
del state.rargs[:nargs]
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _process_opts(self, arg: str, state: ParsingState) -> None:
|
||||||
|
explicit_value = None
|
||||||
|
# Long option handling happens in two parts. The first part is
|
||||||
|
# supporting explicitly attached values. In any case, we will try
|
||||||
|
# to long match the option first.
|
||||||
|
if "=" in arg:
|
||||||
|
long_opt, explicit_value = arg.split("=", 1)
|
||||||
|
else:
|
||||||
|
long_opt = arg
|
||||||
|
norm_long_opt = normalize_opt(long_opt, self.ctx)
|
||||||
|
|
||||||
|
# At this point we will match the (assumed) long option through
|
||||||
|
# the long option matching code. Note that this allows options
|
||||||
|
# like "-foo" to be matched as long options.
|
||||||
|
try:
|
||||||
|
self._match_long_opt(norm_long_opt, explicit_value, state)
|
||||||
|
except NoSuchOption:
|
||||||
|
# At this point the long option matching failed, and we need
|
||||||
|
# to try with short options. However there is a special rule
|
||||||
|
# which says, that if we have a two character options prefix
|
||||||
|
# (applies to "--foo" for instance), we do not dispatch to the
|
||||||
|
# short option code and will instead raise the no option
|
||||||
|
# error.
|
||||||
|
if arg[:2] not in self._opt_prefixes:
|
||||||
|
self._match_short_opt(arg, state)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.ignore_unknown_options:
|
||||||
|
raise
|
||||||
|
|
||||||
|
state.largs.append(arg)
|
||||||
0
dist/ba_data/python-site-packages/click/py.typed
vendored
Normal file
0
dist/ba_data/python-site-packages/click/py.typed
vendored
Normal file
581
dist/ba_data/python-site-packages/click/shell_completion.py
vendored
Normal file
581
dist/ba_data/python-site-packages/click/shell_completion.py
vendored
Normal file
|
|
@ -0,0 +1,581 @@
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import typing as t
|
||||||
|
from gettext import gettext as _
|
||||||
|
|
||||||
|
from .core import Argument
|
||||||
|
from .core import BaseCommand
|
||||||
|
from .core import Context
|
||||||
|
from .core import MultiCommand
|
||||||
|
from .core import Option
|
||||||
|
from .core import Parameter
|
||||||
|
from .core import ParameterSource
|
||||||
|
from .parser import split_arg_string
|
||||||
|
from .utils import echo
|
||||||
|
|
||||||
|
|
||||||
|
def shell_complete(
|
||||||
|
cli: BaseCommand,
|
||||||
|
ctx_args: t.Dict[str, t.Any],
|
||||||
|
prog_name: str,
|
||||||
|
complete_var: str,
|
||||||
|
instruction: str,
|
||||||
|
) -> int:
|
||||||
|
"""Perform shell completion for the given CLI program.
|
||||||
|
|
||||||
|
:param cli: Command being called.
|
||||||
|
:param ctx_args: Extra arguments to pass to
|
||||||
|
``cli.make_context``.
|
||||||
|
:param prog_name: Name of the executable in the shell.
|
||||||
|
:param complete_var: Name of the environment variable that holds
|
||||||
|
the completion instruction.
|
||||||
|
:param instruction: Value of ``complete_var`` with the completion
|
||||||
|
instruction and shell, in the form ``instruction_shell``.
|
||||||
|
:return: Status code to exit with.
|
||||||
|
"""
|
||||||
|
shell, _, instruction = instruction.partition("_")
|
||||||
|
comp_cls = get_completion_class(shell)
|
||||||
|
|
||||||
|
if comp_cls is None:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
comp = comp_cls(cli, ctx_args, prog_name, complete_var)
|
||||||
|
|
||||||
|
if instruction == "source":
|
||||||
|
echo(comp.source())
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if instruction == "complete":
|
||||||
|
echo(comp.complete())
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
class CompletionItem:
|
||||||
|
"""Represents a completion value and metadata about the value. The
|
||||||
|
default metadata is ``type`` to indicate special shell handling,
|
||||||
|
and ``help`` if a shell supports showing a help string next to the
|
||||||
|
value.
|
||||||
|
|
||||||
|
Arbitrary parameters can be passed when creating the object, and
|
||||||
|
accessed using ``item.attr``. If an attribute wasn't passed,
|
||||||
|
accessing it returns ``None``.
|
||||||
|
|
||||||
|
:param value: The completion suggestion.
|
||||||
|
:param type: Tells the shell script to provide special completion
|
||||||
|
support for the type. Click uses ``"dir"`` and ``"file"``.
|
||||||
|
:param help: String shown next to the value if supported.
|
||||||
|
:param kwargs: Arbitrary metadata. The built-in implementations
|
||||||
|
don't use this, but custom type completions paired with custom
|
||||||
|
shell support could use it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ("value", "type", "help", "_info")
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
value: t.Any,
|
||||||
|
type: str = "plain",
|
||||||
|
help: t.Optional[str] = None,
|
||||||
|
**kwargs: t.Any,
|
||||||
|
) -> None:
|
||||||
|
self.value = value
|
||||||
|
self.type = type
|
||||||
|
self.help = help
|
||||||
|
self._info = kwargs
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> t.Any:
|
||||||
|
return self._info.get(name)
|
||||||
|
|
||||||
|
|
||||||
|
# Only Bash >= 4.4 has the nosort option.
|
||||||
|
_SOURCE_BASH = """\
|
||||||
|
%(complete_func)s() {
|
||||||
|
local IFS=$'\\n'
|
||||||
|
local response
|
||||||
|
|
||||||
|
response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \
|
||||||
|
%(complete_var)s=bash_complete $1)
|
||||||
|
|
||||||
|
for completion in $response; do
|
||||||
|
IFS=',' read type value <<< "$completion"
|
||||||
|
|
||||||
|
if [[ $type == 'dir' ]]; then
|
||||||
|
COMPREPLY=()
|
||||||
|
compopt -o dirnames
|
||||||
|
elif [[ $type == 'file' ]]; then
|
||||||
|
COMPREPLY=()
|
||||||
|
compopt -o default
|
||||||
|
elif [[ $type == 'plain' ]]; then
|
||||||
|
COMPREPLY+=($value)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
%(complete_func)s_setup() {
|
||||||
|
complete -o nosort -F %(complete_func)s %(prog_name)s
|
||||||
|
}
|
||||||
|
|
||||||
|
%(complete_func)s_setup;
|
||||||
|
"""
|
||||||
|
|
||||||
|
_SOURCE_ZSH = """\
|
||||||
|
#compdef %(prog_name)s
|
||||||
|
|
||||||
|
%(complete_func)s() {
|
||||||
|
local -a completions
|
||||||
|
local -a completions_with_descriptions
|
||||||
|
local -a response
|
||||||
|
(( ! $+commands[%(prog_name)s] )) && return 1
|
||||||
|
|
||||||
|
response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \
|
||||||
|
%(complete_var)s=zsh_complete %(prog_name)s)}")
|
||||||
|
|
||||||
|
for type key descr in ${response}; do
|
||||||
|
if [[ "$type" == "plain" ]]; then
|
||||||
|
if [[ "$descr" == "_" ]]; then
|
||||||
|
completions+=("$key")
|
||||||
|
else
|
||||||
|
completions_with_descriptions+=("$key":"$descr")
|
||||||
|
fi
|
||||||
|
elif [[ "$type" == "dir" ]]; then
|
||||||
|
_path_files -/
|
||||||
|
elif [[ "$type" == "file" ]]; then
|
||||||
|
_path_files -f
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$completions_with_descriptions" ]; then
|
||||||
|
_describe -V unsorted completions_with_descriptions -U
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$completions" ]; then
|
||||||
|
compadd -U -V unsorted -a completions
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
compdef %(complete_func)s %(prog_name)s;
|
||||||
|
"""
|
||||||
|
|
||||||
|
_SOURCE_FISH = """\
|
||||||
|
function %(complete_func)s;
|
||||||
|
set -l response;
|
||||||
|
|
||||||
|
for value in (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \
|
||||||
|
COMP_CWORD=(commandline -t) %(prog_name)s);
|
||||||
|
set response $response $value;
|
||||||
|
end;
|
||||||
|
|
||||||
|
for completion in $response;
|
||||||
|
set -l metadata (string split "," $completion);
|
||||||
|
|
||||||
|
if test $metadata[1] = "dir";
|
||||||
|
__fish_complete_directories $metadata[2];
|
||||||
|
else if test $metadata[1] = "file";
|
||||||
|
__fish_complete_path $metadata[2];
|
||||||
|
else if test $metadata[1] = "plain";
|
||||||
|
echo $metadata[2];
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
complete --no-files --command %(prog_name)s --arguments \
|
||||||
|
"(%(complete_func)s)";
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ShellComplete:
|
||||||
|
"""Base class for providing shell completion support. A subclass for
|
||||||
|
a given shell will override attributes and methods to implement the
|
||||||
|
completion instructions (``source`` and ``complete``).
|
||||||
|
|
||||||
|
:param cli: Command being called.
|
||||||
|
:param prog_name: Name of the executable in the shell.
|
||||||
|
:param complete_var: Name of the environment variable that holds
|
||||||
|
the completion instruction.
|
||||||
|
|
||||||
|
.. versionadded:: 8.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
name: t.ClassVar[str]
|
||||||
|
"""Name to register the shell as with :func:`add_completion_class`.
|
||||||
|
This is used in completion instructions (``{name}_source`` and
|
||||||
|
``{name}_complete``).
|
||||||
|
"""
|
||||||
|
|
||||||
|
source_template: t.ClassVar[str]
|
||||||
|
"""Completion script template formatted by :meth:`source`. This must
|
||||||
|
be provided by subclasses.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
cli: BaseCommand,
|
||||||
|
ctx_args: t.Dict[str, t.Any],
|
||||||
|
prog_name: str,
|
||||||
|
complete_var: str,
|
||||||
|
) -> None:
|
||||||
|
self.cli = cli
|
||||||
|
self.ctx_args = ctx_args
|
||||||
|
self.prog_name = prog_name
|
||||||
|
self.complete_var = complete_var
|
||||||
|
|
||||||
|
@property
|
||||||
|
def func_name(self) -> str:
|
||||||
|
"""The name of the shell function defined by the completion
|
||||||
|
script.
|
||||||
|
"""
|
||||||
|
safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), re.ASCII)
|
||||||
|
return f"_{safe_name}_completion"
|
||||||
|
|
||||||
|
def source_vars(self) -> t.Dict[str, t.Any]:
|
||||||
|
"""Vars for formatting :attr:`source_template`.
|
||||||
|
|
||||||
|
By default this provides ``complete_func``, ``complete_var``,
|
||||||
|
and ``prog_name``.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"complete_func": self.func_name,
|
||||||
|
"complete_var": self.complete_var,
|
||||||
|
"prog_name": self.prog_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
def source(self) -> str:
|
||||||
|
"""Produce the shell script that defines the completion
|
||||||
|
function. By default this ``%``-style formats
|
||||||
|
:attr:`source_template` with the dict returned by
|
||||||
|
:meth:`source_vars`.
|
||||||
|
"""
|
||||||
|
return self.source_template % self.source_vars()
|
||||||
|
|
||||||
|
def get_completion_args(self) -> t.Tuple[t.List[str], str]:
|
||||||
|
"""Use the env vars defined by the shell script to return a
|
||||||
|
tuple of ``args, incomplete``. This must be implemented by
|
||||||
|
subclasses.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_completions(
|
||||||
|
self, args: t.List[str], incomplete: str
|
||||||
|
) -> t.List[CompletionItem]:
|
||||||
|
"""Determine the context and last complete command or parameter
|
||||||
|
from the complete args. Call that object's ``shell_complete``
|
||||||
|
method to get the completions for the incomplete value.
|
||||||
|
|
||||||
|
:param args: List of complete args before the incomplete value.
|
||||||
|
:param incomplete: Value being completed. May be empty.
|
||||||
|
"""
|
||||||
|
ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args)
|
||||||
|
obj, incomplete = _resolve_incomplete(ctx, args, incomplete)
|
||||||
|
return obj.shell_complete(ctx, incomplete)
|
||||||
|
|
||||||
|
def format_completion(self, item: CompletionItem) -> str:
|
||||||
|
"""Format a completion item into the form recognized by the
|
||||||
|
shell script. This must be implemented by subclasses.
|
||||||
|
|
||||||
|
:param item: Completion item to format.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def complete(self) -> str:
|
||||||
|
"""Produce the completion data to send back to the shell.
|
||||||
|
|
||||||
|
By default this calls :meth:`get_completion_args`, gets the
|
||||||
|
completions, then calls :meth:`format_completion` for each
|
||||||
|
completion.
|
||||||
|
"""
|
||||||
|
args, incomplete = self.get_completion_args()
|
||||||
|
completions = self.get_completions(args, incomplete)
|
||||||
|
out = [self.format_completion(item) for item in completions]
|
||||||
|
return "\n".join(out)
|
||||||
|
|
||||||
|
|
||||||
|
class BashComplete(ShellComplete):
|
||||||
|
"""Shell completion for Bash."""
|
||||||
|
|
||||||
|
name = "bash"
|
||||||
|
source_template = _SOURCE_BASH
|
||||||
|
|
||||||
|
def _check_version(self) -> None:
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
output = subprocess.run(
|
||||||
|
["bash", "-c", "echo ${BASH_VERSION}"], stdout=subprocess.PIPE
|
||||||
|
)
|
||||||
|
match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode())
|
||||||
|
|
||||||
|
if match is not None:
|
||||||
|
major, minor = match.groups()
|
||||||
|
|
||||||
|
if major < "4" or major == "4" and minor < "4":
|
||||||
|
raise RuntimeError(
|
||||||
|
_(
|
||||||
|
"Shell completion is not supported for Bash"
|
||||||
|
" versions older than 4.4."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(
|
||||||
|
_("Couldn't detect Bash version, shell completion is not supported.")
|
||||||
|
)
|
||||||
|
|
||||||
|
def source(self) -> str:
|
||||||
|
self._check_version()
|
||||||
|
return super().source()
|
||||||
|
|
||||||
|
def get_completion_args(self) -> t.Tuple[t.List[str], str]:
|
||||||
|
cwords = split_arg_string(os.environ["COMP_WORDS"])
|
||||||
|
cword = int(os.environ["COMP_CWORD"])
|
||||||
|
args = cwords[1:cword]
|
||||||
|
|
||||||
|
try:
|
||||||
|
incomplete = cwords[cword]
|
||||||
|
except IndexError:
|
||||||
|
incomplete = ""
|
||||||
|
|
||||||
|
return args, incomplete
|
||||||
|
|
||||||
|
def format_completion(self, item: CompletionItem) -> str:
|
||||||
|
return f"{item.type},{item.value}"
|
||||||
|
|
||||||
|
|
||||||
|
class ZshComplete(ShellComplete):
|
||||||
|
"""Shell completion for Zsh."""
|
||||||
|
|
||||||
|
name = "zsh"
|
||||||
|
source_template = _SOURCE_ZSH
|
||||||
|
|
||||||
|
def get_completion_args(self) -> t.Tuple[t.List[str], str]:
|
||||||
|
cwords = split_arg_string(os.environ["COMP_WORDS"])
|
||||||
|
cword = int(os.environ["COMP_CWORD"])
|
||||||
|
args = cwords[1:cword]
|
||||||
|
|
||||||
|
try:
|
||||||
|
incomplete = cwords[cword]
|
||||||
|
except IndexError:
|
||||||
|
incomplete = ""
|
||||||
|
|
||||||
|
return args, incomplete
|
||||||
|
|
||||||
|
def format_completion(self, item: CompletionItem) -> str:
|
||||||
|
return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}"
|
||||||
|
|
||||||
|
|
||||||
|
class FishComplete(ShellComplete):
|
||||||
|
"""Shell completion for Fish."""
|
||||||
|
|
||||||
|
name = "fish"
|
||||||
|
source_template = _SOURCE_FISH
|
||||||
|
|
||||||
|
def get_completion_args(self) -> t.Tuple[t.List[str], str]:
|
||||||
|
cwords = split_arg_string(os.environ["COMP_WORDS"])
|
||||||
|
incomplete = os.environ["COMP_CWORD"]
|
||||||
|
args = cwords[1:]
|
||||||
|
|
||||||
|
# Fish stores the partial word in both COMP_WORDS and
|
||||||
|
# COMP_CWORD, remove it from complete args.
|
||||||
|
if incomplete and args and args[-1] == incomplete:
|
||||||
|
args.pop()
|
||||||
|
|
||||||
|
return args, incomplete
|
||||||
|
|
||||||
|
def format_completion(self, item: CompletionItem) -> str:
|
||||||
|
if item.help:
|
||||||
|
return f"{item.type},{item.value}\t{item.help}"
|
||||||
|
|
||||||
|
return f"{item.type},{item.value}"
|
||||||
|
|
||||||
|
|
||||||
|
_available_shells: t.Dict[str, t.Type[ShellComplete]] = {
|
||||||
|
"bash": BashComplete,
|
||||||
|
"fish": FishComplete,
|
||||||
|
"zsh": ZshComplete,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def add_completion_class(
|
||||||
|
cls: t.Type[ShellComplete], name: t.Optional[str] = None
|
||||||
|
) -> None:
|
||||||
|
"""Register a :class:`ShellComplete` subclass under the given name.
|
||||||
|
The name will be provided by the completion instruction environment
|
||||||
|
variable during completion.
|
||||||
|
|
||||||
|
:param cls: The completion class that will handle completion for the
|
||||||
|
shell.
|
||||||
|
:param name: Name to register the class under. Defaults to the
|
||||||
|
class's ``name`` attribute.
|
||||||
|
"""
|
||||||
|
if name is None:
|
||||||
|
name = cls.name
|
||||||
|
|
||||||
|
_available_shells[name] = cls
|
||||||
|
|
||||||
|
|
||||||
|
def get_completion_class(shell: str) -> t.Optional[t.Type[ShellComplete]]:
|
||||||
|
"""Look up a registered :class:`ShellComplete` subclass by the name
|
||||||
|
provided by the completion instruction environment variable. If the
|
||||||
|
name isn't registered, returns ``None``.
|
||||||
|
|
||||||
|
:param shell: Name the class is registered under.
|
||||||
|
"""
|
||||||
|
return _available_shells.get(shell)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool:
|
||||||
|
"""Determine if the given parameter is an argument that can still
|
||||||
|
accept values.
|
||||||
|
|
||||||
|
:param ctx: Invocation context for the command represented by the
|
||||||
|
parsed complete args.
|
||||||
|
:param param: Argument object being checked.
|
||||||
|
"""
|
||||||
|
if not isinstance(param, Argument):
|
||||||
|
return False
|
||||||
|
|
||||||
|
assert param.name is not None
|
||||||
|
value = ctx.params[param.name]
|
||||||
|
return (
|
||||||
|
param.nargs == -1
|
||||||
|
or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE
|
||||||
|
or (
|
||||||
|
param.nargs > 1
|
||||||
|
and isinstance(value, (tuple, list))
|
||||||
|
and len(value) < param.nargs
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _start_of_option(value: str) -> bool:
|
||||||
|
"""Check if the value looks like the start of an option."""
|
||||||
|
if not value:
|
||||||
|
return False
|
||||||
|
|
||||||
|
c = value[0]
|
||||||
|
# Allow "/" since that starts a path.
|
||||||
|
return not c.isalnum() and c != "/"
|
||||||
|
|
||||||
|
|
||||||
|
def _is_incomplete_option(args: t.List[str], param: Parameter) -> bool:
|
||||||
|
"""Determine if the given parameter is an option that needs a value.
|
||||||
|
|
||||||
|
:param args: List of complete args before the incomplete value.
|
||||||
|
:param param: Option object being checked.
|
||||||
|
"""
|
||||||
|
if not isinstance(param, Option):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if param.is_flag:
|
||||||
|
return False
|
||||||
|
|
||||||
|
last_option = None
|
||||||
|
|
||||||
|
for index, arg in enumerate(reversed(args)):
|
||||||
|
if index + 1 > param.nargs:
|
||||||
|
break
|
||||||
|
|
||||||
|
if _start_of_option(arg):
|
||||||
|
last_option = arg
|
||||||
|
|
||||||
|
return last_option is not None and last_option in param.opts
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_context(
|
||||||
|
cli: BaseCommand, ctx_args: t.Dict[str, t.Any], prog_name: str, args: t.List[str]
|
||||||
|
) -> Context:
|
||||||
|
"""Produce the context hierarchy starting with the command and
|
||||||
|
traversing the complete arguments. This only follows the commands,
|
||||||
|
it doesn't trigger input prompts or callbacks.
|
||||||
|
|
||||||
|
:param cli: Command being called.
|
||||||
|
:param prog_name: Name of the executable in the shell.
|
||||||
|
:param args: List of complete args before the incomplete value.
|
||||||
|
"""
|
||||||
|
ctx_args["resilient_parsing"] = True
|
||||||
|
ctx = cli.make_context(prog_name, args.copy(), **ctx_args)
|
||||||
|
args = ctx.protected_args + ctx.args
|
||||||
|
|
||||||
|
while args:
|
||||||
|
command = ctx.command
|
||||||
|
|
||||||
|
if isinstance(command, MultiCommand):
|
||||||
|
if not command.chain:
|
||||||
|
name, cmd, args = command.resolve_command(ctx, args)
|
||||||
|
|
||||||
|
if cmd is None:
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True)
|
||||||
|
args = ctx.protected_args + ctx.args
|
||||||
|
else:
|
||||||
|
while args:
|
||||||
|
name, cmd, args = command.resolve_command(ctx, args)
|
||||||
|
|
||||||
|
if cmd is None:
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
sub_ctx = cmd.make_context(
|
||||||
|
name,
|
||||||
|
args,
|
||||||
|
parent=ctx,
|
||||||
|
allow_extra_args=True,
|
||||||
|
allow_interspersed_args=False,
|
||||||
|
resilient_parsing=True,
|
||||||
|
)
|
||||||
|
args = sub_ctx.args
|
||||||
|
|
||||||
|
ctx = sub_ctx
|
||||||
|
args = [*sub_ctx.protected_args, *sub_ctx.args]
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_incomplete(
|
||||||
|
ctx: Context, args: t.List[str], incomplete: str
|
||||||
|
) -> t.Tuple[t.Union[BaseCommand, Parameter], str]:
|
||||||
|
"""Find the Click object that will handle the completion of the
|
||||||
|
incomplete value. Return the object and the incomplete value.
|
||||||
|
|
||||||
|
:param ctx: Invocation context for the command represented by
|
||||||
|
the parsed complete args.
|
||||||
|
:param args: List of complete args before the incomplete value.
|
||||||
|
:param incomplete: Value being completed. May be empty.
|
||||||
|
"""
|
||||||
|
# Different shells treat an "=" between a long option name and
|
||||||
|
# value differently. Might keep the value joined, return the "="
|
||||||
|
# as a separate item, or return the split name and value. Always
|
||||||
|
# split and discard the "=" to make completion easier.
|
||||||
|
if incomplete == "=":
|
||||||
|
incomplete = ""
|
||||||
|
elif "=" in incomplete and _start_of_option(incomplete):
|
||||||
|
name, _, incomplete = incomplete.partition("=")
|
||||||
|
args.append(name)
|
||||||
|
|
||||||
|
# The "--" marker tells Click to stop treating values as options
|
||||||
|
# even if they start with the option character. If it hasn't been
|
||||||
|
# given and the incomplete arg looks like an option, the current
|
||||||
|
# command will provide option name completions.
|
||||||
|
if "--" not in args and _start_of_option(incomplete):
|
||||||
|
return ctx.command, incomplete
|
||||||
|
|
||||||
|
params = ctx.command.get_params(ctx)
|
||||||
|
|
||||||
|
# If the last complete arg is an option name with an incomplete
|
||||||
|
# value, the option will provide value completions.
|
||||||
|
for param in params:
|
||||||
|
if _is_incomplete_option(args, param):
|
||||||
|
return param, incomplete
|
||||||
|
|
||||||
|
# It's not an option name or value. The first argument without a
|
||||||
|
# parsed value will provide value completions.
|
||||||
|
for param in params:
|
||||||
|
if _is_incomplete_argument(ctx, param):
|
||||||
|
return param, incomplete
|
||||||
|
|
||||||
|
# There were no unparsed arguments, the command may be a group that
|
||||||
|
# will provide command name completions.
|
||||||
|
return ctx.command, incomplete
|
||||||
806
dist/ba_data/python-site-packages/click/termui.py
vendored
Normal file
806
dist/ba_data/python-site-packages/click/termui.py
vendored
Normal file
|
|
@ -0,0 +1,806 @@
|
||||||
|
import inspect
|
||||||
|
import io
|
||||||
|
import itertools
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import typing as t
|
||||||
|
from gettext import gettext as _
|
||||||
|
|
||||||
|
from ._compat import isatty
|
||||||
|
from ._compat import strip_ansi
|
||||||
|
from ._compat import WIN
|
||||||
|
from .exceptions import Abort
|
||||||
|
from .exceptions import UsageError
|
||||||
|
from .globals import resolve_color_default
|
||||||
|
from .types import Choice
|
||||||
|
from .types import convert_type
|
||||||
|
from .types import ParamType
|
||||||
|
from .utils import echo
|
||||||
|
from .utils import LazyFile
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
from ._termui_impl import ProgressBar
|
||||||
|
|
||||||
|
V = t.TypeVar("V")
|
||||||
|
|
||||||
|
# The prompt functions to use. The doc tools currently override these
|
||||||
|
# functions to customize how they work.
|
||||||
|
visible_prompt_func: t.Callable[[str], str] = input
|
||||||
|
|
||||||
|
_ansi_colors = {
|
||||||
|
"black": 30,
|
||||||
|
"red": 31,
|
||||||
|
"green": 32,
|
||||||
|
"yellow": 33,
|
||||||
|
"blue": 34,
|
||||||
|
"magenta": 35,
|
||||||
|
"cyan": 36,
|
||||||
|
"white": 37,
|
||||||
|
"reset": 39,
|
||||||
|
"bright_black": 90,
|
||||||
|
"bright_red": 91,
|
||||||
|
"bright_green": 92,
|
||||||
|
"bright_yellow": 93,
|
||||||
|
"bright_blue": 94,
|
||||||
|
"bright_magenta": 95,
|
||||||
|
"bright_cyan": 96,
|
||||||
|
"bright_white": 97,
|
||||||
|
}
|
||||||
|
_ansi_reset_all = "\033[0m"
|
||||||
|
|
||||||
|
|
||||||
|
def hidden_prompt_func(prompt: str) -> str:
|
||||||
|
import getpass
|
||||||
|
|
||||||
|
return getpass.getpass(prompt)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_prompt(
|
||||||
|
text: str,
|
||||||
|
suffix: str,
|
||||||
|
show_default: bool = False,
|
||||||
|
default: t.Optional[t.Any] = None,
|
||||||
|
show_choices: bool = True,
|
||||||
|
type: t.Optional[ParamType] = None,
|
||||||
|
) -> str:
|
||||||
|
prompt = text
|
||||||
|
if type is not None and show_choices and isinstance(type, Choice):
|
||||||
|
prompt += f" ({', '.join(map(str, type.choices))})"
|
||||||
|
if default is not None and show_default:
|
||||||
|
prompt = f"{prompt} [{_format_default(default)}]"
|
||||||
|
return f"{prompt}{suffix}"
|
||||||
|
|
||||||
|
|
||||||
|
def _format_default(default: t.Any) -> t.Any:
|
||||||
|
if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"):
|
||||||
|
return default.name # type: ignore
|
||||||
|
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def prompt(
|
||||||
|
text: str,
|
||||||
|
default: t.Optional[t.Any] = None,
|
||||||
|
hide_input: bool = False,
|
||||||
|
confirmation_prompt: t.Union[bool, str] = False,
|
||||||
|
type: t.Optional[t.Union[ParamType, t.Any]] = None,
|
||||||
|
value_proc: t.Optional[t.Callable[[str], t.Any]] = None,
|
||||||
|
prompt_suffix: str = ": ",
|
||||||
|
show_default: bool = True,
|
||||||
|
err: bool = False,
|
||||||
|
show_choices: bool = True,
|
||||||
|
) -> t.Any:
|
||||||
|
"""Prompts a user for input. This is a convenience function that can
|
||||||
|
be used to prompt a user for input later.
|
||||||
|
|
||||||
|
If the user aborts the input by sending an interrupt signal, this
|
||||||
|
function will catch it and raise a :exc:`Abort` exception.
|
||||||
|
|
||||||
|
:param text: the text to show for the prompt.
|
||||||
|
:param default: the default value to use if no input happens. If this
|
||||||
|
is not given it will prompt until it's aborted.
|
||||||
|
:param hide_input: if this is set to true then the input value will
|
||||||
|
be hidden.
|
||||||
|
:param confirmation_prompt: Prompt a second time to confirm the
|
||||||
|
value. Can be set to a string instead of ``True`` to customize
|
||||||
|
the message.
|
||||||
|
:param type: the type to use to check the value against.
|
||||||
|
:param value_proc: if this parameter is provided it's a function that
|
||||||
|
is invoked instead of the type conversion to
|
||||||
|
convert a value.
|
||||||
|
:param prompt_suffix: a suffix that should be added to the prompt.
|
||||||
|
:param show_default: shows or hides the default value in the prompt.
|
||||||
|
:param err: if set to true the file defaults to ``stderr`` instead of
|
||||||
|
``stdout``, the same as with echo.
|
||||||
|
:param show_choices: Show or hide choices if the passed type is a Choice.
|
||||||
|
For example if type is a Choice of either day or week,
|
||||||
|
show_choices is true and text is "Group by" then the
|
||||||
|
prompt will be "Group by (day, week): ".
|
||||||
|
|
||||||
|
.. versionadded:: 8.0
|
||||||
|
``confirmation_prompt`` can be a custom string.
|
||||||
|
|
||||||
|
.. versionadded:: 7.0
|
||||||
|
Added the ``show_choices`` parameter.
|
||||||
|
|
||||||
|
.. versionadded:: 6.0
|
||||||
|
Added unicode support for cmd.exe on Windows.
|
||||||
|
|
||||||
|
.. versionadded:: 4.0
|
||||||
|
Added the `err` parameter.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def prompt_func(text: str) -> str:
|
||||||
|
f = hidden_prompt_func if hide_input else visible_prompt_func
|
||||||
|
try:
|
||||||
|
# Write the prompt separately so that we get nice
|
||||||
|
# coloring through colorama on Windows
|
||||||
|
echo(text.rstrip(" "), nl=False, err=err)
|
||||||
|
# Echo a space to stdout to work around an issue where
|
||||||
|
# readline causes backspace to clear the whole line.
|
||||||
|
return f(" ")
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
# getpass doesn't print a newline if the user aborts input with ^C.
|
||||||
|
# Allegedly this behavior is inherited from getpass(3).
|
||||||
|
# A doc bug has been filed at https://bugs.python.org/issue24711
|
||||||
|
if hide_input:
|
||||||
|
echo(None, err=err)
|
||||||
|
raise Abort() from None
|
||||||
|
|
||||||
|
if value_proc is None:
|
||||||
|
value_proc = convert_type(type, default)
|
||||||
|
|
||||||
|
prompt = _build_prompt(
|
||||||
|
text, prompt_suffix, show_default, default, show_choices, type
|
||||||
|
)
|
||||||
|
|
||||||
|
if confirmation_prompt:
|
||||||
|
if confirmation_prompt is True:
|
||||||
|
confirmation_prompt = _("Repeat for confirmation")
|
||||||
|
|
||||||
|
confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
while True:
|
||||||
|
value = prompt_func(prompt)
|
||||||
|
if value:
|
||||||
|
break
|
||||||
|
elif default is not None:
|
||||||
|
value = default
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
result = value_proc(value)
|
||||||
|
except UsageError as e:
|
||||||
|
if hide_input:
|
||||||
|
echo(_("Error: The value you entered was invalid."), err=err)
|
||||||
|
else:
|
||||||
|
echo(_("Error: {e.message}").format(e=e), err=err) # noqa: B306
|
||||||
|
continue
|
||||||
|
if not confirmation_prompt:
|
||||||
|
return result
|
||||||
|
while True:
|
||||||
|
value2 = prompt_func(confirmation_prompt)
|
||||||
|
if value2:
|
||||||
|
break
|
||||||
|
if value == value2:
|
||||||
|
return result
|
||||||
|
echo(_("Error: The two entered values do not match."), err=err)
|
||||||
|
|
||||||
|
|
||||||
|
def confirm(
|
||||||
|
text: str,
|
||||||
|
default: t.Optional[bool] = False,
|
||||||
|
abort: bool = False,
|
||||||
|
prompt_suffix: str = ": ",
|
||||||
|
show_default: bool = True,
|
||||||
|
err: bool = False,
|
||||||
|
) -> bool:
|
||||||
|
"""Prompts for confirmation (yes/no question).
|
||||||
|
|
||||||
|
If the user aborts the input by sending a interrupt signal this
|
||||||
|
function will catch it and raise a :exc:`Abort` exception.
|
||||||
|
|
||||||
|
:param text: the question to ask.
|
||||||
|
:param default: The default value to use when no input is given. If
|
||||||
|
``None``, repeat until input is given.
|
||||||
|
:param abort: if this is set to `True` a negative answer aborts the
|
||||||
|
exception by raising :exc:`Abort`.
|
||||||
|
:param prompt_suffix: a suffix that should be added to the prompt.
|
||||||
|
:param show_default: shows or hides the default value in the prompt.
|
||||||
|
:param err: if set to true the file defaults to ``stderr`` instead of
|
||||||
|
``stdout``, the same as with echo.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
Repeat until input is given if ``default`` is ``None``.
|
||||||
|
|
||||||
|
.. versionadded:: 4.0
|
||||||
|
Added the ``err`` parameter.
|
||||||
|
"""
|
||||||
|
prompt = _build_prompt(
|
||||||
|
text,
|
||||||
|
prompt_suffix,
|
||||||
|
show_default,
|
||||||
|
"y/n" if default is None else ("Y/n" if default else "y/N"),
|
||||||
|
)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# Write the prompt separately so that we get nice
|
||||||
|
# coloring through colorama on Windows
|
||||||
|
echo(prompt.rstrip(" "), nl=False, err=err)
|
||||||
|
# Echo a space to stdout to work around an issue where
|
||||||
|
# readline causes backspace to clear the whole line.
|
||||||
|
value = visible_prompt_func(" ").lower().strip()
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
raise Abort() from None
|
||||||
|
if value in ("y", "yes"):
|
||||||
|
rv = True
|
||||||
|
elif value in ("n", "no"):
|
||||||
|
rv = False
|
||||||
|
elif default is not None and value == "":
|
||||||
|
rv = default
|
||||||
|
else:
|
||||||
|
echo(_("Error: invalid input"), err=err)
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
if abort and not rv:
|
||||||
|
raise Abort()
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def get_terminal_size() -> os.terminal_size:
|
||||||
|
"""Returns the current size of the terminal as tuple in the form
|
||||||
|
``(width, height)`` in columns and rows.
|
||||||
|
|
||||||
|
.. deprecated:: 8.0
|
||||||
|
Will be removed in Click 8.1. Use
|
||||||
|
:func:`shutil.get_terminal_size` instead.
|
||||||
|
"""
|
||||||
|
import shutil
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"'click.get_terminal_size()' is deprecated and will be removed"
|
||||||
|
" in Click 8.1. Use 'shutil.get_terminal_size()' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return shutil.get_terminal_size()
|
||||||
|
|
||||||
|
|
||||||
|
def echo_via_pager(
|
||||||
|
text_or_generator: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str],
|
||||||
|
color: t.Optional[bool] = None,
|
||||||
|
) -> None:
|
||||||
|
"""This function takes a text and shows it via an environment specific
|
||||||
|
pager on stdout.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
Added the `color` flag.
|
||||||
|
|
||||||
|
:param text_or_generator: the text to page, or alternatively, a
|
||||||
|
generator emitting the text to page.
|
||||||
|
:param color: controls if the pager supports ANSI colors or not. The
|
||||||
|
default is autodetection.
|
||||||
|
"""
|
||||||
|
color = resolve_color_default(color)
|
||||||
|
|
||||||
|
if inspect.isgeneratorfunction(text_or_generator):
|
||||||
|
i = t.cast(t.Callable[[], t.Iterable[str]], text_or_generator)()
|
||||||
|
elif isinstance(text_or_generator, str):
|
||||||
|
i = [text_or_generator]
|
||||||
|
else:
|
||||||
|
i = iter(t.cast(t.Iterable[str], text_or_generator))
|
||||||
|
|
||||||
|
# convert every element of i to a text type if necessary
|
||||||
|
text_generator = (el if isinstance(el, str) else str(el) for el in i)
|
||||||
|
|
||||||
|
from ._termui_impl import pager
|
||||||
|
|
||||||
|
return pager(itertools.chain(text_generator, "\n"), color)
|
||||||
|
|
||||||
|
|
||||||
|
def progressbar(
|
||||||
|
iterable: t.Optional[t.Iterable[V]] = None,
|
||||||
|
length: t.Optional[int] = None,
|
||||||
|
label: t.Optional[str] = None,
|
||||||
|
show_eta: bool = True,
|
||||||
|
show_percent: t.Optional[bool] = None,
|
||||||
|
show_pos: bool = False,
|
||||||
|
item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None,
|
||||||
|
fill_char: str = "#",
|
||||||
|
empty_char: str = "-",
|
||||||
|
bar_template: str = "%(label)s [%(bar)s] %(info)s",
|
||||||
|
info_sep: str = " ",
|
||||||
|
width: int = 36,
|
||||||
|
file: t.Optional[t.TextIO] = None,
|
||||||
|
color: t.Optional[bool] = None,
|
||||||
|
update_min_steps: int = 1,
|
||||||
|
) -> "ProgressBar[V]":
|
||||||
|
"""This function creates an iterable context manager that can be used
|
||||||
|
to iterate over something while showing a progress bar. It will
|
||||||
|
either iterate over the `iterable` or `length` items (that are counted
|
||||||
|
up). While iteration happens, this function will print a rendered
|
||||||
|
progress bar to the given `file` (defaults to stdout) and will attempt
|
||||||
|
to calculate remaining time and more. By default, this progress bar
|
||||||
|
will not be rendered if the file is not a terminal.
|
||||||
|
|
||||||
|
The context manager creates the progress bar. When the context
|
||||||
|
manager is entered the progress bar is already created. With every
|
||||||
|
iteration over the progress bar, the iterable passed to the bar is
|
||||||
|
advanced and the bar is updated. When the context manager exits,
|
||||||
|
a newline is printed and the progress bar is finalized on screen.
|
||||||
|
|
||||||
|
Note: The progress bar is currently designed for use cases where the
|
||||||
|
total progress can be expected to take at least several seconds.
|
||||||
|
Because of this, the ProgressBar class object won't display
|
||||||
|
progress that is considered too fast, and progress where the time
|
||||||
|
between steps is less than a second.
|
||||||
|
|
||||||
|
No printing must happen or the progress bar will be unintentionally
|
||||||
|
destroyed.
|
||||||
|
|
||||||
|
Example usage::
|
||||||
|
|
||||||
|
with progressbar(items) as bar:
|
||||||
|
for item in bar:
|
||||||
|
do_something_with(item)
|
||||||
|
|
||||||
|
Alternatively, if no iterable is specified, one can manually update the
|
||||||
|
progress bar through the `update()` method instead of directly
|
||||||
|
iterating over the progress bar. The update method accepts the number
|
||||||
|
of steps to increment the bar with::
|
||||||
|
|
||||||
|
with progressbar(length=chunks.total_bytes) as bar:
|
||||||
|
for chunk in chunks:
|
||||||
|
process_chunk(chunk)
|
||||||
|
bar.update(chunks.bytes)
|
||||||
|
|
||||||
|
The ``update()`` method also takes an optional value specifying the
|
||||||
|
``current_item`` at the new position. This is useful when used
|
||||||
|
together with ``item_show_func`` to customize the output for each
|
||||||
|
manual step::
|
||||||
|
|
||||||
|
with click.progressbar(
|
||||||
|
length=total_size,
|
||||||
|
label='Unzipping archive',
|
||||||
|
item_show_func=lambda a: a.filename
|
||||||
|
) as bar:
|
||||||
|
for archive in zip_file:
|
||||||
|
archive.extract()
|
||||||
|
bar.update(archive.size, archive)
|
||||||
|
|
||||||
|
:param iterable: an iterable to iterate over. If not provided the length
|
||||||
|
is required.
|
||||||
|
:param length: the number of items to iterate over. By default the
|
||||||
|
progressbar will attempt to ask the iterator about its
|
||||||
|
length, which might or might not work. If an iterable is
|
||||||
|
also provided this parameter can be used to override the
|
||||||
|
length. If an iterable is not provided the progress bar
|
||||||
|
will iterate over a range of that length.
|
||||||
|
:param label: the label to show next to the progress bar.
|
||||||
|
:param show_eta: enables or disables the estimated time display. This is
|
||||||
|
automatically disabled if the length cannot be
|
||||||
|
determined.
|
||||||
|
:param show_percent: enables or disables the percentage display. The
|
||||||
|
default is `True` if the iterable has a length or
|
||||||
|
`False` if not.
|
||||||
|
:param show_pos: enables or disables the absolute position display. The
|
||||||
|
default is `False`.
|
||||||
|
:param item_show_func: A function called with the current item which
|
||||||
|
can return a string to show next to the progress bar. If the
|
||||||
|
function returns ``None`` nothing is shown. The current item can
|
||||||
|
be ``None``, such as when entering and exiting the bar.
|
||||||
|
:param fill_char: the character to use to show the filled part of the
|
||||||
|
progress bar.
|
||||||
|
:param empty_char: the character to use to show the non-filled part of
|
||||||
|
the progress bar.
|
||||||
|
:param bar_template: the format string to use as template for the bar.
|
||||||
|
The parameters in it are ``label`` for the label,
|
||||||
|
``bar`` for the progress bar and ``info`` for the
|
||||||
|
info section.
|
||||||
|
:param info_sep: the separator between multiple info items (eta etc.)
|
||||||
|
:param width: the width of the progress bar in characters, 0 means full
|
||||||
|
terminal width
|
||||||
|
:param file: The file to write to. If this is not a terminal then
|
||||||
|
only the label is printed.
|
||||||
|
:param color: controls if the terminal supports ANSI colors or not. The
|
||||||
|
default is autodetection. This is only needed if ANSI
|
||||||
|
codes are included anywhere in the progress bar output
|
||||||
|
which is not the case by default.
|
||||||
|
:param update_min_steps: Render only when this many updates have
|
||||||
|
completed. This allows tuning for very fast iterators.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
Output is shown even if execution time is less than 0.5 seconds.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
``item_show_func`` shows the current item, not the previous one.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
Labels are echoed if the output is not a TTY. Reverts a change
|
||||||
|
in 7.0 that removed all output.
|
||||||
|
|
||||||
|
.. versionadded:: 8.0
|
||||||
|
Added the ``update_min_steps`` parameter.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.0
|
||||||
|
Added the ``color`` parameter. Added the ``update`` method to
|
||||||
|
the object.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
from ._termui_impl import ProgressBar
|
||||||
|
|
||||||
|
color = resolve_color_default(color)
|
||||||
|
return ProgressBar(
|
||||||
|
iterable=iterable,
|
||||||
|
length=length,
|
||||||
|
show_eta=show_eta,
|
||||||
|
show_percent=show_percent,
|
||||||
|
show_pos=show_pos,
|
||||||
|
item_show_func=item_show_func,
|
||||||
|
fill_char=fill_char,
|
||||||
|
empty_char=empty_char,
|
||||||
|
bar_template=bar_template,
|
||||||
|
info_sep=info_sep,
|
||||||
|
file=file,
|
||||||
|
label=label,
|
||||||
|
width=width,
|
||||||
|
color=color,
|
||||||
|
update_min_steps=update_min_steps,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def clear() -> None:
|
||||||
|
"""Clears the terminal screen. This will have the effect of clearing
|
||||||
|
the whole visible space of the terminal and moving the cursor to the
|
||||||
|
top left. This does not do anything if not connected to a terminal.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
if not isatty(sys.stdout):
|
||||||
|
return
|
||||||
|
if WIN:
|
||||||
|
os.system("cls")
|
||||||
|
else:
|
||||||
|
sys.stdout.write("\033[2J\033[1;1H")
|
||||||
|
|
||||||
|
|
||||||
|
def _interpret_color(
|
||||||
|
color: t.Union[int, t.Tuple[int, int, int], str], offset: int = 0
|
||||||
|
) -> str:
|
||||||
|
if isinstance(color, int):
|
||||||
|
return f"{38 + offset};5;{color:d}"
|
||||||
|
|
||||||
|
if isinstance(color, (tuple, list)):
|
||||||
|
r, g, b = color
|
||||||
|
return f"{38 + offset};2;{r:d};{g:d};{b:d}"
|
||||||
|
|
||||||
|
return str(_ansi_colors[color] + offset)
|
||||||
|
|
||||||
|
|
||||||
|
def style(
|
||||||
|
text: t.Any,
|
||||||
|
fg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None,
|
||||||
|
bg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None,
|
||||||
|
bold: t.Optional[bool] = None,
|
||||||
|
dim: t.Optional[bool] = None,
|
||||||
|
underline: t.Optional[bool] = None,
|
||||||
|
overline: t.Optional[bool] = None,
|
||||||
|
italic: t.Optional[bool] = None,
|
||||||
|
blink: t.Optional[bool] = None,
|
||||||
|
reverse: t.Optional[bool] = None,
|
||||||
|
strikethrough: t.Optional[bool] = None,
|
||||||
|
reset: bool = True,
|
||||||
|
) -> str:
|
||||||
|
"""Styles a text with ANSI styles and returns the new string. By
|
||||||
|
default the styling is self contained which means that at the end
|
||||||
|
of the string a reset code is issued. This can be prevented by
|
||||||
|
passing ``reset=False``.
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
|
||||||
|
click.echo(click.style('Hello World!', fg='green'))
|
||||||
|
click.echo(click.style('ATTENTION!', blink=True))
|
||||||
|
click.echo(click.style('Some things', reverse=True, fg='cyan'))
|
||||||
|
click.echo(click.style('More colors', fg=(255, 12, 128), bg=117))
|
||||||
|
|
||||||
|
Supported color names:
|
||||||
|
|
||||||
|
* ``black`` (might be a gray)
|
||||||
|
* ``red``
|
||||||
|
* ``green``
|
||||||
|
* ``yellow`` (might be an orange)
|
||||||
|
* ``blue``
|
||||||
|
* ``magenta``
|
||||||
|
* ``cyan``
|
||||||
|
* ``white`` (might be light gray)
|
||||||
|
* ``bright_black``
|
||||||
|
* ``bright_red``
|
||||||
|
* ``bright_green``
|
||||||
|
* ``bright_yellow``
|
||||||
|
* ``bright_blue``
|
||||||
|
* ``bright_magenta``
|
||||||
|
* ``bright_cyan``
|
||||||
|
* ``bright_white``
|
||||||
|
* ``reset`` (reset the color code only)
|
||||||
|
|
||||||
|
If the terminal supports it, color may also be specified as:
|
||||||
|
|
||||||
|
- An integer in the interval [0, 255]. The terminal must support
|
||||||
|
8-bit/256-color mode.
|
||||||
|
- An RGB tuple of three integers in [0, 255]. The terminal must
|
||||||
|
support 24-bit/true-color mode.
|
||||||
|
|
||||||
|
See https://en.wikipedia.org/wiki/ANSI_color and
|
||||||
|
https://gist.github.com/XVilka/8346728 for more information.
|
||||||
|
|
||||||
|
:param text: the string to style with ansi codes.
|
||||||
|
:param fg: if provided this will become the foreground color.
|
||||||
|
:param bg: if provided this will become the background color.
|
||||||
|
:param bold: if provided this will enable or disable bold mode.
|
||||||
|
:param dim: if provided this will enable or disable dim mode. This is
|
||||||
|
badly supported.
|
||||||
|
:param underline: if provided this will enable or disable underline.
|
||||||
|
:param overline: if provided this will enable or disable overline.
|
||||||
|
:param italic: if provided this will enable or disable italic.
|
||||||
|
:param blink: if provided this will enable or disable blinking.
|
||||||
|
:param reverse: if provided this will enable or disable inverse
|
||||||
|
rendering (foreground becomes background and the
|
||||||
|
other way round).
|
||||||
|
:param strikethrough: if provided this will enable or disable
|
||||||
|
striking through text.
|
||||||
|
:param reset: by default a reset-all code is added at the end of the
|
||||||
|
string which means that styles do not carry over. This
|
||||||
|
can be disabled to compose styles.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
A non-string ``message`` is converted to a string.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
Added support for 256 and RGB color codes.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
Added the ``strikethrough``, ``italic``, and ``overline``
|
||||||
|
parameters.
|
||||||
|
|
||||||
|
.. versionchanged:: 7.0
|
||||||
|
Added support for bright colors.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
if not isinstance(text, str):
|
||||||
|
text = str(text)
|
||||||
|
|
||||||
|
bits = []
|
||||||
|
|
||||||
|
if fg:
|
||||||
|
try:
|
||||||
|
bits.append(f"\033[{_interpret_color(fg)}m")
|
||||||
|
except KeyError:
|
||||||
|
raise TypeError(f"Unknown color {fg!r}") from None
|
||||||
|
|
||||||
|
if bg:
|
||||||
|
try:
|
||||||
|
bits.append(f"\033[{_interpret_color(bg, 10)}m")
|
||||||
|
except KeyError:
|
||||||
|
raise TypeError(f"Unknown color {bg!r}") from None
|
||||||
|
|
||||||
|
if bold is not None:
|
||||||
|
bits.append(f"\033[{1 if bold else 22}m")
|
||||||
|
if dim is not None:
|
||||||
|
bits.append(f"\033[{2 if dim else 22}m")
|
||||||
|
if underline is not None:
|
||||||
|
bits.append(f"\033[{4 if underline else 24}m")
|
||||||
|
if overline is not None:
|
||||||
|
bits.append(f"\033[{53 if overline else 55}m")
|
||||||
|
if italic is not None:
|
||||||
|
bits.append(f"\033[{3 if italic else 23}m")
|
||||||
|
if blink is not None:
|
||||||
|
bits.append(f"\033[{5 if blink else 25}m")
|
||||||
|
if reverse is not None:
|
||||||
|
bits.append(f"\033[{7 if reverse else 27}m")
|
||||||
|
if strikethrough is not None:
|
||||||
|
bits.append(f"\033[{9 if strikethrough else 29}m")
|
||||||
|
bits.append(text)
|
||||||
|
if reset:
|
||||||
|
bits.append(_ansi_reset_all)
|
||||||
|
return "".join(bits)
|
||||||
|
|
||||||
|
|
||||||
|
def unstyle(text: str) -> str:
|
||||||
|
"""Removes ANSI styling information from a string. Usually it's not
|
||||||
|
necessary to use this function as Click's echo function will
|
||||||
|
automatically remove styling if necessary.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
:param text: the text to remove style information from.
|
||||||
|
"""
|
||||||
|
return strip_ansi(text)
|
||||||
|
|
||||||
|
|
||||||
|
def secho(
|
||||||
|
message: t.Optional[t.Any] = None,
|
||||||
|
file: t.Optional[t.IO[t.AnyStr]] = None,
|
||||||
|
nl: bool = True,
|
||||||
|
err: bool = False,
|
||||||
|
color: t.Optional[bool] = None,
|
||||||
|
**styles: t.Any,
|
||||||
|
) -> None:
|
||||||
|
"""This function combines :func:`echo` and :func:`style` into one
|
||||||
|
call. As such the following two calls are the same::
|
||||||
|
|
||||||
|
click.secho('Hello World!', fg='green')
|
||||||
|
click.echo(click.style('Hello World!', fg='green'))
|
||||||
|
|
||||||
|
All keyword arguments are forwarded to the underlying functions
|
||||||
|
depending on which one they go with.
|
||||||
|
|
||||||
|
Non-string types will be converted to :class:`str`. However,
|
||||||
|
:class:`bytes` are passed directly to :meth:`echo` without applying
|
||||||
|
style. If you want to style bytes that represent text, call
|
||||||
|
:meth:`bytes.decode` first.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
A non-string ``message`` is converted to a string. Bytes are
|
||||||
|
passed through without style applied.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
if message is not None and not isinstance(message, (bytes, bytearray)):
|
||||||
|
message = style(message, **styles)
|
||||||
|
|
||||||
|
return echo(message, file=file, nl=nl, err=err, color=color)
|
||||||
|
|
||||||
|
|
||||||
|
def edit(
|
||||||
|
text: t.Optional[t.AnyStr] = None,
|
||||||
|
editor: t.Optional[str] = None,
|
||||||
|
env: t.Optional[t.Mapping[str, str]] = None,
|
||||||
|
require_save: bool = True,
|
||||||
|
extension: str = ".txt",
|
||||||
|
filename: t.Optional[str] = None,
|
||||||
|
) -> t.Optional[t.AnyStr]:
|
||||||
|
r"""Edits the given text in the defined editor. If an editor is given
|
||||||
|
(should be the full path to the executable but the regular operating
|
||||||
|
system search path is used for finding the executable) it overrides
|
||||||
|
the detected editor. Optionally, some environment variables can be
|
||||||
|
used. If the editor is closed without changes, `None` is returned. In
|
||||||
|
case a file is edited directly the return value is always `None` and
|
||||||
|
`require_save` and `extension` are ignored.
|
||||||
|
|
||||||
|
If the editor cannot be opened a :exc:`UsageError` is raised.
|
||||||
|
|
||||||
|
Note for Windows: to simplify cross-platform usage, the newlines are
|
||||||
|
automatically converted from POSIX to Windows and vice versa. As such,
|
||||||
|
the message here will have ``\n`` as newline markers.
|
||||||
|
|
||||||
|
:param text: the text to edit.
|
||||||
|
:param editor: optionally the editor to use. Defaults to automatic
|
||||||
|
detection.
|
||||||
|
:param env: environment variables to forward to the editor.
|
||||||
|
:param require_save: if this is true, then not saving in the editor
|
||||||
|
will make the return value become `None`.
|
||||||
|
:param extension: the extension to tell the editor about. This defaults
|
||||||
|
to `.txt` but changing this might change syntax
|
||||||
|
highlighting.
|
||||||
|
:param filename: if provided it will edit this file instead of the
|
||||||
|
provided text contents. It will not use a temporary
|
||||||
|
file as an indirection in that case.
|
||||||
|
"""
|
||||||
|
from ._termui_impl import Editor
|
||||||
|
|
||||||
|
ed = Editor(editor=editor, env=env, require_save=require_save, extension=extension)
|
||||||
|
|
||||||
|
if filename is None:
|
||||||
|
return ed.edit(text)
|
||||||
|
|
||||||
|
ed.edit_file(filename)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def launch(url: str, wait: bool = False, locate: bool = False) -> int:
|
||||||
|
"""This function launches the given URL (or filename) in the default
|
||||||
|
viewer application for this file type. If this is an executable, it
|
||||||
|
might launch the executable in a new session. The return value is
|
||||||
|
the exit code of the launched application. Usually, ``0`` indicates
|
||||||
|
success.
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
|
||||||
|
click.launch('https://click.palletsprojects.com/')
|
||||||
|
click.launch('/my/downloaded/file', locate=True)
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
:param url: URL or filename of the thing to launch.
|
||||||
|
:param wait: Wait for the program to exit before returning. This
|
||||||
|
only works if the launched program blocks. In particular,
|
||||||
|
``xdg-open`` on Linux does not block.
|
||||||
|
:param locate: if this is set to `True` then instead of launching the
|
||||||
|
application associated with the URL it will attempt to
|
||||||
|
launch a file manager with the file located. This
|
||||||
|
might have weird effects if the URL does not point to
|
||||||
|
the filesystem.
|
||||||
|
"""
|
||||||
|
from ._termui_impl import open_url
|
||||||
|
|
||||||
|
return open_url(url, wait=wait, locate=locate)
|
||||||
|
|
||||||
|
|
||||||
|
# If this is provided, getchar() calls into this instead. This is used
|
||||||
|
# for unittesting purposes.
|
||||||
|
_getchar: t.Optional[t.Callable[[bool], str]] = None
|
||||||
|
|
||||||
|
|
||||||
|
def getchar(echo: bool = False) -> str:
|
||||||
|
"""Fetches a single character from the terminal and returns it. This
|
||||||
|
will always return a unicode character and under certain rare
|
||||||
|
circumstances this might return more than one character. The
|
||||||
|
situations which more than one character is returned is when for
|
||||||
|
whatever reason multiple characters end up in the terminal buffer or
|
||||||
|
standard input was not actually a terminal.
|
||||||
|
|
||||||
|
Note that this will always read from the terminal, even if something
|
||||||
|
is piped into the standard input.
|
||||||
|
|
||||||
|
Note for Windows: in rare cases when typing non-ASCII characters, this
|
||||||
|
function might wait for a second character and then return both at once.
|
||||||
|
This is because certain Unicode characters look like special-key markers.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
:param echo: if set to `True`, the character read will also show up on
|
||||||
|
the terminal. The default is to not show it.
|
||||||
|
"""
|
||||||
|
global _getchar
|
||||||
|
|
||||||
|
if _getchar is None:
|
||||||
|
from ._termui_impl import getchar as f
|
||||||
|
|
||||||
|
_getchar = f
|
||||||
|
|
||||||
|
return _getchar(echo)
|
||||||
|
|
||||||
|
|
||||||
|
def raw_terminal() -> t.ContextManager[int]:
|
||||||
|
from ._termui_impl import raw_terminal as f
|
||||||
|
|
||||||
|
return f()
|
||||||
|
|
||||||
|
|
||||||
|
def pause(info: t.Optional[str] = None, err: bool = False) -> None:
|
||||||
|
"""This command stops execution and waits for the user to press any
|
||||||
|
key to continue. This is similar to the Windows batch "pause"
|
||||||
|
command. If the program is not run through a terminal, this command
|
||||||
|
will instead do nothing.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. versionadded:: 4.0
|
||||||
|
Added the `err` parameter.
|
||||||
|
|
||||||
|
:param info: The message to print before pausing. Defaults to
|
||||||
|
``"Press any key to continue..."``.
|
||||||
|
:param err: if set to message goes to ``stderr`` instead of
|
||||||
|
``stdout``, the same as with echo.
|
||||||
|
"""
|
||||||
|
if not isatty(sys.stdin) or not isatty(sys.stdout):
|
||||||
|
return
|
||||||
|
|
||||||
|
if info is None:
|
||||||
|
info = _("Press any key to continue...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if info:
|
||||||
|
echo(info, nl=False, err=err)
|
||||||
|
try:
|
||||||
|
getchar()
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
if info:
|
||||||
|
echo(err=err)
|
||||||
479
dist/ba_data/python-site-packages/click/testing.py
vendored
Normal file
479
dist/ba_data/python-site-packages/click/testing.py
vendored
Normal file
|
|
@ -0,0 +1,479 @@
|
||||||
|
import contextlib
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import typing as t
|
||||||
|
from types import TracebackType
|
||||||
|
|
||||||
|
from . import formatting
|
||||||
|
from . import termui
|
||||||
|
from . import utils
|
||||||
|
from ._compat import _find_binary_reader
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
from .core import BaseCommand
|
||||||
|
|
||||||
|
|
||||||
|
class EchoingStdin:
|
||||||
|
def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None:
|
||||||
|
self._input = input
|
||||||
|
self._output = output
|
||||||
|
self._paused = False
|
||||||
|
|
||||||
|
def __getattr__(self, x: str) -> t.Any:
|
||||||
|
return getattr(self._input, x)
|
||||||
|
|
||||||
|
def _echo(self, rv: bytes) -> bytes:
|
||||||
|
if not self._paused:
|
||||||
|
self._output.write(rv)
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def read(self, n: int = -1) -> bytes:
|
||||||
|
return self._echo(self._input.read(n))
|
||||||
|
|
||||||
|
def read1(self, n: int = -1) -> bytes:
|
||||||
|
return self._echo(self._input.read1(n)) # type: ignore
|
||||||
|
|
||||||
|
def readline(self, n: int = -1) -> bytes:
|
||||||
|
return self._echo(self._input.readline(n))
|
||||||
|
|
||||||
|
def readlines(self) -> t.List[bytes]:
|
||||||
|
return [self._echo(x) for x in self._input.readlines()]
|
||||||
|
|
||||||
|
def __iter__(self) -> t.Iterator[bytes]:
|
||||||
|
return iter(self._echo(x) for x in self._input)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return repr(self._input)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _pause_echo(stream: t.Optional[EchoingStdin]) -> t.Iterator[None]:
|
||||||
|
if stream is None:
|
||||||
|
yield
|
||||||
|
else:
|
||||||
|
stream._paused = True
|
||||||
|
yield
|
||||||
|
stream._paused = False
|
||||||
|
|
||||||
|
|
||||||
|
class _NamedTextIOWrapper(io.TextIOWrapper):
|
||||||
|
def __init__(
|
||||||
|
self, buffer: t.BinaryIO, name: str, mode: str, **kwargs: t.Any
|
||||||
|
) -> None:
|
||||||
|
super().__init__(buffer, **kwargs)
|
||||||
|
self._name = name
|
||||||
|
self._mode = mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mode(self) -> str:
|
||||||
|
return self._mode
|
||||||
|
|
||||||
|
|
||||||
|
def make_input_stream(
|
||||||
|
input: t.Optional[t.Union[str, bytes, t.IO]], charset: str
|
||||||
|
) -> t.BinaryIO:
|
||||||
|
# Is already an input stream.
|
||||||
|
if hasattr(input, "read"):
|
||||||
|
rv = _find_binary_reader(t.cast(t.IO, input))
|
||||||
|
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
|
||||||
|
raise TypeError("Could not find binary reader for input stream.")
|
||||||
|
|
||||||
|
if input is None:
|
||||||
|
input = b""
|
||||||
|
elif isinstance(input, str):
|
||||||
|
input = input.encode(charset)
|
||||||
|
|
||||||
|
return io.BytesIO(t.cast(bytes, input))
|
||||||
|
|
||||||
|
|
||||||
|
class Result:
|
||||||
|
"""Holds the captured result of an invoked CLI script."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
runner: "CliRunner",
|
||||||
|
stdout_bytes: bytes,
|
||||||
|
stderr_bytes: t.Optional[bytes],
|
||||||
|
return_value: t.Any,
|
||||||
|
exit_code: int,
|
||||||
|
exception: t.Optional[BaseException],
|
||||||
|
exc_info: t.Optional[
|
||||||
|
t.Tuple[t.Type[BaseException], BaseException, TracebackType]
|
||||||
|
] = None,
|
||||||
|
):
|
||||||
|
#: The runner that created the result
|
||||||
|
self.runner = runner
|
||||||
|
#: The standard output as bytes.
|
||||||
|
self.stdout_bytes = stdout_bytes
|
||||||
|
#: The standard error as bytes, or None if not available
|
||||||
|
self.stderr_bytes = stderr_bytes
|
||||||
|
#: The value returned from the invoked command.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 8.0
|
||||||
|
self.return_value = return_value
|
||||||
|
#: The exit code as integer.
|
||||||
|
self.exit_code = exit_code
|
||||||
|
#: The exception that happened if one did.
|
||||||
|
self.exception = exception
|
||||||
|
#: The traceback
|
||||||
|
self.exc_info = exc_info
|
||||||
|
|
||||||
|
@property
|
||||||
|
def output(self) -> str:
|
||||||
|
"""The (standard) output as unicode string."""
|
||||||
|
return self.stdout
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stdout(self) -> str:
|
||||||
|
"""The standard output as unicode string."""
|
||||||
|
return self.stdout_bytes.decode(self.runner.charset, "replace").replace(
|
||||||
|
"\r\n", "\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stderr(self) -> str:
|
||||||
|
"""The standard error as unicode string."""
|
||||||
|
if self.stderr_bytes is None:
|
||||||
|
raise ValueError("stderr not separately captured")
|
||||||
|
return self.stderr_bytes.decode(self.runner.charset, "replace").replace(
|
||||||
|
"\r\n", "\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
exc_str = repr(self.exception) if self.exception else "okay"
|
||||||
|
return f"<{type(self).__name__} {exc_str}>"
|
||||||
|
|
||||||
|
|
||||||
|
class CliRunner:
|
||||||
|
"""The CLI runner provides functionality to invoke a Click command line
|
||||||
|
script for unittesting purposes in a isolated environment. This only
|
||||||
|
works in single-threaded systems without any concurrency as it changes the
|
||||||
|
global interpreter state.
|
||||||
|
|
||||||
|
:param charset: the character set for the input and output data.
|
||||||
|
:param env: a dictionary with environment variables for overriding.
|
||||||
|
:param echo_stdin: if this is set to `True`, then reading from stdin writes
|
||||||
|
to stdout. This is useful for showing examples in
|
||||||
|
some circumstances. Note that regular prompts
|
||||||
|
will automatically echo the input.
|
||||||
|
:param mix_stderr: if this is set to `False`, then stdout and stderr are
|
||||||
|
preserved as independent streams. This is useful for
|
||||||
|
Unix-philosophy apps that have predictable stdout and
|
||||||
|
noisy stderr, such that each may be measured
|
||||||
|
independently
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
charset: str = "utf-8",
|
||||||
|
env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
|
||||||
|
echo_stdin: bool = False,
|
||||||
|
mix_stderr: bool = True,
|
||||||
|
) -> None:
|
||||||
|
self.charset = charset
|
||||||
|
self.env = env or {}
|
||||||
|
self.echo_stdin = echo_stdin
|
||||||
|
self.mix_stderr = mix_stderr
|
||||||
|
|
||||||
|
def get_default_prog_name(self, cli: "BaseCommand") -> str:
|
||||||
|
"""Given a command object it will return the default program name
|
||||||
|
for it. The default is the `name` attribute or ``"root"`` if not
|
||||||
|
set.
|
||||||
|
"""
|
||||||
|
return cli.name or "root"
|
||||||
|
|
||||||
|
def make_env(
|
||||||
|
self, overrides: t.Optional[t.Mapping[str, t.Optional[str]]] = None
|
||||||
|
) -> t.Mapping[str, t.Optional[str]]:
|
||||||
|
"""Returns the environment overrides for invoking a script."""
|
||||||
|
rv = dict(self.env)
|
||||||
|
if overrides:
|
||||||
|
rv.update(overrides)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def isolation(
|
||||||
|
self,
|
||||||
|
input: t.Optional[t.Union[str, bytes, t.IO]] = None,
|
||||||
|
env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
|
||||||
|
color: bool = False,
|
||||||
|
) -> t.Iterator[t.Tuple[io.BytesIO, t.Optional[io.BytesIO]]]:
|
||||||
|
"""A context manager that sets up the isolation for invoking of a
|
||||||
|
command line tool. This sets up stdin with the given input data
|
||||||
|
and `os.environ` with the overrides from the given dictionary.
|
||||||
|
This also rebinds some internals in Click to be mocked (like the
|
||||||
|
prompt functionality).
|
||||||
|
|
||||||
|
This is automatically done in the :meth:`invoke` method.
|
||||||
|
|
||||||
|
:param input: the input stream to put into sys.stdin.
|
||||||
|
:param env: the environment overrides as dictionary.
|
||||||
|
:param color: whether the output should contain color codes. The
|
||||||
|
application can still override this explicitly.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
``stderr`` is opened with ``errors="backslashreplace"``
|
||||||
|
instead of the default ``"strict"``.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.0
|
||||||
|
Added the ``color`` parameter.
|
||||||
|
"""
|
||||||
|
bytes_input = make_input_stream(input, self.charset)
|
||||||
|
echo_input = None
|
||||||
|
|
||||||
|
old_stdin = sys.stdin
|
||||||
|
old_stdout = sys.stdout
|
||||||
|
old_stderr = sys.stderr
|
||||||
|
old_forced_width = formatting.FORCED_WIDTH
|
||||||
|
formatting.FORCED_WIDTH = 80
|
||||||
|
|
||||||
|
env = self.make_env(env)
|
||||||
|
|
||||||
|
bytes_output = io.BytesIO()
|
||||||
|
|
||||||
|
if self.echo_stdin:
|
||||||
|
bytes_input = echo_input = t.cast(
|
||||||
|
t.BinaryIO, EchoingStdin(bytes_input, bytes_output)
|
||||||
|
)
|
||||||
|
|
||||||
|
sys.stdin = text_input = _NamedTextIOWrapper(
|
||||||
|
bytes_input, encoding=self.charset, name="<stdin>", mode="r"
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.echo_stdin:
|
||||||
|
# Force unbuffered reads, otherwise TextIOWrapper reads a
|
||||||
|
# large chunk which is echoed early.
|
||||||
|
text_input._CHUNK_SIZE = 1 # type: ignore
|
||||||
|
|
||||||
|
sys.stdout = _NamedTextIOWrapper(
|
||||||
|
bytes_output, encoding=self.charset, name="<stdout>", mode="w"
|
||||||
|
)
|
||||||
|
|
||||||
|
bytes_error = None
|
||||||
|
if self.mix_stderr:
|
||||||
|
sys.stderr = sys.stdout
|
||||||
|
else:
|
||||||
|
bytes_error = io.BytesIO()
|
||||||
|
sys.stderr = _NamedTextIOWrapper(
|
||||||
|
bytes_error,
|
||||||
|
encoding=self.charset,
|
||||||
|
name="<stderr>",
|
||||||
|
mode="w",
|
||||||
|
errors="backslashreplace",
|
||||||
|
)
|
||||||
|
|
||||||
|
@_pause_echo(echo_input) # type: ignore
|
||||||
|
def visible_input(prompt: t.Optional[str] = None) -> str:
|
||||||
|
sys.stdout.write(prompt or "")
|
||||||
|
val = text_input.readline().rstrip("\r\n")
|
||||||
|
sys.stdout.write(f"{val}\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
return val
|
||||||
|
|
||||||
|
@_pause_echo(echo_input) # type: ignore
|
||||||
|
def hidden_input(prompt: t.Optional[str] = None) -> str:
|
||||||
|
sys.stdout.write(f"{prompt or ''}\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
return text_input.readline().rstrip("\r\n")
|
||||||
|
|
||||||
|
@_pause_echo(echo_input) # type: ignore
|
||||||
|
def _getchar(echo: bool) -> str:
|
||||||
|
char = sys.stdin.read(1)
|
||||||
|
|
||||||
|
if echo:
|
||||||
|
sys.stdout.write(char)
|
||||||
|
|
||||||
|
sys.stdout.flush()
|
||||||
|
return char
|
||||||
|
|
||||||
|
default_color = color
|
||||||
|
|
||||||
|
def should_strip_ansi(
|
||||||
|
stream: t.Optional[t.IO] = None, color: t.Optional[bool] = None
|
||||||
|
) -> bool:
|
||||||
|
if color is None:
|
||||||
|
return not default_color
|
||||||
|
return not color
|
||||||
|
|
||||||
|
old_visible_prompt_func = termui.visible_prompt_func
|
||||||
|
old_hidden_prompt_func = termui.hidden_prompt_func
|
||||||
|
old__getchar_func = termui._getchar
|
||||||
|
old_should_strip_ansi = utils.should_strip_ansi # type: ignore
|
||||||
|
termui.visible_prompt_func = visible_input
|
||||||
|
termui.hidden_prompt_func = hidden_input
|
||||||
|
termui._getchar = _getchar
|
||||||
|
utils.should_strip_ansi = should_strip_ansi # type: ignore
|
||||||
|
|
||||||
|
old_env = {}
|
||||||
|
try:
|
||||||
|
for key, value in env.items():
|
||||||
|
old_env[key] = os.environ.get(key)
|
||||||
|
if value is None:
|
||||||
|
try:
|
||||||
|
del os.environ[key]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
os.environ[key] = value
|
||||||
|
yield (bytes_output, bytes_error)
|
||||||
|
finally:
|
||||||
|
for key, value in old_env.items():
|
||||||
|
if value is None:
|
||||||
|
try:
|
||||||
|
del os.environ[key]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
os.environ[key] = value
|
||||||
|
sys.stdout = old_stdout
|
||||||
|
sys.stderr = old_stderr
|
||||||
|
sys.stdin = old_stdin
|
||||||
|
termui.visible_prompt_func = old_visible_prompt_func
|
||||||
|
termui.hidden_prompt_func = old_hidden_prompt_func
|
||||||
|
termui._getchar = old__getchar_func
|
||||||
|
utils.should_strip_ansi = old_should_strip_ansi # type: ignore
|
||||||
|
formatting.FORCED_WIDTH = old_forced_width
|
||||||
|
|
||||||
|
def invoke(
|
||||||
|
self,
|
||||||
|
cli: "BaseCommand",
|
||||||
|
args: t.Optional[t.Union[str, t.Sequence[str]]] = None,
|
||||||
|
input: t.Optional[t.Union[str, bytes, t.IO]] = None,
|
||||||
|
env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
|
||||||
|
catch_exceptions: bool = True,
|
||||||
|
color: bool = False,
|
||||||
|
**extra: t.Any,
|
||||||
|
) -> Result:
|
||||||
|
"""Invokes a command in an isolated environment. The arguments are
|
||||||
|
forwarded directly to the command line script, the `extra` keyword
|
||||||
|
arguments are passed to the :meth:`~clickpkg.Command.main` function of
|
||||||
|
the command.
|
||||||
|
|
||||||
|
This returns a :class:`Result` object.
|
||||||
|
|
||||||
|
:param cli: the command to invoke
|
||||||
|
:param args: the arguments to invoke. It may be given as an iterable
|
||||||
|
or a string. When given as string it will be interpreted
|
||||||
|
as a Unix shell command. More details at
|
||||||
|
:func:`shlex.split`.
|
||||||
|
:param input: the input data for `sys.stdin`.
|
||||||
|
:param env: the environment overrides.
|
||||||
|
:param catch_exceptions: Whether to catch any other exceptions than
|
||||||
|
``SystemExit``.
|
||||||
|
:param extra: the keyword arguments to pass to :meth:`main`.
|
||||||
|
:param color: whether the output should contain color codes. The
|
||||||
|
application can still override this explicitly.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
The result object has the ``return_value`` attribute with
|
||||||
|
the value returned from the invoked command.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.0
|
||||||
|
Added the ``color`` parameter.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
Added the ``catch_exceptions`` parameter.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The result object has the ``exc_info`` attribute with the
|
||||||
|
traceback if available.
|
||||||
|
"""
|
||||||
|
exc_info = None
|
||||||
|
with self.isolation(input=input, env=env, color=color) as outstreams:
|
||||||
|
return_value = None
|
||||||
|
exception: t.Optional[BaseException] = None
|
||||||
|
exit_code = 0
|
||||||
|
|
||||||
|
if isinstance(args, str):
|
||||||
|
args = shlex.split(args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
prog_name = extra.pop("prog_name")
|
||||||
|
except KeyError:
|
||||||
|
prog_name = self.get_default_prog_name(cli)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return_value = cli.main(args=args or (), prog_name=prog_name, **extra)
|
||||||
|
except SystemExit as e:
|
||||||
|
exc_info = sys.exc_info()
|
||||||
|
e_code = t.cast(t.Optional[t.Union[int, t.Any]], e.code)
|
||||||
|
|
||||||
|
if e_code is None:
|
||||||
|
e_code = 0
|
||||||
|
|
||||||
|
if e_code != 0:
|
||||||
|
exception = e
|
||||||
|
|
||||||
|
if not isinstance(e_code, int):
|
||||||
|
sys.stdout.write(str(e_code))
|
||||||
|
sys.stdout.write("\n")
|
||||||
|
e_code = 1
|
||||||
|
|
||||||
|
exit_code = e_code
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if not catch_exceptions:
|
||||||
|
raise
|
||||||
|
exception = e
|
||||||
|
exit_code = 1
|
||||||
|
exc_info = sys.exc_info()
|
||||||
|
finally:
|
||||||
|
sys.stdout.flush()
|
||||||
|
stdout = outstreams[0].getvalue()
|
||||||
|
if self.mix_stderr:
|
||||||
|
stderr = None
|
||||||
|
else:
|
||||||
|
stderr = outstreams[1].getvalue() # type: ignore
|
||||||
|
|
||||||
|
return Result(
|
||||||
|
runner=self,
|
||||||
|
stdout_bytes=stdout,
|
||||||
|
stderr_bytes=stderr,
|
||||||
|
return_value=return_value,
|
||||||
|
exit_code=exit_code,
|
||||||
|
exception=exception,
|
||||||
|
exc_info=exc_info, # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def isolated_filesystem(
|
||||||
|
self, temp_dir: t.Optional[t.Union[str, os.PathLike]] = None
|
||||||
|
) -> t.Iterator[str]:
|
||||||
|
"""A context manager that creates a temporary directory and
|
||||||
|
changes the current working directory to it. This isolates tests
|
||||||
|
that affect the contents of the CWD to prevent them from
|
||||||
|
interfering with each other.
|
||||||
|
|
||||||
|
:param temp_dir: Create the temporary directory under this
|
||||||
|
directory. If given, the created directory is not removed
|
||||||
|
when exiting.
|
||||||
|
|
||||||
|
.. versionchanged:: 8.0
|
||||||
|
Added the ``temp_dir`` parameter.
|
||||||
|
"""
|
||||||
|
cwd = os.getcwd()
|
||||||
|
dt = tempfile.mkdtemp(dir=temp_dir) # type: ignore[type-var]
|
||||||
|
os.chdir(dt)
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield t.cast(str, dt)
|
||||||
|
finally:
|
||||||
|
os.chdir(cwd)
|
||||||
|
|
||||||
|
if temp_dir is None:
|
||||||
|
try:
|
||||||
|
shutil.rmtree(dt)
|
||||||
|
except OSError: # noqa: B014
|
||||||
|
pass
|
||||||
1049
dist/ba_data/python-site-packages/click/types.py
vendored
Normal file
1049
dist/ba_data/python-site-packages/click/types.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
588
dist/ba_data/python-site-packages/click/utils.py
vendored
Normal file
588
dist/ba_data/python-site-packages/click/utils.py
vendored
Normal file
|
|
@ -0,0 +1,588 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import typing as t
|
||||||
|
from functools import update_wrapper
|
||||||
|
from types import ModuleType
|
||||||
|
|
||||||
|
from ._compat import _default_text_stderr
|
||||||
|
from ._compat import _default_text_stdout
|
||||||
|
from ._compat import _find_binary_writer
|
||||||
|
from ._compat import auto_wrap_for_ansi
|
||||||
|
from ._compat import binary_streams
|
||||||
|
from ._compat import get_filesystem_encoding
|
||||||
|
from ._compat import open_stream
|
||||||
|
from ._compat import should_strip_ansi
|
||||||
|
from ._compat import strip_ansi
|
||||||
|
from ._compat import text_streams
|
||||||
|
from ._compat import WIN
|
||||||
|
from .globals import resolve_color_default
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
|
|
||||||
|
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||||
|
|
||||||
|
|
||||||
|
def _posixify(name: str) -> str:
|
||||||
|
return "-".join(name.split()).lower()
|
||||||
|
|
||||||
|
|
||||||
|
def safecall(func: F) -> F:
|
||||||
|
"""Wraps a function so that it swallows exceptions."""
|
||||||
|
|
||||||
|
def wrapper(*args, **kwargs): # type: ignore
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return update_wrapper(t.cast(F, wrapper), func)
|
||||||
|
|
||||||
|
|
||||||
|
def make_str(value: t.Any) -> str:
|
||||||
|
"""Converts a value into a valid string."""
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
try:
|
||||||
|
return value.decode(get_filesystem_encoding())
|
||||||
|
except UnicodeError:
|
||||||
|
return value.decode("utf-8", "replace")
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def make_default_short_help(help: str, max_length: int = 45) -> str:
|
||||||
|
"""Returns a condensed version of help string."""
|
||||||
|
# Consider only the first paragraph.
|
||||||
|
paragraph_end = help.find("\n\n")
|
||||||
|
|
||||||
|
if paragraph_end != -1:
|
||||||
|
help = help[:paragraph_end]
|
||||||
|
|
||||||
|
# Collapse newlines, tabs, and spaces.
|
||||||
|
words = help.split()
|
||||||
|
|
||||||
|
if not words:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# The first paragraph started with a "no rewrap" marker, ignore it.
|
||||||
|
if words[0] == "\b":
|
||||||
|
words = words[1:]
|
||||||
|
|
||||||
|
total_length = 0
|
||||||
|
last_index = len(words) - 1
|
||||||
|
|
||||||
|
for i, word in enumerate(words):
|
||||||
|
total_length += len(word) + (i > 0)
|
||||||
|
|
||||||
|
if total_length > max_length: # too long, truncate
|
||||||
|
break
|
||||||
|
|
||||||
|
if word[-1] == ".": # sentence end, truncate without "..."
|
||||||
|
return " ".join(words[: i + 1])
|
||||||
|
|
||||||
|
if total_length == max_length and i != last_index:
|
||||||
|
break # not at sentence end, truncate with "..."
|
||||||
|
else:
|
||||||
|
return " ".join(words) # no truncation needed
|
||||||
|
|
||||||
|
# Account for the length of the suffix.
|
||||||
|
total_length += len("...")
|
||||||
|
|
||||||
|
# remove words until the length is short enough
|
||||||
|
while i > 0:
|
||||||
|
total_length -= len(words[i]) + (i > 0)
|
||||||
|
|
||||||
|
if total_length <= max_length:
|
||||||
|
break
|
||||||
|
|
||||||
|
i -= 1
|
||||||
|
|
||||||
|
return " ".join(words[:i]) + "..."
|
||||||
|
|
||||||
|
|
||||||
|
class LazyFile:
|
||||||
|
"""A lazy file works like a regular file but it does not fully open
|
||||||
|
the file but it does perform some basic checks early to see if the
|
||||||
|
filename parameter does make sense. This is useful for safely opening
|
||||||
|
files for writing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
filename: str,
|
||||||
|
mode: str = "r",
|
||||||
|
encoding: t.Optional[str] = None,
|
||||||
|
errors: t.Optional[str] = "strict",
|
||||||
|
atomic: bool = False,
|
||||||
|
):
|
||||||
|
self.name = filename
|
||||||
|
self.mode = mode
|
||||||
|
self.encoding = encoding
|
||||||
|
self.errors = errors
|
||||||
|
self.atomic = atomic
|
||||||
|
self._f: t.Optional[t.IO]
|
||||||
|
|
||||||
|
if filename == "-":
|
||||||
|
self._f, self.should_close = open_stream(filename, mode, encoding, errors)
|
||||||
|
else:
|
||||||
|
if "r" in mode:
|
||||||
|
# Open and close the file in case we're opening it for
|
||||||
|
# reading so that we can catch at least some errors in
|
||||||
|
# some cases early.
|
||||||
|
open(filename, mode).close()
|
||||||
|
self._f = None
|
||||||
|
self.should_close = True
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> t.Any:
|
||||||
|
return getattr(self.open(), name)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
if self._f is not None:
|
||||||
|
return repr(self._f)
|
||||||
|
return f"<unopened file '{self.name}' {self.mode}>"
|
||||||
|
|
||||||
|
def open(self) -> t.IO:
|
||||||
|
"""Opens the file if it's not yet open. This call might fail with
|
||||||
|
a :exc:`FileError`. Not handling this error will produce an error
|
||||||
|
that Click shows.
|
||||||
|
"""
|
||||||
|
if self._f is not None:
|
||||||
|
return self._f
|
||||||
|
try:
|
||||||
|
rv, self.should_close = open_stream(
|
||||||
|
self.name, self.mode, self.encoding, self.errors, atomic=self.atomic
|
||||||
|
)
|
||||||
|
except OSError as e: # noqa: E402
|
||||||
|
from .exceptions import FileError
|
||||||
|
|
||||||
|
raise FileError(self.name, hint=e.strerror) from e
|
||||||
|
self._f = rv
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
"""Closes the underlying file, no matter what."""
|
||||||
|
if self._f is not None:
|
||||||
|
self._f.close()
|
||||||
|
|
||||||
|
def close_intelligently(self) -> None:
|
||||||
|
"""This function only closes the file if it was opened by the lazy
|
||||||
|
file wrapper. For instance this will never close stdin.
|
||||||
|
"""
|
||||||
|
if self.should_close:
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def __enter__(self) -> "LazyFile":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, tb): # type: ignore
|
||||||
|
self.close_intelligently()
|
||||||
|
|
||||||
|
def __iter__(self) -> t.Iterator[t.AnyStr]:
|
||||||
|
self.open()
|
||||||
|
return iter(self._f) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
class KeepOpenFile:
|
||||||
|
def __init__(self, file: t.IO) -> None:
|
||||||
|
self._file = file
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> t.Any:
|
||||||
|
return getattr(self._file, name)
|
||||||
|
|
||||||
|
def __enter__(self) -> "KeepOpenFile":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, tb): # type: ignore
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return repr(self._file)
|
||||||
|
|
||||||
|
def __iter__(self) -> t.Iterator[t.AnyStr]:
|
||||||
|
return iter(self._file)
|
||||||
|
|
||||||
|
|
||||||
|
def echo(
|
||||||
|
message: t.Optional[t.Any] = None,
|
||||||
|
file: t.Optional[t.IO[t.Any]] = None,
|
||||||
|
nl: bool = True,
|
||||||
|
err: bool = False,
|
||||||
|
color: t.Optional[bool] = None,
|
||||||
|
) -> None:
|
||||||
|
"""Print a message and newline to stdout or a file. This should be
|
||||||
|
used instead of :func:`print` because it provides better support
|
||||||
|
for different data, files, and environments.
|
||||||
|
|
||||||
|
Compared to :func:`print`, this does the following:
|
||||||
|
|
||||||
|
- Ensures that the output encoding is not misconfigured on Linux.
|
||||||
|
- Supports Unicode in the Windows console.
|
||||||
|
- Supports writing to binary outputs, and supports writing bytes
|
||||||
|
to text outputs.
|
||||||
|
- Supports colors and styles on Windows.
|
||||||
|
- Removes ANSI color and style codes if the output does not look
|
||||||
|
like an interactive terminal.
|
||||||
|
- Always flushes the output.
|
||||||
|
|
||||||
|
:param message: The string or bytes to output. Other objects are
|
||||||
|
converted to strings.
|
||||||
|
:param file: The file to write to. Defaults to ``stdout``.
|
||||||
|
:param err: Write to ``stderr`` instead of ``stdout``.
|
||||||
|
:param nl: Print a newline after the message. Enabled by default.
|
||||||
|
:param color: Force showing or hiding colors and other styles. By
|
||||||
|
default Click will remove color if the output does not look like
|
||||||
|
an interactive terminal.
|
||||||
|
|
||||||
|
.. versionchanged:: 6.0
|
||||||
|
Support Unicode output on the Windows console. Click does not
|
||||||
|
modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()``
|
||||||
|
will still not support Unicode.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.0
|
||||||
|
Added the ``color`` parameter.
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
Added the ``err`` parameter.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0
|
||||||
|
Support colors on Windows if colorama is installed.
|
||||||
|
"""
|
||||||
|
if file is None:
|
||||||
|
if err:
|
||||||
|
file = _default_text_stderr()
|
||||||
|
else:
|
||||||
|
file = _default_text_stdout()
|
||||||
|
|
||||||
|
# Convert non bytes/text into the native string type.
|
||||||
|
if message is not None and not isinstance(message, (str, bytes, bytearray)):
|
||||||
|
out: t.Optional[t.Union[str, bytes]] = str(message)
|
||||||
|
else:
|
||||||
|
out = message
|
||||||
|
|
||||||
|
if nl:
|
||||||
|
out = out or ""
|
||||||
|
if isinstance(out, str):
|
||||||
|
out += "\n"
|
||||||
|
else:
|
||||||
|
out += b"\n"
|
||||||
|
|
||||||
|
if not out:
|
||||||
|
file.flush()
|
||||||
|
return
|
||||||
|
|
||||||
|
# If there is a message and the value looks like bytes, we manually
|
||||||
|
# need to find the binary stream and write the message in there.
|
||||||
|
# This is done separately so that most stream types will work as you
|
||||||
|
# would expect. Eg: you can write to StringIO for other cases.
|
||||||
|
if isinstance(out, (bytes, bytearray)):
|
||||||
|
binary_file = _find_binary_writer(file)
|
||||||
|
|
||||||
|
if binary_file is not None:
|
||||||
|
file.flush()
|
||||||
|
binary_file.write(out)
|
||||||
|
binary_file.flush()
|
||||||
|
return
|
||||||
|
|
||||||
|
# ANSI style code support. For no message or bytes, nothing happens.
|
||||||
|
# When outputting to a file instead of a terminal, strip codes.
|
||||||
|
else:
|
||||||
|
color = resolve_color_default(color)
|
||||||
|
|
||||||
|
if should_strip_ansi(file, color):
|
||||||
|
out = strip_ansi(out)
|
||||||
|
elif WIN:
|
||||||
|
if auto_wrap_for_ansi is not None:
|
||||||
|
file = auto_wrap_for_ansi(file) # type: ignore
|
||||||
|
elif not color:
|
||||||
|
out = strip_ansi(out)
|
||||||
|
|
||||||
|
file.write(out) # type: ignore
|
||||||
|
file.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def get_binary_stream(name: "te.Literal['stdin', 'stdout', 'stderr']") -> t.BinaryIO:
|
||||||
|
"""Returns a system stream for byte processing.
|
||||||
|
|
||||||
|
:param name: the name of the stream to open. Valid names are ``'stdin'``,
|
||||||
|
``'stdout'`` and ``'stderr'``
|
||||||
|
"""
|
||||||
|
opener = binary_streams.get(name)
|
||||||
|
if opener is None:
|
||||||
|
raise TypeError(f"Unknown standard stream '{name}'")
|
||||||
|
return opener()
|
||||||
|
|
||||||
|
|
||||||
|
def get_text_stream(
|
||||||
|
name: "te.Literal['stdin', 'stdout', 'stderr']",
|
||||||
|
encoding: t.Optional[str] = None,
|
||||||
|
errors: t.Optional[str] = "strict",
|
||||||
|
) -> t.TextIO:
|
||||||
|
"""Returns a system stream for text processing. This usually returns
|
||||||
|
a wrapped stream around a binary stream returned from
|
||||||
|
:func:`get_binary_stream` but it also can take shortcuts for already
|
||||||
|
correctly configured streams.
|
||||||
|
|
||||||
|
:param name: the name of the stream to open. Valid names are ``'stdin'``,
|
||||||
|
``'stdout'`` and ``'stderr'``
|
||||||
|
:param encoding: overrides the detected default encoding.
|
||||||
|
:param errors: overrides the default error mode.
|
||||||
|
"""
|
||||||
|
opener = text_streams.get(name)
|
||||||
|
if opener is None:
|
||||||
|
raise TypeError(f"Unknown standard stream '{name}'")
|
||||||
|
return opener(encoding, errors)
|
||||||
|
|
||||||
|
|
||||||
|
def open_file(
|
||||||
|
filename: str,
|
||||||
|
mode: str = "r",
|
||||||
|
encoding: t.Optional[str] = None,
|
||||||
|
errors: t.Optional[str] = "strict",
|
||||||
|
lazy: bool = False,
|
||||||
|
atomic: bool = False,
|
||||||
|
) -> t.IO:
|
||||||
|
"""Open a file, with extra behavior to handle ``'-'`` to indicate
|
||||||
|
a standard stream, lazy open on write, and atomic write. Similar to
|
||||||
|
the behavior of the :class:`~click.File` param type.
|
||||||
|
|
||||||
|
If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is
|
||||||
|
wrapped so that using it in a context manager will not close it.
|
||||||
|
This makes it possible to use the function without accidentally
|
||||||
|
closing a standard stream:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
with open_file(filename) as f:
|
||||||
|
...
|
||||||
|
|
||||||
|
:param filename: The name of the file to open, or ``'-'`` for
|
||||||
|
``stdin``/``stdout``.
|
||||||
|
:param mode: The mode in which to open the file.
|
||||||
|
:param encoding: The encoding to decode or encode a file opened in
|
||||||
|
text mode.
|
||||||
|
:param errors: The error handling mode.
|
||||||
|
:param lazy: Wait to open the file until it is accessed. For read
|
||||||
|
mode, the file is temporarily opened to raise access errors
|
||||||
|
early, then closed until it is read again.
|
||||||
|
:param atomic: Write to a temporary file and replace the given file
|
||||||
|
on close.
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
"""
|
||||||
|
if lazy:
|
||||||
|
return t.cast(t.IO, LazyFile(filename, mode, encoding, errors, atomic=atomic))
|
||||||
|
|
||||||
|
f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic)
|
||||||
|
|
||||||
|
if not should_close:
|
||||||
|
f = t.cast(t.IO, KeepOpenFile(f))
|
||||||
|
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def get_os_args() -> t.Sequence[str]:
|
||||||
|
"""Returns the argument part of ``sys.argv``, removing the first
|
||||||
|
value which is the name of the script.
|
||||||
|
|
||||||
|
.. deprecated:: 8.0
|
||||||
|
Will be removed in Click 8.1. Access ``sys.argv[1:]`` directly
|
||||||
|
instead.
|
||||||
|
"""
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"'get_os_args' is deprecated and will be removed in Click 8.1."
|
||||||
|
" Access 'sys.argv[1:]' directly instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return sys.argv[1:]
|
||||||
|
|
||||||
|
|
||||||
|
def format_filename(
|
||||||
|
filename: t.Union[str, bytes, os.PathLike], shorten: bool = False
|
||||||
|
) -> str:
|
||||||
|
"""Formats a filename for user display. The main purpose of this
|
||||||
|
function is to ensure that the filename can be displayed at all. This
|
||||||
|
will decode the filename to unicode if necessary in a way that it will
|
||||||
|
not fail. Optionally, it can shorten the filename to not include the
|
||||||
|
full path to the filename.
|
||||||
|
|
||||||
|
:param filename: formats a filename for UI display. This will also convert
|
||||||
|
the filename into unicode without failing.
|
||||||
|
:param shorten: this optionally shortens the filename to strip of the
|
||||||
|
path that leads up to it.
|
||||||
|
"""
|
||||||
|
if shorten:
|
||||||
|
filename = os.path.basename(filename)
|
||||||
|
|
||||||
|
return os.fsdecode(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str:
|
||||||
|
r"""Returns the config folder for the application. The default behavior
|
||||||
|
is to return whatever is most appropriate for the operating system.
|
||||||
|
|
||||||
|
To give you an idea, for an app called ``"Foo Bar"``, something like
|
||||||
|
the following folders could be returned:
|
||||||
|
|
||||||
|
Mac OS X:
|
||||||
|
``~/Library/Application Support/Foo Bar``
|
||||||
|
Mac OS X (POSIX):
|
||||||
|
``~/.foo-bar``
|
||||||
|
Unix:
|
||||||
|
``~/.config/foo-bar``
|
||||||
|
Unix (POSIX):
|
||||||
|
``~/.foo-bar``
|
||||||
|
Windows (roaming):
|
||||||
|
``C:\Users\<user>\AppData\Roaming\Foo Bar``
|
||||||
|
Windows (not roaming):
|
||||||
|
``C:\Users\<user>\AppData\Local\Foo Bar``
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
:param app_name: the application name. This should be properly capitalized
|
||||||
|
and can contain whitespace.
|
||||||
|
:param roaming: controls if the folder should be roaming or not on Windows.
|
||||||
|
Has no affect otherwise.
|
||||||
|
:param force_posix: if this is set to `True` then on any POSIX system the
|
||||||
|
folder will be stored in the home folder with a leading
|
||||||
|
dot instead of the XDG config home or darwin's
|
||||||
|
application support folder.
|
||||||
|
"""
|
||||||
|
if WIN:
|
||||||
|
key = "APPDATA" if roaming else "LOCALAPPDATA"
|
||||||
|
folder = os.environ.get(key)
|
||||||
|
if folder is None:
|
||||||
|
folder = os.path.expanduser("~")
|
||||||
|
return os.path.join(folder, app_name)
|
||||||
|
if force_posix:
|
||||||
|
return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}"))
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
return os.path.join(
|
||||||
|
os.path.expanduser("~/Library/Application Support"), app_name
|
||||||
|
)
|
||||||
|
return os.path.join(
|
||||||
|
os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")),
|
||||||
|
_posixify(app_name),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PacifyFlushWrapper:
|
||||||
|
"""This wrapper is used to catch and suppress BrokenPipeErrors resulting
|
||||||
|
from ``.flush()`` being called on broken pipe during the shutdown/final-GC
|
||||||
|
of the Python interpreter. Notably ``.flush()`` is always called on
|
||||||
|
``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any
|
||||||
|
other cleanup code, and the case where the underlying file is not a broken
|
||||||
|
pipe, all calls and attributes are proxied.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, wrapped: t.IO) -> None:
|
||||||
|
self.wrapped = wrapped
|
||||||
|
|
||||||
|
def flush(self) -> None:
|
||||||
|
try:
|
||||||
|
self.wrapped.flush()
|
||||||
|
except OSError as e:
|
||||||
|
import errno
|
||||||
|
|
||||||
|
if e.errno != errno.EPIPE:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def __getattr__(self, attr: str) -> t.Any:
|
||||||
|
return getattr(self.wrapped, attr)
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_program_name(
|
||||||
|
path: t.Optional[str] = None, _main: ModuleType = sys.modules["__main__"]
|
||||||
|
) -> str:
|
||||||
|
"""Determine the command used to run the program, for use in help
|
||||||
|
text. If a file or entry point was executed, the file name is
|
||||||
|
returned. If ``python -m`` was used to execute a module or package,
|
||||||
|
``python -m name`` is returned.
|
||||||
|
|
||||||
|
This doesn't try to be too precise, the goal is to give a concise
|
||||||
|
name for help text. Files are only shown as their name without the
|
||||||
|
path. ``python`` is only shown for modules, and the full path to
|
||||||
|
``sys.executable`` is not shown.
|
||||||
|
|
||||||
|
:param path: The Python file being executed. Python puts this in
|
||||||
|
``sys.argv[0]``, which is used by default.
|
||||||
|
:param _main: The ``__main__`` module. This should only be passed
|
||||||
|
during internal testing.
|
||||||
|
|
||||||
|
.. versionadded:: 8.0
|
||||||
|
Based on command args detection in the Werkzeug reloader.
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
|
if not path:
|
||||||
|
path = sys.argv[0]
|
||||||
|
|
||||||
|
# The value of __package__ indicates how Python was called. It may
|
||||||
|
# not exist if a setuptools script is installed as an egg. It may be
|
||||||
|
# set incorrectly for entry points created with pip on Windows.
|
||||||
|
if getattr(_main, "__package__", None) is None or (
|
||||||
|
os.name == "nt"
|
||||||
|
and _main.__package__ == ""
|
||||||
|
and not os.path.exists(path)
|
||||||
|
and os.path.exists(f"{path}.exe")
|
||||||
|
):
|
||||||
|
# Executed a file, like "python app.py".
|
||||||
|
return os.path.basename(path)
|
||||||
|
|
||||||
|
# Executed a module, like "python -m example".
|
||||||
|
# Rewritten by Python from "-m script" to "/path/to/script.py".
|
||||||
|
# Need to look at main module to determine how it was executed.
|
||||||
|
py_module = t.cast(str, _main.__package__)
|
||||||
|
name = os.path.splitext(os.path.basename(path))[0]
|
||||||
|
|
||||||
|
# A submodule like "example.cli".
|
||||||
|
if name != "__main__":
|
||||||
|
py_module = f"{py_module}.{name}"
|
||||||
|
|
||||||
|
return f"python -m {py_module.lstrip('.')}"
|
||||||
|
|
||||||
|
|
||||||
|
def _expand_args(
|
||||||
|
args: t.Iterable[str],
|
||||||
|
*,
|
||||||
|
user: bool = True,
|
||||||
|
env: bool = True,
|
||||||
|
glob_recursive: bool = True,
|
||||||
|
) -> t.List[str]:
|
||||||
|
"""Simulate Unix shell expansion with Python functions.
|
||||||
|
|
||||||
|
See :func:`glob.glob`, :func:`os.path.expanduser`, and
|
||||||
|
:func:`os.path.expandvars`.
|
||||||
|
|
||||||
|
This intended for use on Windows, where the shell does not do any
|
||||||
|
expansion. It may not exactly match what a Unix shell would do.
|
||||||
|
|
||||||
|
:param args: List of command line arguments to expand.
|
||||||
|
:param user: Expand user home directory.
|
||||||
|
:param env: Expand environment variables.
|
||||||
|
:param glob_recursive: ``**`` matches directories recursively.
|
||||||
|
|
||||||
|
.. versionadded:: 8.0
|
||||||
|
|
||||||
|
:meta private:
|
||||||
|
"""
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
out = []
|
||||||
|
|
||||||
|
for arg in args:
|
||||||
|
if user:
|
||||||
|
arg = os.path.expanduser(arg)
|
||||||
|
|
||||||
|
if env:
|
||||||
|
arg = os.path.expandvars(arg)
|
||||||
|
|
||||||
|
matches = glob(arg, recursive=glob_recursive)
|
||||||
|
|
||||||
|
if not matches:
|
||||||
|
out.append(arg)
|
||||||
|
else:
|
||||||
|
out.extend(matches)
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
@ -43,4 +43,4 @@ from .signals import template_rendered as template_rendered
|
||||||
from .templating import render_template as render_template
|
from .templating import render_template as render_template
|
||||||
from .templating import render_template_string as render_template_string
|
from .templating import render_template_string as render_template_string
|
||||||
|
|
||||||
__version__ = "2.0.1"
|
__version__ = "2.0.3"
|
||||||
|
|
|
||||||
131
dist/ba_data/python-site-packages/flask/app.py
vendored
131
dist/ba_data/python-site-packages/flask/app.py
vendored
|
|
@ -58,17 +58,12 @@ from .signals import request_started
|
||||||
from .signals import request_tearing_down
|
from .signals import request_tearing_down
|
||||||
from .templating import DispatchingJinjaLoader
|
from .templating import DispatchingJinjaLoader
|
||||||
from .templating import Environment
|
from .templating import Environment
|
||||||
from .typing import AfterRequestCallable
|
from .typing import BeforeFirstRequestCallable
|
||||||
from .typing import BeforeRequestCallable
|
|
||||||
from .typing import ErrorHandlerCallable
|
|
||||||
from .typing import ResponseReturnValue
|
from .typing import ResponseReturnValue
|
||||||
from .typing import TeardownCallable
|
from .typing import TeardownCallable
|
||||||
from .typing import TemplateContextProcessorCallable
|
|
||||||
from .typing import TemplateFilterCallable
|
from .typing import TemplateFilterCallable
|
||||||
from .typing import TemplateGlobalCallable
|
from .typing import TemplateGlobalCallable
|
||||||
from .typing import TemplateTestCallable
|
from .typing import TemplateTestCallable
|
||||||
from .typing import URLDefaultCallable
|
|
||||||
from .typing import URLValuePreprocessorCallable
|
|
||||||
from .wrappers import Request
|
from .wrappers import Request
|
||||||
from .wrappers import Response
|
from .wrappers import Response
|
||||||
|
|
||||||
|
|
@ -77,6 +72,7 @@ if t.TYPE_CHECKING:
|
||||||
from .blueprints import Blueprint
|
from .blueprints import Blueprint
|
||||||
from .testing import FlaskClient
|
from .testing import FlaskClient
|
||||||
from .testing import FlaskCliRunner
|
from .testing import FlaskCliRunner
|
||||||
|
from .typing import ErrorHandlerCallable
|
||||||
|
|
||||||
if sys.version_info >= (3, 8):
|
if sys.version_info >= (3, 8):
|
||||||
iscoroutinefunction = inspect.iscoroutinefunction
|
iscoroutinefunction = inspect.iscoroutinefunction
|
||||||
|
|
@ -365,7 +361,8 @@ class Flask(Scaffold):
|
||||||
#: .. versionadded:: 1.1.0
|
#: .. versionadded:: 1.1.0
|
||||||
url_map_class = Map
|
url_map_class = Map
|
||||||
|
|
||||||
#: the test client that is used with when `test_client` is used.
|
#: The :meth:`test_client` method creates an instance of this test
|
||||||
|
#: client class. Defaults to :class:`~flask.testing.FlaskClient`.
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 0.7
|
#: .. versionadded:: 0.7
|
||||||
test_client_class: t.Optional[t.Type["FlaskClient"]] = None
|
test_client_class: t.Optional[t.Type["FlaskClient"]] = None
|
||||||
|
|
@ -388,7 +385,7 @@ class Flask(Scaffold):
|
||||||
self,
|
self,
|
||||||
import_name: str,
|
import_name: str,
|
||||||
static_url_path: t.Optional[str] = None,
|
static_url_path: t.Optional[str] = None,
|
||||||
static_folder: t.Optional[str] = "static",
|
static_folder: t.Optional[t.Union[str, os.PathLike]] = "static",
|
||||||
static_host: t.Optional[str] = None,
|
static_host: t.Optional[str] = None,
|
||||||
host_matching: bool = False,
|
host_matching: bool = False,
|
||||||
subdomain_matching: bool = False,
|
subdomain_matching: bool = False,
|
||||||
|
|
@ -439,7 +436,7 @@ class Flask(Scaffold):
|
||||||
#: :meth:`before_first_request` decorator.
|
#: :meth:`before_first_request` decorator.
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 0.8
|
#: .. versionadded:: 0.8
|
||||||
self.before_first_request_funcs: t.List[BeforeRequestCallable] = []
|
self.before_first_request_funcs: t.List[BeforeFirstRequestCallable] = []
|
||||||
|
|
||||||
#: A list of functions that are called when the application context
|
#: A list of functions that are called when the application context
|
||||||
#: is destroyed. Since the application context is also torn down
|
#: is destroyed. Since the application context is also torn down
|
||||||
|
|
@ -743,20 +740,21 @@ class Flask(Scaffold):
|
||||||
:param context: the context as a dictionary that is updated in place
|
:param context: the context as a dictionary that is updated in place
|
||||||
to add extra variables.
|
to add extra variables.
|
||||||
"""
|
"""
|
||||||
funcs: t.Iterable[
|
names: t.Iterable[t.Optional[str]] = (None,)
|
||||||
TemplateContextProcessorCallable
|
|
||||||
] = self.template_context_processors[None]
|
# A template may be rendered outside a request context.
|
||||||
reqctx = _request_ctx_stack.top
|
if request:
|
||||||
if reqctx is not None:
|
names = chain(names, reversed(request.blueprints))
|
||||||
for bp in request.blueprints:
|
|
||||||
if bp in self.template_context_processors:
|
# The values passed to render_template take precedence. Keep a
|
||||||
funcs = chain(funcs, self.template_context_processors[bp])
|
# copy to re-apply after all context functions.
|
||||||
orig_ctx = context.copy()
|
orig_ctx = context.copy()
|
||||||
for func in funcs:
|
|
||||||
|
for name in names:
|
||||||
|
if name in self.template_context_processors:
|
||||||
|
for func in self.template_context_processors[name]:
|
||||||
context.update(func())
|
context.update(func())
|
||||||
# make sure the original values win. This makes it possible to
|
|
||||||
# easier add new variables in context processors without breaking
|
|
||||||
# existing views.
|
|
||||||
context.update(orig_ctx)
|
context.update(orig_ctx)
|
||||||
|
|
||||||
def make_shell_context(self) -> dict:
|
def make_shell_context(self) -> dict:
|
||||||
|
|
@ -1211,7 +1209,9 @@ class Flask(Scaffold):
|
||||||
self.jinja_env.globals[name or f.__name__] = f
|
self.jinja_env.globals[name or f.__name__] = f
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def before_first_request(self, f: BeforeRequestCallable) -> BeforeRequestCallable:
|
def before_first_request(
|
||||||
|
self, f: BeforeFirstRequestCallable
|
||||||
|
) -> BeforeFirstRequestCallable:
|
||||||
"""Registers a function to be run before the first request to this
|
"""Registers a function to be run before the first request to this
|
||||||
instance of the application.
|
instance of the application.
|
||||||
|
|
||||||
|
|
@ -1265,16 +1265,19 @@ class Flask(Scaffold):
|
||||||
self.shell_context_processors.append(f)
|
self.shell_context_processors.append(f)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def _find_error_handler(self, e: Exception) -> t.Optional[ErrorHandlerCallable]:
|
def _find_error_handler(
|
||||||
|
self, e: Exception
|
||||||
|
) -> t.Optional["ErrorHandlerCallable[Exception]"]:
|
||||||
"""Return a registered error handler for an exception in this order:
|
"""Return a registered error handler for an exception in this order:
|
||||||
blueprint handler for a specific code, app handler for a specific code,
|
blueprint handler for a specific code, app handler for a specific code,
|
||||||
blueprint handler for an exception class, app handler for an exception
|
blueprint handler for an exception class, app handler for an exception
|
||||||
class, or ``None`` if a suitable handler is not found.
|
class, or ``None`` if a suitable handler is not found.
|
||||||
"""
|
"""
|
||||||
exc_class, code = self._get_exc_class_and_code(type(e))
|
exc_class, code = self._get_exc_class_and_code(type(e))
|
||||||
|
names = (*request.blueprints, None)
|
||||||
|
|
||||||
for c in [code, None]:
|
for c in (code, None) if code is not None else (None,):
|
||||||
for name in chain(request.blueprints, [None]):
|
for name in names:
|
||||||
handler_map = self.error_handler_spec[name][c]
|
handler_map = self.error_handler_spec[name][c]
|
||||||
|
|
||||||
if not handler_map:
|
if not handler_map:
|
||||||
|
|
@ -1301,7 +1304,7 @@ class Flask(Scaffold):
|
||||||
|
|
||||||
.. versionchanged:: 1.0
|
.. versionchanged:: 1.0
|
||||||
Exceptions are looked up by code *and* by MRO, so
|
Exceptions are looked up by code *and* by MRO, so
|
||||||
``HTTPExcpetion`` subclasses can be handled with a catch-all
|
``HTTPException`` subclasses can be handled with a catch-all
|
||||||
handler for the base ``HTTPException``.
|
handler for the base ``HTTPException``.
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
|
|
@ -1616,7 +1619,7 @@ class Flask(Scaffold):
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Install Flask with the 'async' extra in order to use async views."
|
"Install Flask with the 'async' extra in order to use async views."
|
||||||
)
|
) from None
|
||||||
|
|
||||||
# Check that Werkzeug isn't using its fallback ContextVar class.
|
# Check that Werkzeug isn't using its fallback ContextVar class.
|
||||||
if ContextVar.__module__ == "werkzeug.local":
|
if ContextVar.__module__ == "werkzeug.local":
|
||||||
|
|
@ -1722,7 +1725,7 @@ class Flask(Scaffold):
|
||||||
" response. The return type must be a string,"
|
" response. The return type must be a string,"
|
||||||
" dict, tuple, Response instance, or WSGI"
|
" dict, tuple, Response instance, or WSGI"
|
||||||
f" callable, but it was a {type(rv).__name__}."
|
f" callable, but it was a {type(rv).__name__}."
|
||||||
).with_traceback(sys.exc_info()[2])
|
).with_traceback(sys.exc_info()[2]) from None
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"The view function did not return a valid"
|
"The view function did not return a valid"
|
||||||
|
|
@ -1794,16 +1797,18 @@ class Flask(Scaffold):
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
"""
|
"""
|
||||||
funcs: t.Iterable[URLDefaultCallable] = self.url_default_functions[None]
|
names: t.Iterable[t.Optional[str]] = (None,)
|
||||||
|
|
||||||
|
# url_for may be called outside a request context, parse the
|
||||||
|
# passed endpoint instead of using request.blueprints.
|
||||||
if "." in endpoint:
|
if "." in endpoint:
|
||||||
# This is called by url_for, which can be called outside a
|
names = chain(
|
||||||
# request, can't use request.blueprints.
|
names, reversed(_split_blueprint_path(endpoint.rpartition(".")[0]))
|
||||||
bps = _split_blueprint_path(endpoint.rpartition(".")[0])
|
)
|
||||||
bp_funcs = chain.from_iterable(self.url_default_functions[bp] for bp in bps)
|
|
||||||
funcs = chain(funcs, bp_funcs)
|
|
||||||
|
|
||||||
for func in funcs:
|
for name in names:
|
||||||
|
if name in self.url_default_functions:
|
||||||
|
for func in self.url_default_functions[name]:
|
||||||
func(endpoint, values)
|
func(endpoint, values)
|
||||||
|
|
||||||
def handle_url_build_error(
|
def handle_url_build_error(
|
||||||
|
|
@ -1839,22 +1844,18 @@ class Flask(Scaffold):
|
||||||
value is handled as if it was the return value from the view, and
|
value is handled as if it was the return value from the view, and
|
||||||
further request handling is stopped.
|
further request handling is stopped.
|
||||||
"""
|
"""
|
||||||
|
names = (None, *reversed(request.blueprints))
|
||||||
|
|
||||||
funcs: t.Iterable[URLValuePreprocessorCallable] = self.url_value_preprocessors[
|
for name in names:
|
||||||
None
|
if name in self.url_value_preprocessors:
|
||||||
]
|
for url_func in self.url_value_preprocessors[name]:
|
||||||
for bp in request.blueprints:
|
url_func(request.endpoint, request.view_args)
|
||||||
if bp in self.url_value_preprocessors:
|
|
||||||
funcs = chain(funcs, self.url_value_preprocessors[bp])
|
for name in names:
|
||||||
for func in funcs:
|
if name in self.before_request_funcs:
|
||||||
func(request.endpoint, request.view_args)
|
for before_func in self.before_request_funcs[name]:
|
||||||
|
rv = self.ensure_sync(before_func)()
|
||||||
|
|
||||||
funcs: t.Iterable[BeforeRequestCallable] = self.before_request_funcs[None]
|
|
||||||
for bp in request.blueprints:
|
|
||||||
if bp in self.before_request_funcs:
|
|
||||||
funcs = chain(funcs, self.before_request_funcs[bp])
|
|
||||||
for func in funcs:
|
|
||||||
rv = self.ensure_sync(func)()
|
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
@ -1874,16 +1875,18 @@ class Flask(Scaffold):
|
||||||
instance of :attr:`response_class`.
|
instance of :attr:`response_class`.
|
||||||
"""
|
"""
|
||||||
ctx = _request_ctx_stack.top
|
ctx = _request_ctx_stack.top
|
||||||
funcs: t.Iterable[AfterRequestCallable] = ctx._after_request_functions
|
|
||||||
for bp in request.blueprints:
|
for func in ctx._after_request_functions:
|
||||||
if bp in self.after_request_funcs:
|
response = self.ensure_sync(func)(response)
|
||||||
funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
|
|
||||||
if None in self.after_request_funcs:
|
for name in chain(request.blueprints, (None,)):
|
||||||
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
|
if name in self.after_request_funcs:
|
||||||
for handler in funcs:
|
for func in reversed(self.after_request_funcs[name]):
|
||||||
response = self.ensure_sync(handler)(response)
|
response = self.ensure_sync(func)(response)
|
||||||
|
|
||||||
if not self.session_interface.is_null_session(ctx.session):
|
if not self.session_interface.is_null_session(ctx.session):
|
||||||
self.session_interface.save_session(self, ctx.session, response)
|
self.session_interface.save_session(self, ctx.session, response)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def do_teardown_request(
|
def do_teardown_request(
|
||||||
|
|
@ -1911,14 +1914,12 @@ class Flask(Scaffold):
|
||||||
"""
|
"""
|
||||||
if exc is _sentinel:
|
if exc is _sentinel:
|
||||||
exc = sys.exc_info()[1]
|
exc = sys.exc_info()[1]
|
||||||
funcs: t.Iterable[TeardownCallable] = reversed(
|
|
||||||
self.teardown_request_funcs[None]
|
for name in chain(request.blueprints, (None,)):
|
||||||
)
|
if name in self.teardown_request_funcs:
|
||||||
for bp in request.blueprints:
|
for func in reversed(self.teardown_request_funcs[name]):
|
||||||
if bp in self.teardown_request_funcs:
|
|
||||||
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
|
|
||||||
for func in funcs:
|
|
||||||
self.ensure_sync(func)(exc)
|
self.ensure_sync(func)(exc)
|
||||||
|
|
||||||
request_tearing_down.send(self, exc=exc)
|
request_tearing_down.send(self, exc=exc)
|
||||||
|
|
||||||
def do_teardown_appcontext(
|
def do_teardown_appcontext(
|
||||||
|
|
@ -1940,8 +1941,10 @@ class Flask(Scaffold):
|
||||||
"""
|
"""
|
||||||
if exc is _sentinel:
|
if exc is _sentinel:
|
||||||
exc = sys.exc_info()[1]
|
exc = sys.exc_info()[1]
|
||||||
|
|
||||||
for func in reversed(self.teardown_appcontext_funcs):
|
for func in reversed(self.teardown_appcontext_funcs):
|
||||||
self.ensure_sync(func)(exc)
|
self.ensure_sync(func)(exc)
|
||||||
|
|
||||||
appcontext_tearing_down.send(self, exc=exc)
|
appcontext_tearing_down.send(self, exc=exc)
|
||||||
|
|
||||||
def app_context(self) -> AppContext:
|
def app_context(self) -> AppContext:
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import os
|
||||||
import typing as t
|
import typing as t
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
|
|
@ -6,8 +7,8 @@ from .scaffold import _endpoint_from_view_func
|
||||||
from .scaffold import _sentinel
|
from .scaffold import _sentinel
|
||||||
from .scaffold import Scaffold
|
from .scaffold import Scaffold
|
||||||
from .typing import AfterRequestCallable
|
from .typing import AfterRequestCallable
|
||||||
|
from .typing import BeforeFirstRequestCallable
|
||||||
from .typing import BeforeRequestCallable
|
from .typing import BeforeRequestCallable
|
||||||
from .typing import ErrorHandlerCallable
|
|
||||||
from .typing import TeardownCallable
|
from .typing import TeardownCallable
|
||||||
from .typing import TemplateContextProcessorCallable
|
from .typing import TemplateContextProcessorCallable
|
||||||
from .typing import TemplateFilterCallable
|
from .typing import TemplateFilterCallable
|
||||||
|
|
@ -18,6 +19,7 @@ from .typing import URLValuePreprocessorCallable
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
from .app import Flask
|
from .app import Flask
|
||||||
|
from .typing import ErrorHandlerCallable
|
||||||
|
|
||||||
DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
|
DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
|
||||||
|
|
||||||
|
|
@ -174,7 +176,7 @@ class Blueprint(Scaffold):
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
import_name: str,
|
import_name: str,
|
||||||
static_folder: t.Optional[str] = None,
|
static_folder: t.Optional[t.Union[str, os.PathLike]] = None,
|
||||||
static_url_path: t.Optional[str] = None,
|
static_url_path: t.Optional[str] = None,
|
||||||
template_folder: t.Optional[str] = None,
|
template_folder: t.Optional[str] = None,
|
||||||
url_prefix: t.Optional[str] = None,
|
url_prefix: t.Optional[str] = None,
|
||||||
|
|
@ -292,7 +294,6 @@ class Blueprint(Scaffold):
|
||||||
Registering the same blueprint with the same name multiple
|
Registering the same blueprint with the same name multiple
|
||||||
times is deprecated and will become an error in Flask 2.1.
|
times is deprecated and will become an error in Flask 2.1.
|
||||||
"""
|
"""
|
||||||
first_registration = not any(bp is self for bp in app.blueprints.values())
|
|
||||||
name_prefix = options.get("name_prefix", "")
|
name_prefix = options.get("name_prefix", "")
|
||||||
self_name = options.get("name", self.name)
|
self_name = options.get("name", self.name)
|
||||||
name = f"{name_prefix}.{self_name}".lstrip(".")
|
name = f"{name_prefix}.{self_name}".lstrip(".")
|
||||||
|
|
@ -317,9 +318,12 @@ class Blueprint(Scaffold):
|
||||||
stacklevel=4,
|
stacklevel=4,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
first_bp_registration = not any(bp is self for bp in app.blueprints.values())
|
||||||
|
first_name_registration = name not in app.blueprints
|
||||||
|
|
||||||
app.blueprints[name] = self
|
app.blueprints[name] = self
|
||||||
self._got_registered_once = True
|
self._got_registered_once = True
|
||||||
state = self.make_setup_state(app, options, first_registration)
|
state = self.make_setup_state(app, options, first_bp_registration)
|
||||||
|
|
||||||
if self.has_static_folder:
|
if self.has_static_folder:
|
||||||
state.add_url_rule(
|
state.add_url_rule(
|
||||||
|
|
@ -329,7 +333,7 @@ class Blueprint(Scaffold):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Merge blueprint data into parent.
|
# Merge blueprint data into parent.
|
||||||
if first_registration:
|
if first_bp_registration or first_name_registration:
|
||||||
|
|
||||||
def extend(bp_dict, parent_dict):
|
def extend(bp_dict, parent_dict):
|
||||||
for key, values in bp_dict.items():
|
for key, values in bp_dict.items():
|
||||||
|
|
@ -537,8 +541,8 @@ class Blueprint(Scaffold):
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def before_app_first_request(
|
def before_app_first_request(
|
||||||
self, f: BeforeRequestCallable
|
self, f: BeforeFirstRequestCallable
|
||||||
) -> BeforeRequestCallable:
|
) -> BeforeFirstRequestCallable:
|
||||||
"""Like :meth:`Flask.before_first_request`. Such a function is
|
"""Like :meth:`Flask.before_first_request`. Such a function is
|
||||||
executed before the first request to the application.
|
executed before the first request to the application.
|
||||||
"""
|
"""
|
||||||
|
|
@ -580,7 +584,9 @@ class Blueprint(Scaffold):
|
||||||
handler is used for all requests, even if outside of the blueprint.
|
handler is used for all requests, even if outside of the blueprint.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(f: ErrorHandlerCallable) -> ErrorHandlerCallable:
|
def decorator(
|
||||||
|
f: "ErrorHandlerCallable[Exception]",
|
||||||
|
) -> "ErrorHandlerCallable[Exception]":
|
||||||
self.record_once(lambda s: s.app.errorhandler(code)(f))
|
self.record_once(lambda s: s.app.errorhandler(code)(f))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
|
||||||
49
dist/ba_data/python-site-packages/flask/cli.py
vendored
49
dist/ba_data/python-site-packages/flask/cli.py
vendored
|
|
@ -69,15 +69,16 @@ def find_best_app(script_info, module):
|
||||||
|
|
||||||
if isinstance(app, Flask):
|
if isinstance(app, Flask):
|
||||||
return app
|
return app
|
||||||
except TypeError:
|
except TypeError as e:
|
||||||
if not _called_with_wrong_args(app_factory):
|
if not _called_with_wrong_args(app_factory):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
f"Detected factory {attr_name!r} in module {module.__name__!r},"
|
f"Detected factory {attr_name!r} in module {module.__name__!r},"
|
||||||
" but could not call it without arguments. Use"
|
" but could not call it without arguments. Use"
|
||||||
f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\""
|
f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\""
|
||||||
" to specify arguments."
|
" to specify arguments."
|
||||||
)
|
) from e
|
||||||
|
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
"Failed to find Flask application or factory in module"
|
"Failed to find Flask application or factory in module"
|
||||||
|
|
@ -103,10 +104,13 @@ def call_factory(script_info, app_factory, args=None, kwargs=None):
|
||||||
)
|
)
|
||||||
kwargs["script_info"] = script_info
|
kwargs["script_info"] = script_info
|
||||||
|
|
||||||
|
if not args and len(sig.parameters) == 1:
|
||||||
|
first_parameter = next(iter(sig.parameters.values()))
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not args
|
first_parameter.default is inspect.Parameter.empty
|
||||||
and len(sig.parameters) == 1
|
# **kwargs is reported as an empty default, ignore it
|
||||||
and next(iter(sig.parameters.values())).default is inspect.Parameter.empty
|
and first_parameter.kind is not inspect.Parameter.VAR_KEYWORD
|
||||||
):
|
):
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Script info is deprecated and will not be passed as the"
|
"Script info is deprecated and will not be passed as the"
|
||||||
|
|
@ -158,7 +162,7 @@ def find_app_by_string(script_info, module, app_name):
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
f"Failed to parse {app_name!r} as an attribute name or function call."
|
f"Failed to parse {app_name!r} as an attribute name or function call."
|
||||||
)
|
) from None
|
||||||
|
|
||||||
if isinstance(expr, ast.Name):
|
if isinstance(expr, ast.Name):
|
||||||
name = expr.id
|
name = expr.id
|
||||||
|
|
@ -181,7 +185,7 @@ def find_app_by_string(script_info, module, app_name):
|
||||||
# message with the full expression instead.
|
# message with the full expression instead.
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
f"Failed to parse arguments as literal values: {app_name!r}."
|
f"Failed to parse arguments as literal values: {app_name!r}."
|
||||||
)
|
) from None
|
||||||
else:
|
else:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
f"Failed to parse {app_name!r} as an attribute name or function call."
|
f"Failed to parse {app_name!r} as an attribute name or function call."
|
||||||
|
|
@ -189,17 +193,17 @@ def find_app_by_string(script_info, module, app_name):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
attr = getattr(module, name)
|
attr = getattr(module, name)
|
||||||
except AttributeError:
|
except AttributeError as e:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
f"Failed to find attribute {name!r} in {module.__name__!r}."
|
f"Failed to find attribute {name!r} in {module.__name__!r}."
|
||||||
)
|
) from e
|
||||||
|
|
||||||
# If the attribute is a function, call it with any args and kwargs
|
# If the attribute is a function, call it with any args and kwargs
|
||||||
# to get the real application.
|
# to get the real application.
|
||||||
if inspect.isfunction(attr):
|
if inspect.isfunction(attr):
|
||||||
try:
|
try:
|
||||||
app = call_factory(script_info, attr, args, kwargs)
|
app = call_factory(script_info, attr, args, kwargs)
|
||||||
except TypeError:
|
except TypeError as e:
|
||||||
if not _called_with_wrong_args(attr):
|
if not _called_with_wrong_args(attr):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
@ -207,7 +211,7 @@ def find_app_by_string(script_info, module, app_name):
|
||||||
f"The factory {app_name!r} in module"
|
f"The factory {app_name!r} in module"
|
||||||
f" {module.__name__!r} could not be called with the"
|
f" {module.__name__!r} could not be called with the"
|
||||||
" specified arguments."
|
" specified arguments."
|
||||||
)
|
) from e
|
||||||
else:
|
else:
|
||||||
app = attr
|
app = attr
|
||||||
|
|
||||||
|
|
@ -261,9 +265,9 @@ def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
f"While importing {module_name!r}, an ImportError was"
|
f"While importing {module_name!r}, an ImportError was"
|
||||||
f" raised:\n\n{traceback.format_exc()}"
|
f" raised:\n\n{traceback.format_exc()}"
|
||||||
)
|
) from None
|
||||||
elif raise_if_not_found:
|
elif raise_if_not_found:
|
||||||
raise NoAppException(f"Could not import {module_name!r}.")
|
raise NoAppException(f"Could not import {module_name!r}.") from None
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -312,7 +316,7 @@ class DispatchingApp:
|
||||||
self.loader = loader
|
self.loader = loader
|
||||||
self._app = None
|
self._app = None
|
||||||
self._lock = Lock()
|
self._lock = Lock()
|
||||||
self._bg_loading_exc_info = None
|
self._bg_loading_exc = None
|
||||||
|
|
||||||
if use_eager_loading is None:
|
if use_eager_loading is None:
|
||||||
use_eager_loading = os.environ.get("WERKZEUG_RUN_MAIN") != "true"
|
use_eager_loading = os.environ.get("WERKZEUG_RUN_MAIN") != "true"
|
||||||
|
|
@ -328,23 +332,24 @@ class DispatchingApp:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
try:
|
try:
|
||||||
self._load_unlocked()
|
self._load_unlocked()
|
||||||
except Exception:
|
except Exception as e:
|
||||||
self._bg_loading_exc_info = sys.exc_info()
|
self._bg_loading_exc = e
|
||||||
|
|
||||||
t = Thread(target=_load_app, args=())
|
t = Thread(target=_load_app, args=())
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
def _flush_bg_loading_exception(self):
|
def _flush_bg_loading_exception(self):
|
||||||
__traceback_hide__ = True # noqa: F841
|
__traceback_hide__ = True # noqa: F841
|
||||||
exc_info = self._bg_loading_exc_info
|
exc = self._bg_loading_exc
|
||||||
if exc_info is not None:
|
|
||||||
self._bg_loading_exc_info = None
|
if exc is not None:
|
||||||
raise exc_info
|
self._bg_loading_exc = None
|
||||||
|
raise exc
|
||||||
|
|
||||||
def _load_unlocked(self):
|
def _load_unlocked(self):
|
||||||
__traceback_hide__ = True # noqa: F841
|
__traceback_hide__ = True # noqa: F841
|
||||||
self._app = rv = self.loader()
|
self._app = rv = self.loader()
|
||||||
self._bg_loading_exc_info = None
|
self._bg_loading_exc = None
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
|
|
@ -721,7 +726,7 @@ class CertParamType(click.ParamType):
|
||||||
"Using ad-hoc certificates requires the cryptography library.",
|
"Using ad-hoc certificates requires the cryptography library.",
|
||||||
ctx,
|
ctx,
|
||||||
param,
|
param,
|
||||||
)
|
) from None
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ class Config(dict):
|
||||||
:param variable_name: name of the environment variable
|
:param variable_name: name of the environment variable
|
||||||
:param silent: set to ``True`` if you want silent failure for missing
|
:param silent: set to ``True`` if you want silent failure for missing
|
||||||
files.
|
files.
|
||||||
:return: bool. ``True`` if able to load config, ``False`` otherwise.
|
:return: ``True`` if the file was loaded successfully.
|
||||||
"""
|
"""
|
||||||
rv = os.environ.get(variable_name)
|
rv = os.environ.get(variable_name)
|
||||||
if not rv:
|
if not rv:
|
||||||
|
|
@ -107,6 +107,7 @@ class Config(dict):
|
||||||
root path.
|
root path.
|
||||||
:param silent: set to ``True`` if you want silent failure for missing
|
:param silent: set to ``True`` if you want silent failure for missing
|
||||||
files.
|
files.
|
||||||
|
:return: ``True`` if the file was loaded successfully.
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
`silent` parameter.
|
`silent` parameter.
|
||||||
|
|
@ -185,6 +186,7 @@ class Config(dict):
|
||||||
:type load: ``Callable[[Reader], Mapping]`` where ``Reader``
|
:type load: ``Callable[[Reader], Mapping]`` where ``Reader``
|
||||||
implements a ``read`` method.
|
implements a ``read`` method.
|
||||||
:param silent: Ignore the file if it doesn't exist.
|
:param silent: Ignore the file if it doesn't exist.
|
||||||
|
:return: ``True`` if the file was loaded successfully.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
"""
|
"""
|
||||||
|
|
@ -209,6 +211,7 @@ class Config(dict):
|
||||||
:param filename: The path to the JSON file. This can be an
|
:param filename: The path to the JSON file. This can be an
|
||||||
absolute path or relative to the config root path.
|
absolute path or relative to the config root path.
|
||||||
:param silent: Ignore the file if it doesn't exist.
|
:param silent: Ignore the file if it doesn't exist.
|
||||||
|
:return: ``True`` if the file was loaded successfully.
|
||||||
|
|
||||||
.. deprecated:: 2.0.0
|
.. deprecated:: 2.0.0
|
||||||
Will be removed in Flask 2.1. Use :meth:`from_file` instead.
|
Will be removed in Flask 2.1. Use :meth:`from_file` instead.
|
||||||
|
|
@ -232,6 +235,7 @@ class Config(dict):
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Updates the config like :meth:`update` ignoring items with non-upper
|
"""Updates the config like :meth:`update` ignoring items with non-upper
|
||||||
keys.
|
keys.
|
||||||
|
:return: Always returns ``True``.
|
||||||
|
|
||||||
.. versionadded:: 0.11
|
.. versionadded:: 0.11
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
17
dist/ba_data/python-site-packages/flask/ctx.py
vendored
17
dist/ba_data/python-site-packages/flask/ctx.py
vendored
|
|
@ -130,7 +130,15 @@ def after_this_request(f: AfterRequestCallable) -> AfterRequestCallable:
|
||||||
|
|
||||||
.. versionadded:: 0.9
|
.. versionadded:: 0.9
|
||||||
"""
|
"""
|
||||||
_request_ctx_stack.top._after_request_functions.append(f)
|
top = _request_ctx_stack.top
|
||||||
|
|
||||||
|
if top is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"This decorator can only be used when a request context is"
|
||||||
|
" active, such as within a view function."
|
||||||
|
)
|
||||||
|
|
||||||
|
top._after_request_functions.append(f)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -159,12 +167,13 @@ def copy_current_request_context(f: t.Callable) -> t.Callable:
|
||||||
.. versionadded:: 0.10
|
.. versionadded:: 0.10
|
||||||
"""
|
"""
|
||||||
top = _request_ctx_stack.top
|
top = _request_ctx_stack.top
|
||||||
|
|
||||||
if top is None:
|
if top is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"This decorator can only be used at local scopes "
|
"This decorator can only be used when a request context is"
|
||||||
"when a request context is on the stack. For instance within "
|
" active, such as within a view function."
|
||||||
"view functions."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
reqctx = top.copy()
|
reqctx = top.copy()
|
||||||
|
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -83,10 +83,11 @@ def attach_enctype_error_multidict(request):
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
try:
|
try:
|
||||||
return oldcls.__getitem__(self, key)
|
return oldcls.__getitem__(self, key)
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
if key not in request.form:
|
if key not in request.form:
|
||||||
raise
|
raise
|
||||||
raise DebugFilesKeyError(request, key)
|
|
||||||
|
raise DebugFilesKeyError(request, key) from e
|
||||||
|
|
||||||
newcls.__name__ = oldcls.__name__
|
newcls.__name__ = oldcls.__name__
|
||||||
newcls.__module__ = oldcls.__module__
|
newcls.__module__ = oldcls.__module__
|
||||||
|
|
|
||||||
|
|
@ -714,7 +714,7 @@ def get_root_path(import_name: str) -> str:
|
||||||
# Module already imported and has a file attribute. Use that first.
|
# Module already imported and has a file attribute. Use that first.
|
||||||
mod = sys.modules.get(import_name)
|
mod = sys.modules.get(import_name)
|
||||||
|
|
||||||
if mod is not None and hasattr(mod, "__file__"):
|
if mod is not None and hasattr(mod, "__file__") and mod.__file__ is not None:
|
||||||
return os.path.dirname(os.path.abspath(mod.__file__))
|
return os.path.dirname(os.path.abspath(mod.__file__))
|
||||||
|
|
||||||
# Next attempt: check the loader.
|
# Next attempt: check the loader.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import decimal
|
||||||
import io
|
import io
|
||||||
import json as _json
|
import json as _json
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
@ -47,7 +48,7 @@ class JSONEncoder(_json.JSONEncoder):
|
||||||
"""
|
"""
|
||||||
if isinstance(o, date):
|
if isinstance(o, date):
|
||||||
return http_date(o)
|
return http_date(o)
|
||||||
if isinstance(o, uuid.UUID):
|
if isinstance(o, (decimal.Decimal, uuid.UUID)):
|
||||||
return str(o)
|
return str(o)
|
||||||
if dataclasses and dataclasses.is_dataclass(o):
|
if dataclasses and dataclasses.is_dataclass(o):
|
||||||
return dataclasses.asdict(o)
|
return dataclasses.asdict(o)
|
||||||
|
|
@ -80,6 +81,11 @@ def _dump_arg_defaults(
|
||||||
if bp is not None and bp.json_encoder is not None:
|
if bp is not None and bp.json_encoder is not None:
|
||||||
cls = bp.json_encoder
|
cls = bp.json_encoder
|
||||||
|
|
||||||
|
# Only set a custom encoder if it has custom behavior. This is
|
||||||
|
# faster on PyPy.
|
||||||
|
if cls is not _json.JSONEncoder:
|
||||||
|
kwargs.setdefault("cls", cls)
|
||||||
|
|
||||||
kwargs.setdefault("cls", cls)
|
kwargs.setdefault("cls", cls)
|
||||||
kwargs.setdefault("ensure_ascii", app.config["JSON_AS_ASCII"])
|
kwargs.setdefault("ensure_ascii", app.config["JSON_AS_ASCII"])
|
||||||
kwargs.setdefault("sort_keys", app.config["JSON_SORT_KEYS"])
|
kwargs.setdefault("sort_keys", app.config["JSON_SORT_KEYS"])
|
||||||
|
|
@ -101,9 +107,10 @@ def _load_arg_defaults(
|
||||||
if bp is not None and bp.json_decoder is not None:
|
if bp is not None and bp.json_decoder is not None:
|
||||||
cls = bp.json_decoder
|
cls = bp.json_decoder
|
||||||
|
|
||||||
|
# Only set a custom decoder if it has custom behavior. This is
|
||||||
|
# faster on PyPy.
|
||||||
|
if cls not in {JSONDecoder, _json.JSONDecoder}:
|
||||||
kwargs.setdefault("cls", cls)
|
kwargs.setdefault("cls", cls)
|
||||||
else:
|
|
||||||
kwargs.setdefault("cls", JSONDecoder)
|
|
||||||
|
|
||||||
|
|
||||||
def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str:
|
def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str:
|
||||||
|
|
@ -117,6 +124,9 @@ def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str:
|
||||||
or defaults.
|
or defaults.
|
||||||
:param kwargs: Extra arguments passed to :func:`json.dumps`.
|
:param kwargs: Extra arguments passed to :func:`json.dumps`.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0.2
|
||||||
|
:class:`decimal.Decimal` is supported by converting to a string.
|
||||||
|
|
||||||
.. versionchanged:: 2.0
|
.. versionchanged:: 2.0
|
||||||
``encoding`` is deprecated and will be removed in Flask 2.1.
|
``encoding`` is deprecated and will be removed in Flask 2.1.
|
||||||
|
|
||||||
|
|
@ -324,6 +334,9 @@ def jsonify(*args: t.Any, **kwargs: t.Any) -> "Response":
|
||||||
debug mode or if :data:`JSONIFY_PRETTYPRINT_REGULAR` is ``True``,
|
debug mode or if :data:`JSONIFY_PRETTYPRINT_REGULAR` is ``True``,
|
||||||
the output will be formatted to be easier to read.
|
the output will be formatted to be easier to read.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0.2
|
||||||
|
:class:`decimal.Decimal` is supported by converting to a string.
|
||||||
|
|
||||||
.. versionchanged:: 0.11
|
.. versionchanged:: 0.11
|
||||||
Added support for serializing top-level arrays. This introduces
|
Added support for serializing top-level arrays. This introduces
|
||||||
a security risk in ancient browsers. See :ref:`security-json`.
|
a security risk in ancient browsers. See :ref:`security-json`.
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ from .templating import _default_template_ctx_processor
|
||||||
from .typing import AfterRequestCallable
|
from .typing import AfterRequestCallable
|
||||||
from .typing import AppOrBlueprintKey
|
from .typing import AppOrBlueprintKey
|
||||||
from .typing import BeforeRequestCallable
|
from .typing import BeforeRequestCallable
|
||||||
from .typing import ErrorHandlerCallable
|
from .typing import GenericException
|
||||||
from .typing import TeardownCallable
|
from .typing import TeardownCallable
|
||||||
from .typing import TemplateContextProcessorCallable
|
from .typing import TemplateContextProcessorCallable
|
||||||
from .typing import URLDefaultCallable
|
from .typing import URLDefaultCallable
|
||||||
|
|
@ -29,6 +29,7 @@ from .typing import URLValuePreprocessorCallable
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
from .wrappers import Response
|
from .wrappers import Response
|
||||||
|
from .typing import ErrorHandlerCallable
|
||||||
|
|
||||||
# a singleton sentinel value for parameter defaults
|
# a singleton sentinel value for parameter defaults
|
||||||
_sentinel = object()
|
_sentinel = object()
|
||||||
|
|
@ -91,7 +92,7 @@ class Scaffold:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
import_name: str,
|
import_name: str,
|
||||||
static_folder: t.Optional[str] = None,
|
static_folder: t.Optional[t.Union[str, os.PathLike]] = None,
|
||||||
static_url_path: t.Optional[str] = None,
|
static_url_path: t.Optional[str] = None,
|
||||||
template_folder: t.Optional[str] = None,
|
template_folder: t.Optional[str] = None,
|
||||||
root_path: t.Optional[str] = None,
|
root_path: t.Optional[str] = None,
|
||||||
|
|
@ -100,7 +101,7 @@ class Scaffold:
|
||||||
#: to. Do not change this once it is set by the constructor.
|
#: to. Do not change this once it is set by the constructor.
|
||||||
self.import_name = import_name
|
self.import_name = import_name
|
||||||
|
|
||||||
self.static_folder = static_folder
|
self.static_folder = static_folder # type: ignore
|
||||||
self.static_url_path = static_url_path
|
self.static_url_path = static_url_path
|
||||||
|
|
||||||
#: The path to the templates folder, relative to
|
#: The path to the templates folder, relative to
|
||||||
|
|
@ -144,7 +145,10 @@ class Scaffold:
|
||||||
#: directly and its format may change at any time.
|
#: directly and its format may change at any time.
|
||||||
self.error_handler_spec: t.Dict[
|
self.error_handler_spec: t.Dict[
|
||||||
AppOrBlueprintKey,
|
AppOrBlueprintKey,
|
||||||
t.Dict[t.Optional[int], t.Dict[t.Type[Exception], ErrorHandlerCallable]],
|
t.Dict[
|
||||||
|
t.Optional[int],
|
||||||
|
t.Dict[t.Type[Exception], "ErrorHandlerCallable[Exception]"],
|
||||||
|
],
|
||||||
] = defaultdict(lambda: defaultdict(dict))
|
] = defaultdict(lambda: defaultdict(dict))
|
||||||
|
|
||||||
#: A data structure of functions to call at the beginning of
|
#: A data structure of functions to call at the beginning of
|
||||||
|
|
@ -253,7 +257,7 @@ class Scaffold:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@static_folder.setter
|
@static_folder.setter
|
||||||
def static_folder(self, value: t.Optional[str]) -> None:
|
def static_folder(self, value: t.Optional[t.Union[str, os.PathLike]]) -> None:
|
||||||
if value is not None:
|
if value is not None:
|
||||||
value = os.fspath(value).rstrip(r"\/")
|
value = os.fspath(value).rstrip(r"\/")
|
||||||
|
|
||||||
|
|
@ -643,8 +647,11 @@ class Scaffold:
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def errorhandler(
|
def errorhandler(
|
||||||
self, code_or_exception: t.Union[t.Type[Exception], int]
|
self, code_or_exception: t.Union[t.Type[GenericException], int]
|
||||||
) -> t.Callable[[ErrorHandlerCallable], ErrorHandlerCallable]:
|
) -> t.Callable[
|
||||||
|
["ErrorHandlerCallable[GenericException]"],
|
||||||
|
"ErrorHandlerCallable[GenericException]",
|
||||||
|
]:
|
||||||
"""Register a function to handle errors by code or exception class.
|
"""Register a function to handle errors by code or exception class.
|
||||||
|
|
||||||
A decorator that is used to register a function given an
|
A decorator that is used to register a function given an
|
||||||
|
|
@ -674,7 +681,9 @@ class Scaffold:
|
||||||
an arbitrary exception
|
an arbitrary exception
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(f: ErrorHandlerCallable) -> ErrorHandlerCallable:
|
def decorator(
|
||||||
|
f: "ErrorHandlerCallable[GenericException]",
|
||||||
|
) -> "ErrorHandlerCallable[GenericException]":
|
||||||
self.register_error_handler(code_or_exception, f)
|
self.register_error_handler(code_or_exception, f)
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
@ -683,8 +692,8 @@ class Scaffold:
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def register_error_handler(
|
def register_error_handler(
|
||||||
self,
|
self,
|
||||||
code_or_exception: t.Union[t.Type[Exception], int],
|
code_or_exception: t.Union[t.Type[GenericException], int],
|
||||||
f: ErrorHandlerCallable,
|
f: "ErrorHandlerCallable[GenericException]",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Alternative error attach function to the :meth:`errorhandler`
|
"""Alternative error attach function to the :meth:`errorhandler`
|
||||||
decorator that is more straightforward to use for non decorator
|
decorator that is more straightforward to use for non decorator
|
||||||
|
|
@ -706,9 +715,11 @@ class Scaffold:
|
||||||
f"'{code_or_exception}' is not a recognized HTTP error"
|
f"'{code_or_exception}' is not a recognized HTTP error"
|
||||||
" code. Use a subclass of HTTPException with that code"
|
" code. Use a subclass of HTTPException with that code"
|
||||||
" instead."
|
" instead."
|
||||||
)
|
) from None
|
||||||
|
|
||||||
self.error_handler_spec[None][code][exc_class] = f
|
self.error_handler_spec[None][code][exc_class] = t.cast(
|
||||||
|
"ErrorHandlerCallable[Exception]", f
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_exc_class_and_code(
|
def _get_exc_class_and_code(
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,13 @@ class SessionInterface:
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.session_interface = MySessionInterface()
|
app.session_interface = MySessionInterface()
|
||||||
|
|
||||||
|
Multiple requests with the same session may be sent and handled
|
||||||
|
concurrently. When implementing a new session interface, consider
|
||||||
|
whether reads or writes to the backing store must be synchronized.
|
||||||
|
There is no guarantee on the order in which the session for each
|
||||||
|
request is opened or saved, it will occur in the order that requests
|
||||||
|
begin and end processing.
|
||||||
|
|
||||||
.. versionadded:: 0.8
|
.. versionadded:: 0.8
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -292,20 +299,25 @@ class SessionInterface:
|
||||||
def open_session(
|
def open_session(
|
||||||
self, app: "Flask", request: "Request"
|
self, app: "Flask", request: "Request"
|
||||||
) -> t.Optional[SessionMixin]:
|
) -> t.Optional[SessionMixin]:
|
||||||
"""This method has to be implemented and must either return ``None``
|
"""This is called at the beginning of each request, after
|
||||||
in case the loading failed because of a configuration error or an
|
pushing the request context, before matching the URL.
|
||||||
instance of a session object which implements a dictionary like
|
|
||||||
interface + the methods and attributes on :class:`SessionMixin`.
|
This must return an object which implements a dictionary-like
|
||||||
|
interface as well as the :class:`SessionMixin` interface.
|
||||||
|
|
||||||
|
This will return ``None`` to indicate that loading failed in
|
||||||
|
some way that is not immediately an error. The request
|
||||||
|
context will fall back to using :meth:`make_null_session`
|
||||||
|
in this case.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def save_session(
|
def save_session(
|
||||||
self, app: "Flask", session: SessionMixin, response: "Response"
|
self, app: "Flask", session: SessionMixin, response: "Response"
|
||||||
) -> None:
|
) -> None:
|
||||||
"""This is called for actual sessions returned by :meth:`open_session`
|
"""This is called at the end of each request, after generating
|
||||||
at the end of the request. This is still called during a request
|
a response, before removing the request context. It is skipped
|
||||||
context so if you absolutely need access to the request you can do
|
if :meth:`is_null_session` returns ``True``.
|
||||||
that.
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ except ImportError:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Signalling support is unavailable because the blinker"
|
"Signalling support is unavailable because the blinker"
|
||||||
" library is not installed."
|
" library is not installed."
|
||||||
)
|
) from None
|
||||||
|
|
||||||
connect = connect_via = connected_to = temporarily_connected_to = _fail
|
connect = connect_via = connected_to = temporarily_connected_to = _fail
|
||||||
disconnect = _fail
|
disconnect = _fail
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,15 @@ from werkzeug.test import Client
|
||||||
from werkzeug.urls import url_parse
|
from werkzeug.urls import url_parse
|
||||||
from werkzeug.wrappers import Request as BaseRequest
|
from werkzeug.wrappers import Request as BaseRequest
|
||||||
|
|
||||||
from . import _request_ctx_stack
|
|
||||||
from .cli import ScriptInfo
|
from .cli import ScriptInfo
|
||||||
|
from .globals import _request_ctx_stack
|
||||||
from .json import dumps as json_dumps
|
from .json import dumps as json_dumps
|
||||||
from .sessions import SessionMixin
|
from .sessions import SessionMixin
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
|
from werkzeug.test import TestResponse
|
||||||
|
|
||||||
from .app import Flask
|
from .app import Flask
|
||||||
from .wrappers import Response
|
|
||||||
|
|
||||||
|
|
||||||
class EnvironBuilder(werkzeug.test.EnvironBuilder):
|
class EnvironBuilder(werkzeug.test.EnvironBuilder):
|
||||||
|
|
@ -171,14 +172,15 @@ class FlaskClient(Client):
|
||||||
headers = resp.get_wsgi_headers(c.request.environ)
|
headers = resp.get_wsgi_headers(c.request.environ)
|
||||||
self.cookie_jar.extract_wsgi(c.request.environ, headers)
|
self.cookie_jar.extract_wsgi(c.request.environ, headers)
|
||||||
|
|
||||||
def open( # type: ignore
|
def open(
|
||||||
self,
|
self,
|
||||||
*args: t.Any,
|
*args: t.Any,
|
||||||
as_tuple: bool = False,
|
|
||||||
buffered: bool = False,
|
buffered: bool = False,
|
||||||
follow_redirects: bool = False,
|
follow_redirects: bool = False,
|
||||||
**kwargs: t.Any,
|
**kwargs: t.Any,
|
||||||
) -> "Response":
|
) -> "TestResponse":
|
||||||
|
as_tuple = kwargs.pop("as_tuple", None)
|
||||||
|
|
||||||
# Same logic as super.open, but apply environ_base and preserve_context.
|
# Same logic as super.open, but apply environ_base and preserve_context.
|
||||||
request = None
|
request = None
|
||||||
|
|
||||||
|
|
@ -213,12 +215,28 @@ class FlaskClient(Client):
|
||||||
finally:
|
finally:
|
||||||
builder.close()
|
builder.close()
|
||||||
|
|
||||||
return super().open( # type: ignore
|
if as_tuple is not None:
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"'as_tuple' is deprecated and will be removed in"
|
||||||
|
" Werkzeug 2.1 and Flask 2.1. Use"
|
||||||
|
" 'response.request.environ' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=3,
|
||||||
|
)
|
||||||
|
return super().open(
|
||||||
request,
|
request,
|
||||||
as_tuple=as_tuple,
|
as_tuple=as_tuple,
|
||||||
buffered=buffered,
|
buffered=buffered,
|
||||||
follow_redirects=follow_redirects,
|
follow_redirects=follow_redirects,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
return super().open(
|
||||||
|
request,
|
||||||
|
buffered=buffered,
|
||||||
|
follow_redirects=follow_redirects,
|
||||||
|
)
|
||||||
|
|
||||||
def __enter__(self) -> "FlaskClient":
|
def __enter__(self) -> "FlaskClient":
|
||||||
if self.preserve_context:
|
if self.preserve_context:
|
||||||
|
|
@ -272,7 +290,7 @@ class FlaskCliRunner(CliRunner):
|
||||||
:return: a :class:`~click.testing.Result` object.
|
:return: a :class:`~click.testing.Result` object.
|
||||||
"""
|
"""
|
||||||
if cli is None:
|
if cli is None:
|
||||||
cli = self.app.cli
|
cli = self.app.cli # type: ignore
|
||||||
|
|
||||||
if "obj" not in kwargs:
|
if "obj" not in kwargs:
|
||||||
kwargs["obj"] = ScriptInfo(create_app=lambda: self.app)
|
kwargs["obj"] = ScriptInfo(create_app=lambda: self.app)
|
||||||
|
|
|
||||||
|
|
@ -33,14 +33,17 @@ ResponseReturnValue = t.Union[
|
||||||
"WSGIApplication",
|
"WSGIApplication",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
GenericException = t.TypeVar("GenericException", bound=Exception, contravariant=True)
|
||||||
|
|
||||||
AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named
|
AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named
|
||||||
AfterRequestCallable = t.Callable[["Response"], "Response"]
|
AfterRequestCallable = t.Callable[["Response"], "Response"]
|
||||||
BeforeRequestCallable = t.Callable[[], None]
|
BeforeFirstRequestCallable = t.Callable[[], None]
|
||||||
ErrorHandlerCallable = t.Callable[[Exception], ResponseReturnValue]
|
BeforeRequestCallable = t.Callable[[], t.Optional[ResponseReturnValue]]
|
||||||
TeardownCallable = t.Callable[[t.Optional[BaseException]], "Response"]
|
TeardownCallable = t.Callable[[t.Optional[BaseException]], None]
|
||||||
TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]]
|
TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]]
|
||||||
TemplateFilterCallable = t.Callable[[t.Any], str]
|
TemplateFilterCallable = t.Callable[..., t.Any]
|
||||||
TemplateGlobalCallable = t.Callable[[], t.Any]
|
TemplateGlobalCallable = t.Callable[..., t.Any]
|
||||||
TemplateTestCallable = t.Callable[[t.Any], bool]
|
TemplateTestCallable = t.Callable[..., bool]
|
||||||
URLDefaultCallable = t.Callable[[str, dict], None]
|
URLDefaultCallable = t.Callable[[str, dict], None]
|
||||||
URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None]
|
URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None]
|
||||||
|
ErrorHandlerCallable = t.Callable[[GenericException], ResponseReturnValue]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
|
from .globals import current_app
|
||||||
from .globals import request
|
from .globals import request
|
||||||
from .typing import ResponseReturnValue
|
from .typing import ResponseReturnValue
|
||||||
|
|
||||||
|
|
@ -80,7 +81,7 @@ class View:
|
||||||
|
|
||||||
def view(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValue:
|
def view(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValue:
|
||||||
self = view.view_class(*class_args, **class_kwargs) # type: ignore
|
self = view.view_class(*class_args, **class_kwargs) # type: ignore
|
||||||
return self.dispatch_request(*args, **kwargs)
|
return current_app.ensure_sync(self.dispatch_request)(*args, **kwargs)
|
||||||
|
|
||||||
if cls.decorators:
|
if cls.decorators:
|
||||||
view.__name__ = name
|
view.__name__ = name
|
||||||
|
|
@ -154,4 +155,4 @@ class MethodView(View, metaclass=MethodViewType):
|
||||||
meth = getattr(self, "get", None)
|
meth = getattr(self, "get", None)
|
||||||
|
|
||||||
assert meth is not None, f"Unimplemented method {request.method!r}"
|
assert meth is not None, f"Unimplemented method {request.method!r}"
|
||||||
return meth(*args, **kwargs)
|
return current_app.ensure_sync(meth)(*args, **kwargs)
|
||||||
|
|
|
||||||
45
dist/ba_data/python-site-packages/jinja2/__init__.py
vendored
Normal file
45
dist/ba_data/python-site-packages/jinja2/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
"""Jinja is a template engine written in pure Python. It provides a
|
||||||
|
non-XML syntax that supports inline expressions and an optional
|
||||||
|
sandboxed environment.
|
||||||
|
"""
|
||||||
|
from .bccache import BytecodeCache as BytecodeCache
|
||||||
|
from .bccache import FileSystemBytecodeCache as FileSystemBytecodeCache
|
||||||
|
from .bccache import MemcachedBytecodeCache as MemcachedBytecodeCache
|
||||||
|
from .environment import Environment as Environment
|
||||||
|
from .environment import Template as Template
|
||||||
|
from .exceptions import TemplateAssertionError as TemplateAssertionError
|
||||||
|
from .exceptions import TemplateError as TemplateError
|
||||||
|
from .exceptions import TemplateNotFound as TemplateNotFound
|
||||||
|
from .exceptions import TemplateRuntimeError as TemplateRuntimeError
|
||||||
|
from .exceptions import TemplatesNotFound as TemplatesNotFound
|
||||||
|
from .exceptions import TemplateSyntaxError as TemplateSyntaxError
|
||||||
|
from .exceptions import UndefinedError as UndefinedError
|
||||||
|
from .filters import contextfilter
|
||||||
|
from .filters import environmentfilter
|
||||||
|
from .filters import evalcontextfilter
|
||||||
|
from .loaders import BaseLoader as BaseLoader
|
||||||
|
from .loaders import ChoiceLoader as ChoiceLoader
|
||||||
|
from .loaders import DictLoader as DictLoader
|
||||||
|
from .loaders import FileSystemLoader as FileSystemLoader
|
||||||
|
from .loaders import FunctionLoader as FunctionLoader
|
||||||
|
from .loaders import ModuleLoader as ModuleLoader
|
||||||
|
from .loaders import PackageLoader as PackageLoader
|
||||||
|
from .loaders import PrefixLoader as PrefixLoader
|
||||||
|
from .runtime import ChainableUndefined as ChainableUndefined
|
||||||
|
from .runtime import DebugUndefined as DebugUndefined
|
||||||
|
from .runtime import make_logging_undefined as make_logging_undefined
|
||||||
|
from .runtime import StrictUndefined as StrictUndefined
|
||||||
|
from .runtime import Undefined as Undefined
|
||||||
|
from .utils import clear_caches as clear_caches
|
||||||
|
from .utils import contextfunction
|
||||||
|
from .utils import environmentfunction
|
||||||
|
from .utils import escape
|
||||||
|
from .utils import evalcontextfunction
|
||||||
|
from .utils import is_undefined as is_undefined
|
||||||
|
from .utils import Markup
|
||||||
|
from .utils import pass_context as pass_context
|
||||||
|
from .utils import pass_environment as pass_environment
|
||||||
|
from .utils import pass_eval_context as pass_eval_context
|
||||||
|
from .utils import select_autoescape as select_autoescape
|
||||||
|
|
||||||
|
__version__ = "3.0.3"
|
||||||
6
dist/ba_data/python-site-packages/jinja2/_identifier.py
vendored
Normal file
6
dist/ba_data/python-site-packages/jinja2/_identifier.py
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
# generated by scripts/generate_identifier_pattern.py
|
||||||
|
pattern = re.compile(
|
||||||
|
r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣔ-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఃా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑅳𑄴𑆀-𑆂𑆳-𑇊𑇀-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950
|
||||||
|
)
|
||||||
75
dist/ba_data/python-site-packages/jinja2/async_utils.py
vendored
Normal file
75
dist/ba_data/python-site-packages/jinja2/async_utils.py
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import inspect
|
||||||
|
import typing as t
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from .utils import _PassArg
|
||||||
|
from .utils import pass_eval_context
|
||||||
|
|
||||||
|
V = t.TypeVar("V")
|
||||||
|
|
||||||
|
|
||||||
|
def async_variant(normal_func): # type: ignore
|
||||||
|
def decorator(async_func): # type: ignore
|
||||||
|
pass_arg = _PassArg.from_obj(normal_func)
|
||||||
|
need_eval_context = pass_arg is None
|
||||||
|
|
||||||
|
if pass_arg is _PassArg.environment:
|
||||||
|
|
||||||
|
def is_async(args: t.Any) -> bool:
|
||||||
|
return t.cast(bool, args[0].is_async)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def is_async(args: t.Any) -> bool:
|
||||||
|
return t.cast(bool, args[0].environment.is_async)
|
||||||
|
|
||||||
|
@wraps(normal_func)
|
||||||
|
def wrapper(*args, **kwargs): # type: ignore
|
||||||
|
b = is_async(args)
|
||||||
|
|
||||||
|
if need_eval_context:
|
||||||
|
args = args[1:]
|
||||||
|
|
||||||
|
if b:
|
||||||
|
return async_func(*args, **kwargs)
|
||||||
|
|
||||||
|
return normal_func(*args, **kwargs)
|
||||||
|
|
||||||
|
if need_eval_context:
|
||||||
|
wrapper = pass_eval_context(wrapper)
|
||||||
|
|
||||||
|
wrapper.jinja_async_variant = True
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
_common_primitives = {int, float, bool, str, list, dict, tuple, type(None)}
|
||||||
|
|
||||||
|
|
||||||
|
async def auto_await(value: t.Union[t.Awaitable["V"], "V"]) -> "V":
|
||||||
|
# Avoid a costly call to isawaitable
|
||||||
|
if type(value) in _common_primitives:
|
||||||
|
return t.cast("V", value)
|
||||||
|
|
||||||
|
if inspect.isawaitable(value):
|
||||||
|
return await t.cast("t.Awaitable[V]", value)
|
||||||
|
|
||||||
|
return t.cast("V", value)
|
||||||
|
|
||||||
|
|
||||||
|
async def auto_aiter(
|
||||||
|
iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||||
|
) -> "t.AsyncIterator[V]":
|
||||||
|
if hasattr(iterable, "__aiter__"):
|
||||||
|
async for item in t.cast("t.AsyncIterable[V]", iterable):
|
||||||
|
yield item
|
||||||
|
else:
|
||||||
|
for item in t.cast("t.Iterable[V]", iterable):
|
||||||
|
yield item
|
||||||
|
|
||||||
|
|
||||||
|
async def auto_to_list(
|
||||||
|
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||||
|
) -> t.List["V"]:
|
||||||
|
return [x async for x in auto_aiter(value)]
|
||||||
364
dist/ba_data/python-site-packages/jinja2/bccache.py
vendored
Normal file
364
dist/ba_data/python-site-packages/jinja2/bccache.py
vendored
Normal file
|
|
@ -0,0 +1,364 @@
|
||||||
|
"""The optional bytecode cache system. This is useful if you have very
|
||||||
|
complex template situations and the compilation of all those templates
|
||||||
|
slows down your application too much.
|
||||||
|
|
||||||
|
Situations where this is useful are often forking web applications that
|
||||||
|
are initialized on the first request.
|
||||||
|
"""
|
||||||
|
import errno
|
||||||
|
import fnmatch
|
||||||
|
import marshal
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import stat
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import typing as t
|
||||||
|
from hashlib import sha1
|
||||||
|
from io import BytesIO
|
||||||
|
from types import CodeType
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
|
from .environment import Environment
|
||||||
|
|
||||||
|
class _MemcachedClient(te.Protocol):
|
||||||
|
def get(self, key: str) -> bytes:
|
||||||
|
...
|
||||||
|
|
||||||
|
def set(self, key: str, value: bytes, timeout: t.Optional[int] = None) -> None:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
bc_version = 5
|
||||||
|
# Magic bytes to identify Jinja bytecode cache files. Contains the
|
||||||
|
# Python major and minor version to avoid loading incompatible bytecode
|
||||||
|
# if a project upgrades its Python version.
|
||||||
|
bc_magic = (
|
||||||
|
b"j2"
|
||||||
|
+ pickle.dumps(bc_version, 2)
|
||||||
|
+ pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1], 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Bucket:
|
||||||
|
"""Buckets are used to store the bytecode for one template. It's created
|
||||||
|
and initialized by the bytecode cache and passed to the loading functions.
|
||||||
|
|
||||||
|
The buckets get an internal checksum from the cache assigned and use this
|
||||||
|
to automatically reject outdated cache material. Individual bytecode
|
||||||
|
cache subclasses don't have to care about cache invalidation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, environment: "Environment", key: str, checksum: str) -> None:
|
||||||
|
self.environment = environment
|
||||||
|
self.key = key
|
||||||
|
self.checksum = checksum
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
"""Resets the bucket (unloads the bytecode)."""
|
||||||
|
self.code: t.Optional[CodeType] = None
|
||||||
|
|
||||||
|
def load_bytecode(self, f: t.BinaryIO) -> None:
|
||||||
|
"""Loads bytecode from a file or file like object."""
|
||||||
|
# make sure the magic header is correct
|
||||||
|
magic = f.read(len(bc_magic))
|
||||||
|
if magic != bc_magic:
|
||||||
|
self.reset()
|
||||||
|
return
|
||||||
|
# the source code of the file changed, we need to reload
|
||||||
|
checksum = pickle.load(f)
|
||||||
|
if self.checksum != checksum:
|
||||||
|
self.reset()
|
||||||
|
return
|
||||||
|
# if marshal_load fails then we need to reload
|
||||||
|
try:
|
||||||
|
self.code = marshal.load(f)
|
||||||
|
except (EOFError, ValueError, TypeError):
|
||||||
|
self.reset()
|
||||||
|
return
|
||||||
|
|
||||||
|
def write_bytecode(self, f: t.BinaryIO) -> None:
|
||||||
|
"""Dump the bytecode into the file or file like object passed."""
|
||||||
|
if self.code is None:
|
||||||
|
raise TypeError("can't write empty bucket")
|
||||||
|
f.write(bc_magic)
|
||||||
|
pickle.dump(self.checksum, f, 2)
|
||||||
|
marshal.dump(self.code, f)
|
||||||
|
|
||||||
|
def bytecode_from_string(self, string: bytes) -> None:
|
||||||
|
"""Load bytecode from bytes."""
|
||||||
|
self.load_bytecode(BytesIO(string))
|
||||||
|
|
||||||
|
def bytecode_to_string(self) -> bytes:
|
||||||
|
"""Return the bytecode as bytes."""
|
||||||
|
out = BytesIO()
|
||||||
|
self.write_bytecode(out)
|
||||||
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
class BytecodeCache:
|
||||||
|
"""To implement your own bytecode cache you have to subclass this class
|
||||||
|
and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
|
||||||
|
these methods are passed a :class:`~jinja2.bccache.Bucket`.
|
||||||
|
|
||||||
|
A very basic bytecode cache that saves the bytecode on the file system::
|
||||||
|
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
class MyCache(BytecodeCache):
|
||||||
|
|
||||||
|
def __init__(self, directory):
|
||||||
|
self.directory = directory
|
||||||
|
|
||||||
|
def load_bytecode(self, bucket):
|
||||||
|
filename = path.join(self.directory, bucket.key)
|
||||||
|
if path.exists(filename):
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
bucket.load_bytecode(f)
|
||||||
|
|
||||||
|
def dump_bytecode(self, bucket):
|
||||||
|
filename = path.join(self.directory, bucket.key)
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
bucket.write_bytecode(f)
|
||||||
|
|
||||||
|
A more advanced version of a filesystem based bytecode cache is part of
|
||||||
|
Jinja.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def load_bytecode(self, bucket: Bucket) -> None:
|
||||||
|
"""Subclasses have to override this method to load bytecode into a
|
||||||
|
bucket. If they are not able to find code in the cache for the
|
||||||
|
bucket, it must not do anything.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def dump_bytecode(self, bucket: Bucket) -> None:
|
||||||
|
"""Subclasses have to override this method to write the bytecode
|
||||||
|
from a bucket back to the cache. If it unable to do so it must not
|
||||||
|
fail silently but raise an exception.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""Clears the cache. This method is not used by Jinja but should be
|
||||||
|
implemented to allow applications to clear the bytecode cache used
|
||||||
|
by a particular environment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_cache_key(
|
||||||
|
self, name: str, filename: t.Optional[t.Union[str]] = None
|
||||||
|
) -> str:
|
||||||
|
"""Returns the unique hash key for this template name."""
|
||||||
|
hash = sha1(name.encode("utf-8"))
|
||||||
|
|
||||||
|
if filename is not None:
|
||||||
|
hash.update(f"|{filename}".encode())
|
||||||
|
|
||||||
|
return hash.hexdigest()
|
||||||
|
|
||||||
|
def get_source_checksum(self, source: str) -> str:
|
||||||
|
"""Returns a checksum for the source."""
|
||||||
|
return sha1(source.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
def get_bucket(
|
||||||
|
self,
|
||||||
|
environment: "Environment",
|
||||||
|
name: str,
|
||||||
|
filename: t.Optional[str],
|
||||||
|
source: str,
|
||||||
|
) -> Bucket:
|
||||||
|
"""Return a cache bucket for the given template. All arguments are
|
||||||
|
mandatory but filename may be `None`.
|
||||||
|
"""
|
||||||
|
key = self.get_cache_key(name, filename)
|
||||||
|
checksum = self.get_source_checksum(source)
|
||||||
|
bucket = Bucket(environment, key, checksum)
|
||||||
|
self.load_bytecode(bucket)
|
||||||
|
return bucket
|
||||||
|
|
||||||
|
def set_bucket(self, bucket: Bucket) -> None:
|
||||||
|
"""Put the bucket into the cache."""
|
||||||
|
self.dump_bytecode(bucket)
|
||||||
|
|
||||||
|
|
||||||
|
class FileSystemBytecodeCache(BytecodeCache):
|
||||||
|
"""A bytecode cache that stores bytecode on the filesystem. It accepts
|
||||||
|
two arguments: The directory where the cache items are stored and a
|
||||||
|
pattern string that is used to build the filename.
|
||||||
|
|
||||||
|
If no directory is specified a default cache directory is selected. On
|
||||||
|
Windows the user's temp directory is used, on UNIX systems a directory
|
||||||
|
is created for the user in the system temp directory.
|
||||||
|
|
||||||
|
The pattern can be used to have multiple separate caches operate on the
|
||||||
|
same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
|
||||||
|
is replaced with the cache key.
|
||||||
|
|
||||||
|
>>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
|
||||||
|
|
||||||
|
This bytecode cache supports clearing of the cache using the clear method.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache"
|
||||||
|
) -> None:
|
||||||
|
if directory is None:
|
||||||
|
directory = self._get_default_cache_dir()
|
||||||
|
self.directory = directory
|
||||||
|
self.pattern = pattern
|
||||||
|
|
||||||
|
def _get_default_cache_dir(self) -> str:
|
||||||
|
def _unsafe_dir() -> "te.NoReturn":
|
||||||
|
raise RuntimeError(
|
||||||
|
"Cannot determine safe temp directory. You "
|
||||||
|
"need to explicitly provide one."
|
||||||
|
)
|
||||||
|
|
||||||
|
tmpdir = tempfile.gettempdir()
|
||||||
|
|
||||||
|
# On windows the temporary directory is used specific unless
|
||||||
|
# explicitly forced otherwise. We can just use that.
|
||||||
|
if os.name == "nt":
|
||||||
|
return tmpdir
|
||||||
|
if not hasattr(os, "getuid"):
|
||||||
|
_unsafe_dir()
|
||||||
|
|
||||||
|
dirname = f"_jinja2-cache-{os.getuid()}"
|
||||||
|
actual_dir = os.path.join(tmpdir, dirname)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.mkdir(actual_dir, stat.S_IRWXU)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.EEXIST:
|
||||||
|
raise
|
||||||
|
try:
|
||||||
|
os.chmod(actual_dir, stat.S_IRWXU)
|
||||||
|
actual_dir_stat = os.lstat(actual_dir)
|
||||||
|
if (
|
||||||
|
actual_dir_stat.st_uid != os.getuid()
|
||||||
|
or not stat.S_ISDIR(actual_dir_stat.st_mode)
|
||||||
|
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
|
||||||
|
):
|
||||||
|
_unsafe_dir()
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.EEXIST:
|
||||||
|
raise
|
||||||
|
|
||||||
|
actual_dir_stat = os.lstat(actual_dir)
|
||||||
|
if (
|
||||||
|
actual_dir_stat.st_uid != os.getuid()
|
||||||
|
or not stat.S_ISDIR(actual_dir_stat.st_mode)
|
||||||
|
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
|
||||||
|
):
|
||||||
|
_unsafe_dir()
|
||||||
|
|
||||||
|
return actual_dir
|
||||||
|
|
||||||
|
def _get_cache_filename(self, bucket: Bucket) -> str:
|
||||||
|
return os.path.join(self.directory, self.pattern % (bucket.key,))
|
||||||
|
|
||||||
|
def load_bytecode(self, bucket: Bucket) -> None:
|
||||||
|
filename = self._get_cache_filename(bucket)
|
||||||
|
|
||||||
|
if os.path.exists(filename):
|
||||||
|
with open(filename, "rb") as f:
|
||||||
|
bucket.load_bytecode(f)
|
||||||
|
|
||||||
|
def dump_bytecode(self, bucket: Bucket) -> None:
|
||||||
|
with open(self._get_cache_filename(bucket), "wb") as f:
|
||||||
|
bucket.write_bytecode(f)
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
# imported lazily here because google app-engine doesn't support
|
||||||
|
# write access on the file system and the function does not exist
|
||||||
|
# normally.
|
||||||
|
from os import remove
|
||||||
|
|
||||||
|
files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",))
|
||||||
|
for filename in files:
|
||||||
|
try:
|
||||||
|
remove(os.path.join(self.directory, filename))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MemcachedBytecodeCache(BytecodeCache):
|
||||||
|
"""This class implements a bytecode cache that uses a memcache cache for
|
||||||
|
storing the information. It does not enforce a specific memcache library
|
||||||
|
(tummy's memcache or cmemcache) but will accept any class that provides
|
||||||
|
the minimal interface required.
|
||||||
|
|
||||||
|
Libraries compatible with this class:
|
||||||
|
|
||||||
|
- `cachelib <https://github.com/pallets/cachelib>`_
|
||||||
|
- `python-memcached <https://pypi.org/project/python-memcached/>`_
|
||||||
|
|
||||||
|
(Unfortunately the django cache interface is not compatible because it
|
||||||
|
does not support storing binary data, only text. You can however pass
|
||||||
|
the underlying cache client to the bytecode cache which is available
|
||||||
|
as `django.core.cache.cache._client`.)
|
||||||
|
|
||||||
|
The minimal interface for the client passed to the constructor is this:
|
||||||
|
|
||||||
|
.. class:: MinimalClientInterface
|
||||||
|
|
||||||
|
.. method:: set(key, value[, timeout])
|
||||||
|
|
||||||
|
Stores the bytecode in the cache. `value` is a string and
|
||||||
|
`timeout` the timeout of the key. If timeout is not provided
|
||||||
|
a default timeout or no timeout should be assumed, if it's
|
||||||
|
provided it's an integer with the number of seconds the cache
|
||||||
|
item should exist.
|
||||||
|
|
||||||
|
.. method:: get(key)
|
||||||
|
|
||||||
|
Returns the value for the cache key. If the item does not
|
||||||
|
exist in the cache the return value must be `None`.
|
||||||
|
|
||||||
|
The other arguments to the constructor are the prefix for all keys that
|
||||||
|
is added before the actual cache key and the timeout for the bytecode in
|
||||||
|
the cache system. We recommend a high (or no) timeout.
|
||||||
|
|
||||||
|
This bytecode cache does not support clearing of used items in the cache.
|
||||||
|
The clear method is a no-operation function.
|
||||||
|
|
||||||
|
.. versionadded:: 2.7
|
||||||
|
Added support for ignoring memcache errors through the
|
||||||
|
`ignore_memcache_errors` parameter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
client: "_MemcachedClient",
|
||||||
|
prefix: str = "jinja2/bytecode/",
|
||||||
|
timeout: t.Optional[int] = None,
|
||||||
|
ignore_memcache_errors: bool = True,
|
||||||
|
):
|
||||||
|
self.client = client
|
||||||
|
self.prefix = prefix
|
||||||
|
self.timeout = timeout
|
||||||
|
self.ignore_memcache_errors = ignore_memcache_errors
|
||||||
|
|
||||||
|
def load_bytecode(self, bucket: Bucket) -> None:
|
||||||
|
try:
|
||||||
|
code = self.client.get(self.prefix + bucket.key)
|
||||||
|
except Exception:
|
||||||
|
if not self.ignore_memcache_errors:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
bucket.bytecode_from_string(code)
|
||||||
|
|
||||||
|
def dump_bytecode(self, bucket: Bucket) -> None:
|
||||||
|
key = self.prefix + bucket.key
|
||||||
|
value = bucket.bytecode_to_string()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.timeout is not None:
|
||||||
|
self.client.set(key, value, self.timeout)
|
||||||
|
else:
|
||||||
|
self.client.set(key, value)
|
||||||
|
except Exception:
|
||||||
|
if not self.ignore_memcache_errors:
|
||||||
|
raise
|
||||||
1957
dist/ba_data/python-site-packages/jinja2/compiler.py
vendored
Normal file
1957
dist/ba_data/python-site-packages/jinja2/compiler.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
20
dist/ba_data/python-site-packages/jinja2/constants.py
vendored
Normal file
20
dist/ba_data/python-site-packages/jinja2/constants.py
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#: list of lorem ipsum words used by the lipsum() helper function
|
||||||
|
LOREM_IPSUM_WORDS = """\
|
||||||
|
a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at
|
||||||
|
auctor augue bibendum blandit class commodo condimentum congue consectetuer
|
||||||
|
consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus
|
||||||
|
diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend
|
||||||
|
elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames
|
||||||
|
faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac
|
||||||
|
hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum
|
||||||
|
justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem
|
||||||
|
luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie
|
||||||
|
mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non
|
||||||
|
nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque
|
||||||
|
penatibus per pharetra phasellus placerat platea porta porttitor posuere
|
||||||
|
potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus
|
||||||
|
ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit
|
||||||
|
sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor
|
||||||
|
tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
|
||||||
|
ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
|
||||||
|
viverra volutpat vulputate"""
|
||||||
259
dist/ba_data/python-site-packages/jinja2/debug.py
vendored
Normal file
259
dist/ba_data/python-site-packages/jinja2/debug.py
vendored
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
import platform
|
||||||
|
import sys
|
||||||
|
import typing as t
|
||||||
|
from types import CodeType
|
||||||
|
from types import TracebackType
|
||||||
|
|
||||||
|
from .exceptions import TemplateSyntaxError
|
||||||
|
from .utils import internal_code
|
||||||
|
from .utils import missing
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
from .runtime import Context
|
||||||
|
|
||||||
|
|
||||||
|
def rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException:
|
||||||
|
"""Rewrite the current exception to replace any tracebacks from
|
||||||
|
within compiled template code with tracebacks that look like they
|
||||||
|
came from the template source.
|
||||||
|
|
||||||
|
This must be called within an ``except`` block.
|
||||||
|
|
||||||
|
:param source: For ``TemplateSyntaxError``, the original source if
|
||||||
|
known.
|
||||||
|
:return: The original exception with the rewritten traceback.
|
||||||
|
"""
|
||||||
|
_, exc_value, tb = sys.exc_info()
|
||||||
|
exc_value = t.cast(BaseException, exc_value)
|
||||||
|
tb = t.cast(TracebackType, tb)
|
||||||
|
|
||||||
|
if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated:
|
||||||
|
exc_value.translated = True
|
||||||
|
exc_value.source = source
|
||||||
|
# Remove the old traceback, otherwise the frames from the
|
||||||
|
# compiler still show up.
|
||||||
|
exc_value.with_traceback(None)
|
||||||
|
# Outside of runtime, so the frame isn't executing template
|
||||||
|
# code, but it still needs to point at the template.
|
||||||
|
tb = fake_traceback(
|
||||||
|
exc_value, None, exc_value.filename or "<unknown>", exc_value.lineno
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Skip the frame for the render function.
|
||||||
|
tb = tb.tb_next
|
||||||
|
|
||||||
|
stack = []
|
||||||
|
|
||||||
|
# Build the stack of traceback object, replacing any in template
|
||||||
|
# code with the source file and line information.
|
||||||
|
while tb is not None:
|
||||||
|
# Skip frames decorated with @internalcode. These are internal
|
||||||
|
# calls that aren't useful in template debugging output.
|
||||||
|
if tb.tb_frame.f_code in internal_code:
|
||||||
|
tb = tb.tb_next
|
||||||
|
continue
|
||||||
|
|
||||||
|
template = tb.tb_frame.f_globals.get("__jinja_template__")
|
||||||
|
|
||||||
|
if template is not None:
|
||||||
|
lineno = template.get_corresponding_lineno(tb.tb_lineno)
|
||||||
|
fake_tb = fake_traceback(exc_value, tb, template.filename, lineno)
|
||||||
|
stack.append(fake_tb)
|
||||||
|
else:
|
||||||
|
stack.append(tb)
|
||||||
|
|
||||||
|
tb = tb.tb_next
|
||||||
|
|
||||||
|
tb_next = None
|
||||||
|
|
||||||
|
# Assign tb_next in reverse to avoid circular references.
|
||||||
|
for tb in reversed(stack):
|
||||||
|
tb_next = tb_set_next(tb, tb_next)
|
||||||
|
|
||||||
|
return exc_value.with_traceback(tb_next)
|
||||||
|
|
||||||
|
|
||||||
|
def fake_traceback( # type: ignore
|
||||||
|
exc_value: BaseException, tb: t.Optional[TracebackType], filename: str, lineno: int
|
||||||
|
) -> TracebackType:
|
||||||
|
"""Produce a new traceback object that looks like it came from the
|
||||||
|
template source instead of the compiled code. The filename, line
|
||||||
|
number, and location name will point to the template, and the local
|
||||||
|
variables will be the current template context.
|
||||||
|
|
||||||
|
:param exc_value: The original exception to be re-raised to create
|
||||||
|
the new traceback.
|
||||||
|
:param tb: The original traceback to get the local variables and
|
||||||
|
code info from.
|
||||||
|
:param filename: The template filename.
|
||||||
|
:param lineno: The line number in the template source.
|
||||||
|
"""
|
||||||
|
if tb is not None:
|
||||||
|
# Replace the real locals with the context that would be
|
||||||
|
# available at that point in the template.
|
||||||
|
locals = get_template_locals(tb.tb_frame.f_locals)
|
||||||
|
locals.pop("__jinja_exception__", None)
|
||||||
|
else:
|
||||||
|
locals = {}
|
||||||
|
|
||||||
|
globals = {
|
||||||
|
"__name__": filename,
|
||||||
|
"__file__": filename,
|
||||||
|
"__jinja_exception__": exc_value,
|
||||||
|
}
|
||||||
|
# Raise an exception at the correct line number.
|
||||||
|
code: CodeType = compile(
|
||||||
|
"\n" * (lineno - 1) + "raise __jinja_exception__", filename, "exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build a new code object that points to the template file and
|
||||||
|
# replaces the location with a block name.
|
||||||
|
location = "template"
|
||||||
|
|
||||||
|
if tb is not None:
|
||||||
|
function = tb.tb_frame.f_code.co_name
|
||||||
|
|
||||||
|
if function == "root":
|
||||||
|
location = "top-level template code"
|
||||||
|
elif function.startswith("block_"):
|
||||||
|
location = f"block {function[6:]!r}"
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 8):
|
||||||
|
code = code.replace(co_name=location)
|
||||||
|
else:
|
||||||
|
code = CodeType(
|
||||||
|
code.co_argcount,
|
||||||
|
code.co_kwonlyargcount,
|
||||||
|
code.co_nlocals,
|
||||||
|
code.co_stacksize,
|
||||||
|
code.co_flags,
|
||||||
|
code.co_code,
|
||||||
|
code.co_consts,
|
||||||
|
code.co_names,
|
||||||
|
code.co_varnames,
|
||||||
|
code.co_filename,
|
||||||
|
location,
|
||||||
|
code.co_firstlineno,
|
||||||
|
code.co_lnotab,
|
||||||
|
code.co_freevars,
|
||||||
|
code.co_cellvars,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Execute the new code, which is guaranteed to raise, and return
|
||||||
|
# the new traceback without this frame.
|
||||||
|
try:
|
||||||
|
exec(code, globals, locals)
|
||||||
|
except BaseException:
|
||||||
|
return sys.exc_info()[2].tb_next # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any]:
|
||||||
|
"""Based on the runtime locals, get the context that would be
|
||||||
|
available at that point in the template.
|
||||||
|
"""
|
||||||
|
# Start with the current template context.
|
||||||
|
ctx: "t.Optional[Context]" = real_locals.get("context")
|
||||||
|
|
||||||
|
if ctx is not None:
|
||||||
|
data: t.Dict[str, t.Any] = ctx.get_all().copy()
|
||||||
|
else:
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
# Might be in a derived context that only sets local variables
|
||||||
|
# rather than pushing a context. Local variables follow the scheme
|
||||||
|
# l_depth_name. Find the highest-depth local that has a value for
|
||||||
|
# each name.
|
||||||
|
local_overrides: t.Dict[str, t.Tuple[int, t.Any]] = {}
|
||||||
|
|
||||||
|
for name, value in real_locals.items():
|
||||||
|
if not name.startswith("l_") or value is missing:
|
||||||
|
# Not a template variable, or no longer relevant.
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, depth_str, name = name.split("_", 2)
|
||||||
|
depth = int(depth_str)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
cur_depth = local_overrides.get(name, (-1,))[0]
|
||||||
|
|
||||||
|
if cur_depth < depth:
|
||||||
|
local_overrides[name] = (depth, value)
|
||||||
|
|
||||||
|
# Modify the context with any derived context.
|
||||||
|
for name, (_, value) in local_overrides.items():
|
||||||
|
if value is missing:
|
||||||
|
data.pop(name, None)
|
||||||
|
else:
|
||||||
|
data[name] = value
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 7):
|
||||||
|
# tb_next is directly assignable as of Python 3.7
|
||||||
|
def tb_set_next(
|
||||||
|
tb: TracebackType, tb_next: t.Optional[TracebackType]
|
||||||
|
) -> TracebackType:
|
||||||
|
tb.tb_next = tb_next
|
||||||
|
return tb
|
||||||
|
|
||||||
|
|
||||||
|
elif platform.python_implementation() == "PyPy":
|
||||||
|
# PyPy might have special support, and won't work with ctypes.
|
||||||
|
try:
|
||||||
|
import tputil # type: ignore
|
||||||
|
except ImportError:
|
||||||
|
# Without tproxy support, use the original traceback.
|
||||||
|
def tb_set_next(
|
||||||
|
tb: TracebackType, tb_next: t.Optional[TracebackType]
|
||||||
|
) -> TracebackType:
|
||||||
|
return tb
|
||||||
|
|
||||||
|
else:
|
||||||
|
# With tproxy support, create a proxy around the traceback that
|
||||||
|
# returns the new tb_next.
|
||||||
|
def tb_set_next(
|
||||||
|
tb: TracebackType, tb_next: t.Optional[TracebackType]
|
||||||
|
) -> TracebackType:
|
||||||
|
def controller(op): # type: ignore
|
||||||
|
if op.opname == "__getattribute__" and op.args[0] == "tb_next":
|
||||||
|
return tb_next
|
||||||
|
|
||||||
|
return op.delegate()
|
||||||
|
|
||||||
|
return tputil.make_proxy(controller, obj=tb) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Use ctypes to assign tb_next at the C level since it's read-only
|
||||||
|
# from Python.
|
||||||
|
import ctypes
|
||||||
|
|
||||||
|
class _CTraceback(ctypes.Structure):
|
||||||
|
_fields_ = [
|
||||||
|
# Extra PyObject slots when compiled with Py_TRACE_REFS.
|
||||||
|
("PyObject_HEAD", ctypes.c_byte * object().__sizeof__()),
|
||||||
|
# Only care about tb_next as an object, not a traceback.
|
||||||
|
("tb_next", ctypes.py_object),
|
||||||
|
]
|
||||||
|
|
||||||
|
def tb_set_next(
|
||||||
|
tb: TracebackType, tb_next: t.Optional[TracebackType]
|
||||||
|
) -> TracebackType:
|
||||||
|
c_tb = _CTraceback.from_address(id(tb))
|
||||||
|
|
||||||
|
# Clear out the old tb_next.
|
||||||
|
if tb.tb_next is not None:
|
||||||
|
c_tb_next = ctypes.py_object(tb.tb_next)
|
||||||
|
c_tb.tb_next = ctypes.py_object()
|
||||||
|
ctypes.pythonapi.Py_DecRef(c_tb_next)
|
||||||
|
|
||||||
|
# Assign the new tb_next.
|
||||||
|
if tb_next is not None:
|
||||||
|
c_tb_next = ctypes.py_object(tb_next)
|
||||||
|
ctypes.pythonapi.Py_IncRef(c_tb_next)
|
||||||
|
c_tb.tb_next = c_tb_next
|
||||||
|
|
||||||
|
return tb
|
||||||
48
dist/ba_data/python-site-packages/jinja2/defaults.py
vendored
Normal file
48
dist/ba_data/python-site-packages/jinja2/defaults.py
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
from .filters import FILTERS as DEFAULT_FILTERS # noqa: F401
|
||||||
|
from .tests import TESTS as DEFAULT_TESTS # noqa: F401
|
||||||
|
from .utils import Cycler
|
||||||
|
from .utils import generate_lorem_ipsum
|
||||||
|
from .utils import Joiner
|
||||||
|
from .utils import Namespace
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
|
|
||||||
|
# defaults for the parser / lexer
|
||||||
|
BLOCK_START_STRING = "{%"
|
||||||
|
BLOCK_END_STRING = "%}"
|
||||||
|
VARIABLE_START_STRING = "{{"
|
||||||
|
VARIABLE_END_STRING = "}}"
|
||||||
|
COMMENT_START_STRING = "{#"
|
||||||
|
COMMENT_END_STRING = "#}"
|
||||||
|
LINE_STATEMENT_PREFIX: t.Optional[str] = None
|
||||||
|
LINE_COMMENT_PREFIX: t.Optional[str] = None
|
||||||
|
TRIM_BLOCKS = False
|
||||||
|
LSTRIP_BLOCKS = False
|
||||||
|
NEWLINE_SEQUENCE: "te.Literal['\\n', '\\r\\n', '\\r']" = "\n"
|
||||||
|
KEEP_TRAILING_NEWLINE = False
|
||||||
|
|
||||||
|
# default filters, tests and namespace
|
||||||
|
|
||||||
|
DEFAULT_NAMESPACE = {
|
||||||
|
"range": range,
|
||||||
|
"dict": dict,
|
||||||
|
"lipsum": generate_lorem_ipsum,
|
||||||
|
"cycler": Cycler,
|
||||||
|
"joiner": Joiner,
|
||||||
|
"namespace": Namespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
# default policies
|
||||||
|
DEFAULT_POLICIES: t.Dict[str, t.Any] = {
|
||||||
|
"compiler.ascii_str": True,
|
||||||
|
"urlize.rel": "noopener",
|
||||||
|
"urlize.target": None,
|
||||||
|
"urlize.extra_schemes": None,
|
||||||
|
"truncate.leeway": 5,
|
||||||
|
"json.dumps_function": None,
|
||||||
|
"json.dumps_kwargs": {"sort_keys": True},
|
||||||
|
"ext.i18n.trimmed": False,
|
||||||
|
}
|
||||||
1661
dist/ba_data/python-site-packages/jinja2/environment.py
vendored
Normal file
1661
dist/ba_data/python-site-packages/jinja2/environment.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
166
dist/ba_data/python-site-packages/jinja2/exceptions.py
vendored
Normal file
166
dist/ba_data/python-site-packages/jinja2/exceptions.py
vendored
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
from .runtime import Undefined
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateError(Exception):
|
||||||
|
"""Baseclass for all template errors."""
|
||||||
|
|
||||||
|
def __init__(self, message: t.Optional[str] = None) -> None:
|
||||||
|
super().__init__(message)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self) -> t.Optional[str]:
|
||||||
|
return self.args[0] if self.args else None
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateNotFound(IOError, LookupError, TemplateError):
|
||||||
|
"""Raised if a template does not exist.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.11
|
||||||
|
If the given name is :class:`Undefined` and no message was
|
||||||
|
provided, an :exc:`UndefinedError` is raised.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Silence the Python warning about message being deprecated since
|
||||||
|
# it's not valid here.
|
||||||
|
message: t.Optional[str] = None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: t.Optional[t.Union[str, "Undefined"]],
|
||||||
|
message: t.Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
IOError.__init__(self, name)
|
||||||
|
|
||||||
|
if message is None:
|
||||||
|
from .runtime import Undefined
|
||||||
|
|
||||||
|
if isinstance(name, Undefined):
|
||||||
|
name._fail_with_undefined_error()
|
||||||
|
|
||||||
|
message = name
|
||||||
|
|
||||||
|
self.message = message
|
||||||
|
self.name = name
|
||||||
|
self.templates = [name]
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.message)
|
||||||
|
|
||||||
|
|
||||||
|
class TemplatesNotFound(TemplateNotFound):
|
||||||
|
"""Like :class:`TemplateNotFound` but raised if multiple templates
|
||||||
|
are selected. This is a subclass of :class:`TemplateNotFound`
|
||||||
|
exception, so just catching the base exception will catch both.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.11
|
||||||
|
If a name in the list of names is :class:`Undefined`, a message
|
||||||
|
about it being undefined is shown rather than the empty string.
|
||||||
|
|
||||||
|
.. versionadded:: 2.2
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
names: t.Sequence[t.Union[str, "Undefined"]] = (),
|
||||||
|
message: t.Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
if message is None:
|
||||||
|
from .runtime import Undefined
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
for name in names:
|
||||||
|
if isinstance(name, Undefined):
|
||||||
|
parts.append(name._undefined_message)
|
||||||
|
else:
|
||||||
|
parts.append(name)
|
||||||
|
|
||||||
|
parts_str = ", ".join(map(str, parts))
|
||||||
|
message = f"none of the templates given were found: {parts_str}"
|
||||||
|
|
||||||
|
super().__init__(names[-1] if names else None, message)
|
||||||
|
self.templates = list(names)
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateSyntaxError(TemplateError):
|
||||||
|
"""Raised to tell the user that there is a problem with the template."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
message: str,
|
||||||
|
lineno: int,
|
||||||
|
name: t.Optional[str] = None,
|
||||||
|
filename: t.Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(message)
|
||||||
|
self.lineno = lineno
|
||||||
|
self.name = name
|
||||||
|
self.filename = filename
|
||||||
|
self.source: t.Optional[str] = None
|
||||||
|
|
||||||
|
# this is set to True if the debug.translate_syntax_error
|
||||||
|
# function translated the syntax error into a new traceback
|
||||||
|
self.translated = False
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
# for translated errors we only return the message
|
||||||
|
if self.translated:
|
||||||
|
return t.cast(str, self.message)
|
||||||
|
|
||||||
|
# otherwise attach some stuff
|
||||||
|
location = f"line {self.lineno}"
|
||||||
|
name = self.filename or self.name
|
||||||
|
if name:
|
||||||
|
location = f'File "{name}", {location}'
|
||||||
|
lines = [t.cast(str, self.message), " " + location]
|
||||||
|
|
||||||
|
# if the source is set, add the line to the output
|
||||||
|
if self.source is not None:
|
||||||
|
try:
|
||||||
|
line = self.source.splitlines()[self.lineno - 1]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
lines.append(" " + line.strip())
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def __reduce__(self): # type: ignore
|
||||||
|
# https://bugs.python.org/issue1692335 Exceptions that take
|
||||||
|
# multiple required arguments have problems with pickling.
|
||||||
|
# Without this, raises TypeError: __init__() missing 1 required
|
||||||
|
# positional argument: 'lineno'
|
||||||
|
return self.__class__, (self.message, self.lineno, self.name, self.filename)
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateAssertionError(TemplateSyntaxError):
|
||||||
|
"""Like a template syntax error, but covers cases where something in the
|
||||||
|
template caused an error at compile time that wasn't necessarily caused
|
||||||
|
by a syntax error. However it's a direct subclass of
|
||||||
|
:exc:`TemplateSyntaxError` and has the same attributes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateRuntimeError(TemplateError):
|
||||||
|
"""A generic runtime error in the template engine. Under some situations
|
||||||
|
Jinja may raise this exception.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class UndefinedError(TemplateRuntimeError):
|
||||||
|
"""Raised if a template tries to operate on :class:`Undefined`."""
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityError(TemplateRuntimeError):
|
||||||
|
"""Raised if a template tries to do something insecure if the
|
||||||
|
sandbox is enabled.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class FilterArgumentError(TemplateRuntimeError):
|
||||||
|
"""This error is raised if a filter was called with inappropriate
|
||||||
|
arguments
|
||||||
|
"""
|
||||||
879
dist/ba_data/python-site-packages/jinja2/ext.py
vendored
Normal file
879
dist/ba_data/python-site-packages/jinja2/ext.py
vendored
Normal file
|
|
@ -0,0 +1,879 @@
|
||||||
|
"""Extension API for adding custom tags and behavior."""
|
||||||
|
import pprint
|
||||||
|
import re
|
||||||
|
import typing as t
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from markupsafe import Markup
|
||||||
|
|
||||||
|
from . import defaults
|
||||||
|
from . import nodes
|
||||||
|
from .environment import Environment
|
||||||
|
from .exceptions import TemplateAssertionError
|
||||||
|
from .exceptions import TemplateSyntaxError
|
||||||
|
from .runtime import concat # type: ignore
|
||||||
|
from .runtime import Context
|
||||||
|
from .runtime import Undefined
|
||||||
|
from .utils import import_string
|
||||||
|
from .utils import pass_context
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
|
from .lexer import Token
|
||||||
|
from .lexer import TokenStream
|
||||||
|
from .parser import Parser
|
||||||
|
|
||||||
|
class _TranslationsBasic(te.Protocol):
|
||||||
|
def gettext(self, message: str) -> str:
|
||||||
|
...
|
||||||
|
|
||||||
|
def ngettext(self, singular: str, plural: str, n: int) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class _TranslationsContext(_TranslationsBasic):
|
||||||
|
def pgettext(self, context: str, message: str) -> str:
|
||||||
|
...
|
||||||
|
|
||||||
|
def npgettext(self, context: str, singular: str, plural: str, n: int) -> str:
|
||||||
|
...
|
||||||
|
|
||||||
|
_SupportedTranslations = t.Union[_TranslationsBasic, _TranslationsContext]
|
||||||
|
|
||||||
|
|
||||||
|
# I18N functions available in Jinja templates. If the I18N library
|
||||||
|
# provides ugettext, it will be assigned to gettext.
|
||||||
|
GETTEXT_FUNCTIONS: t.Tuple[str, ...] = (
|
||||||
|
"_",
|
||||||
|
"gettext",
|
||||||
|
"ngettext",
|
||||||
|
"pgettext",
|
||||||
|
"npgettext",
|
||||||
|
)
|
||||||
|
_ws_re = re.compile(r"\s*\n\s*")
|
||||||
|
|
||||||
|
|
||||||
|
class Extension:
|
||||||
|
"""Extensions can be used to add extra functionality to the Jinja template
|
||||||
|
system at the parser level. Custom extensions are bound to an environment
|
||||||
|
but may not store environment specific data on `self`. The reason for
|
||||||
|
this is that an extension can be bound to another environment (for
|
||||||
|
overlays) by creating a copy and reassigning the `environment` attribute.
|
||||||
|
|
||||||
|
As extensions are created by the environment they cannot accept any
|
||||||
|
arguments for configuration. One may want to work around that by using
|
||||||
|
a factory function, but that is not possible as extensions are identified
|
||||||
|
by their import name. The correct way to configure the extension is
|
||||||
|
storing the configuration values on the environment. Because this way the
|
||||||
|
environment ends up acting as central configuration storage the
|
||||||
|
attributes may clash which is why extensions have to ensure that the names
|
||||||
|
they choose for configuration are not too generic. ``prefix`` for example
|
||||||
|
is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
|
||||||
|
name as includes the name of the extension (fragment cache).
|
||||||
|
"""
|
||||||
|
|
||||||
|
identifier: t.ClassVar[str]
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
cls.identifier = f"{cls.__module__}.{cls.__name__}"
|
||||||
|
|
||||||
|
#: if this extension parses this is the list of tags it's listening to.
|
||||||
|
tags: t.Set[str] = set()
|
||||||
|
|
||||||
|
#: the priority of that extension. This is especially useful for
|
||||||
|
#: extensions that preprocess values. A lower value means higher
|
||||||
|
#: priority.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 2.4
|
||||||
|
priority = 100
|
||||||
|
|
||||||
|
def __init__(self, environment: Environment) -> None:
|
||||||
|
self.environment = environment
|
||||||
|
|
||||||
|
def bind(self, environment: Environment) -> "Extension":
|
||||||
|
"""Create a copy of this extension bound to another environment."""
|
||||||
|
rv = t.cast(Extension, object.__new__(self.__class__))
|
||||||
|
rv.__dict__.update(self.__dict__)
|
||||||
|
rv.environment = environment
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def preprocess(
|
||||||
|
self, source: str, name: t.Optional[str], filename: t.Optional[str] = None
|
||||||
|
) -> str:
|
||||||
|
"""This method is called before the actual lexing and can be used to
|
||||||
|
preprocess the source. The `filename` is optional. The return value
|
||||||
|
must be the preprocessed source.
|
||||||
|
"""
|
||||||
|
return source
|
||||||
|
|
||||||
|
def filter_stream(
|
||||||
|
self, stream: "TokenStream"
|
||||||
|
) -> t.Union["TokenStream", t.Iterable["Token"]]:
|
||||||
|
"""It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
|
||||||
|
to filter tokens returned. This method has to return an iterable of
|
||||||
|
:class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a
|
||||||
|
:class:`~jinja2.lexer.TokenStream`.
|
||||||
|
"""
|
||||||
|
return stream
|
||||||
|
|
||||||
|
def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
|
||||||
|
"""If any of the :attr:`tags` matched this method is called with the
|
||||||
|
parser as first argument. The token the parser stream is pointing at
|
||||||
|
is the name token that matched. This method has to return one or a
|
||||||
|
list of multiple nodes.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def attr(
|
||||||
|
self, name: str, lineno: t.Optional[int] = None
|
||||||
|
) -> nodes.ExtensionAttribute:
|
||||||
|
"""Return an attribute node for the current extension. This is useful
|
||||||
|
to pass constants on extensions to generated template code.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
self.attr('_my_attribute', lineno=lineno)
|
||||||
|
"""
|
||||||
|
return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
|
||||||
|
|
||||||
|
def call_method(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
args: t.Optional[t.List[nodes.Expr]] = None,
|
||||||
|
kwargs: t.Optional[t.List[nodes.Keyword]] = None,
|
||||||
|
dyn_args: t.Optional[nodes.Expr] = None,
|
||||||
|
dyn_kwargs: t.Optional[nodes.Expr] = None,
|
||||||
|
lineno: t.Optional[int] = None,
|
||||||
|
) -> nodes.Call:
|
||||||
|
"""Call a method of the extension. This is a shortcut for
|
||||||
|
:meth:`attr` + :class:`jinja2.nodes.Call`.
|
||||||
|
"""
|
||||||
|
if args is None:
|
||||||
|
args = []
|
||||||
|
if kwargs is None:
|
||||||
|
kwargs = []
|
||||||
|
return nodes.Call(
|
||||||
|
self.attr(name, lineno=lineno),
|
||||||
|
args,
|
||||||
|
kwargs,
|
||||||
|
dyn_args,
|
||||||
|
dyn_kwargs,
|
||||||
|
lineno=lineno,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pass_context
|
||||||
|
def _gettext_alias(
|
||||||
|
__context: Context, *args: t.Any, **kwargs: t.Any
|
||||||
|
) -> t.Union[t.Any, Undefined]:
|
||||||
|
return __context.call(__context.resolve("gettext"), *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_new_gettext(func: t.Callable[[str], str]) -> t.Callable[..., str]:
|
||||||
|
@pass_context
|
||||||
|
def gettext(__context: Context, __string: str, **variables: t.Any) -> str:
|
||||||
|
rv = __context.call(func, __string)
|
||||||
|
if __context.eval_ctx.autoescape:
|
||||||
|
rv = Markup(rv)
|
||||||
|
# Always treat as a format string, even if there are no
|
||||||
|
# variables. This makes translation strings more consistent
|
||||||
|
# and predictable. This requires escaping
|
||||||
|
return rv % variables # type: ignore
|
||||||
|
|
||||||
|
return gettext
|
||||||
|
|
||||||
|
|
||||||
|
def _make_new_ngettext(func: t.Callable[[str, str, int], str]) -> t.Callable[..., str]:
|
||||||
|
@pass_context
|
||||||
|
def ngettext(
|
||||||
|
__context: Context,
|
||||||
|
__singular: str,
|
||||||
|
__plural: str,
|
||||||
|
__num: int,
|
||||||
|
**variables: t.Any,
|
||||||
|
) -> str:
|
||||||
|
variables.setdefault("num", __num)
|
||||||
|
rv = __context.call(func, __singular, __plural, __num)
|
||||||
|
if __context.eval_ctx.autoescape:
|
||||||
|
rv = Markup(rv)
|
||||||
|
# Always treat as a format string, see gettext comment above.
|
||||||
|
return rv % variables # type: ignore
|
||||||
|
|
||||||
|
return ngettext
|
||||||
|
|
||||||
|
|
||||||
|
def _make_new_pgettext(func: t.Callable[[str, str], str]) -> t.Callable[..., str]:
|
||||||
|
@pass_context
|
||||||
|
def pgettext(
|
||||||
|
__context: Context, __string_ctx: str, __string: str, **variables: t.Any
|
||||||
|
) -> str:
|
||||||
|
variables.setdefault("context", __string_ctx)
|
||||||
|
rv = __context.call(func, __string_ctx, __string)
|
||||||
|
|
||||||
|
if __context.eval_ctx.autoescape:
|
||||||
|
rv = Markup(rv)
|
||||||
|
|
||||||
|
# Always treat as a format string, see gettext comment above.
|
||||||
|
return rv % variables # type: ignore
|
||||||
|
|
||||||
|
return pgettext
|
||||||
|
|
||||||
|
|
||||||
|
def _make_new_npgettext(
|
||||||
|
func: t.Callable[[str, str, str, int], str]
|
||||||
|
) -> t.Callable[..., str]:
|
||||||
|
@pass_context
|
||||||
|
def npgettext(
|
||||||
|
__context: Context,
|
||||||
|
__string_ctx: str,
|
||||||
|
__singular: str,
|
||||||
|
__plural: str,
|
||||||
|
__num: int,
|
||||||
|
**variables: t.Any,
|
||||||
|
) -> str:
|
||||||
|
variables.setdefault("context", __string_ctx)
|
||||||
|
variables.setdefault("num", __num)
|
||||||
|
rv = __context.call(func, __string_ctx, __singular, __plural, __num)
|
||||||
|
|
||||||
|
if __context.eval_ctx.autoescape:
|
||||||
|
rv = Markup(rv)
|
||||||
|
|
||||||
|
# Always treat as a format string, see gettext comment above.
|
||||||
|
return rv % variables # type: ignore
|
||||||
|
|
||||||
|
return npgettext
|
||||||
|
|
||||||
|
|
||||||
|
class InternationalizationExtension(Extension):
|
||||||
|
"""This extension adds gettext support to Jinja."""
|
||||||
|
|
||||||
|
tags = {"trans"}
|
||||||
|
|
||||||
|
# TODO: the i18n extension is currently reevaluating values in a few
|
||||||
|
# situations. Take this example:
|
||||||
|
# {% trans count=something() %}{{ count }} foo{% pluralize
|
||||||
|
# %}{{ count }} fooss{% endtrans %}
|
||||||
|
# something is called twice here. One time for the gettext value and
|
||||||
|
# the other time for the n-parameter of the ngettext function.
|
||||||
|
|
||||||
|
def __init__(self, environment: Environment) -> None:
|
||||||
|
super().__init__(environment)
|
||||||
|
environment.globals["_"] = _gettext_alias
|
||||||
|
environment.extend(
|
||||||
|
install_gettext_translations=self._install,
|
||||||
|
install_null_translations=self._install_null,
|
||||||
|
install_gettext_callables=self._install_callables,
|
||||||
|
uninstall_gettext_translations=self._uninstall,
|
||||||
|
extract_translations=self._extract,
|
||||||
|
newstyle_gettext=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _install(
|
||||||
|
self, translations: "_SupportedTranslations", newstyle: t.Optional[bool] = None
|
||||||
|
) -> None:
|
||||||
|
# ugettext and ungettext are preferred in case the I18N library
|
||||||
|
# is providing compatibility with older Python versions.
|
||||||
|
gettext = getattr(translations, "ugettext", None)
|
||||||
|
if gettext is None:
|
||||||
|
gettext = translations.gettext
|
||||||
|
ngettext = getattr(translations, "ungettext", None)
|
||||||
|
if ngettext is None:
|
||||||
|
ngettext = translations.ngettext
|
||||||
|
|
||||||
|
pgettext = getattr(translations, "pgettext", None)
|
||||||
|
npgettext = getattr(translations, "npgettext", None)
|
||||||
|
self._install_callables(
|
||||||
|
gettext, ngettext, newstyle=newstyle, pgettext=pgettext, npgettext=npgettext
|
||||||
|
)
|
||||||
|
|
||||||
|
def _install_null(self, newstyle: t.Optional[bool] = None) -> None:
|
||||||
|
import gettext
|
||||||
|
|
||||||
|
translations = gettext.NullTranslations()
|
||||||
|
|
||||||
|
if hasattr(translations, "pgettext"):
|
||||||
|
# Python < 3.8
|
||||||
|
pgettext = translations.pgettext # type: ignore
|
||||||
|
else:
|
||||||
|
|
||||||
|
def pgettext(c: str, s: str) -> str:
|
||||||
|
return s
|
||||||
|
|
||||||
|
if hasattr(translations, "npgettext"):
|
||||||
|
npgettext = translations.npgettext # type: ignore
|
||||||
|
else:
|
||||||
|
|
||||||
|
def npgettext(c: str, s: str, p: str, n: int) -> str:
|
||||||
|
return s if n == 1 else p
|
||||||
|
|
||||||
|
self._install_callables(
|
||||||
|
gettext=translations.gettext,
|
||||||
|
ngettext=translations.ngettext,
|
||||||
|
newstyle=newstyle,
|
||||||
|
pgettext=pgettext,
|
||||||
|
npgettext=npgettext,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _install_callables(
|
||||||
|
self,
|
||||||
|
gettext: t.Callable[[str], str],
|
||||||
|
ngettext: t.Callable[[str, str, int], str],
|
||||||
|
newstyle: t.Optional[bool] = None,
|
||||||
|
pgettext: t.Optional[t.Callable[[str, str], str]] = None,
|
||||||
|
npgettext: t.Optional[t.Callable[[str, str, str, int], str]] = None,
|
||||||
|
) -> None:
|
||||||
|
if newstyle is not None:
|
||||||
|
self.environment.newstyle_gettext = newstyle # type: ignore
|
||||||
|
if self.environment.newstyle_gettext: # type: ignore
|
||||||
|
gettext = _make_new_gettext(gettext)
|
||||||
|
ngettext = _make_new_ngettext(ngettext)
|
||||||
|
|
||||||
|
if pgettext is not None:
|
||||||
|
pgettext = _make_new_pgettext(pgettext)
|
||||||
|
|
||||||
|
if npgettext is not None:
|
||||||
|
npgettext = _make_new_npgettext(npgettext)
|
||||||
|
|
||||||
|
self.environment.globals.update(
|
||||||
|
gettext=gettext, ngettext=ngettext, pgettext=pgettext, npgettext=npgettext
|
||||||
|
)
|
||||||
|
|
||||||
|
def _uninstall(self, translations: "_SupportedTranslations") -> None:
|
||||||
|
for key in ("gettext", "ngettext", "pgettext", "npgettext"):
|
||||||
|
self.environment.globals.pop(key, None)
|
||||||
|
|
||||||
|
def _extract(
|
||||||
|
self,
|
||||||
|
source: t.Union[str, nodes.Template],
|
||||||
|
gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
|
||||||
|
) -> t.Iterator[
|
||||||
|
t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
|
||||||
|
]:
|
||||||
|
if isinstance(source, str):
|
||||||
|
source = self.environment.parse(source)
|
||||||
|
return extract_from_ast(source, gettext_functions)
|
||||||
|
|
||||||
|
def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
|
||||||
|
"""Parse a translatable tag."""
|
||||||
|
lineno = next(parser.stream).lineno
|
||||||
|
num_called_num = False
|
||||||
|
|
||||||
|
# find all the variables referenced. Additionally a variable can be
|
||||||
|
# defined in the body of the trans block too, but this is checked at
|
||||||
|
# a later state.
|
||||||
|
plural_expr: t.Optional[nodes.Expr] = None
|
||||||
|
plural_expr_assignment: t.Optional[nodes.Assign] = None
|
||||||
|
variables: t.Dict[str, nodes.Expr] = {}
|
||||||
|
trimmed = None
|
||||||
|
while parser.stream.current.type != "block_end":
|
||||||
|
if variables:
|
||||||
|
parser.stream.expect("comma")
|
||||||
|
|
||||||
|
# skip colon for python compatibility
|
||||||
|
if parser.stream.skip_if("colon"):
|
||||||
|
break
|
||||||
|
|
||||||
|
token = parser.stream.expect("name")
|
||||||
|
if token.value in variables:
|
||||||
|
parser.fail(
|
||||||
|
f"translatable variable {token.value!r} defined twice.",
|
||||||
|
token.lineno,
|
||||||
|
exc=TemplateAssertionError,
|
||||||
|
)
|
||||||
|
|
||||||
|
# expressions
|
||||||
|
if parser.stream.current.type == "assign":
|
||||||
|
next(parser.stream)
|
||||||
|
variables[token.value] = var = parser.parse_expression()
|
||||||
|
elif trimmed is None and token.value in ("trimmed", "notrimmed"):
|
||||||
|
trimmed = token.value == "trimmed"
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
variables[token.value] = var = nodes.Name(token.value, "load")
|
||||||
|
|
||||||
|
if plural_expr is None:
|
||||||
|
if isinstance(var, nodes.Call):
|
||||||
|
plural_expr = nodes.Name("_trans", "load")
|
||||||
|
variables[token.value] = plural_expr
|
||||||
|
plural_expr_assignment = nodes.Assign(
|
||||||
|
nodes.Name("_trans", "store"), var
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
plural_expr = var
|
||||||
|
num_called_num = token.value == "num"
|
||||||
|
|
||||||
|
parser.stream.expect("block_end")
|
||||||
|
|
||||||
|
plural = None
|
||||||
|
have_plural = False
|
||||||
|
referenced = set()
|
||||||
|
|
||||||
|
# now parse until endtrans or pluralize
|
||||||
|
singular_names, singular = self._parse_block(parser, True)
|
||||||
|
if singular_names:
|
||||||
|
referenced.update(singular_names)
|
||||||
|
if plural_expr is None:
|
||||||
|
plural_expr = nodes.Name(singular_names[0], "load")
|
||||||
|
num_called_num = singular_names[0] == "num"
|
||||||
|
|
||||||
|
# if we have a pluralize block, we parse that too
|
||||||
|
if parser.stream.current.test("name:pluralize"):
|
||||||
|
have_plural = True
|
||||||
|
next(parser.stream)
|
||||||
|
if parser.stream.current.type != "block_end":
|
||||||
|
token = parser.stream.expect("name")
|
||||||
|
if token.value not in variables:
|
||||||
|
parser.fail(
|
||||||
|
f"unknown variable {token.value!r} for pluralization",
|
||||||
|
token.lineno,
|
||||||
|
exc=TemplateAssertionError,
|
||||||
|
)
|
||||||
|
plural_expr = variables[token.value]
|
||||||
|
num_called_num = token.value == "num"
|
||||||
|
parser.stream.expect("block_end")
|
||||||
|
plural_names, plural = self._parse_block(parser, False)
|
||||||
|
next(parser.stream)
|
||||||
|
referenced.update(plural_names)
|
||||||
|
else:
|
||||||
|
next(parser.stream)
|
||||||
|
|
||||||
|
# register free names as simple name expressions
|
||||||
|
for name in referenced:
|
||||||
|
if name not in variables:
|
||||||
|
variables[name] = nodes.Name(name, "load")
|
||||||
|
|
||||||
|
if not have_plural:
|
||||||
|
plural_expr = None
|
||||||
|
elif plural_expr is None:
|
||||||
|
parser.fail("pluralize without variables", lineno)
|
||||||
|
|
||||||
|
if trimmed is None:
|
||||||
|
trimmed = self.environment.policies["ext.i18n.trimmed"]
|
||||||
|
if trimmed:
|
||||||
|
singular = self._trim_whitespace(singular)
|
||||||
|
if plural:
|
||||||
|
plural = self._trim_whitespace(plural)
|
||||||
|
|
||||||
|
node = self._make_node(
|
||||||
|
singular,
|
||||||
|
plural,
|
||||||
|
variables,
|
||||||
|
plural_expr,
|
||||||
|
bool(referenced),
|
||||||
|
num_called_num and have_plural,
|
||||||
|
)
|
||||||
|
node.set_lineno(lineno)
|
||||||
|
if plural_expr_assignment is not None:
|
||||||
|
return [plural_expr_assignment, node]
|
||||||
|
else:
|
||||||
|
return node
|
||||||
|
|
||||||
|
def _trim_whitespace(self, string: str, _ws_re: t.Pattern[str] = _ws_re) -> str:
|
||||||
|
return _ws_re.sub(" ", string.strip())
|
||||||
|
|
||||||
|
def _parse_block(
|
||||||
|
self, parser: "Parser", allow_pluralize: bool
|
||||||
|
) -> t.Tuple[t.List[str], str]:
|
||||||
|
"""Parse until the next block tag with a given name."""
|
||||||
|
referenced = []
|
||||||
|
buf = []
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if parser.stream.current.type == "data":
|
||||||
|
buf.append(parser.stream.current.value.replace("%", "%%"))
|
||||||
|
next(parser.stream)
|
||||||
|
elif parser.stream.current.type == "variable_begin":
|
||||||
|
next(parser.stream)
|
||||||
|
name = parser.stream.expect("name").value
|
||||||
|
referenced.append(name)
|
||||||
|
buf.append(f"%({name})s")
|
||||||
|
parser.stream.expect("variable_end")
|
||||||
|
elif parser.stream.current.type == "block_begin":
|
||||||
|
next(parser.stream)
|
||||||
|
if parser.stream.current.test("name:endtrans"):
|
||||||
|
break
|
||||||
|
elif parser.stream.current.test("name:pluralize"):
|
||||||
|
if allow_pluralize:
|
||||||
|
break
|
||||||
|
parser.fail(
|
||||||
|
"a translatable section can have only one pluralize section"
|
||||||
|
)
|
||||||
|
parser.fail(
|
||||||
|
"control structures in translatable sections are not allowed"
|
||||||
|
)
|
||||||
|
elif parser.stream.eos:
|
||||||
|
parser.fail("unclosed translation block")
|
||||||
|
else:
|
||||||
|
raise RuntimeError("internal parser error")
|
||||||
|
|
||||||
|
return referenced, concat(buf)
|
||||||
|
|
||||||
|
def _make_node(
|
||||||
|
self,
|
||||||
|
singular: str,
|
||||||
|
plural: t.Optional[str],
|
||||||
|
variables: t.Dict[str, nodes.Expr],
|
||||||
|
plural_expr: t.Optional[nodes.Expr],
|
||||||
|
vars_referenced: bool,
|
||||||
|
num_called_num: bool,
|
||||||
|
) -> nodes.Output:
|
||||||
|
"""Generates a useful node from the data provided."""
|
||||||
|
newstyle = self.environment.newstyle_gettext # type: ignore
|
||||||
|
node: nodes.Expr
|
||||||
|
|
||||||
|
# no variables referenced? no need to escape for old style
|
||||||
|
# gettext invocations only if there are vars.
|
||||||
|
if not vars_referenced and not newstyle:
|
||||||
|
singular = singular.replace("%%", "%")
|
||||||
|
if plural:
|
||||||
|
plural = plural.replace("%%", "%")
|
||||||
|
|
||||||
|
# singular only:
|
||||||
|
if plural_expr is None:
|
||||||
|
gettext = nodes.Name("gettext", "load")
|
||||||
|
node = nodes.Call(gettext, [nodes.Const(singular)], [], None, None)
|
||||||
|
|
||||||
|
# singular and plural
|
||||||
|
else:
|
||||||
|
ngettext = nodes.Name("ngettext", "load")
|
||||||
|
node = nodes.Call(
|
||||||
|
ngettext,
|
||||||
|
[nodes.Const(singular), nodes.Const(plural), plural_expr],
|
||||||
|
[],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# in case newstyle gettext is used, the method is powerful
|
||||||
|
# enough to handle the variable expansion and autoescape
|
||||||
|
# handling itself
|
||||||
|
if newstyle:
|
||||||
|
for key, value in variables.items():
|
||||||
|
# the function adds that later anyways in case num was
|
||||||
|
# called num, so just skip it.
|
||||||
|
if num_called_num and key == "num":
|
||||||
|
continue
|
||||||
|
node.kwargs.append(nodes.Keyword(key, value))
|
||||||
|
|
||||||
|
# otherwise do that here
|
||||||
|
else:
|
||||||
|
# mark the return value as safe if we are in an
|
||||||
|
# environment with autoescaping turned on
|
||||||
|
node = nodes.MarkSafeIfAutoescape(node)
|
||||||
|
if variables:
|
||||||
|
node = nodes.Mod(
|
||||||
|
node,
|
||||||
|
nodes.Dict(
|
||||||
|
[
|
||||||
|
nodes.Pair(nodes.Const(key), value)
|
||||||
|
for key, value in variables.items()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return nodes.Output([node])
|
||||||
|
|
||||||
|
|
||||||
|
class ExprStmtExtension(Extension):
|
||||||
|
"""Adds a `do` tag to Jinja that works like the print statement just
|
||||||
|
that it doesn't print the return value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
tags = {"do"}
|
||||||
|
|
||||||
|
def parse(self, parser: "Parser") -> nodes.ExprStmt:
|
||||||
|
node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
|
||||||
|
node.node = parser.parse_tuple()
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
class LoopControlExtension(Extension):
|
||||||
|
"""Adds break and continue to the template engine."""
|
||||||
|
|
||||||
|
tags = {"break", "continue"}
|
||||||
|
|
||||||
|
def parse(self, parser: "Parser") -> t.Union[nodes.Break, nodes.Continue]:
|
||||||
|
token = next(parser.stream)
|
||||||
|
if token.value == "break":
|
||||||
|
return nodes.Break(lineno=token.lineno)
|
||||||
|
return nodes.Continue(lineno=token.lineno)
|
||||||
|
|
||||||
|
|
||||||
|
class WithExtension(Extension):
|
||||||
|
def __init__(self, environment: Environment) -> None:
|
||||||
|
super().__init__(environment)
|
||||||
|
warnings.warn(
|
||||||
|
"The 'with' extension is deprecated and will be removed in"
|
||||||
|
" Jinja 3.1. This is built in now.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AutoEscapeExtension(Extension):
|
||||||
|
def __init__(self, environment: Environment) -> None:
|
||||||
|
super().__init__(environment)
|
||||||
|
warnings.warn(
|
||||||
|
"The 'autoescape' extension is deprecated and will be"
|
||||||
|
" removed in Jinja 3.1. This is built in now.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DebugExtension(Extension):
|
||||||
|
"""A ``{% debug %}`` tag that dumps the available variables,
|
||||||
|
filters, and tests.
|
||||||
|
|
||||||
|
.. code-block:: html+jinja
|
||||||
|
|
||||||
|
<pre>{% debug %}</pre>
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
{'context': {'cycler': <class 'jinja2.utils.Cycler'>,
|
||||||
|
...,
|
||||||
|
'namespace': <class 'jinja2.utils.Namespace'>},
|
||||||
|
'filters': ['abs', 'attr', 'batch', 'capitalize', 'center', 'count', 'd',
|
||||||
|
..., 'urlencode', 'urlize', 'wordcount', 'wordwrap', 'xmlattr'],
|
||||||
|
'tests': ['!=', '<', '<=', '==', '>', '>=', 'callable', 'defined',
|
||||||
|
..., 'odd', 'sameas', 'sequence', 'string', 'undefined', 'upper']}
|
||||||
|
|
||||||
|
.. versionadded:: 2.11.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
tags = {"debug"}
|
||||||
|
|
||||||
|
def parse(self, parser: "Parser") -> nodes.Output:
|
||||||
|
lineno = parser.stream.expect("name:debug").lineno
|
||||||
|
context = nodes.ContextReference()
|
||||||
|
result = self.call_method("_render", [context], lineno=lineno)
|
||||||
|
return nodes.Output([result], lineno=lineno)
|
||||||
|
|
||||||
|
def _render(self, context: Context) -> str:
|
||||||
|
result = {
|
||||||
|
"context": context.get_all(),
|
||||||
|
"filters": sorted(self.environment.filters.keys()),
|
||||||
|
"tests": sorted(self.environment.tests.keys()),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set the depth since the intent is to show the top few names.
|
||||||
|
return pprint.pformat(result, depth=3, compact=True)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_from_ast(
|
||||||
|
ast: nodes.Template,
|
||||||
|
gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
|
||||||
|
babel_style: bool = True,
|
||||||
|
) -> t.Iterator[
|
||||||
|
t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
|
||||||
|
]:
|
||||||
|
"""Extract localizable strings from the given template node. Per
|
||||||
|
default this function returns matches in babel style that means non string
|
||||||
|
parameters as well as keyword arguments are returned as `None`. This
|
||||||
|
allows Babel to figure out what you really meant if you are using
|
||||||
|
gettext functions that allow keyword arguments for placeholder expansion.
|
||||||
|
If you don't want that behavior set the `babel_style` parameter to `False`
|
||||||
|
which causes only strings to be returned and parameters are always stored
|
||||||
|
in tuples. As a consequence invalid gettext calls (calls without a single
|
||||||
|
string parameter or string parameters after non-string parameters) are
|
||||||
|
skipped.
|
||||||
|
|
||||||
|
This example explains the behavior:
|
||||||
|
|
||||||
|
>>> from jinja2 import Environment
|
||||||
|
>>> env = Environment()
|
||||||
|
>>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
|
||||||
|
>>> list(extract_from_ast(node))
|
||||||
|
[(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
|
||||||
|
>>> list(extract_from_ast(node, babel_style=False))
|
||||||
|
[(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
|
||||||
|
|
||||||
|
For every string found this function yields a ``(lineno, function,
|
||||||
|
message)`` tuple, where:
|
||||||
|
|
||||||
|
* ``lineno`` is the number of the line on which the string was found,
|
||||||
|
* ``function`` is the name of the ``gettext`` function used (if the
|
||||||
|
string was extracted from embedded Python code), and
|
||||||
|
* ``message`` is the string, or a tuple of strings for functions
|
||||||
|
with multiple string arguments.
|
||||||
|
|
||||||
|
This extraction function operates on the AST and is because of that unable
|
||||||
|
to extract any comments. For comment support you have to use the babel
|
||||||
|
extraction interface or extract comments yourself.
|
||||||
|
"""
|
||||||
|
out: t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]
|
||||||
|
|
||||||
|
for node in ast.find_all(nodes.Call):
|
||||||
|
if (
|
||||||
|
not isinstance(node.node, nodes.Name)
|
||||||
|
or node.node.name not in gettext_functions
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
strings: t.List[t.Optional[str]] = []
|
||||||
|
|
||||||
|
for arg in node.args:
|
||||||
|
if isinstance(arg, nodes.Const) and isinstance(arg.value, str):
|
||||||
|
strings.append(arg.value)
|
||||||
|
else:
|
||||||
|
strings.append(None)
|
||||||
|
|
||||||
|
for _ in node.kwargs:
|
||||||
|
strings.append(None)
|
||||||
|
if node.dyn_args is not None:
|
||||||
|
strings.append(None)
|
||||||
|
if node.dyn_kwargs is not None:
|
||||||
|
strings.append(None)
|
||||||
|
|
||||||
|
if not babel_style:
|
||||||
|
out = tuple(x for x in strings if x is not None)
|
||||||
|
|
||||||
|
if not out:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if len(strings) == 1:
|
||||||
|
out = strings[0]
|
||||||
|
else:
|
||||||
|
out = tuple(strings)
|
||||||
|
|
||||||
|
yield node.lineno, node.node.name, out
|
||||||
|
|
||||||
|
|
||||||
|
class _CommentFinder:
|
||||||
|
"""Helper class to find comments in a token stream. Can only
|
||||||
|
find comments for gettext calls forwards. Once the comment
|
||||||
|
from line 4 is found, a comment for line 1 will not return a
|
||||||
|
usable value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, tokens: t.Sequence[t.Tuple[int, str, str]], comment_tags: t.Sequence[str]
|
||||||
|
) -> None:
|
||||||
|
self.tokens = tokens
|
||||||
|
self.comment_tags = comment_tags
|
||||||
|
self.offset = 0
|
||||||
|
self.last_lineno = 0
|
||||||
|
|
||||||
|
def find_backwards(self, offset: int) -> t.List[str]:
|
||||||
|
try:
|
||||||
|
for _, token_type, token_value in reversed(
|
||||||
|
self.tokens[self.offset : offset]
|
||||||
|
):
|
||||||
|
if token_type in ("comment", "linecomment"):
|
||||||
|
try:
|
||||||
|
prefix, comment = token_value.split(None, 1)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
if prefix in self.comment_tags:
|
||||||
|
return [comment.rstrip()]
|
||||||
|
return []
|
||||||
|
finally:
|
||||||
|
self.offset = offset
|
||||||
|
|
||||||
|
def find_comments(self, lineno: int) -> t.List[str]:
|
||||||
|
if not self.comment_tags or self.last_lineno > lineno:
|
||||||
|
return []
|
||||||
|
for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset :]):
|
||||||
|
if token_lineno > lineno:
|
||||||
|
return self.find_backwards(self.offset + idx)
|
||||||
|
return self.find_backwards(len(self.tokens))
|
||||||
|
|
||||||
|
|
||||||
|
def babel_extract(
|
||||||
|
fileobj: t.BinaryIO,
|
||||||
|
keywords: t.Sequence[str],
|
||||||
|
comment_tags: t.Sequence[str],
|
||||||
|
options: t.Dict[str, t.Any],
|
||||||
|
) -> t.Iterator[
|
||||||
|
t.Tuple[
|
||||||
|
int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]], t.List[str]
|
||||||
|
]
|
||||||
|
]:
|
||||||
|
"""Babel extraction method for Jinja templates.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.3
|
||||||
|
Basic support for translation comments was added. If `comment_tags`
|
||||||
|
is now set to a list of keywords for extraction, the extractor will
|
||||||
|
try to find the best preceding comment that begins with one of the
|
||||||
|
keywords. For best results, make sure to not have more than one
|
||||||
|
gettext call in one line of code and the matching comment in the
|
||||||
|
same line or the line before.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.5.1
|
||||||
|
The `newstyle_gettext` flag can be set to `True` to enable newstyle
|
||||||
|
gettext calls.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
A `silent` option can now be provided. If set to `False` template
|
||||||
|
syntax errors are propagated instead of being ignored.
|
||||||
|
|
||||||
|
:param fileobj: the file-like object the messages should be extracted from
|
||||||
|
:param keywords: a list of keywords (i.e. function names) that should be
|
||||||
|
recognized as translation functions
|
||||||
|
:param comment_tags: a list of translator tags to search for and include
|
||||||
|
in the results.
|
||||||
|
:param options: a dictionary of additional options (optional)
|
||||||
|
:return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
|
||||||
|
(comments will be empty currently)
|
||||||
|
"""
|
||||||
|
extensions: t.Dict[t.Type[Extension], None] = {}
|
||||||
|
|
||||||
|
for extension_name in options.get("extensions", "").split(","):
|
||||||
|
extension_name = extension_name.strip()
|
||||||
|
|
||||||
|
if not extension_name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
extensions[import_string(extension_name)] = None
|
||||||
|
|
||||||
|
if InternationalizationExtension not in extensions:
|
||||||
|
extensions[InternationalizationExtension] = None
|
||||||
|
|
||||||
|
def getbool(options: t.Mapping[str, str], key: str, default: bool = False) -> bool:
|
||||||
|
return options.get(key, str(default)).lower() in {"1", "on", "yes", "true"}
|
||||||
|
|
||||||
|
silent = getbool(options, "silent", True)
|
||||||
|
environment = Environment(
|
||||||
|
options.get("block_start_string", defaults.BLOCK_START_STRING),
|
||||||
|
options.get("block_end_string", defaults.BLOCK_END_STRING),
|
||||||
|
options.get("variable_start_string", defaults.VARIABLE_START_STRING),
|
||||||
|
options.get("variable_end_string", defaults.VARIABLE_END_STRING),
|
||||||
|
options.get("comment_start_string", defaults.COMMENT_START_STRING),
|
||||||
|
options.get("comment_end_string", defaults.COMMENT_END_STRING),
|
||||||
|
options.get("line_statement_prefix") or defaults.LINE_STATEMENT_PREFIX,
|
||||||
|
options.get("line_comment_prefix") or defaults.LINE_COMMENT_PREFIX,
|
||||||
|
getbool(options, "trim_blocks", defaults.TRIM_BLOCKS),
|
||||||
|
getbool(options, "lstrip_blocks", defaults.LSTRIP_BLOCKS),
|
||||||
|
defaults.NEWLINE_SEQUENCE,
|
||||||
|
getbool(options, "keep_trailing_newline", defaults.KEEP_TRAILING_NEWLINE),
|
||||||
|
tuple(extensions),
|
||||||
|
cache_size=0,
|
||||||
|
auto_reload=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
if getbool(options, "trimmed"):
|
||||||
|
environment.policies["ext.i18n.trimmed"] = True
|
||||||
|
if getbool(options, "newstyle_gettext"):
|
||||||
|
environment.newstyle_gettext = True # type: ignore
|
||||||
|
|
||||||
|
source = fileobj.read().decode(options.get("encoding", "utf-8"))
|
||||||
|
try:
|
||||||
|
node = environment.parse(source)
|
||||||
|
tokens = list(environment.lex(environment.preprocess(source)))
|
||||||
|
except TemplateSyntaxError:
|
||||||
|
if not silent:
|
||||||
|
raise
|
||||||
|
# skip templates with syntax errors
|
||||||
|
return
|
||||||
|
|
||||||
|
finder = _CommentFinder(tokens, comment_tags)
|
||||||
|
for lineno, func, message in extract_from_ast(node, keywords):
|
||||||
|
yield lineno, func, message, finder.find_comments(lineno)
|
||||||
|
|
||||||
|
|
||||||
|
#: nicer import names
|
||||||
|
i18n = InternationalizationExtension
|
||||||
|
do = ExprStmtExtension
|
||||||
|
loopcontrols = LoopControlExtension
|
||||||
|
with_ = WithExtension
|
||||||
|
autoescape = AutoEscapeExtension
|
||||||
|
debug = DebugExtension
|
||||||
1824
dist/ba_data/python-site-packages/jinja2/filters.py
vendored
Normal file
1824
dist/ba_data/python-site-packages/jinja2/filters.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
318
dist/ba_data/python-site-packages/jinja2/idtracking.py
vendored
Normal file
318
dist/ba_data/python-site-packages/jinja2/idtracking.py
vendored
Normal file
|
|
@ -0,0 +1,318 @@
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
from . import nodes
|
||||||
|
from .visitor import NodeVisitor
|
||||||
|
|
||||||
|
VAR_LOAD_PARAMETER = "param"
|
||||||
|
VAR_LOAD_RESOLVE = "resolve"
|
||||||
|
VAR_LOAD_ALIAS = "alias"
|
||||||
|
VAR_LOAD_UNDEFINED = "undefined"
|
||||||
|
|
||||||
|
|
||||||
|
def find_symbols(
|
||||||
|
nodes: t.Iterable[nodes.Node], parent_symbols: t.Optional["Symbols"] = None
|
||||||
|
) -> "Symbols":
|
||||||
|
sym = Symbols(parent=parent_symbols)
|
||||||
|
visitor = FrameSymbolVisitor(sym)
|
||||||
|
for node in nodes:
|
||||||
|
visitor.visit(node)
|
||||||
|
return sym
|
||||||
|
|
||||||
|
|
||||||
|
def symbols_for_node(
|
||||||
|
node: nodes.Node, parent_symbols: t.Optional["Symbols"] = None
|
||||||
|
) -> "Symbols":
|
||||||
|
sym = Symbols(parent=parent_symbols)
|
||||||
|
sym.analyze_node(node)
|
||||||
|
return sym
|
||||||
|
|
||||||
|
|
||||||
|
class Symbols:
|
||||||
|
def __init__(
|
||||||
|
self, parent: t.Optional["Symbols"] = None, level: t.Optional[int] = None
|
||||||
|
) -> None:
|
||||||
|
if level is None:
|
||||||
|
if parent is None:
|
||||||
|
level = 0
|
||||||
|
else:
|
||||||
|
level = parent.level + 1
|
||||||
|
|
||||||
|
self.level: int = level
|
||||||
|
self.parent = parent
|
||||||
|
self.refs: t.Dict[str, str] = {}
|
||||||
|
self.loads: t.Dict[str, t.Any] = {}
|
||||||
|
self.stores: t.Set[str] = set()
|
||||||
|
|
||||||
|
def analyze_node(self, node: nodes.Node, **kwargs: t.Any) -> None:
|
||||||
|
visitor = RootVisitor(self)
|
||||||
|
visitor.visit(node, **kwargs)
|
||||||
|
|
||||||
|
def _define_ref(
|
||||||
|
self, name: str, load: t.Optional[t.Tuple[str, t.Optional[str]]] = None
|
||||||
|
) -> str:
|
||||||
|
ident = f"l_{self.level}_{name}"
|
||||||
|
self.refs[name] = ident
|
||||||
|
if load is not None:
|
||||||
|
self.loads[ident] = load
|
||||||
|
return ident
|
||||||
|
|
||||||
|
def find_load(self, target: str) -> t.Optional[t.Any]:
|
||||||
|
if target in self.loads:
|
||||||
|
return self.loads[target]
|
||||||
|
|
||||||
|
if self.parent is not None:
|
||||||
|
return self.parent.find_load(target)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_ref(self, name: str) -> t.Optional[str]:
|
||||||
|
if name in self.refs:
|
||||||
|
return self.refs[name]
|
||||||
|
|
||||||
|
if self.parent is not None:
|
||||||
|
return self.parent.find_ref(name)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def ref(self, name: str) -> str:
|
||||||
|
rv = self.find_ref(name)
|
||||||
|
if rv is None:
|
||||||
|
raise AssertionError(
|
||||||
|
"Tried to resolve a name to a reference that was"
|
||||||
|
f" unknown to the frame ({name!r})"
|
||||||
|
)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def copy(self) -> "Symbols":
|
||||||
|
rv = t.cast(Symbols, object.__new__(self.__class__))
|
||||||
|
rv.__dict__.update(self.__dict__)
|
||||||
|
rv.refs = self.refs.copy()
|
||||||
|
rv.loads = self.loads.copy()
|
||||||
|
rv.stores = self.stores.copy()
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def store(self, name: str) -> None:
|
||||||
|
self.stores.add(name)
|
||||||
|
|
||||||
|
# If we have not see the name referenced yet, we need to figure
|
||||||
|
# out what to set it to.
|
||||||
|
if name not in self.refs:
|
||||||
|
# If there is a parent scope we check if the name has a
|
||||||
|
# reference there. If it does it means we might have to alias
|
||||||
|
# to a variable there.
|
||||||
|
if self.parent is not None:
|
||||||
|
outer_ref = self.parent.find_ref(name)
|
||||||
|
if outer_ref is not None:
|
||||||
|
self._define_ref(name, load=(VAR_LOAD_ALIAS, outer_ref))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Otherwise we can just set it to undefined.
|
||||||
|
self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None))
|
||||||
|
|
||||||
|
def declare_parameter(self, name: str) -> str:
|
||||||
|
self.stores.add(name)
|
||||||
|
return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None))
|
||||||
|
|
||||||
|
def load(self, name: str) -> None:
|
||||||
|
if self.find_ref(name) is None:
|
||||||
|
self._define_ref(name, load=(VAR_LOAD_RESOLVE, name))
|
||||||
|
|
||||||
|
def branch_update(self, branch_symbols: t.Sequence["Symbols"]) -> None:
|
||||||
|
stores: t.Dict[str, int] = {}
|
||||||
|
for branch in branch_symbols:
|
||||||
|
for target in branch.stores:
|
||||||
|
if target in self.stores:
|
||||||
|
continue
|
||||||
|
stores[target] = stores.get(target, 0) + 1
|
||||||
|
|
||||||
|
for sym in branch_symbols:
|
||||||
|
self.refs.update(sym.refs)
|
||||||
|
self.loads.update(sym.loads)
|
||||||
|
self.stores.update(sym.stores)
|
||||||
|
|
||||||
|
for name, branch_count in stores.items():
|
||||||
|
if branch_count == len(branch_symbols):
|
||||||
|
continue
|
||||||
|
|
||||||
|
target = self.find_ref(name) # type: ignore
|
||||||
|
assert target is not None, "should not happen"
|
||||||
|
|
||||||
|
if self.parent is not None:
|
||||||
|
outer_target = self.parent.find_ref(name)
|
||||||
|
if outer_target is not None:
|
||||||
|
self.loads[target] = (VAR_LOAD_ALIAS, outer_target)
|
||||||
|
continue
|
||||||
|
self.loads[target] = (VAR_LOAD_RESOLVE, name)
|
||||||
|
|
||||||
|
def dump_stores(self) -> t.Dict[str, str]:
|
||||||
|
rv: t.Dict[str, str] = {}
|
||||||
|
node: t.Optional["Symbols"] = self
|
||||||
|
|
||||||
|
while node is not None:
|
||||||
|
for name in sorted(node.stores):
|
||||||
|
if name not in rv:
|
||||||
|
rv[name] = self.find_ref(name) # type: ignore
|
||||||
|
|
||||||
|
node = node.parent
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def dump_param_targets(self) -> t.Set[str]:
|
||||||
|
rv = set()
|
||||||
|
node: t.Optional["Symbols"] = self
|
||||||
|
|
||||||
|
while node is not None:
|
||||||
|
for target, (instr, _) in self.loads.items():
|
||||||
|
if instr == VAR_LOAD_PARAMETER:
|
||||||
|
rv.add(target)
|
||||||
|
|
||||||
|
node = node.parent
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class RootVisitor(NodeVisitor):
|
||||||
|
def __init__(self, symbols: "Symbols") -> None:
|
||||||
|
self.sym_visitor = FrameSymbolVisitor(symbols)
|
||||||
|
|
||||||
|
def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None:
|
||||||
|
for child in node.iter_child_nodes():
|
||||||
|
self.sym_visitor.visit(child)
|
||||||
|
|
||||||
|
visit_Template = _simple_visit
|
||||||
|
visit_Block = _simple_visit
|
||||||
|
visit_Macro = _simple_visit
|
||||||
|
visit_FilterBlock = _simple_visit
|
||||||
|
visit_Scope = _simple_visit
|
||||||
|
visit_If = _simple_visit
|
||||||
|
visit_ScopedEvalContextModifier = _simple_visit
|
||||||
|
|
||||||
|
def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
|
||||||
|
for child in node.body:
|
||||||
|
self.sym_visitor.visit(child)
|
||||||
|
|
||||||
|
def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
|
||||||
|
for child in node.iter_child_nodes(exclude=("call",)):
|
||||||
|
self.sym_visitor.visit(child)
|
||||||
|
|
||||||
|
def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
|
||||||
|
for child in node.body:
|
||||||
|
self.sym_visitor.visit(child)
|
||||||
|
|
||||||
|
def visit_For(
|
||||||
|
self, node: nodes.For, for_branch: str = "body", **kwargs: t.Any
|
||||||
|
) -> None:
|
||||||
|
if for_branch == "body":
|
||||||
|
self.sym_visitor.visit(node.target, store_as_param=True)
|
||||||
|
branch = node.body
|
||||||
|
elif for_branch == "else":
|
||||||
|
branch = node.else_
|
||||||
|
elif for_branch == "test":
|
||||||
|
self.sym_visitor.visit(node.target, store_as_param=True)
|
||||||
|
if node.test is not None:
|
||||||
|
self.sym_visitor.visit(node.test)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Unknown for branch")
|
||||||
|
|
||||||
|
if branch:
|
||||||
|
for item in branch:
|
||||||
|
self.sym_visitor.visit(item)
|
||||||
|
|
||||||
|
def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
|
||||||
|
for target in node.targets:
|
||||||
|
self.sym_visitor.visit(target)
|
||||||
|
for child in node.body:
|
||||||
|
self.sym_visitor.visit(child)
|
||||||
|
|
||||||
|
def generic_visit(self, node: nodes.Node, *args: t.Any, **kwargs: t.Any) -> None:
|
||||||
|
raise NotImplementedError(f"Cannot find symbols for {type(node).__name__!r}")
|
||||||
|
|
||||||
|
|
||||||
|
class FrameSymbolVisitor(NodeVisitor):
|
||||||
|
"""A visitor for `Frame.inspect`."""
|
||||||
|
|
||||||
|
def __init__(self, symbols: "Symbols") -> None:
|
||||||
|
self.symbols = symbols
|
||||||
|
|
||||||
|
def visit_Name(
|
||||||
|
self, node: nodes.Name, store_as_param: bool = False, **kwargs: t.Any
|
||||||
|
) -> None:
|
||||||
|
"""All assignments to names go through this function."""
|
||||||
|
if store_as_param or node.ctx == "param":
|
||||||
|
self.symbols.declare_parameter(node.name)
|
||||||
|
elif node.ctx == "store":
|
||||||
|
self.symbols.store(node.name)
|
||||||
|
elif node.ctx == "load":
|
||||||
|
self.symbols.load(node.name)
|
||||||
|
|
||||||
|
def visit_NSRef(self, node: nodes.NSRef, **kwargs: t.Any) -> None:
|
||||||
|
self.symbols.load(node.name)
|
||||||
|
|
||||||
|
def visit_If(self, node: nodes.If, **kwargs: t.Any) -> None:
|
||||||
|
self.visit(node.test, **kwargs)
|
||||||
|
original_symbols = self.symbols
|
||||||
|
|
||||||
|
def inner_visit(nodes: t.Iterable[nodes.Node]) -> "Symbols":
|
||||||
|
self.symbols = rv = original_symbols.copy()
|
||||||
|
|
||||||
|
for subnode in nodes:
|
||||||
|
self.visit(subnode, **kwargs)
|
||||||
|
|
||||||
|
self.symbols = original_symbols
|
||||||
|
return rv
|
||||||
|
|
||||||
|
body_symbols = inner_visit(node.body)
|
||||||
|
elif_symbols = inner_visit(node.elif_)
|
||||||
|
else_symbols = inner_visit(node.else_ or ())
|
||||||
|
self.symbols.branch_update([body_symbols, elif_symbols, else_symbols])
|
||||||
|
|
||||||
|
def visit_Macro(self, node: nodes.Macro, **kwargs: t.Any) -> None:
|
||||||
|
self.symbols.store(node.name)
|
||||||
|
|
||||||
|
def visit_Import(self, node: nodes.Import, **kwargs: t.Any) -> None:
|
||||||
|
self.generic_visit(node, **kwargs)
|
||||||
|
self.symbols.store(node.target)
|
||||||
|
|
||||||
|
def visit_FromImport(self, node: nodes.FromImport, **kwargs: t.Any) -> None:
|
||||||
|
self.generic_visit(node, **kwargs)
|
||||||
|
|
||||||
|
for name in node.names:
|
||||||
|
if isinstance(name, tuple):
|
||||||
|
self.symbols.store(name[1])
|
||||||
|
else:
|
||||||
|
self.symbols.store(name)
|
||||||
|
|
||||||
|
def visit_Assign(self, node: nodes.Assign, **kwargs: t.Any) -> None:
|
||||||
|
"""Visit assignments in the correct order."""
|
||||||
|
self.visit(node.node, **kwargs)
|
||||||
|
self.visit(node.target, **kwargs)
|
||||||
|
|
||||||
|
def visit_For(self, node: nodes.For, **kwargs: t.Any) -> None:
|
||||||
|
"""Visiting stops at for blocks. However the block sequence
|
||||||
|
is visited as part of the outer scope.
|
||||||
|
"""
|
||||||
|
self.visit(node.iter, **kwargs)
|
||||||
|
|
||||||
|
def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
|
||||||
|
self.visit(node.call, **kwargs)
|
||||||
|
|
||||||
|
def visit_FilterBlock(self, node: nodes.FilterBlock, **kwargs: t.Any) -> None:
|
||||||
|
self.visit(node.filter, **kwargs)
|
||||||
|
|
||||||
|
def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
|
||||||
|
for target in node.values:
|
||||||
|
self.visit(target)
|
||||||
|
|
||||||
|
def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
|
||||||
|
"""Stop visiting at block assigns."""
|
||||||
|
self.visit(node.target, **kwargs)
|
||||||
|
|
||||||
|
def visit_Scope(self, node: nodes.Scope, **kwargs: t.Any) -> None:
|
||||||
|
"""Stop visiting at scopes."""
|
||||||
|
|
||||||
|
def visit_Block(self, node: nodes.Block, **kwargs: t.Any) -> None:
|
||||||
|
"""Stop visiting at blocks."""
|
||||||
|
|
||||||
|
def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
|
||||||
|
"""Do not visit into overlay scopes."""
|
||||||
869
dist/ba_data/python-site-packages/jinja2/lexer.py
vendored
Normal file
869
dist/ba_data/python-site-packages/jinja2/lexer.py
vendored
Normal file
|
|
@ -0,0 +1,869 @@
|
||||||
|
"""Implements a Jinja / Python combination lexer. The ``Lexer`` class
|
||||||
|
is used to do some preprocessing. It filters out invalid operators like
|
||||||
|
the bitshift operators we don't allow in templates. It separates
|
||||||
|
template code and python code in expressions.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import typing as t
|
||||||
|
from ast import literal_eval
|
||||||
|
from collections import deque
|
||||||
|
from sys import intern
|
||||||
|
|
||||||
|
from ._identifier import pattern as name_re
|
||||||
|
from .exceptions import TemplateSyntaxError
|
||||||
|
from .utils import LRUCache
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
|
from .environment import Environment
|
||||||
|
|
||||||
|
# cache for the lexers. Exists in order to be able to have multiple
|
||||||
|
# environments with the same lexer
|
||||||
|
_lexer_cache: t.MutableMapping[t.Tuple, "Lexer"] = LRUCache(50) # type: ignore
|
||||||
|
|
||||||
|
# static regular expressions
|
||||||
|
whitespace_re = re.compile(r"\s+")
|
||||||
|
newline_re = re.compile(r"(\r\n|\r|\n)")
|
||||||
|
string_re = re.compile(
|
||||||
|
r"('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S
|
||||||
|
)
|
||||||
|
integer_re = re.compile(
|
||||||
|
r"""
|
||||||
|
(
|
||||||
|
0b(_?[0-1])+ # binary
|
||||||
|
|
|
||||||
|
0o(_?[0-7])+ # octal
|
||||||
|
|
|
||||||
|
0x(_?[\da-f])+ # hex
|
||||||
|
|
|
||||||
|
[1-9](_?\d)* # decimal
|
||||||
|
|
|
||||||
|
0(_?0)* # decimal zero
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
re.IGNORECASE | re.VERBOSE,
|
||||||
|
)
|
||||||
|
float_re = re.compile(
|
||||||
|
r"""
|
||||||
|
(?<!\.) # doesn't start with a .
|
||||||
|
(\d+_)*\d+ # digits, possibly _ separated
|
||||||
|
(
|
||||||
|
(\.(\d+_)*\d+)? # optional fractional part
|
||||||
|
e[+\-]?(\d+_)*\d+ # exponent part
|
||||||
|
|
|
||||||
|
\.(\d+_)*\d+ # required fractional part
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
re.IGNORECASE | re.VERBOSE,
|
||||||
|
)
|
||||||
|
|
||||||
|
# internal the tokens and keep references to them
|
||||||
|
TOKEN_ADD = intern("add")
|
||||||
|
TOKEN_ASSIGN = intern("assign")
|
||||||
|
TOKEN_COLON = intern("colon")
|
||||||
|
TOKEN_COMMA = intern("comma")
|
||||||
|
TOKEN_DIV = intern("div")
|
||||||
|
TOKEN_DOT = intern("dot")
|
||||||
|
TOKEN_EQ = intern("eq")
|
||||||
|
TOKEN_FLOORDIV = intern("floordiv")
|
||||||
|
TOKEN_GT = intern("gt")
|
||||||
|
TOKEN_GTEQ = intern("gteq")
|
||||||
|
TOKEN_LBRACE = intern("lbrace")
|
||||||
|
TOKEN_LBRACKET = intern("lbracket")
|
||||||
|
TOKEN_LPAREN = intern("lparen")
|
||||||
|
TOKEN_LT = intern("lt")
|
||||||
|
TOKEN_LTEQ = intern("lteq")
|
||||||
|
TOKEN_MOD = intern("mod")
|
||||||
|
TOKEN_MUL = intern("mul")
|
||||||
|
TOKEN_NE = intern("ne")
|
||||||
|
TOKEN_PIPE = intern("pipe")
|
||||||
|
TOKEN_POW = intern("pow")
|
||||||
|
TOKEN_RBRACE = intern("rbrace")
|
||||||
|
TOKEN_RBRACKET = intern("rbracket")
|
||||||
|
TOKEN_RPAREN = intern("rparen")
|
||||||
|
TOKEN_SEMICOLON = intern("semicolon")
|
||||||
|
TOKEN_SUB = intern("sub")
|
||||||
|
TOKEN_TILDE = intern("tilde")
|
||||||
|
TOKEN_WHITESPACE = intern("whitespace")
|
||||||
|
TOKEN_FLOAT = intern("float")
|
||||||
|
TOKEN_INTEGER = intern("integer")
|
||||||
|
TOKEN_NAME = intern("name")
|
||||||
|
TOKEN_STRING = intern("string")
|
||||||
|
TOKEN_OPERATOR = intern("operator")
|
||||||
|
TOKEN_BLOCK_BEGIN = intern("block_begin")
|
||||||
|
TOKEN_BLOCK_END = intern("block_end")
|
||||||
|
TOKEN_VARIABLE_BEGIN = intern("variable_begin")
|
||||||
|
TOKEN_VARIABLE_END = intern("variable_end")
|
||||||
|
TOKEN_RAW_BEGIN = intern("raw_begin")
|
||||||
|
TOKEN_RAW_END = intern("raw_end")
|
||||||
|
TOKEN_COMMENT_BEGIN = intern("comment_begin")
|
||||||
|
TOKEN_COMMENT_END = intern("comment_end")
|
||||||
|
TOKEN_COMMENT = intern("comment")
|
||||||
|
TOKEN_LINESTATEMENT_BEGIN = intern("linestatement_begin")
|
||||||
|
TOKEN_LINESTATEMENT_END = intern("linestatement_end")
|
||||||
|
TOKEN_LINECOMMENT_BEGIN = intern("linecomment_begin")
|
||||||
|
TOKEN_LINECOMMENT_END = intern("linecomment_end")
|
||||||
|
TOKEN_LINECOMMENT = intern("linecomment")
|
||||||
|
TOKEN_DATA = intern("data")
|
||||||
|
TOKEN_INITIAL = intern("initial")
|
||||||
|
TOKEN_EOF = intern("eof")
|
||||||
|
|
||||||
|
# bind operators to token types
|
||||||
|
operators = {
|
||||||
|
"+": TOKEN_ADD,
|
||||||
|
"-": TOKEN_SUB,
|
||||||
|
"/": TOKEN_DIV,
|
||||||
|
"//": TOKEN_FLOORDIV,
|
||||||
|
"*": TOKEN_MUL,
|
||||||
|
"%": TOKEN_MOD,
|
||||||
|
"**": TOKEN_POW,
|
||||||
|
"~": TOKEN_TILDE,
|
||||||
|
"[": TOKEN_LBRACKET,
|
||||||
|
"]": TOKEN_RBRACKET,
|
||||||
|
"(": TOKEN_LPAREN,
|
||||||
|
")": TOKEN_RPAREN,
|
||||||
|
"{": TOKEN_LBRACE,
|
||||||
|
"}": TOKEN_RBRACE,
|
||||||
|
"==": TOKEN_EQ,
|
||||||
|
"!=": TOKEN_NE,
|
||||||
|
">": TOKEN_GT,
|
||||||
|
">=": TOKEN_GTEQ,
|
||||||
|
"<": TOKEN_LT,
|
||||||
|
"<=": TOKEN_LTEQ,
|
||||||
|
"=": TOKEN_ASSIGN,
|
||||||
|
".": TOKEN_DOT,
|
||||||
|
":": TOKEN_COLON,
|
||||||
|
"|": TOKEN_PIPE,
|
||||||
|
",": TOKEN_COMMA,
|
||||||
|
";": TOKEN_SEMICOLON,
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse_operators = {v: k for k, v in operators.items()}
|
||||||
|
assert len(operators) == len(reverse_operators), "operators dropped"
|
||||||
|
operator_re = re.compile(
|
||||||
|
f"({'|'.join(re.escape(x) for x in sorted(operators, key=lambda x: -len(x)))})"
|
||||||
|
)
|
||||||
|
|
||||||
|
ignored_tokens = frozenset(
|
||||||
|
[
|
||||||
|
TOKEN_COMMENT_BEGIN,
|
||||||
|
TOKEN_COMMENT,
|
||||||
|
TOKEN_COMMENT_END,
|
||||||
|
TOKEN_WHITESPACE,
|
||||||
|
TOKEN_LINECOMMENT_BEGIN,
|
||||||
|
TOKEN_LINECOMMENT_END,
|
||||||
|
TOKEN_LINECOMMENT,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
ignore_if_empty = frozenset(
|
||||||
|
[TOKEN_WHITESPACE, TOKEN_DATA, TOKEN_COMMENT, TOKEN_LINECOMMENT]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _describe_token_type(token_type: str) -> str:
|
||||||
|
if token_type in reverse_operators:
|
||||||
|
return reverse_operators[token_type]
|
||||||
|
|
||||||
|
return {
|
||||||
|
TOKEN_COMMENT_BEGIN: "begin of comment",
|
||||||
|
TOKEN_COMMENT_END: "end of comment",
|
||||||
|
TOKEN_COMMENT: "comment",
|
||||||
|
TOKEN_LINECOMMENT: "comment",
|
||||||
|
TOKEN_BLOCK_BEGIN: "begin of statement block",
|
||||||
|
TOKEN_BLOCK_END: "end of statement block",
|
||||||
|
TOKEN_VARIABLE_BEGIN: "begin of print statement",
|
||||||
|
TOKEN_VARIABLE_END: "end of print statement",
|
||||||
|
TOKEN_LINESTATEMENT_BEGIN: "begin of line statement",
|
||||||
|
TOKEN_LINESTATEMENT_END: "end of line statement",
|
||||||
|
TOKEN_DATA: "template data / text",
|
||||||
|
TOKEN_EOF: "end of template",
|
||||||
|
}.get(token_type, token_type)
|
||||||
|
|
||||||
|
|
||||||
|
def describe_token(token: "Token") -> str:
|
||||||
|
"""Returns a description of the token."""
|
||||||
|
if token.type == TOKEN_NAME:
|
||||||
|
return token.value
|
||||||
|
|
||||||
|
return _describe_token_type(token.type)
|
||||||
|
|
||||||
|
|
||||||
|
def describe_token_expr(expr: str) -> str:
|
||||||
|
"""Like `describe_token` but for token expressions."""
|
||||||
|
if ":" in expr:
|
||||||
|
type, value = expr.split(":", 1)
|
||||||
|
|
||||||
|
if type == TOKEN_NAME:
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
type = expr
|
||||||
|
|
||||||
|
return _describe_token_type(type)
|
||||||
|
|
||||||
|
|
||||||
|
def count_newlines(value: str) -> int:
|
||||||
|
"""Count the number of newline characters in the string. This is
|
||||||
|
useful for extensions that filter a stream.
|
||||||
|
"""
|
||||||
|
return len(newline_re.findall(value))
|
||||||
|
|
||||||
|
|
||||||
|
def compile_rules(environment: "Environment") -> t.List[t.Tuple[str, str]]:
|
||||||
|
"""Compiles all the rules from the environment into a list of rules."""
|
||||||
|
e = re.escape
|
||||||
|
rules = [
|
||||||
|
(
|
||||||
|
len(environment.comment_start_string),
|
||||||
|
TOKEN_COMMENT_BEGIN,
|
||||||
|
e(environment.comment_start_string),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
len(environment.block_start_string),
|
||||||
|
TOKEN_BLOCK_BEGIN,
|
||||||
|
e(environment.block_start_string),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
len(environment.variable_start_string),
|
||||||
|
TOKEN_VARIABLE_BEGIN,
|
||||||
|
e(environment.variable_start_string),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
if environment.line_statement_prefix is not None:
|
||||||
|
rules.append(
|
||||||
|
(
|
||||||
|
len(environment.line_statement_prefix),
|
||||||
|
TOKEN_LINESTATEMENT_BEGIN,
|
||||||
|
r"^[ \t\v]*" + e(environment.line_statement_prefix),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if environment.line_comment_prefix is not None:
|
||||||
|
rules.append(
|
||||||
|
(
|
||||||
|
len(environment.line_comment_prefix),
|
||||||
|
TOKEN_LINECOMMENT_BEGIN,
|
||||||
|
r"(?:^|(?<=\S))[^\S\r\n]*" + e(environment.line_comment_prefix),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return [x[1:] for x in sorted(rules, reverse=True)]
|
||||||
|
|
||||||
|
|
||||||
|
class Failure:
|
||||||
|
"""Class that raises a `TemplateSyntaxError` if called.
|
||||||
|
Used by the `Lexer` to specify known errors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, message: str, cls: t.Type[TemplateSyntaxError] = TemplateSyntaxError
|
||||||
|
) -> None:
|
||||||
|
self.message = message
|
||||||
|
self.error_class = cls
|
||||||
|
|
||||||
|
def __call__(self, lineno: int, filename: str) -> "te.NoReturn":
|
||||||
|
raise self.error_class(self.message, lineno, filename)
|
||||||
|
|
||||||
|
|
||||||
|
class Token(t.NamedTuple):
|
||||||
|
lineno: int
|
||||||
|
type: str
|
||||||
|
value: str
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return describe_token(self)
|
||||||
|
|
||||||
|
def test(self, expr: str) -> bool:
|
||||||
|
"""Test a token against a token expression. This can either be a
|
||||||
|
token type or ``'token_type:token_value'``. This can only test
|
||||||
|
against string values and types.
|
||||||
|
"""
|
||||||
|
# here we do a regular string equality check as test_any is usually
|
||||||
|
# passed an iterable of not interned strings.
|
||||||
|
if self.type == expr:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if ":" in expr:
|
||||||
|
return expr.split(":", 1) == [self.type, self.value]
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_any(self, *iterable: str) -> bool:
|
||||||
|
"""Test against multiple token expressions."""
|
||||||
|
return any(self.test(expr) for expr in iterable)
|
||||||
|
|
||||||
|
|
||||||
|
class TokenStreamIterator:
|
||||||
|
"""The iterator for tokenstreams. Iterate over the stream
|
||||||
|
until the eof token is reached.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, stream: "TokenStream") -> None:
|
||||||
|
self.stream = stream
|
||||||
|
|
||||||
|
def __iter__(self) -> "TokenStreamIterator":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self) -> Token:
|
||||||
|
token = self.stream.current
|
||||||
|
|
||||||
|
if token.type is TOKEN_EOF:
|
||||||
|
self.stream.close()
|
||||||
|
raise StopIteration
|
||||||
|
|
||||||
|
next(self.stream)
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
class TokenStream:
|
||||||
|
"""A token stream is an iterable that yields :class:`Token`\\s. The
|
||||||
|
parser however does not iterate over it but calls :meth:`next` to go
|
||||||
|
one token ahead. The current active token is stored as :attr:`current`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
generator: t.Iterable[Token],
|
||||||
|
name: t.Optional[str],
|
||||||
|
filename: t.Optional[str],
|
||||||
|
):
|
||||||
|
self._iter = iter(generator)
|
||||||
|
self._pushed: "te.Deque[Token]" = deque()
|
||||||
|
self.name = name
|
||||||
|
self.filename = filename
|
||||||
|
self.closed = False
|
||||||
|
self.current = Token(1, TOKEN_INITIAL, "")
|
||||||
|
next(self)
|
||||||
|
|
||||||
|
def __iter__(self) -> TokenStreamIterator:
|
||||||
|
return TokenStreamIterator(self)
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
return bool(self._pushed) or self.current.type is not TOKEN_EOF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def eos(self) -> bool:
|
||||||
|
"""Are we at the end of the stream?"""
|
||||||
|
return not self
|
||||||
|
|
||||||
|
def push(self, token: Token) -> None:
|
||||||
|
"""Push a token back to the stream."""
|
||||||
|
self._pushed.append(token)
|
||||||
|
|
||||||
|
def look(self) -> Token:
|
||||||
|
"""Look at the next token."""
|
||||||
|
old_token = next(self)
|
||||||
|
result = self.current
|
||||||
|
self.push(result)
|
||||||
|
self.current = old_token
|
||||||
|
return result
|
||||||
|
|
||||||
|
def skip(self, n: int = 1) -> None:
|
||||||
|
"""Got n tokens ahead."""
|
||||||
|
for _ in range(n):
|
||||||
|
next(self)
|
||||||
|
|
||||||
|
def next_if(self, expr: str) -> t.Optional[Token]:
|
||||||
|
"""Perform the token test and return the token if it matched.
|
||||||
|
Otherwise the return value is `None`.
|
||||||
|
"""
|
||||||
|
if self.current.test(expr):
|
||||||
|
return next(self)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def skip_if(self, expr: str) -> bool:
|
||||||
|
"""Like :meth:`next_if` but only returns `True` or `False`."""
|
||||||
|
return self.next_if(expr) is not None
|
||||||
|
|
||||||
|
def __next__(self) -> Token:
|
||||||
|
"""Go one token ahead and return the old one.
|
||||||
|
|
||||||
|
Use the built-in :func:`next` instead of calling this directly.
|
||||||
|
"""
|
||||||
|
rv = self.current
|
||||||
|
|
||||||
|
if self._pushed:
|
||||||
|
self.current = self._pushed.popleft()
|
||||||
|
elif self.current.type is not TOKEN_EOF:
|
||||||
|
try:
|
||||||
|
self.current = next(self._iter)
|
||||||
|
except StopIteration:
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
"""Close the stream."""
|
||||||
|
self.current = Token(self.current.lineno, TOKEN_EOF, "")
|
||||||
|
self._iter = iter(())
|
||||||
|
self.closed = True
|
||||||
|
|
||||||
|
def expect(self, expr: str) -> Token:
|
||||||
|
"""Expect a given token type and return it. This accepts the same
|
||||||
|
argument as :meth:`jinja2.lexer.Token.test`.
|
||||||
|
"""
|
||||||
|
if not self.current.test(expr):
|
||||||
|
expr = describe_token_expr(expr)
|
||||||
|
|
||||||
|
if self.current.type is TOKEN_EOF:
|
||||||
|
raise TemplateSyntaxError(
|
||||||
|
f"unexpected end of template, expected {expr!r}.",
|
||||||
|
self.current.lineno,
|
||||||
|
self.name,
|
||||||
|
self.filename,
|
||||||
|
)
|
||||||
|
|
||||||
|
raise TemplateSyntaxError(
|
||||||
|
f"expected token {expr!r}, got {describe_token(self.current)!r}",
|
||||||
|
self.current.lineno,
|
||||||
|
self.name,
|
||||||
|
self.filename,
|
||||||
|
)
|
||||||
|
|
||||||
|
return next(self)
|
||||||
|
|
||||||
|
|
||||||
|
def get_lexer(environment: "Environment") -> "Lexer":
|
||||||
|
"""Return a lexer which is probably cached."""
|
||||||
|
key = (
|
||||||
|
environment.block_start_string,
|
||||||
|
environment.block_end_string,
|
||||||
|
environment.variable_start_string,
|
||||||
|
environment.variable_end_string,
|
||||||
|
environment.comment_start_string,
|
||||||
|
environment.comment_end_string,
|
||||||
|
environment.line_statement_prefix,
|
||||||
|
environment.line_comment_prefix,
|
||||||
|
environment.trim_blocks,
|
||||||
|
environment.lstrip_blocks,
|
||||||
|
environment.newline_sequence,
|
||||||
|
environment.keep_trailing_newline,
|
||||||
|
)
|
||||||
|
lexer = _lexer_cache.get(key)
|
||||||
|
|
||||||
|
if lexer is None:
|
||||||
|
_lexer_cache[key] = lexer = Lexer(environment)
|
||||||
|
|
||||||
|
return lexer
|
||||||
|
|
||||||
|
|
||||||
|
class OptionalLStrip(tuple):
|
||||||
|
"""A special tuple for marking a point in the state that can have
|
||||||
|
lstrip applied.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
# Even though it looks like a no-op, creating instances fails
|
||||||
|
# without this.
|
||||||
|
def __new__(cls, *members, **kwargs): # type: ignore
|
||||||
|
return super().__new__(cls, members)
|
||||||
|
|
||||||
|
|
||||||
|
class _Rule(t.NamedTuple):
|
||||||
|
pattern: t.Pattern[str]
|
||||||
|
tokens: t.Union[str, t.Tuple[str, ...], t.Tuple[Failure]]
|
||||||
|
command: t.Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
class Lexer:
|
||||||
|
"""Class that implements a lexer for a given environment. Automatically
|
||||||
|
created by the environment class, usually you don't have to do that.
|
||||||
|
|
||||||
|
Note that the lexer is not automatically bound to an environment.
|
||||||
|
Multiple environments can share the same lexer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, environment: "Environment") -> None:
|
||||||
|
# shortcuts
|
||||||
|
e = re.escape
|
||||||
|
|
||||||
|
def c(x: str) -> t.Pattern[str]:
|
||||||
|
return re.compile(x, re.M | re.S)
|
||||||
|
|
||||||
|
# lexing rules for tags
|
||||||
|
tag_rules: t.List[_Rule] = [
|
||||||
|
_Rule(whitespace_re, TOKEN_WHITESPACE, None),
|
||||||
|
_Rule(float_re, TOKEN_FLOAT, None),
|
||||||
|
_Rule(integer_re, TOKEN_INTEGER, None),
|
||||||
|
_Rule(name_re, TOKEN_NAME, None),
|
||||||
|
_Rule(string_re, TOKEN_STRING, None),
|
||||||
|
_Rule(operator_re, TOKEN_OPERATOR, None),
|
||||||
|
]
|
||||||
|
|
||||||
|
# assemble the root lexing rule. because "|" is ungreedy
|
||||||
|
# we have to sort by length so that the lexer continues working
|
||||||
|
# as expected when we have parsing rules like <% for block and
|
||||||
|
# <%= for variables. (if someone wants asp like syntax)
|
||||||
|
# variables are just part of the rules if variable processing
|
||||||
|
# is required.
|
||||||
|
root_tag_rules = compile_rules(environment)
|
||||||
|
|
||||||
|
block_start_re = e(environment.block_start_string)
|
||||||
|
block_end_re = e(environment.block_end_string)
|
||||||
|
comment_end_re = e(environment.comment_end_string)
|
||||||
|
variable_end_re = e(environment.variable_end_string)
|
||||||
|
|
||||||
|
# block suffix if trimming is enabled
|
||||||
|
block_suffix_re = "\\n?" if environment.trim_blocks else ""
|
||||||
|
|
||||||
|
# If lstrip is enabled, it should not be applied if there is any
|
||||||
|
# non-whitespace between the newline and block.
|
||||||
|
self.lstrip_unless_re = c(r"[^ \t]") if environment.lstrip_blocks else None
|
||||||
|
|
||||||
|
self.newline_sequence = environment.newline_sequence
|
||||||
|
self.keep_trailing_newline = environment.keep_trailing_newline
|
||||||
|
|
||||||
|
root_raw_re = (
|
||||||
|
fr"(?P<raw_begin>{block_start_re}(\-|\+|)\s*raw\s*"
|
||||||
|
fr"(?:\-{block_end_re}\s*|{block_end_re}))"
|
||||||
|
)
|
||||||
|
root_parts_re = "|".join(
|
||||||
|
[root_raw_re] + [fr"(?P<{n}>{r}(\-|\+|))" for n, r in root_tag_rules]
|
||||||
|
)
|
||||||
|
|
||||||
|
# global lexing rules
|
||||||
|
self.rules: t.Dict[str, t.List[_Rule]] = {
|
||||||
|
"root": [
|
||||||
|
# directives
|
||||||
|
_Rule(
|
||||||
|
c(fr"(.*?)(?:{root_parts_re})"),
|
||||||
|
OptionalLStrip(TOKEN_DATA, "#bygroup"), # type: ignore
|
||||||
|
"#bygroup",
|
||||||
|
),
|
||||||
|
# data
|
||||||
|
_Rule(c(".+"), TOKEN_DATA, None),
|
||||||
|
],
|
||||||
|
# comments
|
||||||
|
TOKEN_COMMENT_BEGIN: [
|
||||||
|
_Rule(
|
||||||
|
c(
|
||||||
|
fr"(.*?)((?:\+{comment_end_re}|\-{comment_end_re}\s*"
|
||||||
|
fr"|{comment_end_re}{block_suffix_re}))"
|
||||||
|
),
|
||||||
|
(TOKEN_COMMENT, TOKEN_COMMENT_END),
|
||||||
|
"#pop",
|
||||||
|
),
|
||||||
|
_Rule(c(r"(.)"), (Failure("Missing end of comment tag"),), None),
|
||||||
|
],
|
||||||
|
# blocks
|
||||||
|
TOKEN_BLOCK_BEGIN: [
|
||||||
|
_Rule(
|
||||||
|
c(
|
||||||
|
fr"(?:\+{block_end_re}|\-{block_end_re}\s*"
|
||||||
|
fr"|{block_end_re}{block_suffix_re})"
|
||||||
|
),
|
||||||
|
TOKEN_BLOCK_END,
|
||||||
|
"#pop",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
+ tag_rules,
|
||||||
|
# variables
|
||||||
|
TOKEN_VARIABLE_BEGIN: [
|
||||||
|
_Rule(
|
||||||
|
c(fr"\-{variable_end_re}\s*|{variable_end_re}"),
|
||||||
|
TOKEN_VARIABLE_END,
|
||||||
|
"#pop",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
+ tag_rules,
|
||||||
|
# raw block
|
||||||
|
TOKEN_RAW_BEGIN: [
|
||||||
|
_Rule(
|
||||||
|
c(
|
||||||
|
fr"(.*?)((?:{block_start_re}(\-|\+|))\s*endraw\s*"
|
||||||
|
fr"(?:\+{block_end_re}|\-{block_end_re}\s*"
|
||||||
|
fr"|{block_end_re}{block_suffix_re}))"
|
||||||
|
),
|
||||||
|
OptionalLStrip(TOKEN_DATA, TOKEN_RAW_END), # type: ignore
|
||||||
|
"#pop",
|
||||||
|
),
|
||||||
|
_Rule(c(r"(.)"), (Failure("Missing end of raw directive"),), None),
|
||||||
|
],
|
||||||
|
# line statements
|
||||||
|
TOKEN_LINESTATEMENT_BEGIN: [
|
||||||
|
_Rule(c(r"\s*(\n|$)"), TOKEN_LINESTATEMENT_END, "#pop")
|
||||||
|
]
|
||||||
|
+ tag_rules,
|
||||||
|
# line comments
|
||||||
|
TOKEN_LINECOMMENT_BEGIN: [
|
||||||
|
_Rule(
|
||||||
|
c(r"(.*?)()(?=\n|$)"),
|
||||||
|
(TOKEN_LINECOMMENT, TOKEN_LINECOMMENT_END),
|
||||||
|
"#pop",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _normalize_newlines(self, value: str) -> str:
|
||||||
|
"""Replace all newlines with the configured sequence in strings
|
||||||
|
and template data.
|
||||||
|
"""
|
||||||
|
return newline_re.sub(self.newline_sequence, value)
|
||||||
|
|
||||||
|
def tokenize(
|
||||||
|
self,
|
||||||
|
source: str,
|
||||||
|
name: t.Optional[str] = None,
|
||||||
|
filename: t.Optional[str] = None,
|
||||||
|
state: t.Optional[str] = None,
|
||||||
|
) -> TokenStream:
|
||||||
|
"""Calls tokeniter + tokenize and wraps it in a token stream."""
|
||||||
|
stream = self.tokeniter(source, name, filename, state)
|
||||||
|
return TokenStream(self.wrap(stream, name, filename), name, filename)
|
||||||
|
|
||||||
|
def wrap(
|
||||||
|
self,
|
||||||
|
stream: t.Iterable[t.Tuple[int, str, str]],
|
||||||
|
name: t.Optional[str] = None,
|
||||||
|
filename: t.Optional[str] = None,
|
||||||
|
) -> t.Iterator[Token]:
|
||||||
|
"""This is called with the stream as returned by `tokenize` and wraps
|
||||||
|
every token in a :class:`Token` and converts the value.
|
||||||
|
"""
|
||||||
|
for lineno, token, value_str in stream:
|
||||||
|
if token in ignored_tokens:
|
||||||
|
continue
|
||||||
|
|
||||||
|
value: t.Any = value_str
|
||||||
|
|
||||||
|
if token == TOKEN_LINESTATEMENT_BEGIN:
|
||||||
|
token = TOKEN_BLOCK_BEGIN
|
||||||
|
elif token == TOKEN_LINESTATEMENT_END:
|
||||||
|
token = TOKEN_BLOCK_END
|
||||||
|
# we are not interested in those tokens in the parser
|
||||||
|
elif token in (TOKEN_RAW_BEGIN, TOKEN_RAW_END):
|
||||||
|
continue
|
||||||
|
elif token == TOKEN_DATA:
|
||||||
|
value = self._normalize_newlines(value_str)
|
||||||
|
elif token == "keyword":
|
||||||
|
token = value_str
|
||||||
|
elif token == TOKEN_NAME:
|
||||||
|
value = value_str
|
||||||
|
|
||||||
|
if not value.isidentifier():
|
||||||
|
raise TemplateSyntaxError(
|
||||||
|
"Invalid character in identifier", lineno, name, filename
|
||||||
|
)
|
||||||
|
elif token == TOKEN_STRING:
|
||||||
|
# try to unescape string
|
||||||
|
try:
|
||||||
|
value = (
|
||||||
|
self._normalize_newlines(value_str[1:-1])
|
||||||
|
.encode("ascii", "backslashreplace")
|
||||||
|
.decode("unicode-escape")
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
msg = str(e).split(":")[-1].strip()
|
||||||
|
raise TemplateSyntaxError(msg, lineno, name, filename) from e
|
||||||
|
elif token == TOKEN_INTEGER:
|
||||||
|
value = int(value_str.replace("_", ""), 0)
|
||||||
|
elif token == TOKEN_FLOAT:
|
||||||
|
# remove all "_" first to support more Python versions
|
||||||
|
value = literal_eval(value_str.replace("_", ""))
|
||||||
|
elif token == TOKEN_OPERATOR:
|
||||||
|
token = operators[value_str]
|
||||||
|
|
||||||
|
yield Token(lineno, token, value)
|
||||||
|
|
||||||
|
def tokeniter(
|
||||||
|
self,
|
||||||
|
source: str,
|
||||||
|
name: t.Optional[str],
|
||||||
|
filename: t.Optional[str] = None,
|
||||||
|
state: t.Optional[str] = None,
|
||||||
|
) -> t.Iterator[t.Tuple[int, str, str]]:
|
||||||
|
"""This method tokenizes the text and returns the tokens in a
|
||||||
|
generator. Use this method if you just want to tokenize a template.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
Only ``\\n``, ``\\r\\n`` and ``\\r`` are treated as line
|
||||||
|
breaks.
|
||||||
|
"""
|
||||||
|
lines = newline_re.split(source)[::2]
|
||||||
|
|
||||||
|
if not self.keep_trailing_newline and lines[-1] == "":
|
||||||
|
del lines[-1]
|
||||||
|
|
||||||
|
source = "\n".join(lines)
|
||||||
|
pos = 0
|
||||||
|
lineno = 1
|
||||||
|
stack = ["root"]
|
||||||
|
|
||||||
|
if state is not None and state != "root":
|
||||||
|
assert state in ("variable", "block"), "invalid state"
|
||||||
|
stack.append(state + "_begin")
|
||||||
|
|
||||||
|
statetokens = self.rules[stack[-1]]
|
||||||
|
source_length = len(source)
|
||||||
|
balancing_stack: t.List[str] = []
|
||||||
|
lstrip_unless_re = self.lstrip_unless_re
|
||||||
|
newlines_stripped = 0
|
||||||
|
line_starting = True
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# tokenizer loop
|
||||||
|
for regex, tokens, new_state in statetokens:
|
||||||
|
m = regex.match(source, pos)
|
||||||
|
|
||||||
|
# if no match we try again with the next rule
|
||||||
|
if m is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# we only match blocks and variables if braces / parentheses
|
||||||
|
# are balanced. continue parsing with the lower rule which
|
||||||
|
# is the operator rule. do this only if the end tags look
|
||||||
|
# like operators
|
||||||
|
if balancing_stack and tokens in (
|
||||||
|
TOKEN_VARIABLE_END,
|
||||||
|
TOKEN_BLOCK_END,
|
||||||
|
TOKEN_LINESTATEMENT_END,
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# tuples support more options
|
||||||
|
if isinstance(tokens, tuple):
|
||||||
|
groups = m.groups()
|
||||||
|
|
||||||
|
if isinstance(tokens, OptionalLStrip):
|
||||||
|
# Rule supports lstrip. Match will look like
|
||||||
|
# text, block type, whitespace control, type, control, ...
|
||||||
|
text = groups[0]
|
||||||
|
# Skipping the text and first type, every other group is the
|
||||||
|
# whitespace control for each type. One of the groups will be
|
||||||
|
# -, +, or empty string instead of None.
|
||||||
|
strip_sign = next(g for g in groups[2::2] if g is not None)
|
||||||
|
|
||||||
|
if strip_sign == "-":
|
||||||
|
# Strip all whitespace between the text and the tag.
|
||||||
|
stripped = text.rstrip()
|
||||||
|
newlines_stripped = text[len(stripped) :].count("\n")
|
||||||
|
groups = [stripped, *groups[1:]]
|
||||||
|
elif (
|
||||||
|
# Not marked for preserving whitespace.
|
||||||
|
strip_sign != "+"
|
||||||
|
# lstrip is enabled.
|
||||||
|
and lstrip_unless_re is not None
|
||||||
|
# Not a variable expression.
|
||||||
|
and not m.groupdict().get(TOKEN_VARIABLE_BEGIN)
|
||||||
|
):
|
||||||
|
# The start of text between the last newline and the tag.
|
||||||
|
l_pos = text.rfind("\n") + 1
|
||||||
|
|
||||||
|
if l_pos > 0 or line_starting:
|
||||||
|
# If there's only whitespace between the newline and the
|
||||||
|
# tag, strip it.
|
||||||
|
if not lstrip_unless_re.search(text, l_pos):
|
||||||
|
groups = [text[:l_pos], *groups[1:]]
|
||||||
|
|
||||||
|
for idx, token in enumerate(tokens):
|
||||||
|
# failure group
|
||||||
|
if token.__class__ is Failure:
|
||||||
|
raise token(lineno, filename)
|
||||||
|
# bygroup is a bit more complex, in that case we
|
||||||
|
# yield for the current token the first named
|
||||||
|
# group that matched
|
||||||
|
elif token == "#bygroup":
|
||||||
|
for key, value in m.groupdict().items():
|
||||||
|
if value is not None:
|
||||||
|
yield lineno, key, value
|
||||||
|
lineno += value.count("\n")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{regex!r} wanted to resolve the token dynamically"
|
||||||
|
" but no group matched"
|
||||||
|
)
|
||||||
|
# normal group
|
||||||
|
else:
|
||||||
|
data = groups[idx]
|
||||||
|
|
||||||
|
if data or token not in ignore_if_empty:
|
||||||
|
yield lineno, token, data
|
||||||
|
|
||||||
|
lineno += data.count("\n") + newlines_stripped
|
||||||
|
newlines_stripped = 0
|
||||||
|
|
||||||
|
# strings as token just are yielded as it.
|
||||||
|
else:
|
||||||
|
data = m.group()
|
||||||
|
|
||||||
|
# update brace/parentheses balance
|
||||||
|
if tokens == TOKEN_OPERATOR:
|
||||||
|
if data == "{":
|
||||||
|
balancing_stack.append("}")
|
||||||
|
elif data == "(":
|
||||||
|
balancing_stack.append(")")
|
||||||
|
elif data == "[":
|
||||||
|
balancing_stack.append("]")
|
||||||
|
elif data in ("}", ")", "]"):
|
||||||
|
if not balancing_stack:
|
||||||
|
raise TemplateSyntaxError(
|
||||||
|
f"unexpected '{data}'", lineno, name, filename
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_op = balancing_stack.pop()
|
||||||
|
|
||||||
|
if expected_op != data:
|
||||||
|
raise TemplateSyntaxError(
|
||||||
|
f"unexpected '{data}', expected '{expected_op}'",
|
||||||
|
lineno,
|
||||||
|
name,
|
||||||
|
filename,
|
||||||
|
)
|
||||||
|
|
||||||
|
# yield items
|
||||||
|
if data or tokens not in ignore_if_empty:
|
||||||
|
yield lineno, tokens, data
|
||||||
|
|
||||||
|
lineno += data.count("\n")
|
||||||
|
|
||||||
|
line_starting = m.group()[-1:] == "\n"
|
||||||
|
# fetch new position into new variable so that we can check
|
||||||
|
# if there is a internal parsing error which would result
|
||||||
|
# in an infinite loop
|
||||||
|
pos2 = m.end()
|
||||||
|
|
||||||
|
# handle state changes
|
||||||
|
if new_state is not None:
|
||||||
|
# remove the uppermost state
|
||||||
|
if new_state == "#pop":
|
||||||
|
stack.pop()
|
||||||
|
# resolve the new state by group checking
|
||||||
|
elif new_state == "#bygroup":
|
||||||
|
for key, value in m.groupdict().items():
|
||||||
|
if value is not None:
|
||||||
|
stack.append(key)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{regex!r} wanted to resolve the new state dynamically"
|
||||||
|
f" but no group matched"
|
||||||
|
)
|
||||||
|
# direct state name given
|
||||||
|
else:
|
||||||
|
stack.append(new_state)
|
||||||
|
|
||||||
|
statetokens = self.rules[stack[-1]]
|
||||||
|
# we are still at the same position and no stack change.
|
||||||
|
# this means a loop without break condition, avoid that and
|
||||||
|
# raise error
|
||||||
|
elif pos2 == pos:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{regex!r} yielded empty string without stack change"
|
||||||
|
)
|
||||||
|
|
||||||
|
# publish new function and start again
|
||||||
|
pos = pos2
|
||||||
|
break
|
||||||
|
# if loop terminated without break we haven't found a single match
|
||||||
|
# either we are at the end of the file or we have a problem
|
||||||
|
else:
|
||||||
|
# end of text
|
||||||
|
if pos >= source_length:
|
||||||
|
return
|
||||||
|
|
||||||
|
# something went wrong
|
||||||
|
raise TemplateSyntaxError(
|
||||||
|
f"unexpected char {source[pos]!r} at {pos}", lineno, name, filename
|
||||||
|
)
|
||||||
652
dist/ba_data/python-site-packages/jinja2/loaders.py
vendored
Normal file
652
dist/ba_data/python-site-packages/jinja2/loaders.py
vendored
Normal file
|
|
@ -0,0 +1,652 @@
|
||||||
|
"""API and implementations for loading templates from different data
|
||||||
|
sources.
|
||||||
|
"""
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import typing as t
|
||||||
|
import weakref
|
||||||
|
import zipimport
|
||||||
|
from collections import abc
|
||||||
|
from hashlib import sha1
|
||||||
|
from importlib import import_module
|
||||||
|
from types import ModuleType
|
||||||
|
|
||||||
|
from .exceptions import TemplateNotFound
|
||||||
|
from .utils import internalcode
|
||||||
|
from .utils import open_if_exists
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
from .environment import Environment
|
||||||
|
from .environment import Template
|
||||||
|
|
||||||
|
|
||||||
|
def split_template_path(template: str) -> t.List[str]:
|
||||||
|
"""Split a path into segments and perform a sanity check. If it detects
|
||||||
|
'..' in the path it will raise a `TemplateNotFound` error.
|
||||||
|
"""
|
||||||
|
pieces = []
|
||||||
|
for piece in template.split("/"):
|
||||||
|
if (
|
||||||
|
os.path.sep in piece
|
||||||
|
or (os.path.altsep and os.path.altsep in piece)
|
||||||
|
or piece == os.path.pardir
|
||||||
|
):
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
elif piece and piece != ".":
|
||||||
|
pieces.append(piece)
|
||||||
|
return pieces
|
||||||
|
|
||||||
|
|
||||||
|
class BaseLoader:
|
||||||
|
"""Baseclass for all loaders. Subclass this and override `get_source` to
|
||||||
|
implement a custom loading mechanism. The environment provides a
|
||||||
|
`get_template` method that calls the loader's `load` method to get the
|
||||||
|
:class:`Template` object.
|
||||||
|
|
||||||
|
A very basic example for a loader that looks up templates on the file
|
||||||
|
system could look like this::
|
||||||
|
|
||||||
|
from jinja2 import BaseLoader, TemplateNotFound
|
||||||
|
from os.path import join, exists, getmtime
|
||||||
|
|
||||||
|
class MyLoader(BaseLoader):
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
def get_source(self, environment, template):
|
||||||
|
path = join(self.path, template)
|
||||||
|
if not exists(path):
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
mtime = getmtime(path)
|
||||||
|
with open(path) as f:
|
||||||
|
source = f.read()
|
||||||
|
return source, path, lambda: mtime == getmtime(path)
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: if set to `False` it indicates that the loader cannot provide access
|
||||||
|
#: to the source of templates.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 2.4
|
||||||
|
has_source_access = True
|
||||||
|
|
||||||
|
def get_source(
|
||||||
|
self, environment: "Environment", template: str
|
||||||
|
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
||||||
|
"""Get the template source, filename and reload helper for a template.
|
||||||
|
It's passed the environment and template name and has to return a
|
||||||
|
tuple in the form ``(source, filename, uptodate)`` or raise a
|
||||||
|
`TemplateNotFound` error if it can't locate the template.
|
||||||
|
|
||||||
|
The source part of the returned tuple must be the source of the
|
||||||
|
template as a string. The filename should be the name of the
|
||||||
|
file on the filesystem if it was loaded from there, otherwise
|
||||||
|
``None``. The filename is used by Python for the tracebacks
|
||||||
|
if no loader extension is used.
|
||||||
|
|
||||||
|
The last item in the tuple is the `uptodate` function. If auto
|
||||||
|
reloading is enabled it's always called to check if the template
|
||||||
|
changed. No arguments are passed so the function must store the
|
||||||
|
old state somewhere (for example in a closure). If it returns `False`
|
||||||
|
the template will be reloaded.
|
||||||
|
"""
|
||||||
|
if not self.has_source_access:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"{type(self).__name__} cannot provide access to the source"
|
||||||
|
)
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
def list_templates(self) -> t.List[str]:
|
||||||
|
"""Iterates over all templates. If the loader does not support that
|
||||||
|
it should raise a :exc:`TypeError` which is the default behavior.
|
||||||
|
"""
|
||||||
|
raise TypeError("this loader cannot iterate over all templates")
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def load(
|
||||||
|
self,
|
||||||
|
environment: "Environment",
|
||||||
|
name: str,
|
||||||
|
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||||
|
) -> "Template":
|
||||||
|
"""Loads a template. This method looks up the template in the cache
|
||||||
|
or loads one by calling :meth:`get_source`. Subclasses should not
|
||||||
|
override this method as loaders working on collections of other
|
||||||
|
loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
|
||||||
|
will not call this method but `get_source` directly.
|
||||||
|
"""
|
||||||
|
code = None
|
||||||
|
if globals is None:
|
||||||
|
globals = {}
|
||||||
|
|
||||||
|
# first we try to get the source for this template together
|
||||||
|
# with the filename and the uptodate function.
|
||||||
|
source, filename, uptodate = self.get_source(environment, name)
|
||||||
|
|
||||||
|
# try to load the code from the bytecode cache if there is a
|
||||||
|
# bytecode cache configured.
|
||||||
|
bcc = environment.bytecode_cache
|
||||||
|
if bcc is not None:
|
||||||
|
bucket = bcc.get_bucket(environment, name, filename, source)
|
||||||
|
code = bucket.code
|
||||||
|
|
||||||
|
# if we don't have code so far (not cached, no longer up to
|
||||||
|
# date) etc. we compile the template
|
||||||
|
if code is None:
|
||||||
|
code = environment.compile(source, name, filename)
|
||||||
|
|
||||||
|
# if the bytecode cache is available and the bucket doesn't
|
||||||
|
# have a code so far, we give the bucket the new code and put
|
||||||
|
# it back to the bytecode cache.
|
||||||
|
if bcc is not None and bucket.code is None:
|
||||||
|
bucket.code = code
|
||||||
|
bcc.set_bucket(bucket)
|
||||||
|
|
||||||
|
return environment.template_class.from_code(
|
||||||
|
environment, code, globals, uptodate
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FileSystemLoader(BaseLoader):
|
||||||
|
"""Load templates from a directory in the file system.
|
||||||
|
|
||||||
|
The path can be relative or absolute. Relative paths are relative to
|
||||||
|
the current working directory.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
loader = FileSystemLoader("templates")
|
||||||
|
|
||||||
|
A list of paths can be given. The directories will be searched in
|
||||||
|
order, stopping at the first matching template.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
loader = FileSystemLoader(["/override/templates", "/default/templates"])
|
||||||
|
|
||||||
|
:param searchpath: A path, or list of paths, to the directory that
|
||||||
|
contains the templates.
|
||||||
|
:param encoding: Use this encoding to read the text from template
|
||||||
|
files.
|
||||||
|
:param followlinks: Follow symbolic links in the path.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.8
|
||||||
|
Added the ``followlinks`` parameter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
searchpath: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]],
|
||||||
|
encoding: str = "utf-8",
|
||||||
|
followlinks: bool = False,
|
||||||
|
) -> None:
|
||||||
|
if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str):
|
||||||
|
searchpath = [searchpath]
|
||||||
|
|
||||||
|
self.searchpath = [os.fspath(p) for p in searchpath]
|
||||||
|
self.encoding = encoding
|
||||||
|
self.followlinks = followlinks
|
||||||
|
|
||||||
|
def get_source(
|
||||||
|
self, environment: "Environment", template: str
|
||||||
|
) -> t.Tuple[str, str, t.Callable[[], bool]]:
|
||||||
|
pieces = split_template_path(template)
|
||||||
|
for searchpath in self.searchpath:
|
||||||
|
filename = os.path.join(searchpath, *pieces)
|
||||||
|
f = open_if_exists(filename)
|
||||||
|
if f is None:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
contents = f.read().decode(self.encoding)
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
mtime = os.path.getmtime(filename)
|
||||||
|
|
||||||
|
def uptodate() -> bool:
|
||||||
|
try:
|
||||||
|
return os.path.getmtime(filename) == mtime
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return contents, filename, uptodate
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
def list_templates(self) -> t.List[str]:
|
||||||
|
found = set()
|
||||||
|
for searchpath in self.searchpath:
|
||||||
|
walk_dir = os.walk(searchpath, followlinks=self.followlinks)
|
||||||
|
for dirpath, _, filenames in walk_dir:
|
||||||
|
for filename in filenames:
|
||||||
|
template = (
|
||||||
|
os.path.join(dirpath, filename)[len(searchpath) :]
|
||||||
|
.strip(os.path.sep)
|
||||||
|
.replace(os.path.sep, "/")
|
||||||
|
)
|
||||||
|
if template[:2] == "./":
|
||||||
|
template = template[2:]
|
||||||
|
if template not in found:
|
||||||
|
found.add(template)
|
||||||
|
return sorted(found)
|
||||||
|
|
||||||
|
|
||||||
|
class PackageLoader(BaseLoader):
|
||||||
|
"""Load templates from a directory in a Python package.
|
||||||
|
|
||||||
|
:param package_name: Import name of the package that contains the
|
||||||
|
template directory.
|
||||||
|
:param package_path: Directory within the imported package that
|
||||||
|
contains the templates.
|
||||||
|
:param encoding: Encoding of template files.
|
||||||
|
|
||||||
|
The following example looks up templates in the ``pages`` directory
|
||||||
|
within the ``project.ui`` package.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
loader = PackageLoader("project.ui", "pages")
|
||||||
|
|
||||||
|
Only packages installed as directories (standard pip behavior) or
|
||||||
|
zip/egg files (less common) are supported. The Python API for
|
||||||
|
introspecting data in packages is too limited to support other
|
||||||
|
installation methods the way this loader requires.
|
||||||
|
|
||||||
|
There is limited support for :pep:`420` namespace packages. The
|
||||||
|
template directory is assumed to only be in one namespace
|
||||||
|
contributor. Zip files contributing to a namespace are not
|
||||||
|
supported.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
No longer uses ``setuptools`` as a dependency.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
Limited PEP 420 namespace package support.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
package_name: str,
|
||||||
|
package_path: "str" = "templates",
|
||||||
|
encoding: str = "utf-8",
|
||||||
|
) -> None:
|
||||||
|
package_path = os.path.normpath(package_path).rstrip(os.path.sep)
|
||||||
|
|
||||||
|
# normpath preserves ".", which isn't valid in zip paths.
|
||||||
|
if package_path == os.path.curdir:
|
||||||
|
package_path = ""
|
||||||
|
elif package_path[:2] == os.path.curdir + os.path.sep:
|
||||||
|
package_path = package_path[2:]
|
||||||
|
|
||||||
|
self.package_path = package_path
|
||||||
|
self.package_name = package_name
|
||||||
|
self.encoding = encoding
|
||||||
|
|
||||||
|
# Make sure the package exists. This also makes namespace
|
||||||
|
# packages work, otherwise get_loader returns None.
|
||||||
|
import_module(package_name)
|
||||||
|
spec = importlib.util.find_spec(package_name)
|
||||||
|
assert spec is not None, "An import spec was not found for the package."
|
||||||
|
loader = spec.loader
|
||||||
|
assert loader is not None, "A loader was not found for the package."
|
||||||
|
self._loader = loader
|
||||||
|
self._archive = None
|
||||||
|
template_root = None
|
||||||
|
|
||||||
|
if isinstance(loader, zipimport.zipimporter):
|
||||||
|
self._archive = loader.archive
|
||||||
|
pkgdir = next(iter(spec.submodule_search_locations)) # type: ignore
|
||||||
|
template_root = os.path.join(pkgdir, package_path)
|
||||||
|
else:
|
||||||
|
roots: t.List[str] = []
|
||||||
|
|
||||||
|
# One element for regular packages, multiple for namespace
|
||||||
|
# packages, or None for single module file.
|
||||||
|
if spec.submodule_search_locations:
|
||||||
|
roots.extend(spec.submodule_search_locations)
|
||||||
|
# A single module file, use the parent directory instead.
|
||||||
|
elif spec.origin is not None:
|
||||||
|
roots.append(os.path.dirname(spec.origin))
|
||||||
|
|
||||||
|
for root in roots:
|
||||||
|
root = os.path.join(root, package_path)
|
||||||
|
|
||||||
|
if os.path.isdir(root):
|
||||||
|
template_root = root
|
||||||
|
break
|
||||||
|
|
||||||
|
if template_root is None:
|
||||||
|
raise ValueError(
|
||||||
|
f"The {package_name!r} package was not installed in a"
|
||||||
|
" way that PackageLoader understands."
|
||||||
|
)
|
||||||
|
|
||||||
|
self._template_root = template_root
|
||||||
|
|
||||||
|
def get_source(
|
||||||
|
self, environment: "Environment", template: str
|
||||||
|
) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
|
||||||
|
p = os.path.join(self._template_root, *split_template_path(template))
|
||||||
|
up_to_date: t.Optional[t.Callable[[], bool]]
|
||||||
|
|
||||||
|
if self._archive is None:
|
||||||
|
# Package is a directory.
|
||||||
|
if not os.path.isfile(p):
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
with open(p, "rb") as f:
|
||||||
|
source = f.read()
|
||||||
|
|
||||||
|
mtime = os.path.getmtime(p)
|
||||||
|
|
||||||
|
def up_to_date() -> bool:
|
||||||
|
return os.path.isfile(p) and os.path.getmtime(p) == mtime
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Package is a zip file.
|
||||||
|
try:
|
||||||
|
source = self._loader.get_data(p) # type: ignore
|
||||||
|
except OSError as e:
|
||||||
|
raise TemplateNotFound(template) from e
|
||||||
|
|
||||||
|
# Could use the zip's mtime for all template mtimes, but
|
||||||
|
# would need to safely reload the module if it's out of
|
||||||
|
# date, so just report it as always current.
|
||||||
|
up_to_date = None
|
||||||
|
|
||||||
|
return source.decode(self.encoding), p, up_to_date
|
||||||
|
|
||||||
|
def list_templates(self) -> t.List[str]:
|
||||||
|
results: t.List[str] = []
|
||||||
|
|
||||||
|
if self._archive is None:
|
||||||
|
# Package is a directory.
|
||||||
|
offset = len(self._template_root)
|
||||||
|
|
||||||
|
for dirpath, _, filenames in os.walk(self._template_root):
|
||||||
|
dirpath = dirpath[offset:].lstrip(os.path.sep)
|
||||||
|
results.extend(
|
||||||
|
os.path.join(dirpath, name).replace(os.path.sep, "/")
|
||||||
|
for name in filenames
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if not hasattr(self._loader, "_files"):
|
||||||
|
raise TypeError(
|
||||||
|
"This zip import does not have the required"
|
||||||
|
" metadata to list templates."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Package is a zip file.
|
||||||
|
prefix = (
|
||||||
|
self._template_root[len(self._archive) :].lstrip(os.path.sep)
|
||||||
|
+ os.path.sep
|
||||||
|
)
|
||||||
|
offset = len(prefix)
|
||||||
|
|
||||||
|
for name in self._loader._files.keys(): # type: ignore
|
||||||
|
# Find names under the templates directory that aren't directories.
|
||||||
|
if name.startswith(prefix) and name[-1] != os.path.sep:
|
||||||
|
results.append(name[offset:].replace(os.path.sep, "/"))
|
||||||
|
|
||||||
|
results.sort()
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
class DictLoader(BaseLoader):
|
||||||
|
"""Loads a template from a Python dict mapping template names to
|
||||||
|
template source. This loader is useful for unittesting:
|
||||||
|
|
||||||
|
>>> loader = DictLoader({'index.html': 'source here'})
|
||||||
|
|
||||||
|
Because auto reloading is rarely useful this is disabled per default.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, mapping: t.Mapping[str, str]) -> None:
|
||||||
|
self.mapping = mapping
|
||||||
|
|
||||||
|
def get_source(
|
||||||
|
self, environment: "Environment", template: str
|
||||||
|
) -> t.Tuple[str, None, t.Callable[[], bool]]:
|
||||||
|
if template in self.mapping:
|
||||||
|
source = self.mapping[template]
|
||||||
|
return source, None, lambda: source == self.mapping.get(template)
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
def list_templates(self) -> t.List[str]:
|
||||||
|
return sorted(self.mapping)
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionLoader(BaseLoader):
|
||||||
|
"""A loader that is passed a function which does the loading. The
|
||||||
|
function receives the name of the template and has to return either
|
||||||
|
a string with the template source, a tuple in the form ``(source,
|
||||||
|
filename, uptodatefunc)`` or `None` if the template does not exist.
|
||||||
|
|
||||||
|
>>> def load_template(name):
|
||||||
|
... if name == 'index.html':
|
||||||
|
... return '...'
|
||||||
|
...
|
||||||
|
>>> loader = FunctionLoader(load_template)
|
||||||
|
|
||||||
|
The `uptodatefunc` is a function that is called if autoreload is enabled
|
||||||
|
and has to return `True` if the template is still up to date. For more
|
||||||
|
details have a look at :meth:`BaseLoader.get_source` which has the same
|
||||||
|
return value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
load_func: t.Callable[
|
||||||
|
[str],
|
||||||
|
t.Optional[
|
||||||
|
t.Union[
|
||||||
|
str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
) -> None:
|
||||||
|
self.load_func = load_func
|
||||||
|
|
||||||
|
def get_source(
|
||||||
|
self, environment: "Environment", template: str
|
||||||
|
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
||||||
|
rv = self.load_func(template)
|
||||||
|
|
||||||
|
if rv is None:
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
if isinstance(rv, str):
|
||||||
|
return rv, None, None
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class PrefixLoader(BaseLoader):
|
||||||
|
"""A loader that is passed a dict of loaders where each loader is bound
|
||||||
|
to a prefix. The prefix is delimited from the template by a slash per
|
||||||
|
default, which can be changed by setting the `delimiter` argument to
|
||||||
|
something else::
|
||||||
|
|
||||||
|
loader = PrefixLoader({
|
||||||
|
'app1': PackageLoader('mypackage.app1'),
|
||||||
|
'app2': PackageLoader('mypackage.app2')
|
||||||
|
})
|
||||||
|
|
||||||
|
By loading ``'app1/index.html'`` the file from the app1 package is loaded,
|
||||||
|
by loading ``'app2/index.html'`` the file from the second.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/"
|
||||||
|
) -> None:
|
||||||
|
self.mapping = mapping
|
||||||
|
self.delimiter = delimiter
|
||||||
|
|
||||||
|
def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]:
|
||||||
|
try:
|
||||||
|
prefix, name = template.split(self.delimiter, 1)
|
||||||
|
loader = self.mapping[prefix]
|
||||||
|
except (ValueError, KeyError) as e:
|
||||||
|
raise TemplateNotFound(template) from e
|
||||||
|
return loader, name
|
||||||
|
|
||||||
|
def get_source(
|
||||||
|
self, environment: "Environment", template: str
|
||||||
|
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
||||||
|
loader, name = self.get_loader(template)
|
||||||
|
try:
|
||||||
|
return loader.get_source(environment, name)
|
||||||
|
except TemplateNotFound as e:
|
||||||
|
# re-raise the exception with the correct filename here.
|
||||||
|
# (the one that includes the prefix)
|
||||||
|
raise TemplateNotFound(template) from e
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def load(
|
||||||
|
self,
|
||||||
|
environment: "Environment",
|
||||||
|
name: str,
|
||||||
|
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||||
|
) -> "Template":
|
||||||
|
loader, local_name = self.get_loader(name)
|
||||||
|
try:
|
||||||
|
return loader.load(environment, local_name, globals)
|
||||||
|
except TemplateNotFound as e:
|
||||||
|
# re-raise the exception with the correct filename here.
|
||||||
|
# (the one that includes the prefix)
|
||||||
|
raise TemplateNotFound(name) from e
|
||||||
|
|
||||||
|
def list_templates(self) -> t.List[str]:
|
||||||
|
result = []
|
||||||
|
for prefix, loader in self.mapping.items():
|
||||||
|
for template in loader.list_templates():
|
||||||
|
result.append(prefix + self.delimiter + template)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class ChoiceLoader(BaseLoader):
|
||||||
|
"""This loader works like the `PrefixLoader` just that no prefix is
|
||||||
|
specified. If a template could not be found by one loader the next one
|
||||||
|
is tried.
|
||||||
|
|
||||||
|
>>> loader = ChoiceLoader([
|
||||||
|
... FileSystemLoader('/path/to/user/templates'),
|
||||||
|
... FileSystemLoader('/path/to/system/templates')
|
||||||
|
... ])
|
||||||
|
|
||||||
|
This is useful if you want to allow users to override builtin templates
|
||||||
|
from a different location.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, loaders: t.Sequence[BaseLoader]) -> None:
|
||||||
|
self.loaders = loaders
|
||||||
|
|
||||||
|
def get_source(
|
||||||
|
self, environment: "Environment", template: str
|
||||||
|
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
||||||
|
for loader in self.loaders:
|
||||||
|
try:
|
||||||
|
return loader.get_source(environment, template)
|
||||||
|
except TemplateNotFound:
|
||||||
|
pass
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def load(
|
||||||
|
self,
|
||||||
|
environment: "Environment",
|
||||||
|
name: str,
|
||||||
|
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||||
|
) -> "Template":
|
||||||
|
for loader in self.loaders:
|
||||||
|
try:
|
||||||
|
return loader.load(environment, name, globals)
|
||||||
|
except TemplateNotFound:
|
||||||
|
pass
|
||||||
|
raise TemplateNotFound(name)
|
||||||
|
|
||||||
|
def list_templates(self) -> t.List[str]:
|
||||||
|
found = set()
|
||||||
|
for loader in self.loaders:
|
||||||
|
found.update(loader.list_templates())
|
||||||
|
return sorted(found)
|
||||||
|
|
||||||
|
|
||||||
|
class _TemplateModule(ModuleType):
|
||||||
|
"""Like a normal module but with support for weak references"""
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleLoader(BaseLoader):
|
||||||
|
"""This loader loads templates from precompiled templates.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
>>> loader = ChoiceLoader([
|
||||||
|
... ModuleLoader('/path/to/compiled/templates'),
|
||||||
|
... FileSystemLoader('/path/to/templates')
|
||||||
|
... ])
|
||||||
|
|
||||||
|
Templates can be precompiled with :meth:`Environment.compile_templates`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
has_source_access = False
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, path: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]]
|
||||||
|
) -> None:
|
||||||
|
package_name = f"_jinja2_module_templates_{id(self):x}"
|
||||||
|
|
||||||
|
# create a fake module that looks for the templates in the
|
||||||
|
# path given.
|
||||||
|
mod = _TemplateModule(package_name)
|
||||||
|
|
||||||
|
if not isinstance(path, abc.Iterable) or isinstance(path, str):
|
||||||
|
path = [path]
|
||||||
|
|
||||||
|
mod.__path__ = [os.fspath(p) for p in path] # type: ignore
|
||||||
|
|
||||||
|
sys.modules[package_name] = weakref.proxy(
|
||||||
|
mod, lambda x: sys.modules.pop(package_name, None)
|
||||||
|
)
|
||||||
|
|
||||||
|
# the only strong reference, the sys.modules entry is weak
|
||||||
|
# so that the garbage collector can remove it once the
|
||||||
|
# loader that created it goes out of business.
|
||||||
|
self.module = mod
|
||||||
|
self.package_name = package_name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_template_key(name: str) -> str:
|
||||||
|
return "tmpl_" + sha1(name.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_module_filename(name: str) -> str:
|
||||||
|
return ModuleLoader.get_template_key(name) + ".py"
|
||||||
|
|
||||||
|
@internalcode
|
||||||
|
def load(
|
||||||
|
self,
|
||||||
|
environment: "Environment",
|
||||||
|
name: str,
|
||||||
|
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||||
|
) -> "Template":
|
||||||
|
key = self.get_template_key(name)
|
||||||
|
module = f"{self.package_name}.{key}"
|
||||||
|
mod = getattr(self.module, module, None)
|
||||||
|
|
||||||
|
if mod is None:
|
||||||
|
try:
|
||||||
|
mod = __import__(module, None, None, ["root"])
|
||||||
|
except ImportError as e:
|
||||||
|
raise TemplateNotFound(name) from e
|
||||||
|
|
||||||
|
# remove the entry from sys.modules, we only want the attribute
|
||||||
|
# on the module object we have stored on the loader.
|
||||||
|
sys.modules.pop(module, None)
|
||||||
|
|
||||||
|
if globals is None:
|
||||||
|
globals = {}
|
||||||
|
|
||||||
|
return environment.template_class.from_module_dict(
|
||||||
|
environment, mod.__dict__, globals
|
||||||
|
)
|
||||||
111
dist/ba_data/python-site-packages/jinja2/meta.py
vendored
Normal file
111
dist/ba_data/python-site-packages/jinja2/meta.py
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
"""Functions that expose information about templates that might be
|
||||||
|
interesting for introspection.
|
||||||
|
"""
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
from . import nodes
|
||||||
|
from .compiler import CodeGenerator
|
||||||
|
from .compiler import Frame
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
from .environment import Environment
|
||||||
|
|
||||||
|
|
||||||
|
class TrackingCodeGenerator(CodeGenerator):
|
||||||
|
"""We abuse the code generator for introspection."""
|
||||||
|
|
||||||
|
def __init__(self, environment: "Environment") -> None:
|
||||||
|
super().__init__(environment, "<introspection>", "<introspection>")
|
||||||
|
self.undeclared_identifiers: t.Set[str] = set()
|
||||||
|
|
||||||
|
def write(self, x: str) -> None:
|
||||||
|
"""Don't write."""
|
||||||
|
|
||||||
|
def enter_frame(self, frame: Frame) -> None:
|
||||||
|
"""Remember all undeclared identifiers."""
|
||||||
|
super().enter_frame(frame)
|
||||||
|
|
||||||
|
for _, (action, param) in frame.symbols.loads.items():
|
||||||
|
if action == "resolve" and param not in self.environment.globals:
|
||||||
|
self.undeclared_identifiers.add(param)
|
||||||
|
|
||||||
|
|
||||||
|
def find_undeclared_variables(ast: nodes.Template) -> t.Set[str]:
|
||||||
|
"""Returns a set of all variables in the AST that will be looked up from
|
||||||
|
the context at runtime. Because at compile time it's not known which
|
||||||
|
variables will be used depending on the path the execution takes at
|
||||||
|
runtime, all variables are returned.
|
||||||
|
|
||||||
|
>>> from jinja2 import Environment, meta
|
||||||
|
>>> env = Environment()
|
||||||
|
>>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
|
||||||
|
>>> meta.find_undeclared_variables(ast) == {'bar'}
|
||||||
|
True
|
||||||
|
|
||||||
|
.. admonition:: Implementation
|
||||||
|
|
||||||
|
Internally the code generator is used for finding undeclared variables.
|
||||||
|
This is good to know because the code generator might raise a
|
||||||
|
:exc:`TemplateAssertionError` during compilation and as a matter of
|
||||||
|
fact this function can currently raise that exception as well.
|
||||||
|
"""
|
||||||
|
codegen = TrackingCodeGenerator(ast.environment) # type: ignore
|
||||||
|
codegen.visit(ast)
|
||||||
|
return codegen.undeclared_identifiers
|
||||||
|
|
||||||
|
|
||||||
|
_ref_types = (nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include)
|
||||||
|
_RefType = t.Union[nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include]
|
||||||
|
|
||||||
|
|
||||||
|
def find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str]]:
|
||||||
|
"""Finds all the referenced templates from the AST. This will return an
|
||||||
|
iterator over all the hardcoded template extensions, inclusions and
|
||||||
|
imports. If dynamic inheritance or inclusion is used, `None` will be
|
||||||
|
yielded.
|
||||||
|
|
||||||
|
>>> from jinja2 import Environment, meta
|
||||||
|
>>> env = Environment()
|
||||||
|
>>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
|
||||||
|
>>> list(meta.find_referenced_templates(ast))
|
||||||
|
['layout.html', None]
|
||||||
|
|
||||||
|
This function is useful for dependency tracking. For example if you want
|
||||||
|
to rebuild parts of the website after a layout template has changed.
|
||||||
|
"""
|
||||||
|
template_name: t.Any
|
||||||
|
|
||||||
|
for node in ast.find_all(_ref_types):
|
||||||
|
template: nodes.Expr = node.template # type: ignore
|
||||||
|
|
||||||
|
if not isinstance(template, nodes.Const):
|
||||||
|
# a tuple with some non consts in there
|
||||||
|
if isinstance(template, (nodes.Tuple, nodes.List)):
|
||||||
|
for template_name in template.items:
|
||||||
|
# something const, only yield the strings and ignore
|
||||||
|
# non-string consts that really just make no sense
|
||||||
|
if isinstance(template_name, nodes.Const):
|
||||||
|
if isinstance(template_name.value, str):
|
||||||
|
yield template_name.value
|
||||||
|
# something dynamic in there
|
||||||
|
else:
|
||||||
|
yield None
|
||||||
|
# something dynamic we don't know about here
|
||||||
|
else:
|
||||||
|
yield None
|
||||||
|
continue
|
||||||
|
# constant is a basestring, direct template name
|
||||||
|
if isinstance(template.value, str):
|
||||||
|
yield template.value
|
||||||
|
# a tuple or list (latter *should* not happen) made of consts,
|
||||||
|
# yield the consts that are strings. We could warn here for
|
||||||
|
# non string values
|
||||||
|
elif isinstance(node, nodes.Include) and isinstance(
|
||||||
|
template.value, (tuple, list)
|
||||||
|
):
|
||||||
|
for template_name in template.value:
|
||||||
|
if isinstance(template_name, str):
|
||||||
|
yield template_name
|
||||||
|
# something else we don't care about, we could warn here
|
||||||
|
else:
|
||||||
|
yield None
|
||||||
124
dist/ba_data/python-site-packages/jinja2/nativetypes.py
vendored
Normal file
124
dist/ba_data/python-site-packages/jinja2/nativetypes.py
vendored
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
import typing as t
|
||||||
|
from ast import literal_eval
|
||||||
|
from ast import parse
|
||||||
|
from itertools import chain
|
||||||
|
from itertools import islice
|
||||||
|
|
||||||
|
from . import nodes
|
||||||
|
from .compiler import CodeGenerator
|
||||||
|
from .compiler import Frame
|
||||||
|
from .compiler import has_safe_repr
|
||||||
|
from .environment import Environment
|
||||||
|
from .environment import Template
|
||||||
|
|
||||||
|
|
||||||
|
def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]:
|
||||||
|
"""Return a native Python type from the list of compiled nodes. If
|
||||||
|
the result is a single node, its value is returned. Otherwise, the
|
||||||
|
nodes are concatenated as strings. If the result can be parsed with
|
||||||
|
:func:`ast.literal_eval`, the parsed value is returned. Otherwise,
|
||||||
|
the string is returned.
|
||||||
|
|
||||||
|
:param values: Iterable of outputs to concatenate.
|
||||||
|
"""
|
||||||
|
head = list(islice(values, 2))
|
||||||
|
|
||||||
|
if not head:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if len(head) == 1:
|
||||||
|
raw = head[0]
|
||||||
|
if not isinstance(raw, str):
|
||||||
|
return raw
|
||||||
|
else:
|
||||||
|
raw = "".join([str(v) for v in chain(head, values)])
|
||||||
|
|
||||||
|
try:
|
||||||
|
return literal_eval(
|
||||||
|
# In Python 3.10+ ast.literal_eval removes leading spaces/tabs
|
||||||
|
# from the given string. For backwards compatibility we need to
|
||||||
|
# parse the string ourselves without removing leading spaces/tabs.
|
||||||
|
parse(raw, mode="eval")
|
||||||
|
)
|
||||||
|
except (ValueError, SyntaxError, MemoryError):
|
||||||
|
return raw
|
||||||
|
|
||||||
|
|
||||||
|
class NativeCodeGenerator(CodeGenerator):
|
||||||
|
"""A code generator which renders Python types by not adding
|
||||||
|
``str()`` around output nodes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _default_finalize(value: t.Any) -> t.Any:
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _output_const_repr(self, group: t.Iterable[t.Any]) -> str:
|
||||||
|
return repr("".join([str(v) for v in group]))
|
||||||
|
|
||||||
|
def _output_child_to_const(
|
||||||
|
self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
|
||||||
|
) -> t.Any:
|
||||||
|
const = node.as_const(frame.eval_ctx)
|
||||||
|
|
||||||
|
if not has_safe_repr(const):
|
||||||
|
raise nodes.Impossible()
|
||||||
|
|
||||||
|
if isinstance(node, nodes.TemplateData):
|
||||||
|
return const
|
||||||
|
|
||||||
|
return finalize.const(const) # type: ignore
|
||||||
|
|
||||||
|
def _output_child_pre(
|
||||||
|
self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
|
||||||
|
) -> None:
|
||||||
|
if finalize.src is not None:
|
||||||
|
self.write(finalize.src)
|
||||||
|
|
||||||
|
def _output_child_post(
|
||||||
|
self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
|
||||||
|
) -> None:
|
||||||
|
if finalize.src is not None:
|
||||||
|
self.write(")")
|
||||||
|
|
||||||
|
|
||||||
|
class NativeEnvironment(Environment):
|
||||||
|
"""An environment that renders templates to native Python types."""
|
||||||
|
|
||||||
|
code_generator_class = NativeCodeGenerator
|
||||||
|
|
||||||
|
|
||||||
|
class NativeTemplate(Template):
|
||||||
|
environment_class = NativeEnvironment
|
||||||
|
|
||||||
|
def render(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||||
|
"""Render the template to produce a native Python type. If the
|
||||||
|
result is a single node, its value is returned. Otherwise, the
|
||||||
|
nodes are concatenated as strings. If the result can be parsed
|
||||||
|
with :func:`ast.literal_eval`, the parsed value is returned.
|
||||||
|
Otherwise, the string is returned.
|
||||||
|
"""
|
||||||
|
ctx = self.new_context(dict(*args, **kwargs))
|
||||||
|
|
||||||
|
try:
|
||||||
|
return native_concat(self.root_render_func(ctx)) # type: ignore
|
||||||
|
except Exception:
|
||||||
|
return self.environment.handle_exception()
|
||||||
|
|
||||||
|
async def render_async(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||||
|
if not self.environment.is_async:
|
||||||
|
raise RuntimeError(
|
||||||
|
"The environment was not created with async mode enabled."
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx = self.new_context(dict(*args, **kwargs))
|
||||||
|
|
||||||
|
try:
|
||||||
|
return native_concat(
|
||||||
|
[n async for n in self.root_render_func(ctx)] # type: ignore
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return self.environment.handle_exception()
|
||||||
|
|
||||||
|
|
||||||
|
NativeEnvironment.template_class = NativeTemplate
|
||||||
1204
dist/ba_data/python-site-packages/jinja2/nodes.py
vendored
Normal file
1204
dist/ba_data/python-site-packages/jinja2/nodes.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
47
dist/ba_data/python-site-packages/jinja2/optimizer.py
vendored
Normal file
47
dist/ba_data/python-site-packages/jinja2/optimizer.py
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
"""The optimizer tries to constant fold expressions and modify the AST
|
||||||
|
in place so that it should be faster to evaluate.
|
||||||
|
|
||||||
|
Because the AST does not contain all the scoping information and the
|
||||||
|
compiler has to find that out, we cannot do all the optimizations we
|
||||||
|
want. For example, loop unrolling doesn't work because unrolled loops
|
||||||
|
would have a different scope. The solution would be a second syntax tree
|
||||||
|
that stored the scoping rules.
|
||||||
|
"""
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
from . import nodes
|
||||||
|
from .visitor import NodeTransformer
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
from .environment import Environment
|
||||||
|
|
||||||
|
|
||||||
|
def optimize(node: nodes.Node, environment: "Environment") -> nodes.Node:
|
||||||
|
"""The context hint can be used to perform an static optimization
|
||||||
|
based on the context given."""
|
||||||
|
optimizer = Optimizer(environment)
|
||||||
|
return t.cast(nodes.Node, optimizer.visit(node))
|
||||||
|
|
||||||
|
|
||||||
|
class Optimizer(NodeTransformer):
|
||||||
|
def __init__(self, environment: "t.Optional[Environment]") -> None:
|
||||||
|
self.environment = environment
|
||||||
|
|
||||||
|
def generic_visit(
|
||||||
|
self, node: nodes.Node, *args: t.Any, **kwargs: t.Any
|
||||||
|
) -> nodes.Node:
|
||||||
|
node = super().generic_visit(node, *args, **kwargs)
|
||||||
|
|
||||||
|
# Do constant folding. Some other nodes besides Expr have
|
||||||
|
# as_const, but folding them causes errors later on.
|
||||||
|
if isinstance(node, nodes.Expr):
|
||||||
|
try:
|
||||||
|
return nodes.Const.from_untrusted(
|
||||||
|
node.as_const(args[0] if args else None),
|
||||||
|
lineno=node.lineno,
|
||||||
|
environment=self.environment,
|
||||||
|
)
|
||||||
|
except nodes.Impossible:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return node
|
||||||
1040
dist/ba_data/python-site-packages/jinja2/parser.py
vendored
Normal file
1040
dist/ba_data/python-site-packages/jinja2/parser.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
0
dist/ba_data/python-site-packages/jinja2/py.typed
vendored
Normal file
0
dist/ba_data/python-site-packages/jinja2/py.typed
vendored
Normal file
1104
dist/ba_data/python-site-packages/jinja2/runtime.py
vendored
Normal file
1104
dist/ba_data/python-site-packages/jinja2/runtime.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
428
dist/ba_data/python-site-packages/jinja2/sandbox.py
vendored
Normal file
428
dist/ba_data/python-site-packages/jinja2/sandbox.py
vendored
Normal file
|
|
@ -0,0 +1,428 @@
|
||||||
|
"""A sandbox layer that ensures unsafe operations cannot be performed.
|
||||||
|
Useful when the template itself comes from an untrusted source.
|
||||||
|
"""
|
||||||
|
import operator
|
||||||
|
import types
|
||||||
|
import typing as t
|
||||||
|
from _string import formatter_field_name_split # type: ignore
|
||||||
|
from collections import abc
|
||||||
|
from collections import deque
|
||||||
|
from string import Formatter
|
||||||
|
|
||||||
|
from markupsafe import EscapeFormatter
|
||||||
|
from markupsafe import Markup
|
||||||
|
|
||||||
|
from .environment import Environment
|
||||||
|
from .exceptions import SecurityError
|
||||||
|
from .runtime import Context
|
||||||
|
from .runtime import Undefined
|
||||||
|
|
||||||
|
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||||
|
|
||||||
|
#: maximum number of items a range may produce
|
||||||
|
MAX_RANGE = 100000
|
||||||
|
|
||||||
|
#: Unsafe function attributes.
|
||||||
|
UNSAFE_FUNCTION_ATTRIBUTES: t.Set[str] = set()
|
||||||
|
|
||||||
|
#: Unsafe method attributes. Function attributes are unsafe for methods too.
|
||||||
|
UNSAFE_METHOD_ATTRIBUTES: t.Set[str] = set()
|
||||||
|
|
||||||
|
#: unsafe generator attributes.
|
||||||
|
UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"}
|
||||||
|
|
||||||
|
#: unsafe attributes on coroutines
|
||||||
|
UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"}
|
||||||
|
|
||||||
|
#: unsafe attributes on async generators
|
||||||
|
UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"}
|
||||||
|
|
||||||
|
_mutable_spec: t.Tuple[t.Tuple[t.Type, t.FrozenSet[str]], ...] = (
|
||||||
|
(
|
||||||
|
abc.MutableSet,
|
||||||
|
frozenset(
|
||||||
|
[
|
||||||
|
"add",
|
||||||
|
"clear",
|
||||||
|
"difference_update",
|
||||||
|
"discard",
|
||||||
|
"pop",
|
||||||
|
"remove",
|
||||||
|
"symmetric_difference_update",
|
||||||
|
"update",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
abc.MutableMapping,
|
||||||
|
frozenset(["clear", "pop", "popitem", "setdefault", "update"]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
abc.MutableSequence,
|
||||||
|
frozenset(["append", "reverse", "insert", "sort", "extend", "remove"]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
deque,
|
||||||
|
frozenset(
|
||||||
|
[
|
||||||
|
"append",
|
||||||
|
"appendleft",
|
||||||
|
"clear",
|
||||||
|
"extend",
|
||||||
|
"extendleft",
|
||||||
|
"pop",
|
||||||
|
"popleft",
|
||||||
|
"remove",
|
||||||
|
"rotate",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def inspect_format_method(callable: t.Callable) -> t.Optional[str]:
|
||||||
|
if not isinstance(
|
||||||
|
callable, (types.MethodType, types.BuiltinMethodType)
|
||||||
|
) or callable.__name__ not in ("format", "format_map"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
obj = callable.__self__
|
||||||
|
|
||||||
|
if isinstance(obj, str):
|
||||||
|
return obj
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def safe_range(*args: int) -> range:
|
||||||
|
"""A range that can't generate ranges with a length of more than
|
||||||
|
MAX_RANGE items.
|
||||||
|
"""
|
||||||
|
rng = range(*args)
|
||||||
|
|
||||||
|
if len(rng) > MAX_RANGE:
|
||||||
|
raise OverflowError(
|
||||||
|
"Range too big. The sandbox blocks ranges larger than"
|
||||||
|
f" MAX_RANGE ({MAX_RANGE})."
|
||||||
|
)
|
||||||
|
|
||||||
|
return rng
|
||||||
|
|
||||||
|
|
||||||
|
def unsafe(f: F) -> F:
|
||||||
|
"""Marks a function or method as unsafe.
|
||||||
|
|
||||||
|
.. code-block: python
|
||||||
|
|
||||||
|
@unsafe
|
||||||
|
def delete(self):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
f.unsafe_callable = True # type: ignore
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def is_internal_attribute(obj: t.Any, attr: str) -> bool:
|
||||||
|
"""Test if the attribute given is an internal python attribute. For
|
||||||
|
example this function returns `True` for the `func_code` attribute of
|
||||||
|
python objects. This is useful if the environment method
|
||||||
|
:meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.
|
||||||
|
|
||||||
|
>>> from jinja2.sandbox import is_internal_attribute
|
||||||
|
>>> is_internal_attribute(str, "mro")
|
||||||
|
True
|
||||||
|
>>> is_internal_attribute(str, "upper")
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
if isinstance(obj, types.FunctionType):
|
||||||
|
if attr in UNSAFE_FUNCTION_ATTRIBUTES:
|
||||||
|
return True
|
||||||
|
elif isinstance(obj, types.MethodType):
|
||||||
|
if attr in UNSAFE_FUNCTION_ATTRIBUTES or attr in UNSAFE_METHOD_ATTRIBUTES:
|
||||||
|
return True
|
||||||
|
elif isinstance(obj, type):
|
||||||
|
if attr == "mro":
|
||||||
|
return True
|
||||||
|
elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)):
|
||||||
|
return True
|
||||||
|
elif isinstance(obj, types.GeneratorType):
|
||||||
|
if attr in UNSAFE_GENERATOR_ATTRIBUTES:
|
||||||
|
return True
|
||||||
|
elif hasattr(types, "CoroutineType") and isinstance(obj, types.CoroutineType):
|
||||||
|
if attr in UNSAFE_COROUTINE_ATTRIBUTES:
|
||||||
|
return True
|
||||||
|
elif hasattr(types, "AsyncGeneratorType") and isinstance(
|
||||||
|
obj, types.AsyncGeneratorType
|
||||||
|
):
|
||||||
|
if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES:
|
||||||
|
return True
|
||||||
|
return attr.startswith("__")
|
||||||
|
|
||||||
|
|
||||||
|
def modifies_known_mutable(obj: t.Any, attr: str) -> bool:
|
||||||
|
"""This function checks if an attribute on a builtin mutable object
|
||||||
|
(list, dict, set or deque) or the corresponding ABCs would modify it
|
||||||
|
if called.
|
||||||
|
|
||||||
|
>>> modifies_known_mutable({}, "clear")
|
||||||
|
True
|
||||||
|
>>> modifies_known_mutable({}, "keys")
|
||||||
|
False
|
||||||
|
>>> modifies_known_mutable([], "append")
|
||||||
|
True
|
||||||
|
>>> modifies_known_mutable([], "index")
|
||||||
|
False
|
||||||
|
|
||||||
|
If called with an unsupported object, ``False`` is returned.
|
||||||
|
|
||||||
|
>>> modifies_known_mutable("foo", "upper")
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
for typespec, unsafe in _mutable_spec:
|
||||||
|
if isinstance(obj, typespec):
|
||||||
|
return attr in unsafe
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class SandboxedEnvironment(Environment):
|
||||||
|
"""The sandboxed environment. It works like the regular environment but
|
||||||
|
tells the compiler to generate sandboxed code. Additionally subclasses of
|
||||||
|
this environment may override the methods that tell the runtime what
|
||||||
|
attributes or functions are safe to access.
|
||||||
|
|
||||||
|
If the template tries to access insecure code a :exc:`SecurityError` is
|
||||||
|
raised. However also other exceptions may occur during the rendering so
|
||||||
|
the caller has to ensure that all exceptions are caught.
|
||||||
|
"""
|
||||||
|
|
||||||
|
sandboxed = True
|
||||||
|
|
||||||
|
#: default callback table for the binary operators. A copy of this is
|
||||||
|
#: available on each instance of a sandboxed environment as
|
||||||
|
#: :attr:`binop_table`
|
||||||
|
default_binop_table: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
|
||||||
|
"+": operator.add,
|
||||||
|
"-": operator.sub,
|
||||||
|
"*": operator.mul,
|
||||||
|
"/": operator.truediv,
|
||||||
|
"//": operator.floordiv,
|
||||||
|
"**": operator.pow,
|
||||||
|
"%": operator.mod,
|
||||||
|
}
|
||||||
|
|
||||||
|
#: default callback table for the unary operators. A copy of this is
|
||||||
|
#: available on each instance of a sandboxed environment as
|
||||||
|
#: :attr:`unop_table`
|
||||||
|
default_unop_table: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
|
||||||
|
"+": operator.pos,
|
||||||
|
"-": operator.neg,
|
||||||
|
}
|
||||||
|
|
||||||
|
#: a set of binary operators that should be intercepted. Each operator
|
||||||
|
#: that is added to this set (empty by default) is delegated to the
|
||||||
|
#: :meth:`call_binop` method that will perform the operator. The default
|
||||||
|
#: operator callback is specified by :attr:`binop_table`.
|
||||||
|
#:
|
||||||
|
#: The following binary operators are interceptable:
|
||||||
|
#: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
|
||||||
|
#:
|
||||||
|
#: The default operation form the operator table corresponds to the
|
||||||
|
#: builtin function. Intercepted calls are always slower than the native
|
||||||
|
#: operator call, so make sure only to intercept the ones you are
|
||||||
|
#: interested in.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 2.6
|
||||||
|
intercepted_binops: t.FrozenSet[str] = frozenset()
|
||||||
|
|
||||||
|
#: a set of unary operators that should be intercepted. Each operator
|
||||||
|
#: that is added to this set (empty by default) is delegated to the
|
||||||
|
#: :meth:`call_unop` method that will perform the operator. The default
|
||||||
|
#: operator callback is specified by :attr:`unop_table`.
|
||||||
|
#:
|
||||||
|
#: The following unary operators are interceptable: ``+``, ``-``
|
||||||
|
#:
|
||||||
|
#: The default operation form the operator table corresponds to the
|
||||||
|
#: builtin function. Intercepted calls are always slower than the native
|
||||||
|
#: operator call, so make sure only to intercept the ones you are
|
||||||
|
#: interested in.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 2.6
|
||||||
|
intercepted_unops: t.FrozenSet[str] = frozenset()
|
||||||
|
|
||||||
|
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.globals["range"] = safe_range
|
||||||
|
self.binop_table = self.default_binop_table.copy()
|
||||||
|
self.unop_table = self.default_unop_table.copy()
|
||||||
|
|
||||||
|
def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
|
||||||
|
"""The sandboxed environment will call this method to check if the
|
||||||
|
attribute of an object is safe to access. Per default all attributes
|
||||||
|
starting with an underscore are considered private as well as the
|
||||||
|
special attributes of internal python objects as returned by the
|
||||||
|
:func:`is_internal_attribute` function.
|
||||||
|
"""
|
||||||
|
return not (attr.startswith("_") or is_internal_attribute(obj, attr))
|
||||||
|
|
||||||
|
def is_safe_callable(self, obj: t.Any) -> bool:
|
||||||
|
"""Check if an object is safely callable. By default callables
|
||||||
|
are considered safe unless decorated with :func:`unsafe`.
|
||||||
|
|
||||||
|
This also recognizes the Django convention of setting
|
||||||
|
``func.alters_data = True``.
|
||||||
|
"""
|
||||||
|
return not (
|
||||||
|
getattr(obj, "unsafe_callable", False) or getattr(obj, "alters_data", False)
|
||||||
|
)
|
||||||
|
|
||||||
|
def call_binop(
|
||||||
|
self, context: Context, operator: str, left: t.Any, right: t.Any
|
||||||
|
) -> t.Any:
|
||||||
|
"""For intercepted binary operator calls (:meth:`intercepted_binops`)
|
||||||
|
this function is executed instead of the builtin operator. This can
|
||||||
|
be used to fine tune the behavior of certain operators.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
"""
|
||||||
|
return self.binop_table[operator](left, right)
|
||||||
|
|
||||||
|
def call_unop(self, context: Context, operator: str, arg: t.Any) -> t.Any:
|
||||||
|
"""For intercepted unary operator calls (:meth:`intercepted_unops`)
|
||||||
|
this function is executed instead of the builtin operator. This can
|
||||||
|
be used to fine tune the behavior of certain operators.
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
"""
|
||||||
|
return self.unop_table[operator](arg)
|
||||||
|
|
||||||
|
def getitem(
|
||||||
|
self, obj: t.Any, argument: t.Union[str, t.Any]
|
||||||
|
) -> t.Union[t.Any, Undefined]:
|
||||||
|
"""Subscribe an object from sandboxed code."""
|
||||||
|
try:
|
||||||
|
return obj[argument]
|
||||||
|
except (TypeError, LookupError):
|
||||||
|
if isinstance(argument, str):
|
||||||
|
try:
|
||||||
|
attr = str(argument)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
value = getattr(obj, attr)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if self.is_safe_attribute(obj, argument, value):
|
||||||
|
return value
|
||||||
|
return self.unsafe_undefined(obj, argument)
|
||||||
|
return self.undefined(obj=obj, name=argument)
|
||||||
|
|
||||||
|
def getattr(self, obj: t.Any, attribute: str) -> t.Union[t.Any, Undefined]:
|
||||||
|
"""Subscribe an object from sandboxed code and prefer the
|
||||||
|
attribute. The attribute passed *must* be a bytestring.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
value = getattr(obj, attribute)
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
return obj[attribute]
|
||||||
|
except (TypeError, LookupError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if self.is_safe_attribute(obj, attribute, value):
|
||||||
|
return value
|
||||||
|
return self.unsafe_undefined(obj, attribute)
|
||||||
|
return self.undefined(obj=obj, name=attribute)
|
||||||
|
|
||||||
|
def unsafe_undefined(self, obj: t.Any, attribute: str) -> Undefined:
|
||||||
|
"""Return an undefined object for unsafe attributes."""
|
||||||
|
return self.undefined(
|
||||||
|
f"access to attribute {attribute!r} of"
|
||||||
|
f" {type(obj).__name__!r} object is unsafe.",
|
||||||
|
name=attribute,
|
||||||
|
obj=obj,
|
||||||
|
exc=SecurityError,
|
||||||
|
)
|
||||||
|
|
||||||
|
def format_string(
|
||||||
|
self,
|
||||||
|
s: str,
|
||||||
|
args: t.Tuple[t.Any, ...],
|
||||||
|
kwargs: t.Dict[str, t.Any],
|
||||||
|
format_func: t.Optional[t.Callable] = None,
|
||||||
|
) -> str:
|
||||||
|
"""If a format call is detected, then this is routed through this
|
||||||
|
method so that our safety sandbox can be used for it.
|
||||||
|
"""
|
||||||
|
formatter: SandboxedFormatter
|
||||||
|
if isinstance(s, Markup):
|
||||||
|
formatter = SandboxedEscapeFormatter(self, escape=s.escape)
|
||||||
|
else:
|
||||||
|
formatter = SandboxedFormatter(self)
|
||||||
|
|
||||||
|
if format_func is not None and format_func.__name__ == "format_map":
|
||||||
|
if len(args) != 1 or kwargs:
|
||||||
|
raise TypeError(
|
||||||
|
"format_map() takes exactly one argument"
|
||||||
|
f" {len(args) + (kwargs is not None)} given"
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs = args[0]
|
||||||
|
args = ()
|
||||||
|
|
||||||
|
rv = formatter.vformat(s, args, kwargs)
|
||||||
|
return type(s)(rv)
|
||||||
|
|
||||||
|
def call(
|
||||||
|
__self, # noqa: B902
|
||||||
|
__context: Context,
|
||||||
|
__obj: t.Any,
|
||||||
|
*args: t.Any,
|
||||||
|
**kwargs: t.Any,
|
||||||
|
) -> t.Any:
|
||||||
|
"""Call an object from sandboxed code."""
|
||||||
|
fmt = inspect_format_method(__obj)
|
||||||
|
if fmt is not None:
|
||||||
|
return __self.format_string(fmt, args, kwargs, __obj)
|
||||||
|
|
||||||
|
# the double prefixes are to avoid double keyword argument
|
||||||
|
# errors when proxying the call.
|
||||||
|
if not __self.is_safe_callable(__obj):
|
||||||
|
raise SecurityError(f"{__obj!r} is not safely callable")
|
||||||
|
return __context.call(__obj, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ImmutableSandboxedEnvironment(SandboxedEnvironment):
|
||||||
|
"""Works exactly like the regular `SandboxedEnvironment` but does not
|
||||||
|
permit modifications on the builtin mutable objects `list`, `set`, and
|
||||||
|
`dict` by using the :func:`modifies_known_mutable` function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
|
||||||
|
if not super().is_safe_attribute(obj, attr, value):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return not modifies_known_mutable(obj, attr)
|
||||||
|
|
||||||
|
|
||||||
|
class SandboxedFormatter(Formatter):
|
||||||
|
def __init__(self, env: Environment, **kwargs: t.Any) -> None:
|
||||||
|
self._env = env
|
||||||
|
super().__init__(**kwargs) # type: ignore
|
||||||
|
|
||||||
|
def get_field(
|
||||||
|
self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any]
|
||||||
|
) -> t.Tuple[t.Any, str]:
|
||||||
|
first, rest = formatter_field_name_split(field_name)
|
||||||
|
obj = self.get_value(first, args, kwargs)
|
||||||
|
for is_attr, i in rest:
|
||||||
|
if is_attr:
|
||||||
|
obj = self._env.getattr(obj, i)
|
||||||
|
else:
|
||||||
|
obj = self._env.getitem(obj, i)
|
||||||
|
return obj, first
|
||||||
|
|
||||||
|
|
||||||
|
class SandboxedEscapeFormatter(SandboxedFormatter, EscapeFormatter):
|
||||||
|
pass
|
||||||
255
dist/ba_data/python-site-packages/jinja2/tests.py
vendored
Normal file
255
dist/ba_data/python-site-packages/jinja2/tests.py
vendored
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
"""Built-in template tests used with the ``is`` operator."""
|
||||||
|
import operator
|
||||||
|
import typing as t
|
||||||
|
from collections import abc
|
||||||
|
from numbers import Number
|
||||||
|
|
||||||
|
from .runtime import Undefined
|
||||||
|
from .utils import pass_environment
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
from .environment import Environment
|
||||||
|
|
||||||
|
|
||||||
|
def test_odd(value: int) -> bool:
|
||||||
|
"""Return true if the variable is odd."""
|
||||||
|
return value % 2 == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_even(value: int) -> bool:
|
||||||
|
"""Return true if the variable is even."""
|
||||||
|
return value % 2 == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_divisibleby(value: int, num: int) -> bool:
|
||||||
|
"""Check if a variable is divisible by a number."""
|
||||||
|
return value % num == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_defined(value: t.Any) -> bool:
|
||||||
|
"""Return true if the variable is defined:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{% if variable is defined %}
|
||||||
|
value of variable: {{ variable }}
|
||||||
|
{% else %}
|
||||||
|
variable is not defined
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
See the :func:`default` filter for a simple way to set undefined
|
||||||
|
variables.
|
||||||
|
"""
|
||||||
|
return not isinstance(value, Undefined)
|
||||||
|
|
||||||
|
|
||||||
|
def test_undefined(value: t.Any) -> bool:
|
||||||
|
"""Like :func:`defined` but the other way round."""
|
||||||
|
return isinstance(value, Undefined)
|
||||||
|
|
||||||
|
|
||||||
|
@pass_environment
|
||||||
|
def test_filter(env: "Environment", value: str) -> bool:
|
||||||
|
"""Check if a filter exists by name. Useful if a filter may be
|
||||||
|
optionally available.
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{% if 'markdown' is filter %}
|
||||||
|
{{ value | markdown }}
|
||||||
|
{% else %}
|
||||||
|
{{ value }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
"""
|
||||||
|
return value in env.filters
|
||||||
|
|
||||||
|
|
||||||
|
@pass_environment
|
||||||
|
def test_test(env: "Environment", value: str) -> bool:
|
||||||
|
"""Check if a test exists by name. Useful if a test may be
|
||||||
|
optionally available.
|
||||||
|
|
||||||
|
.. code-block:: jinja
|
||||||
|
|
||||||
|
{% if 'loud' is test %}
|
||||||
|
{% if value is loud %}
|
||||||
|
{{ value|upper }}
|
||||||
|
{% else %}
|
||||||
|
{{ value|lower }}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{{ value }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
"""
|
||||||
|
return value in env.tests
|
||||||
|
|
||||||
|
|
||||||
|
def test_none(value: t.Any) -> bool:
|
||||||
|
"""Return true if the variable is none."""
|
||||||
|
return value is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_boolean(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is a boolean value.
|
||||||
|
|
||||||
|
.. versionadded:: 2.11
|
||||||
|
"""
|
||||||
|
return value is True or value is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_false(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is False.
|
||||||
|
|
||||||
|
.. versionadded:: 2.11
|
||||||
|
"""
|
||||||
|
return value is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_true(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is True.
|
||||||
|
|
||||||
|
.. versionadded:: 2.11
|
||||||
|
"""
|
||||||
|
return value is True
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: The existing 'number' test matches booleans and floats
|
||||||
|
def test_integer(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is an integer.
|
||||||
|
|
||||||
|
.. versionadded:: 2.11
|
||||||
|
"""
|
||||||
|
return isinstance(value, int) and value is not True and value is not False
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: The existing 'number' test matches booleans and integers
|
||||||
|
def test_float(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is a float.
|
||||||
|
|
||||||
|
.. versionadded:: 2.11
|
||||||
|
"""
|
||||||
|
return isinstance(value, float)
|
||||||
|
|
||||||
|
|
||||||
|
def test_lower(value: str) -> bool:
|
||||||
|
"""Return true if the variable is lowercased."""
|
||||||
|
return str(value).islower()
|
||||||
|
|
||||||
|
|
||||||
|
def test_upper(value: str) -> bool:
|
||||||
|
"""Return true if the variable is uppercased."""
|
||||||
|
return str(value).isupper()
|
||||||
|
|
||||||
|
|
||||||
|
def test_string(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is a string."""
|
||||||
|
return isinstance(value, str)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mapping(value: t.Any) -> bool:
|
||||||
|
"""Return true if the object is a mapping (dict etc.).
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
"""
|
||||||
|
return isinstance(value, abc.Mapping)
|
||||||
|
|
||||||
|
|
||||||
|
def test_number(value: t.Any) -> bool:
|
||||||
|
"""Return true if the variable is a number."""
|
||||||
|
return isinstance(value, Number)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sequence(value: t.Any) -> bool:
|
||||||
|
"""Return true if the variable is a sequence. Sequences are variables
|
||||||
|
that are iterable.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
len(value)
|
||||||
|
value.__getitem__
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def test_sameas(value: t.Any, other: t.Any) -> bool:
|
||||||
|
"""Check if an object points to the same memory address than another
|
||||||
|
object:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{% if foo.attribute is sameas false %}
|
||||||
|
the foo attribute really is the `False` singleton
|
||||||
|
{% endif %}
|
||||||
|
"""
|
||||||
|
return value is other
|
||||||
|
|
||||||
|
|
||||||
|
def test_iterable(value: t.Any) -> bool:
|
||||||
|
"""Check if it's possible to iterate over an object."""
|
||||||
|
try:
|
||||||
|
iter(value)
|
||||||
|
except TypeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def test_escaped(value: t.Any) -> bool:
|
||||||
|
"""Check if the value is escaped."""
|
||||||
|
return hasattr(value, "__html__")
|
||||||
|
|
||||||
|
|
||||||
|
def test_in(value: t.Any, seq: t.Container) -> bool:
|
||||||
|
"""Check if value is in seq.
|
||||||
|
|
||||||
|
.. versionadded:: 2.10
|
||||||
|
"""
|
||||||
|
return value in seq
|
||||||
|
|
||||||
|
|
||||||
|
TESTS = {
|
||||||
|
"odd": test_odd,
|
||||||
|
"even": test_even,
|
||||||
|
"divisibleby": test_divisibleby,
|
||||||
|
"defined": test_defined,
|
||||||
|
"undefined": test_undefined,
|
||||||
|
"filter": test_filter,
|
||||||
|
"test": test_test,
|
||||||
|
"none": test_none,
|
||||||
|
"boolean": test_boolean,
|
||||||
|
"false": test_false,
|
||||||
|
"true": test_true,
|
||||||
|
"integer": test_integer,
|
||||||
|
"float": test_float,
|
||||||
|
"lower": test_lower,
|
||||||
|
"upper": test_upper,
|
||||||
|
"string": test_string,
|
||||||
|
"mapping": test_mapping,
|
||||||
|
"number": test_number,
|
||||||
|
"sequence": test_sequence,
|
||||||
|
"iterable": test_iterable,
|
||||||
|
"callable": callable,
|
||||||
|
"sameas": test_sameas,
|
||||||
|
"escaped": test_escaped,
|
||||||
|
"in": test_in,
|
||||||
|
"==": operator.eq,
|
||||||
|
"eq": operator.eq,
|
||||||
|
"equalto": operator.eq,
|
||||||
|
"!=": operator.ne,
|
||||||
|
"ne": operator.ne,
|
||||||
|
">": operator.gt,
|
||||||
|
"gt": operator.gt,
|
||||||
|
"greaterthan": operator.gt,
|
||||||
|
"ge": operator.ge,
|
||||||
|
">=": operator.ge,
|
||||||
|
"<": operator.lt,
|
||||||
|
"lt": operator.lt,
|
||||||
|
"lessthan": operator.lt,
|
||||||
|
"<=": operator.le,
|
||||||
|
"le": operator.le,
|
||||||
|
}
|
||||||
854
dist/ba_data/python-site-packages/jinja2/utils.py
vendored
Normal file
854
dist/ba_data/python-site-packages/jinja2/utils.py
vendored
Normal file
|
|
@ -0,0 +1,854 @@
|
||||||
|
import enum
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import typing as t
|
||||||
|
import warnings
|
||||||
|
from collections import abc
|
||||||
|
from collections import deque
|
||||||
|
from random import choice
|
||||||
|
from random import randrange
|
||||||
|
from threading import Lock
|
||||||
|
from types import CodeType
|
||||||
|
from urllib.parse import quote_from_bytes
|
||||||
|
|
||||||
|
import markupsafe
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
|
|
||||||
|
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||||
|
|
||||||
|
# special singleton representing missing values for the runtime
|
||||||
|
missing: t.Any = type("MissingType", (), {"__repr__": lambda x: "missing"})()
|
||||||
|
|
||||||
|
internal_code: t.MutableSet[CodeType] = set()
|
||||||
|
|
||||||
|
concat = "".join
|
||||||
|
|
||||||
|
|
||||||
|
def pass_context(f: F) -> F:
|
||||||
|
"""Pass the :class:`~jinja2.runtime.Context` as the first argument
|
||||||
|
to the decorated function when called while rendering a template.
|
||||||
|
|
||||||
|
Can be used on functions, filters, and tests.
|
||||||
|
|
||||||
|
If only ``Context.eval_context`` is needed, use
|
||||||
|
:func:`pass_eval_context`. If only ``Context.environment`` is
|
||||||
|
needed, use :func:`pass_environment`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.0.0
|
||||||
|
Replaces ``contextfunction`` and ``contextfilter``.
|
||||||
|
"""
|
||||||
|
f.jinja_pass_arg = _PassArg.context # type: ignore
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def pass_eval_context(f: F) -> F:
|
||||||
|
"""Pass the :class:`~jinja2.nodes.EvalContext` as the first argument
|
||||||
|
to the decorated function when called while rendering a template.
|
||||||
|
See :ref:`eval-context`.
|
||||||
|
|
||||||
|
Can be used on functions, filters, and tests.
|
||||||
|
|
||||||
|
If only ``EvalContext.environment`` is needed, use
|
||||||
|
:func:`pass_environment`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.0.0
|
||||||
|
Replaces ``evalcontextfunction`` and ``evalcontextfilter``.
|
||||||
|
"""
|
||||||
|
f.jinja_pass_arg = _PassArg.eval_context # type: ignore
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def pass_environment(f: F) -> F:
|
||||||
|
"""Pass the :class:`~jinja2.Environment` as the first argument to
|
||||||
|
the decorated function when called while rendering a template.
|
||||||
|
|
||||||
|
Can be used on functions, filters, and tests.
|
||||||
|
|
||||||
|
.. versionadded:: 3.0.0
|
||||||
|
Replaces ``environmentfunction`` and ``environmentfilter``.
|
||||||
|
"""
|
||||||
|
f.jinja_pass_arg = _PassArg.environment # type: ignore
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
class _PassArg(enum.Enum):
|
||||||
|
context = enum.auto()
|
||||||
|
eval_context = enum.auto()
|
||||||
|
environment = enum.auto()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_obj(cls, obj: F) -> t.Optional["_PassArg"]:
|
||||||
|
if hasattr(obj, "jinja_pass_arg"):
|
||||||
|
return obj.jinja_pass_arg # type: ignore
|
||||||
|
|
||||||
|
for prefix in "context", "eval_context", "environment":
|
||||||
|
squashed = prefix.replace("_", "")
|
||||||
|
|
||||||
|
for name in f"{squashed}function", f"{squashed}filter":
|
||||||
|
if getattr(obj, name, False) is True:
|
||||||
|
warnings.warn(
|
||||||
|
f"{name!r} is deprecated and will stop working"
|
||||||
|
f" in Jinja 3.1. Use 'pass_{prefix}' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return cls[prefix]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def contextfunction(f: F) -> F:
|
||||||
|
"""Pass the context as the first argument to the decorated function.
|
||||||
|
|
||||||
|
.. deprecated:: 3.0
|
||||||
|
Will be removed in Jinja 3.1. Use :func:`~jinja2.pass_context`
|
||||||
|
instead.
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
"'contextfunction' is renamed to 'pass_context', the old name"
|
||||||
|
" will be removed in Jinja 3.1.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return pass_context(f)
|
||||||
|
|
||||||
|
|
||||||
|
def evalcontextfunction(f: F) -> F:
|
||||||
|
"""Pass the eval context as the first argument to the decorated
|
||||||
|
function.
|
||||||
|
|
||||||
|
.. deprecated:: 3.0
|
||||||
|
Will be removed in Jinja 3.1. Use
|
||||||
|
:func:`~jinja2.pass_eval_context` instead.
|
||||||
|
|
||||||
|
.. versionadded:: 2.4
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
"'evalcontextfunction' is renamed to 'pass_eval_context', the"
|
||||||
|
" old name will be removed in Jinja 3.1.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return pass_eval_context(f)
|
||||||
|
|
||||||
|
|
||||||
|
def environmentfunction(f: F) -> F:
|
||||||
|
"""Pass the environment as the first argument to the decorated
|
||||||
|
function.
|
||||||
|
|
||||||
|
.. deprecated:: 3.0
|
||||||
|
Will be removed in Jinja 3.1. Use
|
||||||
|
:func:`~jinja2.pass_environment` instead.
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
"'environmentfunction' is renamed to 'pass_environment', the"
|
||||||
|
" old name will be removed in Jinja 3.1.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return pass_environment(f)
|
||||||
|
|
||||||
|
|
||||||
|
def internalcode(f: F) -> F:
|
||||||
|
"""Marks the function as internally used"""
|
||||||
|
internal_code.add(f.__code__)
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def is_undefined(obj: t.Any) -> bool:
|
||||||
|
"""Check if the object passed is undefined. This does nothing more than
|
||||||
|
performing an instance check against :class:`Undefined` but looks nicer.
|
||||||
|
This can be used for custom filters or tests that want to react to
|
||||||
|
undefined variables. For example a custom default filter can look like
|
||||||
|
this::
|
||||||
|
|
||||||
|
def default(var, default=''):
|
||||||
|
if is_undefined(var):
|
||||||
|
return default
|
||||||
|
return var
|
||||||
|
"""
|
||||||
|
from .runtime import Undefined
|
||||||
|
|
||||||
|
return isinstance(obj, Undefined)
|
||||||
|
|
||||||
|
|
||||||
|
def consume(iterable: t.Iterable[t.Any]) -> None:
|
||||||
|
"""Consumes an iterable without doing anything with it."""
|
||||||
|
for _ in iterable:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def clear_caches() -> None:
|
||||||
|
"""Jinja keeps internal caches for environments and lexers. These are
|
||||||
|
used so that Jinja doesn't have to recreate environments and lexers all
|
||||||
|
the time. Normally you don't have to care about that but if you are
|
||||||
|
measuring memory consumption you may want to clean the caches.
|
||||||
|
"""
|
||||||
|
from .environment import get_spontaneous_environment
|
||||||
|
from .lexer import _lexer_cache
|
||||||
|
|
||||||
|
get_spontaneous_environment.cache_clear()
|
||||||
|
_lexer_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
|
def import_string(import_name: str, silent: bool = False) -> t.Any:
|
||||||
|
"""Imports an object based on a string. This is useful if you want to
|
||||||
|
use import paths as endpoints or something similar. An import path can
|
||||||
|
be specified either in dotted notation (``xml.sax.saxutils.escape``)
|
||||||
|
or with a colon as object delimiter (``xml.sax.saxutils:escape``).
|
||||||
|
|
||||||
|
If the `silent` is True the return value will be `None` if the import
|
||||||
|
fails.
|
||||||
|
|
||||||
|
:return: imported object
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if ":" in import_name:
|
||||||
|
module, obj = import_name.split(":", 1)
|
||||||
|
elif "." in import_name:
|
||||||
|
module, _, obj = import_name.rpartition(".")
|
||||||
|
else:
|
||||||
|
return __import__(import_name)
|
||||||
|
return getattr(__import__(module, None, None, [obj]), obj)
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
if not silent:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO]:
|
||||||
|
"""Returns a file descriptor for the filename if that file exists,
|
||||||
|
otherwise ``None``.
|
||||||
|
"""
|
||||||
|
if not os.path.isfile(filename):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return open(filename, mode)
|
||||||
|
|
||||||
|
|
||||||
|
def object_type_repr(obj: t.Any) -> str:
|
||||||
|
"""Returns the name of the object's type. For some recognized
|
||||||
|
singletons the name of the object is returned instead. (For
|
||||||
|
example for `None` and `Ellipsis`).
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
return "None"
|
||||||
|
elif obj is Ellipsis:
|
||||||
|
return "Ellipsis"
|
||||||
|
|
||||||
|
cls = type(obj)
|
||||||
|
|
||||||
|
if cls.__module__ == "builtins":
|
||||||
|
return f"{cls.__name__} object"
|
||||||
|
|
||||||
|
return f"{cls.__module__}.{cls.__name__} object"
|
||||||
|
|
||||||
|
|
||||||
|
def pformat(obj: t.Any) -> str:
|
||||||
|
"""Format an object using :func:`pprint.pformat`."""
|
||||||
|
from pprint import pformat # type: ignore
|
||||||
|
|
||||||
|
return pformat(obj)
|
||||||
|
|
||||||
|
|
||||||
|
_http_re = re.compile(
|
||||||
|
r"""
|
||||||
|
^
|
||||||
|
(
|
||||||
|
(https?://|www\.) # scheme or www
|
||||||
|
(([\w%-]+\.)+)? # subdomain
|
||||||
|
(
|
||||||
|
[a-z]{2,63} # basic tld
|
||||||
|
|
|
||||||
|
xn--[\w%]{2,59} # idna tld
|
||||||
|
)
|
||||||
|
|
|
||||||
|
([\w%-]{2,63}\.)+ # basic domain
|
||||||
|
(com|net|int|edu|gov|org|info|mil) # basic tld
|
||||||
|
|
|
||||||
|
(https?://) # scheme
|
||||||
|
(
|
||||||
|
(([\d]{1,3})(\.[\d]{1,3}){3}) # IPv4
|
||||||
|
|
|
||||||
|
(\[([\da-f]{0,4}:){2}([\da-f]{0,4}:?){1,6}]) # IPv6
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(?::[\d]{1,5})? # port
|
||||||
|
(?:[/?#]\S*)? # path, query, and fragment
|
||||||
|
$
|
||||||
|
""",
|
||||||
|
re.IGNORECASE | re.VERBOSE,
|
||||||
|
)
|
||||||
|
_email_re = re.compile(r"^\S+@\w[\w.-]*\.\w+$")
|
||||||
|
|
||||||
|
|
||||||
|
def urlize(
|
||||||
|
text: str,
|
||||||
|
trim_url_limit: t.Optional[int] = None,
|
||||||
|
rel: t.Optional[str] = None,
|
||||||
|
target: t.Optional[str] = None,
|
||||||
|
extra_schemes: t.Optional[t.Iterable[str]] = None,
|
||||||
|
) -> str:
|
||||||
|
"""Convert URLs in text into clickable links.
|
||||||
|
|
||||||
|
This may not recognize links in some situations. Usually, a more
|
||||||
|
comprehensive formatter, such as a Markdown library, is a better
|
||||||
|
choice.
|
||||||
|
|
||||||
|
Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email
|
||||||
|
addresses. Links with trailing punctuation (periods, commas, closing
|
||||||
|
parentheses) and leading punctuation (opening parentheses) are
|
||||||
|
recognized excluding the punctuation. Email addresses that include
|
||||||
|
header fields are not recognized (for example,
|
||||||
|
``mailto:address@example.com?cc=copy@example.com``).
|
||||||
|
|
||||||
|
:param text: Original text containing URLs to link.
|
||||||
|
:param trim_url_limit: Shorten displayed URL values to this length.
|
||||||
|
:param target: Add the ``target`` attribute to links.
|
||||||
|
:param rel: Add the ``rel`` attribute to links.
|
||||||
|
:param extra_schemes: Recognize URLs that start with these schemes
|
||||||
|
in addition to the default behavior.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The ``extra_schemes`` parameter was added.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
Generate ``https://`` links for URLs without a scheme.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The parsing rules were updated. Recognize email addresses with
|
||||||
|
or without the ``mailto:`` scheme. Validate IP addresses. Ignore
|
||||||
|
parentheses and brackets in more cases.
|
||||||
|
"""
|
||||||
|
if trim_url_limit is not None:
|
||||||
|
|
||||||
|
def trim_url(x: str) -> str:
|
||||||
|
if len(x) > trim_url_limit: # type: ignore
|
||||||
|
return f"{x[:trim_url_limit]}..."
|
||||||
|
|
||||||
|
return x
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def trim_url(x: str) -> str:
|
||||||
|
return x
|
||||||
|
|
||||||
|
words = re.split(r"(\s+)", str(markupsafe.escape(text)))
|
||||||
|
rel_attr = f' rel="{markupsafe.escape(rel)}"' if rel else ""
|
||||||
|
target_attr = f' target="{markupsafe.escape(target)}"' if target else ""
|
||||||
|
|
||||||
|
for i, word in enumerate(words):
|
||||||
|
head, middle, tail = "", word, ""
|
||||||
|
match = re.match(r"^([(<]|<)+", middle)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
head = match.group()
|
||||||
|
middle = middle[match.end() :]
|
||||||
|
|
||||||
|
# Unlike lead, which is anchored to the start of the string,
|
||||||
|
# need to check that the string ends with any of the characters
|
||||||
|
# before trying to match all of them, to avoid backtracking.
|
||||||
|
if middle.endswith((")", ">", ".", ",", "\n", ">")):
|
||||||
|
match = re.search(r"([)>.,\n]|>)+$", middle)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
tail = match.group()
|
||||||
|
middle = middle[: match.start()]
|
||||||
|
|
||||||
|
# Prefer balancing parentheses in URLs instead of ignoring a
|
||||||
|
# trailing character.
|
||||||
|
for start_char, end_char in ("(", ")"), ("<", ">"), ("<", ">"):
|
||||||
|
start_count = middle.count(start_char)
|
||||||
|
|
||||||
|
if start_count <= middle.count(end_char):
|
||||||
|
# Balanced, or lighter on the left
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Move as many as possible from the tail to balance
|
||||||
|
for _ in range(min(start_count, tail.count(end_char))):
|
||||||
|
end_index = tail.index(end_char) + len(end_char)
|
||||||
|
# Move anything in the tail before the end char too
|
||||||
|
middle += tail[:end_index]
|
||||||
|
tail = tail[end_index:]
|
||||||
|
|
||||||
|
if _http_re.match(middle):
|
||||||
|
if middle.startswith("https://") or middle.startswith("http://"):
|
||||||
|
middle = (
|
||||||
|
f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
middle = (
|
||||||
|
f'<a href="https://{middle}"{rel_attr}{target_attr}>'
|
||||||
|
f"{trim_url(middle)}</a>"
|
||||||
|
)
|
||||||
|
|
||||||
|
elif middle.startswith("mailto:") and _email_re.match(middle[7:]):
|
||||||
|
middle = f'<a href="{middle}">{middle[7:]}</a>'
|
||||||
|
|
||||||
|
elif (
|
||||||
|
"@" in middle
|
||||||
|
and not middle.startswith("www.")
|
||||||
|
and ":" not in middle
|
||||||
|
and _email_re.match(middle)
|
||||||
|
):
|
||||||
|
middle = f'<a href="mailto:{middle}">{middle}</a>'
|
||||||
|
|
||||||
|
elif extra_schemes is not None:
|
||||||
|
for scheme in extra_schemes:
|
||||||
|
if middle != scheme and middle.startswith(scheme):
|
||||||
|
middle = f'<a href="{middle}"{rel_attr}{target_attr}>{middle}</a>'
|
||||||
|
|
||||||
|
words[i] = f"{head}{middle}{tail}"
|
||||||
|
|
||||||
|
return "".join(words)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_lorem_ipsum(
|
||||||
|
n: int = 5, html: bool = True, min: int = 20, max: int = 100
|
||||||
|
) -> str:
|
||||||
|
"""Generate some lorem ipsum for the template."""
|
||||||
|
from .constants import LOREM_IPSUM_WORDS
|
||||||
|
|
||||||
|
words = LOREM_IPSUM_WORDS.split()
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for _ in range(n):
|
||||||
|
next_capitalized = True
|
||||||
|
last_comma = last_fullstop = 0
|
||||||
|
word = None
|
||||||
|
last = None
|
||||||
|
p = []
|
||||||
|
|
||||||
|
# each paragraph contains out of 20 to 100 words.
|
||||||
|
for idx, _ in enumerate(range(randrange(min, max))):
|
||||||
|
while True:
|
||||||
|
word = choice(words)
|
||||||
|
if word != last:
|
||||||
|
last = word
|
||||||
|
break
|
||||||
|
if next_capitalized:
|
||||||
|
word = word.capitalize()
|
||||||
|
next_capitalized = False
|
||||||
|
# add commas
|
||||||
|
if idx - randrange(3, 8) > last_comma:
|
||||||
|
last_comma = idx
|
||||||
|
last_fullstop += 2
|
||||||
|
word += ","
|
||||||
|
# add end of sentences
|
||||||
|
if idx - randrange(10, 20) > last_fullstop:
|
||||||
|
last_comma = last_fullstop = idx
|
||||||
|
word += "."
|
||||||
|
next_capitalized = True
|
||||||
|
p.append(word)
|
||||||
|
|
||||||
|
# ensure that the paragraph ends with a dot.
|
||||||
|
p_str = " ".join(p)
|
||||||
|
|
||||||
|
if p_str.endswith(","):
|
||||||
|
p_str = p_str[:-1] + "."
|
||||||
|
elif not p_str.endswith("."):
|
||||||
|
p_str += "."
|
||||||
|
|
||||||
|
result.append(p_str)
|
||||||
|
|
||||||
|
if not html:
|
||||||
|
return "\n\n".join(result)
|
||||||
|
return markupsafe.Markup(
|
||||||
|
"\n".join(f"<p>{markupsafe.escape(x)}</p>" for x in result)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def url_quote(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str:
|
||||||
|
"""Quote a string for use in a URL using the given charset.
|
||||||
|
|
||||||
|
:param obj: String or bytes to quote. Other types are converted to
|
||||||
|
string then encoded to bytes using the given charset.
|
||||||
|
:param charset: Encode text to bytes using this charset.
|
||||||
|
:param for_qs: Quote "/" and use "+" for spaces.
|
||||||
|
"""
|
||||||
|
if not isinstance(obj, bytes):
|
||||||
|
if not isinstance(obj, str):
|
||||||
|
obj = str(obj)
|
||||||
|
|
||||||
|
obj = obj.encode(charset)
|
||||||
|
|
||||||
|
safe = b"" if for_qs else b"/"
|
||||||
|
rv = quote_from_bytes(obj, safe)
|
||||||
|
|
||||||
|
if for_qs:
|
||||||
|
rv = rv.replace("%20", "+")
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def unicode_urlencode(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str:
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"'unicode_urlencode' has been renamed to 'url_quote'. The old"
|
||||||
|
" name will be removed in Jinja 3.1.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return url_quote(obj, charset=charset, for_qs=for_qs)
|
||||||
|
|
||||||
|
|
||||||
|
@abc.MutableMapping.register
|
||||||
|
class LRUCache:
|
||||||
|
"""A simple LRU Cache implementation."""
|
||||||
|
|
||||||
|
# this is fast for small capacities (something below 1000) but doesn't
|
||||||
|
# scale. But as long as it's only used as storage for templates this
|
||||||
|
# won't do any harm.
|
||||||
|
|
||||||
|
def __init__(self, capacity: int) -> None:
|
||||||
|
self.capacity = capacity
|
||||||
|
self._mapping: t.Dict[t.Any, t.Any] = {}
|
||||||
|
self._queue: "te.Deque[t.Any]" = deque()
|
||||||
|
self._postinit()
|
||||||
|
|
||||||
|
def _postinit(self) -> None:
|
||||||
|
# alias all queue methods for faster lookup
|
||||||
|
self._popleft = self._queue.popleft
|
||||||
|
self._pop = self._queue.pop
|
||||||
|
self._remove = self._queue.remove
|
||||||
|
self._wlock = Lock()
|
||||||
|
self._append = self._queue.append
|
||||||
|
|
||||||
|
def __getstate__(self) -> t.Mapping[str, t.Any]:
|
||||||
|
return {
|
||||||
|
"capacity": self.capacity,
|
||||||
|
"_mapping": self._mapping,
|
||||||
|
"_queue": self._queue,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __setstate__(self, d: t.Mapping[str, t.Any]) -> None:
|
||||||
|
self.__dict__.update(d)
|
||||||
|
self._postinit()
|
||||||
|
|
||||||
|
def __getnewargs__(self) -> t.Tuple:
|
||||||
|
return (self.capacity,)
|
||||||
|
|
||||||
|
def copy(self) -> "LRUCache":
|
||||||
|
"""Return a shallow copy of the instance."""
|
||||||
|
rv = self.__class__(self.capacity)
|
||||||
|
rv._mapping.update(self._mapping)
|
||||||
|
rv._queue.extend(self._queue)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def get(self, key: t.Any, default: t.Any = None) -> t.Any:
|
||||||
|
"""Return an item from the cache dict or `default`"""
|
||||||
|
try:
|
||||||
|
return self[key]
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def setdefault(self, key: t.Any, default: t.Any = None) -> t.Any:
|
||||||
|
"""Set `default` if the key is not in the cache otherwise
|
||||||
|
leave unchanged. Return the value of this key.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self[key]
|
||||||
|
except KeyError:
|
||||||
|
self[key] = default
|
||||||
|
return default
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""Clear the cache."""
|
||||||
|
with self._wlock:
|
||||||
|
self._mapping.clear()
|
||||||
|
self._queue.clear()
|
||||||
|
|
||||||
|
def __contains__(self, key: t.Any) -> bool:
|
||||||
|
"""Check if a key exists in this cache."""
|
||||||
|
return key in self._mapping
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
"""Return the current size of the cache."""
|
||||||
|
return len(self._mapping)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<{type(self).__name__} {self._mapping!r}>"
|
||||||
|
|
||||||
|
def __getitem__(self, key: t.Any) -> t.Any:
|
||||||
|
"""Get an item from the cache. Moves the item up so that it has the
|
||||||
|
highest priority then.
|
||||||
|
|
||||||
|
Raise a `KeyError` if it does not exist.
|
||||||
|
"""
|
||||||
|
with self._wlock:
|
||||||
|
rv = self._mapping[key]
|
||||||
|
|
||||||
|
if self._queue[-1] != key:
|
||||||
|
try:
|
||||||
|
self._remove(key)
|
||||||
|
except ValueError:
|
||||||
|
# if something removed the key from the container
|
||||||
|
# when we read, ignore the ValueError that we would
|
||||||
|
# get otherwise.
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._append(key)
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def __setitem__(self, key: t.Any, value: t.Any) -> None:
|
||||||
|
"""Sets the value for an item. Moves the item up so that it
|
||||||
|
has the highest priority then.
|
||||||
|
"""
|
||||||
|
with self._wlock:
|
||||||
|
if key in self._mapping:
|
||||||
|
self._remove(key)
|
||||||
|
elif len(self._mapping) == self.capacity:
|
||||||
|
del self._mapping[self._popleft()]
|
||||||
|
|
||||||
|
self._append(key)
|
||||||
|
self._mapping[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: t.Any) -> None:
|
||||||
|
"""Remove an item from the cache dict.
|
||||||
|
Raise a `KeyError` if it does not exist.
|
||||||
|
"""
|
||||||
|
with self._wlock:
|
||||||
|
del self._mapping[key]
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._remove(key)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]:
|
||||||
|
"""Return a list of items."""
|
||||||
|
result = [(key, self._mapping[key]) for key in list(self._queue)]
|
||||||
|
result.reverse()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def values(self) -> t.Iterable[t.Any]:
|
||||||
|
"""Return a list of all values."""
|
||||||
|
return [x[1] for x in self.items()]
|
||||||
|
|
||||||
|
def keys(self) -> t.Iterable[t.Any]:
|
||||||
|
"""Return a list of all keys ordered by most recent usage."""
|
||||||
|
return list(self)
|
||||||
|
|
||||||
|
def __iter__(self) -> t.Iterator[t.Any]:
|
||||||
|
return reversed(tuple(self._queue))
|
||||||
|
|
||||||
|
def __reversed__(self) -> t.Iterator[t.Any]:
|
||||||
|
"""Iterate over the keys in the cache dict, oldest items
|
||||||
|
coming first.
|
||||||
|
"""
|
||||||
|
return iter(tuple(self._queue))
|
||||||
|
|
||||||
|
__copy__ = copy
|
||||||
|
|
||||||
|
|
||||||
|
def select_autoescape(
|
||||||
|
enabled_extensions: t.Collection[str] = ("html", "htm", "xml"),
|
||||||
|
disabled_extensions: t.Collection[str] = (),
|
||||||
|
default_for_string: bool = True,
|
||||||
|
default: bool = False,
|
||||||
|
) -> t.Callable[[t.Optional[str]], bool]:
|
||||||
|
"""Intelligently sets the initial value of autoescaping based on the
|
||||||
|
filename of the template. This is the recommended way to configure
|
||||||
|
autoescaping if you do not want to write a custom function yourself.
|
||||||
|
|
||||||
|
If you want to enable it for all templates created from strings or
|
||||||
|
for all templates with `.html` and `.xml` extensions::
|
||||||
|
|
||||||
|
from jinja2 import Environment, select_autoescape
|
||||||
|
env = Environment(autoescape=select_autoescape(
|
||||||
|
enabled_extensions=('html', 'xml'),
|
||||||
|
default_for_string=True,
|
||||||
|
))
|
||||||
|
|
||||||
|
Example configuration to turn it on at all times except if the template
|
||||||
|
ends with `.txt`::
|
||||||
|
|
||||||
|
from jinja2 import Environment, select_autoescape
|
||||||
|
env = Environment(autoescape=select_autoescape(
|
||||||
|
disabled_extensions=('txt',),
|
||||||
|
default_for_string=True,
|
||||||
|
default=True,
|
||||||
|
))
|
||||||
|
|
||||||
|
The `enabled_extensions` is an iterable of all the extensions that
|
||||||
|
autoescaping should be enabled for. Likewise `disabled_extensions` is
|
||||||
|
a list of all templates it should be disabled for. If a template is
|
||||||
|
loaded from a string then the default from `default_for_string` is used.
|
||||||
|
If nothing matches then the initial value of autoescaping is set to the
|
||||||
|
value of `default`.
|
||||||
|
|
||||||
|
For security reasons this function operates case insensitive.
|
||||||
|
|
||||||
|
.. versionadded:: 2.9
|
||||||
|
"""
|
||||||
|
enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions)
|
||||||
|
disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions)
|
||||||
|
|
||||||
|
def autoescape(template_name: t.Optional[str]) -> bool:
|
||||||
|
if template_name is None:
|
||||||
|
return default_for_string
|
||||||
|
template_name = template_name.lower()
|
||||||
|
if template_name.endswith(enabled_patterns):
|
||||||
|
return True
|
||||||
|
if template_name.endswith(disabled_patterns):
|
||||||
|
return False
|
||||||
|
return default
|
||||||
|
|
||||||
|
return autoescape
|
||||||
|
|
||||||
|
|
||||||
|
def htmlsafe_json_dumps(
|
||||||
|
obj: t.Any, dumps: t.Optional[t.Callable[..., str]] = None, **kwargs: t.Any
|
||||||
|
) -> markupsafe.Markup:
|
||||||
|
"""Serialize an object to a string of JSON with :func:`json.dumps`,
|
||||||
|
then replace HTML-unsafe characters with Unicode escapes and mark
|
||||||
|
the result safe with :class:`~markupsafe.Markup`.
|
||||||
|
|
||||||
|
This is available in templates as the ``|tojson`` filter.
|
||||||
|
|
||||||
|
The following characters are escaped: ``<``, ``>``, ``&``, ``'``.
|
||||||
|
|
||||||
|
The returned string is safe to render in HTML documents and
|
||||||
|
``<script>`` tags. The exception is in HTML attributes that are
|
||||||
|
double quoted; either use single quotes or the ``|forceescape``
|
||||||
|
filter.
|
||||||
|
|
||||||
|
:param obj: The object to serialize to JSON.
|
||||||
|
:param dumps: The ``dumps`` function to use. Defaults to
|
||||||
|
``env.policies["json.dumps_function"]``, which defaults to
|
||||||
|
:func:`json.dumps`.
|
||||||
|
:param kwargs: Extra arguments to pass to ``dumps``. Merged onto
|
||||||
|
``env.policies["json.dumps_kwargs"]``.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The ``dumper`` parameter is renamed to ``dumps``.
|
||||||
|
|
||||||
|
.. versionadded:: 2.9
|
||||||
|
"""
|
||||||
|
if dumps is None:
|
||||||
|
dumps = json.dumps
|
||||||
|
|
||||||
|
return markupsafe.Markup(
|
||||||
|
dumps(obj, **kwargs)
|
||||||
|
.replace("<", "\\u003c")
|
||||||
|
.replace(">", "\\u003e")
|
||||||
|
.replace("&", "\\u0026")
|
||||||
|
.replace("'", "\\u0027")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Cycler:
|
||||||
|
"""Cycle through values by yield them one at a time, then restarting
|
||||||
|
once the end is reached. Available as ``cycler`` in templates.
|
||||||
|
|
||||||
|
Similar to ``loop.cycle``, but can be used outside loops or across
|
||||||
|
multiple loops. For example, render a list of folders and files in a
|
||||||
|
list, alternating giving them "odd" and "even" classes.
|
||||||
|
|
||||||
|
.. code-block:: html+jinja
|
||||||
|
|
||||||
|
{% set row_class = cycler("odd", "even") %}
|
||||||
|
<ul class="browser">
|
||||||
|
{% for folder in folders %}
|
||||||
|
<li class="folder {{ row_class.next() }}">{{ folder }}
|
||||||
|
{% endfor %}
|
||||||
|
{% for file in files %}
|
||||||
|
<li class="file {{ row_class.next() }}">{{ file }}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
:param items: Each positional argument will be yielded in the order
|
||||||
|
given for each cycle.
|
||||||
|
|
||||||
|
.. versionadded:: 2.1
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *items: t.Any) -> None:
|
||||||
|
if not items:
|
||||||
|
raise RuntimeError("at least one item has to be provided")
|
||||||
|
self.items = items
|
||||||
|
self.pos = 0
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
"""Resets the current item to the first item."""
|
||||||
|
self.pos = 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current(self) -> t.Any:
|
||||||
|
"""Return the current item. Equivalent to the item that will be
|
||||||
|
returned next time :meth:`next` is called.
|
||||||
|
"""
|
||||||
|
return self.items[self.pos]
|
||||||
|
|
||||||
|
def next(self) -> t.Any:
|
||||||
|
"""Return the current item, then advance :attr:`current` to the
|
||||||
|
next item.
|
||||||
|
"""
|
||||||
|
rv = self.current
|
||||||
|
self.pos = (self.pos + 1) % len(self.items)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
__next__ = next
|
||||||
|
|
||||||
|
|
||||||
|
class Joiner:
|
||||||
|
"""A joining helper for templates."""
|
||||||
|
|
||||||
|
def __init__(self, sep: str = ", ") -> None:
|
||||||
|
self.sep = sep
|
||||||
|
self.used = False
|
||||||
|
|
||||||
|
def __call__(self) -> str:
|
||||||
|
if not self.used:
|
||||||
|
self.used = True
|
||||||
|
return ""
|
||||||
|
return self.sep
|
||||||
|
|
||||||
|
|
||||||
|
class Namespace:
|
||||||
|
"""A namespace object that can hold arbitrary attributes. It may be
|
||||||
|
initialized from a dictionary or with keyword arguments."""
|
||||||
|
|
||||||
|
def __init__(*args: t.Any, **kwargs: t.Any) -> None: # noqa: B902
|
||||||
|
self, args = args[0], args[1:]
|
||||||
|
self.__attrs = dict(*args, **kwargs)
|
||||||
|
|
||||||
|
def __getattribute__(self, name: str) -> t.Any:
|
||||||
|
# __class__ is needed for the awaitable check in async mode
|
||||||
|
if name in {"_Namespace__attrs", "__class__"}:
|
||||||
|
return object.__getattribute__(self, name)
|
||||||
|
try:
|
||||||
|
return self.__attrs[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(name) from None
|
||||||
|
|
||||||
|
def __setitem__(self, name: str, value: t.Any) -> None:
|
||||||
|
self.__attrs[name] = value
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<Namespace {self.__attrs!r}>"
|
||||||
|
|
||||||
|
|
||||||
|
class Markup(markupsafe.Markup):
|
||||||
|
def __new__(cls, base="", encoding=None, errors="strict"): # type: ignore
|
||||||
|
warnings.warn(
|
||||||
|
"'jinja2.Markup' is deprecated and will be removed in Jinja"
|
||||||
|
" 3.1. Import 'markupsafe.Markup' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return super().__new__(cls, base, encoding, errors)
|
||||||
|
|
||||||
|
|
||||||
|
def escape(s: t.Any) -> str:
|
||||||
|
warnings.warn(
|
||||||
|
"'jinja2.escape' is deprecated and will be removed in Jinja"
|
||||||
|
" 3.1. Import 'markupsafe.escape' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return markupsafe.escape(s)
|
||||||
92
dist/ba_data/python-site-packages/jinja2/visitor.py
vendored
Normal file
92
dist/ba_data/python-site-packages/jinja2/visitor.py
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
"""API for traversing the AST nodes. Implemented by the compiler and
|
||||||
|
meta introspection.
|
||||||
|
"""
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
from .nodes import Node
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
|
|
||||||
|
class VisitCallable(te.Protocol):
|
||||||
|
def __call__(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class NodeVisitor:
|
||||||
|
"""Walks the abstract syntax tree and call visitor functions for every
|
||||||
|
node found. The visitor functions may return values which will be
|
||||||
|
forwarded by the `visit` method.
|
||||||
|
|
||||||
|
Per default the visitor functions for the nodes are ``'visit_'`` +
|
||||||
|
class name of the node. So a `TryFinally` node visit function would
|
||||||
|
be `visit_TryFinally`. This behavior can be changed by overriding
|
||||||
|
the `get_visitor` function. If no visitor function exists for a node
|
||||||
|
(return value `None`) the `generic_visit` visitor is used instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_visitor(self, node: Node) -> "t.Optional[VisitCallable]":
|
||||||
|
"""Return the visitor function for this node or `None` if no visitor
|
||||||
|
exists for this node. In that case the generic visit function is
|
||||||
|
used instead.
|
||||||
|
"""
|
||||||
|
return getattr(self, f"visit_{type(node).__name__}", None) # type: ignore
|
||||||
|
|
||||||
|
def visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||||
|
"""Visit a node."""
|
||||||
|
f = self.get_visitor(node)
|
||||||
|
|
||||||
|
if f is not None:
|
||||||
|
return f(node, *args, **kwargs)
|
||||||
|
|
||||||
|
return self.generic_visit(node, *args, **kwargs)
|
||||||
|
|
||||||
|
def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||||
|
"""Called if no explicit visitor function exists for a node."""
|
||||||
|
for node in node.iter_child_nodes():
|
||||||
|
self.visit(node, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeTransformer(NodeVisitor):
|
||||||
|
"""Walks the abstract syntax tree and allows modifications of nodes.
|
||||||
|
|
||||||
|
The `NodeTransformer` will walk the AST and use the return value of the
|
||||||
|
visitor functions to replace or remove the old node. If the return
|
||||||
|
value of the visitor function is `None` the node will be removed
|
||||||
|
from the previous location otherwise it's replaced with the return
|
||||||
|
value. The return value may be the original node in which case no
|
||||||
|
replacement takes place.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> Node:
|
||||||
|
for field, old_value in node.iter_fields():
|
||||||
|
if isinstance(old_value, list):
|
||||||
|
new_values = []
|
||||||
|
for value in old_value:
|
||||||
|
if isinstance(value, Node):
|
||||||
|
value = self.visit(value, *args, **kwargs)
|
||||||
|
if value is None:
|
||||||
|
continue
|
||||||
|
elif not isinstance(value, Node):
|
||||||
|
new_values.extend(value)
|
||||||
|
continue
|
||||||
|
new_values.append(value)
|
||||||
|
old_value[:] = new_values
|
||||||
|
elif isinstance(old_value, Node):
|
||||||
|
new_node = self.visit(old_value, *args, **kwargs)
|
||||||
|
if new_node is None:
|
||||||
|
delattr(node, field)
|
||||||
|
else:
|
||||||
|
setattr(node, field, new_node)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def visit_list(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.List[Node]:
|
||||||
|
"""As transformers may return lists in some places this method
|
||||||
|
can be used to enforce a list as return value.
|
||||||
|
"""
|
||||||
|
rv = self.visit(node, *args, **kwargs)
|
||||||
|
|
||||||
|
if not isinstance(rv, list):
|
||||||
|
return [rv]
|
||||||
|
|
||||||
|
return rv
|
||||||
291
dist/ba_data/python-site-packages/markupsafe/__init__.py
vendored
Normal file
291
dist/ba_data/python-site-packages/markupsafe/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
import functools
|
||||||
|
import re
|
||||||
|
import string
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
if t.TYPE_CHECKING:
|
||||||
|
import typing_extensions as te
|
||||||
|
|
||||||
|
class HasHTML(te.Protocol):
|
||||||
|
def __html__(self) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = "2.1.0"
|
||||||
|
|
||||||
|
_striptags_re = re.compile(r"(<!--.*?-->|<[^>]*>)")
|
||||||
|
|
||||||
|
|
||||||
|
def _simple_escaping_wrapper(name: str) -> t.Callable[..., "Markup"]:
|
||||||
|
orig = getattr(str, name)
|
||||||
|
|
||||||
|
@functools.wraps(orig)
|
||||||
|
def wrapped(self: "Markup", *args: t.Any, **kwargs: t.Any) -> "Markup":
|
||||||
|
args = _escape_argspec(list(args), enumerate(args), self.escape) # type: ignore
|
||||||
|
_escape_argspec(kwargs, kwargs.items(), self.escape)
|
||||||
|
return self.__class__(orig(self, *args, **kwargs))
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
class Markup(str):
|
||||||
|
"""A string that is ready to be safely inserted into an HTML or XML
|
||||||
|
document, either because it was escaped or because it was marked
|
||||||
|
safe.
|
||||||
|
|
||||||
|
Passing an object to the constructor converts it to text and wraps
|
||||||
|
it to mark it safe without escaping. To escape the text, use the
|
||||||
|
:meth:`escape` class method instead.
|
||||||
|
|
||||||
|
>>> Markup("Hello, <em>World</em>!")
|
||||||
|
Markup('Hello, <em>World</em>!')
|
||||||
|
>>> Markup(42)
|
||||||
|
Markup('42')
|
||||||
|
>>> Markup.escape("Hello, <em>World</em>!")
|
||||||
|
Markup('Hello <em>World</em>!')
|
||||||
|
|
||||||
|
This implements the ``__html__()`` interface that some frameworks
|
||||||
|
use. Passing an object that implements ``__html__()`` will wrap the
|
||||||
|
output of that method, marking it safe.
|
||||||
|
|
||||||
|
>>> class Foo:
|
||||||
|
... def __html__(self):
|
||||||
|
... return '<a href="/foo">foo</a>'
|
||||||
|
...
|
||||||
|
>>> Markup(Foo())
|
||||||
|
Markup('<a href="/foo">foo</a>')
|
||||||
|
|
||||||
|
This is a subclass of :class:`str`. It has the same methods, but
|
||||||
|
escapes their arguments and returns a ``Markup`` instance.
|
||||||
|
|
||||||
|
>>> Markup("<em>%s</em>") % ("foo & bar",)
|
||||||
|
Markup('<em>foo & bar</em>')
|
||||||
|
>>> Markup("<em>Hello</em> ") + "<foo>"
|
||||||
|
Markup('<em>Hello</em> <foo>')
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __new__(
|
||||||
|
cls, base: t.Any = "", encoding: t.Optional[str] = None, errors: str = "strict"
|
||||||
|
) -> "Markup":
|
||||||
|
if hasattr(base, "__html__"):
|
||||||
|
base = base.__html__()
|
||||||
|
|
||||||
|
if encoding is None:
|
||||||
|
return super().__new__(cls, base)
|
||||||
|
|
||||||
|
return super().__new__(cls, base, encoding, errors)
|
||||||
|
|
||||||
|
def __html__(self) -> "Markup":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __add__(self, other: t.Union[str, "HasHTML"]) -> "Markup":
|
||||||
|
if isinstance(other, str) or hasattr(other, "__html__"):
|
||||||
|
return self.__class__(super().__add__(self.escape(other)))
|
||||||
|
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __radd__(self, other: t.Union[str, "HasHTML"]) -> "Markup":
|
||||||
|
if isinstance(other, str) or hasattr(other, "__html__"):
|
||||||
|
return self.escape(other).__add__(self)
|
||||||
|
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __mul__(self, num: "te.SupportsIndex") -> "Markup":
|
||||||
|
if isinstance(num, int):
|
||||||
|
return self.__class__(super().__mul__(num))
|
||||||
|
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
__rmul__ = __mul__
|
||||||
|
|
||||||
|
def __mod__(self, arg: t.Any) -> "Markup":
|
||||||
|
if isinstance(arg, tuple):
|
||||||
|
# a tuple of arguments, each wrapped
|
||||||
|
arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg)
|
||||||
|
elif hasattr(type(arg), "__getitem__") and not isinstance(arg, str):
|
||||||
|
# a mapping of arguments, wrapped
|
||||||
|
arg = _MarkupEscapeHelper(arg, self.escape)
|
||||||
|
else:
|
||||||
|
# a single argument, wrapped with the helper and a tuple
|
||||||
|
arg = (_MarkupEscapeHelper(arg, self.escape),)
|
||||||
|
|
||||||
|
return self.__class__(super().__mod__(arg))
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"{self.__class__.__name__}({super().__repr__()})"
|
||||||
|
|
||||||
|
def join(self, seq: t.Iterable[t.Union[str, "HasHTML"]]) -> "Markup":
|
||||||
|
return self.__class__(super().join(map(self.escape, seq)))
|
||||||
|
|
||||||
|
join.__doc__ = str.join.__doc__
|
||||||
|
|
||||||
|
def split( # type: ignore
|
||||||
|
self, sep: t.Optional[str] = None, maxsplit: int = -1
|
||||||
|
) -> t.List["Markup"]:
|
||||||
|
return [self.__class__(v) for v in super().split(sep, maxsplit)]
|
||||||
|
|
||||||
|
split.__doc__ = str.split.__doc__
|
||||||
|
|
||||||
|
def rsplit( # type: ignore
|
||||||
|
self, sep: t.Optional[str] = None, maxsplit: int = -1
|
||||||
|
) -> t.List["Markup"]:
|
||||||
|
return [self.__class__(v) for v in super().rsplit(sep, maxsplit)]
|
||||||
|
|
||||||
|
rsplit.__doc__ = str.rsplit.__doc__
|
||||||
|
|
||||||
|
def splitlines(self, keepends: bool = False) -> t.List["Markup"]: # type: ignore
|
||||||
|
return [self.__class__(v) for v in super().splitlines(keepends)]
|
||||||
|
|
||||||
|
splitlines.__doc__ = str.splitlines.__doc__
|
||||||
|
|
||||||
|
def unescape(self) -> str:
|
||||||
|
"""Convert escaped markup back into a text string. This replaces
|
||||||
|
HTML entities with the characters they represent.
|
||||||
|
|
||||||
|
>>> Markup("Main » <em>About</em>").unescape()
|
||||||
|
'Main » <em>About</em>'
|
||||||
|
"""
|
||||||
|
from html import unescape
|
||||||
|
|
||||||
|
return unescape(str(self))
|
||||||
|
|
||||||
|
def striptags(self) -> str:
|
||||||
|
""":meth:`unescape` the markup, remove tags, and normalize
|
||||||
|
whitespace to single spaces.
|
||||||
|
|
||||||
|
>>> Markup("Main »\t<em>About</em>").striptags()
|
||||||
|
'Main » About'
|
||||||
|
"""
|
||||||
|
stripped = " ".join(_striptags_re.sub("", self).split())
|
||||||
|
return Markup(stripped).unescape()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def escape(cls, s: t.Any) -> "Markup":
|
||||||
|
"""Escape a string. Calls :func:`escape` and ensures that for
|
||||||
|
subclasses the correct type is returned.
|
||||||
|
"""
|
||||||
|
rv = escape(s)
|
||||||
|
|
||||||
|
if rv.__class__ is not cls:
|
||||||
|
return cls(rv)
|
||||||
|
|
||||||
|
return rv
|
||||||
|
|
||||||
|
for method in (
|
||||||
|
"__getitem__",
|
||||||
|
"capitalize",
|
||||||
|
"title",
|
||||||
|
"lower",
|
||||||
|
"upper",
|
||||||
|
"replace",
|
||||||
|
"ljust",
|
||||||
|
"rjust",
|
||||||
|
"lstrip",
|
||||||
|
"rstrip",
|
||||||
|
"center",
|
||||||
|
"strip",
|
||||||
|
"translate",
|
||||||
|
"expandtabs",
|
||||||
|
"swapcase",
|
||||||
|
"zfill",
|
||||||
|
):
|
||||||
|
locals()[method] = _simple_escaping_wrapper(method)
|
||||||
|
|
||||||
|
del method
|
||||||
|
|
||||||
|
def partition(self, sep: str) -> t.Tuple["Markup", "Markup", "Markup"]:
|
||||||
|
l, s, r = super().partition(self.escape(sep))
|
||||||
|
cls = self.__class__
|
||||||
|
return cls(l), cls(s), cls(r)
|
||||||
|
|
||||||
|
def rpartition(self, sep: str) -> t.Tuple["Markup", "Markup", "Markup"]:
|
||||||
|
l, s, r = super().rpartition(self.escape(sep))
|
||||||
|
cls = self.__class__
|
||||||
|
return cls(l), cls(s), cls(r)
|
||||||
|
|
||||||
|
def format(self, *args: t.Any, **kwargs: t.Any) -> "Markup":
|
||||||
|
formatter = EscapeFormatter(self.escape)
|
||||||
|
return self.__class__(formatter.vformat(self, args, kwargs))
|
||||||
|
|
||||||
|
def __html_format__(self, format_spec: str) -> "Markup":
|
||||||
|
if format_spec:
|
||||||
|
raise ValueError("Unsupported format specification for Markup.")
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class EscapeFormatter(string.Formatter):
|
||||||
|
__slots__ = ("escape",)
|
||||||
|
|
||||||
|
def __init__(self, escape: t.Callable[[t.Any], Markup]) -> None:
|
||||||
|
self.escape = escape
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def format_field(self, value: t.Any, format_spec: str) -> str:
|
||||||
|
if hasattr(value, "__html_format__"):
|
||||||
|
rv = value.__html_format__(format_spec)
|
||||||
|
elif hasattr(value, "__html__"):
|
||||||
|
if format_spec:
|
||||||
|
raise ValueError(
|
||||||
|
f"Format specifier {format_spec} given, but {type(value)} does not"
|
||||||
|
" define __html_format__. A class that defines __html__ must define"
|
||||||
|
" __html_format__ to work with format specifiers."
|
||||||
|
)
|
||||||
|
rv = value.__html__()
|
||||||
|
else:
|
||||||
|
# We need to make sure the format spec is str here as
|
||||||
|
# otherwise the wrong callback methods are invoked.
|
||||||
|
rv = string.Formatter.format_field(self, value, str(format_spec))
|
||||||
|
return str(self.escape(rv))
|
||||||
|
|
||||||
|
|
||||||
|
_ListOrDict = t.TypeVar("_ListOrDict", list, dict)
|
||||||
|
|
||||||
|
|
||||||
|
def _escape_argspec(
|
||||||
|
obj: _ListOrDict, iterable: t.Iterable[t.Any], escape: t.Callable[[t.Any], Markup]
|
||||||
|
) -> _ListOrDict:
|
||||||
|
"""Helper for various string-wrapped functions."""
|
||||||
|
for key, value in iterable:
|
||||||
|
if isinstance(value, str) or hasattr(value, "__html__"):
|
||||||
|
obj[key] = escape(value)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class _MarkupEscapeHelper:
|
||||||
|
"""Helper for :meth:`Markup.__mod__`."""
|
||||||
|
|
||||||
|
__slots__ = ("obj", "escape")
|
||||||
|
|
||||||
|
def __init__(self, obj: t.Any, escape: t.Callable[[t.Any], Markup]) -> None:
|
||||||
|
self.obj = obj
|
||||||
|
self.escape = escape
|
||||||
|
|
||||||
|
def __getitem__(self, item: t.Any) -> "_MarkupEscapeHelper":
|
||||||
|
return _MarkupEscapeHelper(self.obj[item], self.escape)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.escape(self.obj))
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return str(self.escape(repr(self.obj)))
|
||||||
|
|
||||||
|
def __int__(self) -> int:
|
||||||
|
return int(self.obj)
|
||||||
|
|
||||||
|
def __float__(self) -> float:
|
||||||
|
return float(self.obj)
|
||||||
|
|
||||||
|
|
||||||
|
# circular import
|
||||||
|
try:
|
||||||
|
from ._speedups import escape as escape
|
||||||
|
from ._speedups import escape_silent as escape_silent
|
||||||
|
from ._speedups import soft_str as soft_str
|
||||||
|
except ImportError:
|
||||||
|
from ._native import escape as escape
|
||||||
|
from ._native import escape_silent as escape_silent # noqa: F401
|
||||||
|
from ._native import soft_str as soft_str # noqa: F401
|
||||||
63
dist/ba_data/python-site-packages/markupsafe/_native.py
vendored
Normal file
63
dist/ba_data/python-site-packages/markupsafe/_native.py
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
from . import Markup
|
||||||
|
|
||||||
|
|
||||||
|
def escape(s: t.Any) -> Markup:
|
||||||
|
"""Replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in
|
||||||
|
the string with HTML-safe sequences. Use this if you need to display
|
||||||
|
text that might contain such characters in HTML.
|
||||||
|
|
||||||
|
If the object has an ``__html__`` method, it is called and the
|
||||||
|
return value is assumed to already be safe for HTML.
|
||||||
|
|
||||||
|
:param s: An object to be converted to a string and escaped.
|
||||||
|
:return: A :class:`Markup` string with the escaped text.
|
||||||
|
"""
|
||||||
|
if hasattr(s, "__html__"):
|
||||||
|
return Markup(s.__html__())
|
||||||
|
|
||||||
|
return Markup(
|
||||||
|
str(s)
|
||||||
|
.replace("&", "&")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace("'", "'")
|
||||||
|
.replace('"', """)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_silent(s: t.Optional[t.Any]) -> Markup:
|
||||||
|
"""Like :func:`escape` but treats ``None`` as the empty string.
|
||||||
|
Useful with optional values, as otherwise you get the string
|
||||||
|
``'None'`` when the value is ``None``.
|
||||||
|
|
||||||
|
>>> escape(None)
|
||||||
|
Markup('None')
|
||||||
|
>>> escape_silent(None)
|
||||||
|
Markup('')
|
||||||
|
"""
|
||||||
|
if s is None:
|
||||||
|
return Markup()
|
||||||
|
|
||||||
|
return escape(s)
|
||||||
|
|
||||||
|
|
||||||
|
def soft_str(s: t.Any) -> str:
|
||||||
|
"""Convert an object to a string if it isn't already. This preserves
|
||||||
|
a :class:`Markup` string rather than converting it back to a basic
|
||||||
|
string, so it will still be marked as safe and won't be escaped
|
||||||
|
again.
|
||||||
|
|
||||||
|
>>> value = escape("<User 1>")
|
||||||
|
>>> value
|
||||||
|
Markup('<User 1>')
|
||||||
|
>>> escape(str(value))
|
||||||
|
Markup('&lt;User 1&gt;')
|
||||||
|
>>> escape(soft_str(value))
|
||||||
|
Markup('<User 1>')
|
||||||
|
"""
|
||||||
|
if not isinstance(s, str):
|
||||||
|
return str(s)
|
||||||
|
|
||||||
|
return s
|
||||||
320
dist/ba_data/python-site-packages/markupsafe/_speedups.c
vendored
Normal file
320
dist/ba_data/python-site-packages/markupsafe/_speedups.c
vendored
Normal file
|
|
@ -0,0 +1,320 @@
|
||||||
|
#include <Python.h>
|
||||||
|
|
||||||
|
static PyObject* markup;
|
||||||
|
|
||||||
|
static int
|
||||||
|
init_constants(void)
|
||||||
|
{
|
||||||
|
PyObject *module;
|
||||||
|
|
||||||
|
/* import markup type so that we can mark the return value */
|
||||||
|
module = PyImport_ImportModule("markupsafe");
|
||||||
|
if (!module)
|
||||||
|
return 0;
|
||||||
|
markup = PyObject_GetAttrString(module, "Markup");
|
||||||
|
Py_DECREF(module);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define GET_DELTA(inp, inp_end, delta) \
|
||||||
|
while (inp < inp_end) { \
|
||||||
|
switch (*inp++) { \
|
||||||
|
case '"': \
|
||||||
|
case '\'': \
|
||||||
|
case '&': \
|
||||||
|
delta += 4; \
|
||||||
|
break; \
|
||||||
|
case '<': \
|
||||||
|
case '>': \
|
||||||
|
delta += 3; \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DO_ESCAPE(inp, inp_end, outp) \
|
||||||
|
{ \
|
||||||
|
Py_ssize_t ncopy = 0; \
|
||||||
|
while (inp < inp_end) { \
|
||||||
|
switch (*inp) { \
|
||||||
|
case '"': \
|
||||||
|
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||||
|
outp += ncopy; ncopy = 0; \
|
||||||
|
*outp++ = '&'; \
|
||||||
|
*outp++ = '#'; \
|
||||||
|
*outp++ = '3'; \
|
||||||
|
*outp++ = '4'; \
|
||||||
|
*outp++ = ';'; \
|
||||||
|
break; \
|
||||||
|
case '\'': \
|
||||||
|
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||||
|
outp += ncopy; ncopy = 0; \
|
||||||
|
*outp++ = '&'; \
|
||||||
|
*outp++ = '#'; \
|
||||||
|
*outp++ = '3'; \
|
||||||
|
*outp++ = '9'; \
|
||||||
|
*outp++ = ';'; \
|
||||||
|
break; \
|
||||||
|
case '&': \
|
||||||
|
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||||
|
outp += ncopy; ncopy = 0; \
|
||||||
|
*outp++ = '&'; \
|
||||||
|
*outp++ = 'a'; \
|
||||||
|
*outp++ = 'm'; \
|
||||||
|
*outp++ = 'p'; \
|
||||||
|
*outp++ = ';'; \
|
||||||
|
break; \
|
||||||
|
case '<': \
|
||||||
|
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||||
|
outp += ncopy; ncopy = 0; \
|
||||||
|
*outp++ = '&'; \
|
||||||
|
*outp++ = 'l'; \
|
||||||
|
*outp++ = 't'; \
|
||||||
|
*outp++ = ';'; \
|
||||||
|
break; \
|
||||||
|
case '>': \
|
||||||
|
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||||
|
outp += ncopy; ncopy = 0; \
|
||||||
|
*outp++ = '&'; \
|
||||||
|
*outp++ = 'g'; \
|
||||||
|
*outp++ = 't'; \
|
||||||
|
*outp++ = ';'; \
|
||||||
|
break; \
|
||||||
|
default: \
|
||||||
|
ncopy++; \
|
||||||
|
} \
|
||||||
|
inp++; \
|
||||||
|
} \
|
||||||
|
memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
escape_unicode_kind1(PyUnicodeObject *in)
|
||||||
|
{
|
||||||
|
Py_UCS1 *inp = PyUnicode_1BYTE_DATA(in);
|
||||||
|
Py_UCS1 *inp_end = inp + PyUnicode_GET_LENGTH(in);
|
||||||
|
Py_UCS1 *outp;
|
||||||
|
PyObject *out;
|
||||||
|
Py_ssize_t delta = 0;
|
||||||
|
|
||||||
|
GET_DELTA(inp, inp_end, delta);
|
||||||
|
if (!delta) {
|
||||||
|
Py_INCREF(in);
|
||||||
|
return (PyObject*)in;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta,
|
||||||
|
PyUnicode_IS_ASCII(in) ? 127 : 255);
|
||||||
|
if (!out)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
inp = PyUnicode_1BYTE_DATA(in);
|
||||||
|
outp = PyUnicode_1BYTE_DATA(out);
|
||||||
|
DO_ESCAPE(inp, inp_end, outp);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
escape_unicode_kind2(PyUnicodeObject *in)
|
||||||
|
{
|
||||||
|
Py_UCS2 *inp = PyUnicode_2BYTE_DATA(in);
|
||||||
|
Py_UCS2 *inp_end = inp + PyUnicode_GET_LENGTH(in);
|
||||||
|
Py_UCS2 *outp;
|
||||||
|
PyObject *out;
|
||||||
|
Py_ssize_t delta = 0;
|
||||||
|
|
||||||
|
GET_DELTA(inp, inp_end, delta);
|
||||||
|
if (!delta) {
|
||||||
|
Py_INCREF(in);
|
||||||
|
return (PyObject*)in;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta, 65535);
|
||||||
|
if (!out)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
inp = PyUnicode_2BYTE_DATA(in);
|
||||||
|
outp = PyUnicode_2BYTE_DATA(out);
|
||||||
|
DO_ESCAPE(inp, inp_end, outp);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
escape_unicode_kind4(PyUnicodeObject *in)
|
||||||
|
{
|
||||||
|
Py_UCS4 *inp = PyUnicode_4BYTE_DATA(in);
|
||||||
|
Py_UCS4 *inp_end = inp + PyUnicode_GET_LENGTH(in);
|
||||||
|
Py_UCS4 *outp;
|
||||||
|
PyObject *out;
|
||||||
|
Py_ssize_t delta = 0;
|
||||||
|
|
||||||
|
GET_DELTA(inp, inp_end, delta);
|
||||||
|
if (!delta) {
|
||||||
|
Py_INCREF(in);
|
||||||
|
return (PyObject*)in;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = PyUnicode_New(PyUnicode_GET_LENGTH(in) + delta, 1114111);
|
||||||
|
if (!out)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
inp = PyUnicode_4BYTE_DATA(in);
|
||||||
|
outp = PyUnicode_4BYTE_DATA(out);
|
||||||
|
DO_ESCAPE(inp, inp_end, outp);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
escape_unicode(PyUnicodeObject *in)
|
||||||
|
{
|
||||||
|
if (PyUnicode_READY(in))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
switch (PyUnicode_KIND(in)) {
|
||||||
|
case PyUnicode_1BYTE_KIND:
|
||||||
|
return escape_unicode_kind1(in);
|
||||||
|
case PyUnicode_2BYTE_KIND:
|
||||||
|
return escape_unicode_kind2(in);
|
||||||
|
case PyUnicode_4BYTE_KIND:
|
||||||
|
return escape_unicode_kind4(in);
|
||||||
|
}
|
||||||
|
assert(0); /* shouldn't happen */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
escape(PyObject *self, PyObject *text)
|
||||||
|
{
|
||||||
|
static PyObject *id_html;
|
||||||
|
PyObject *s = NULL, *rv = NULL, *html;
|
||||||
|
|
||||||
|
if (id_html == NULL) {
|
||||||
|
id_html = PyUnicode_InternFromString("__html__");
|
||||||
|
if (id_html == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we don't have to escape integers, bools or floats */
|
||||||
|
if (PyLong_CheckExact(text) ||
|
||||||
|
PyFloat_CheckExact(text) || PyBool_Check(text) ||
|
||||||
|
text == Py_None)
|
||||||
|
return PyObject_CallFunctionObjArgs(markup, text, NULL);
|
||||||
|
|
||||||
|
/* if the object has an __html__ method that performs the escaping */
|
||||||
|
html = PyObject_GetAttr(text ,id_html);
|
||||||
|
if (html) {
|
||||||
|
s = PyObject_CallObject(html, NULL);
|
||||||
|
Py_DECREF(html);
|
||||||
|
if (s == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
/* Convert to Markup object */
|
||||||
|
rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
|
||||||
|
Py_DECREF(s);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* otherwise make the object unicode if it isn't, then escape */
|
||||||
|
PyErr_Clear();
|
||||||
|
if (!PyUnicode_Check(text)) {
|
||||||
|
PyObject *unicode = PyObject_Str(text);
|
||||||
|
if (!unicode)
|
||||||
|
return NULL;
|
||||||
|
s = escape_unicode((PyUnicodeObject*)unicode);
|
||||||
|
Py_DECREF(unicode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
s = escape_unicode((PyUnicodeObject*)text);
|
||||||
|
|
||||||
|
/* convert the unicode string into a markup object. */
|
||||||
|
rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
|
||||||
|
Py_DECREF(s);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
escape_silent(PyObject *self, PyObject *text)
|
||||||
|
{
|
||||||
|
if (text != Py_None)
|
||||||
|
return escape(self, text);
|
||||||
|
return PyObject_CallFunctionObjArgs(markup, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
soft_str(PyObject *self, PyObject *s)
|
||||||
|
{
|
||||||
|
if (!PyUnicode_Check(s))
|
||||||
|
return PyObject_Str(s);
|
||||||
|
Py_INCREF(s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyMethodDef module_methods[] = {
|
||||||
|
{
|
||||||
|
"escape",
|
||||||
|
(PyCFunction)escape,
|
||||||
|
METH_O,
|
||||||
|
"Replace the characters ``&``, ``<``, ``>``, ``'``, and ``\"`` in"
|
||||||
|
" the string with HTML-safe sequences. Use this if you need to display"
|
||||||
|
" text that might contain such characters in HTML.\n\n"
|
||||||
|
"If the object has an ``__html__`` method, it is called and the"
|
||||||
|
" return value is assumed to already be safe for HTML.\n\n"
|
||||||
|
":param s: An object to be converted to a string and escaped.\n"
|
||||||
|
":return: A :class:`Markup` string with the escaped text.\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"escape_silent",
|
||||||
|
(PyCFunction)escape_silent,
|
||||||
|
METH_O,
|
||||||
|
"Like :func:`escape` but treats ``None`` as the empty string."
|
||||||
|
" Useful with optional values, as otherwise you get the string"
|
||||||
|
" ``'None'`` when the value is ``None``.\n\n"
|
||||||
|
">>> escape(None)\n"
|
||||||
|
"Markup('None')\n"
|
||||||
|
">>> escape_silent(None)\n"
|
||||||
|
"Markup('')\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"soft_str",
|
||||||
|
(PyCFunction)soft_str,
|
||||||
|
METH_O,
|
||||||
|
"Convert an object to a string if it isn't already. This preserves"
|
||||||
|
" a :class:`Markup` string rather than converting it back to a basic"
|
||||||
|
" string, so it will still be marked as safe and won't be escaped"
|
||||||
|
" again.\n\n"
|
||||||
|
">>> value = escape(\"<User 1>\")\n"
|
||||||
|
">>> value\n"
|
||||||
|
"Markup('<User 1>')\n"
|
||||||
|
">>> escape(str(value))\n"
|
||||||
|
"Markup('&lt;User 1&gt;')\n"
|
||||||
|
">>> escape(soft_str(value))\n"
|
||||||
|
"Markup('<User 1>')\n"
|
||||||
|
},
|
||||||
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct PyModuleDef module_definition = {
|
||||||
|
PyModuleDef_HEAD_INIT,
|
||||||
|
"markupsafe._speedups",
|
||||||
|
NULL,
|
||||||
|
-1,
|
||||||
|
module_methods,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
PyMODINIT_FUNC
|
||||||
|
PyInit__speedups(void)
|
||||||
|
{
|
||||||
|
if (!init_constants())
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return PyModule_Create(&module_definition);
|
||||||
|
}
|
||||||
BIN
dist/ba_data/python-site-packages/markupsafe/_speedups.cpython-39-x86_64-linux-gnu.so
vendored
Normal file
BIN
dist/ba_data/python-site-packages/markupsafe/_speedups.cpython-39-x86_64-linux-gnu.so
vendored
Normal file
Binary file not shown.
9
dist/ba_data/python-site-packages/markupsafe/_speedups.pyi
vendored
Normal file
9
dist/ba_data/python-site-packages/markupsafe/_speedups.pyi
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from typing import Any
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from . import Markup
|
||||||
|
|
||||||
|
def escape(s: Any) -> Markup: ...
|
||||||
|
def escape_silent(s: Optional[Any]) -> Markup: ...
|
||||||
|
def soft_str(s: Any) -> str: ...
|
||||||
|
def soft_unicode(s: Any) -> str: ...
|
||||||
0
dist/ba_data/python-site-packages/markupsafe/py.typed
vendored
Normal file
0
dist/ba_data/python-site-packages/markupsafe/py.typed
vendored
Normal file
BIN
dist/ba_data/python-site-packages/psutil/_psutil_linux.cpython-39-x86_64-linux-gnu.so
vendored
Normal file
BIN
dist/ba_data/python-site-packages/psutil/_psutil_linux.cpython-39-x86_64-linux-gnu.so
vendored
Normal file
Binary file not shown.
BIN
dist/ba_data/python-site-packages/psutil/_psutil_posix.cpython-39-x86_64-linux-gnu.so
vendored
Normal file
BIN
dist/ba_data/python-site-packages/psutil/_psutil_posix.cpython-39-x86_64-linux-gnu.so
vendored
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue