Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/pip/_vendor/certifi/__init__.py +4 -0
- lib/python3.12/site-packages/pip/_vendor/certifi/__main__.py +12 -0
- lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/certifi/cacert.pem +0 -0
- lib/python3.12/site-packages/pip/_vendor/certifi/core.py +114 -0
- lib/python3.12/site-packages/pip/_vendor/certifi/py.typed +0 -0
- lib/python3.12/site-packages/pip/_vendor/distlib/compat.py +1137 -0
- lib/python3.12/site-packages/pip/_vendor/distlib/database.py +1329 -0
- lib/python3.12/site-packages/pip/_vendor/distlib/index.py +508 -0
- lib/python3.12/site-packages/pip/_vendor/distlib/locators.py +1295 -0
- lib/python3.12/site-packages/pip/_vendor/distlib/manifest.py +384 -0
- lib/python3.12/site-packages/pip/_vendor/distlib/metadata.py +1031 -0
- lib/python3.12/site-packages/pip/_vendor/distlib/resources.py +358 -0
- lib/python3.12/site-packages/pip/_vendor/distlib/util.py +1984 -0
- lib/python3.12/site-packages/pip/_vendor/distro/__init__.py +54 -0
- lib/python3.12/site-packages/pip/_vendor/distro/__main__.py +4 -0
- lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/distro/distro.py +1403 -0
- lib/python3.12/site-packages/pip/_vendor/distro/py.typed +0 -0
- lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/metadata.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/__init__.py +82 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/__main__.py +17 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/cmdline.py +668 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/console.py +70 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/filter.py +70 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/filters/__init__.py +940 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/formatter.py +129 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/lexer.py +963 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__init__.py +362 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/lexers/_mapping.py +589 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/lexers/python.py +1198 -0
- lib/python3.12/site-packages/pip/_vendor/pygments/modeline.py +43 -0
lib/python3.12/site-packages/pip/_vendor/certifi/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .core import contents, where
|
| 2 |
+
|
| 3 |
+
__all__ = ["contents", "where"]
|
| 4 |
+
__version__ = "2024.08.30"
|
lib/python3.12/site-packages/pip/_vendor/certifi/__main__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
|
| 3 |
+
from pip._vendor.certifi import contents, where
|
| 4 |
+
|
| 5 |
+
parser = argparse.ArgumentParser()
|
| 6 |
+
parser.add_argument("-c", "--contents", action="store_true")
|
| 7 |
+
args = parser.parse_args()
|
| 8 |
+
|
| 9 |
+
if args.contents:
|
| 10 |
+
print(contents())
|
| 11 |
+
else:
|
| 12 |
+
print(where())
|
lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (325 Bytes). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc
ADDED
|
Binary file (652 Bytes). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-312.pyc
ADDED
|
Binary file (3.22 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/certifi/cacert.pem
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
lib/python3.12/site-packages/pip/_vendor/certifi/core.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
certifi.py
|
| 3 |
+
~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
This module returns the installation location of cacert.pem or its contents.
|
| 6 |
+
"""
|
| 7 |
+
import sys
|
| 8 |
+
import atexit
|
| 9 |
+
|
| 10 |
+
def exit_cacert_ctx() -> None:
|
| 11 |
+
_CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
if sys.version_info >= (3, 11):
|
| 15 |
+
|
| 16 |
+
from importlib.resources import as_file, files
|
| 17 |
+
|
| 18 |
+
_CACERT_CTX = None
|
| 19 |
+
_CACERT_PATH = None
|
| 20 |
+
|
| 21 |
+
def where() -> str:
|
| 22 |
+
# This is slightly terrible, but we want to delay extracting the file
|
| 23 |
+
# in cases where we're inside of a zipimport situation until someone
|
| 24 |
+
# actually calls where(), but we don't want to re-extract the file
|
| 25 |
+
# on every call of where(), so we'll do it once then store it in a
|
| 26 |
+
# global variable.
|
| 27 |
+
global _CACERT_CTX
|
| 28 |
+
global _CACERT_PATH
|
| 29 |
+
if _CACERT_PATH is None:
|
| 30 |
+
# This is slightly janky, the importlib.resources API wants you to
|
| 31 |
+
# manage the cleanup of this file, so it doesn't actually return a
|
| 32 |
+
# path, it returns a context manager that will give you the path
|
| 33 |
+
# when you enter it and will do any cleanup when you leave it. In
|
| 34 |
+
# the common case of not needing a temporary file, it will just
|
| 35 |
+
# return the file system location and the __exit__() is a no-op.
|
| 36 |
+
#
|
| 37 |
+
# We also have to hold onto the actual context manager, because
|
| 38 |
+
# it will do the cleanup whenever it gets garbage collected, so
|
| 39 |
+
# we will also store that at the global level as well.
|
| 40 |
+
_CACERT_CTX = as_file(files("pip._vendor.certifi").joinpath("cacert.pem"))
|
| 41 |
+
_CACERT_PATH = str(_CACERT_CTX.__enter__())
|
| 42 |
+
atexit.register(exit_cacert_ctx)
|
| 43 |
+
|
| 44 |
+
return _CACERT_PATH
|
| 45 |
+
|
| 46 |
+
def contents() -> str:
|
| 47 |
+
return files("pip._vendor.certifi").joinpath("cacert.pem").read_text(encoding="ascii")
|
| 48 |
+
|
| 49 |
+
elif sys.version_info >= (3, 7):
|
| 50 |
+
|
| 51 |
+
from importlib.resources import path as get_path, read_text
|
| 52 |
+
|
| 53 |
+
_CACERT_CTX = None
|
| 54 |
+
_CACERT_PATH = None
|
| 55 |
+
|
| 56 |
+
def where() -> str:
|
| 57 |
+
# This is slightly terrible, but we want to delay extracting the
|
| 58 |
+
# file in cases where we're inside of a zipimport situation until
|
| 59 |
+
# someone actually calls where(), but we don't want to re-extract
|
| 60 |
+
# the file on every call of where(), so we'll do it once then store
|
| 61 |
+
# it in a global variable.
|
| 62 |
+
global _CACERT_CTX
|
| 63 |
+
global _CACERT_PATH
|
| 64 |
+
if _CACERT_PATH is None:
|
| 65 |
+
# This is slightly janky, the importlib.resources API wants you
|
| 66 |
+
# to manage the cleanup of this file, so it doesn't actually
|
| 67 |
+
# return a path, it returns a context manager that will give
|
| 68 |
+
# you the path when you enter it and will do any cleanup when
|
| 69 |
+
# you leave it. In the common case of not needing a temporary
|
| 70 |
+
# file, it will just return the file system location and the
|
| 71 |
+
# __exit__() is a no-op.
|
| 72 |
+
#
|
| 73 |
+
# We also have to hold onto the actual context manager, because
|
| 74 |
+
# it will do the cleanup whenever it gets garbage collected, so
|
| 75 |
+
# we will also store that at the global level as well.
|
| 76 |
+
_CACERT_CTX = get_path("pip._vendor.certifi", "cacert.pem")
|
| 77 |
+
_CACERT_PATH = str(_CACERT_CTX.__enter__())
|
| 78 |
+
atexit.register(exit_cacert_ctx)
|
| 79 |
+
|
| 80 |
+
return _CACERT_PATH
|
| 81 |
+
|
| 82 |
+
def contents() -> str:
|
| 83 |
+
return read_text("pip._vendor.certifi", "cacert.pem", encoding="ascii")
|
| 84 |
+
|
| 85 |
+
else:
|
| 86 |
+
import os
|
| 87 |
+
import types
|
| 88 |
+
from typing import Union
|
| 89 |
+
|
| 90 |
+
Package = Union[types.ModuleType, str]
|
| 91 |
+
Resource = Union[str, "os.PathLike"]
|
| 92 |
+
|
| 93 |
+
# This fallback will work for Python versions prior to 3.7 that lack the
|
| 94 |
+
# importlib.resources module but relies on the existing `where` function
|
| 95 |
+
# so won't address issues with environments like PyOxidizer that don't set
|
| 96 |
+
# __file__ on modules.
|
| 97 |
+
def read_text(
|
| 98 |
+
package: Package,
|
| 99 |
+
resource: Resource,
|
| 100 |
+
encoding: str = 'utf-8',
|
| 101 |
+
errors: str = 'strict'
|
| 102 |
+
) -> str:
|
| 103 |
+
with open(where(), encoding=encoding) as data:
|
| 104 |
+
return data.read()
|
| 105 |
+
|
| 106 |
+
# If we don't have importlib.resources, then we will just do the old logic
|
| 107 |
+
# of assuming we're on the filesystem and munge the path directly.
|
| 108 |
+
def where() -> str:
|
| 109 |
+
f = os.path.dirname(__file__)
|
| 110 |
+
|
| 111 |
+
return os.path.join(f, "cacert.pem")
|
| 112 |
+
|
| 113 |
+
def contents() -> str:
|
| 114 |
+
return read_text("pip._vendor.certifi", "cacert.pem", encoding="ascii")
|
lib/python3.12/site-packages/pip/_vendor/certifi/py.typed
ADDED
|
File without changes
|
lib/python3.12/site-packages/pip/_vendor/distlib/compat.py
ADDED
|
@@ -0,0 +1,1137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2013-2017 Vinay Sajip.
|
| 4 |
+
# Licensed to the Python Software Foundation under a contributor agreement.
|
| 5 |
+
# See LICENSE.txt and CONTRIBUTORS.txt.
|
| 6 |
+
#
|
| 7 |
+
from __future__ import absolute_import
|
| 8 |
+
|
| 9 |
+
import os
|
| 10 |
+
import re
|
| 11 |
+
import shutil
|
| 12 |
+
import sys
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
import ssl
|
| 16 |
+
except ImportError: # pragma: no cover
|
| 17 |
+
ssl = None
|
| 18 |
+
|
| 19 |
+
if sys.version_info[0] < 3: # pragma: no cover
|
| 20 |
+
from StringIO import StringIO
|
| 21 |
+
string_types = basestring,
|
| 22 |
+
text_type = unicode
|
| 23 |
+
from types import FileType as file_type
|
| 24 |
+
import __builtin__ as builtins
|
| 25 |
+
import ConfigParser as configparser
|
| 26 |
+
from urlparse import urlparse, urlunparse, urljoin, urlsplit, urlunsplit
|
| 27 |
+
from urllib import (urlretrieve, quote as _quote, unquote, url2pathname,
|
| 28 |
+
pathname2url, ContentTooShortError, splittype)
|
| 29 |
+
|
| 30 |
+
def quote(s):
|
| 31 |
+
if isinstance(s, unicode):
|
| 32 |
+
s = s.encode('utf-8')
|
| 33 |
+
return _quote(s)
|
| 34 |
+
|
| 35 |
+
import urllib2
|
| 36 |
+
from urllib2 import (Request, urlopen, URLError, HTTPError,
|
| 37 |
+
HTTPBasicAuthHandler, HTTPPasswordMgr, HTTPHandler,
|
| 38 |
+
HTTPRedirectHandler, build_opener)
|
| 39 |
+
if ssl:
|
| 40 |
+
from urllib2 import HTTPSHandler
|
| 41 |
+
import httplib
|
| 42 |
+
import xmlrpclib
|
| 43 |
+
import Queue as queue
|
| 44 |
+
from HTMLParser import HTMLParser
|
| 45 |
+
import htmlentitydefs
|
| 46 |
+
raw_input = raw_input
|
| 47 |
+
from itertools import ifilter as filter
|
| 48 |
+
from itertools import ifilterfalse as filterfalse
|
| 49 |
+
|
| 50 |
+
# Leaving this around for now, in case it needs resurrecting in some way
|
| 51 |
+
# _userprog = None
|
| 52 |
+
# def splituser(host):
|
| 53 |
+
# """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'."""
|
| 54 |
+
# global _userprog
|
| 55 |
+
# if _userprog is None:
|
| 56 |
+
# import re
|
| 57 |
+
# _userprog = re.compile('^(.*)@(.*)$')
|
| 58 |
+
|
| 59 |
+
# match = _userprog.match(host)
|
| 60 |
+
# if match: return match.group(1, 2)
|
| 61 |
+
# return None, host
|
| 62 |
+
|
| 63 |
+
else: # pragma: no cover
|
| 64 |
+
from io import StringIO
|
| 65 |
+
string_types = str,
|
| 66 |
+
text_type = str
|
| 67 |
+
from io import TextIOWrapper as file_type
|
| 68 |
+
import builtins
|
| 69 |
+
import configparser
|
| 70 |
+
from urllib.parse import (urlparse, urlunparse, urljoin, quote, unquote,
|
| 71 |
+
urlsplit, urlunsplit, splittype)
|
| 72 |
+
from urllib.request import (urlopen, urlretrieve, Request, url2pathname,
|
| 73 |
+
pathname2url, HTTPBasicAuthHandler,
|
| 74 |
+
HTTPPasswordMgr, HTTPHandler,
|
| 75 |
+
HTTPRedirectHandler, build_opener)
|
| 76 |
+
if ssl:
|
| 77 |
+
from urllib.request import HTTPSHandler
|
| 78 |
+
from urllib.error import HTTPError, URLError, ContentTooShortError
|
| 79 |
+
import http.client as httplib
|
| 80 |
+
import urllib.request as urllib2
|
| 81 |
+
import xmlrpc.client as xmlrpclib
|
| 82 |
+
import queue
|
| 83 |
+
from html.parser import HTMLParser
|
| 84 |
+
import html.entities as htmlentitydefs
|
| 85 |
+
raw_input = input
|
| 86 |
+
from itertools import filterfalse
|
| 87 |
+
filter = filter
|
| 88 |
+
|
| 89 |
+
try:
|
| 90 |
+
from ssl import match_hostname, CertificateError
|
| 91 |
+
except ImportError: # pragma: no cover
|
| 92 |
+
|
| 93 |
+
class CertificateError(ValueError):
|
| 94 |
+
pass
|
| 95 |
+
|
| 96 |
+
def _dnsname_match(dn, hostname, max_wildcards=1):
|
| 97 |
+
"""Matching according to RFC 6125, section 6.4.3
|
| 98 |
+
|
| 99 |
+
http://tools.ietf.org/html/rfc6125#section-6.4.3
|
| 100 |
+
"""
|
| 101 |
+
pats = []
|
| 102 |
+
if not dn:
|
| 103 |
+
return False
|
| 104 |
+
|
| 105 |
+
parts = dn.split('.')
|
| 106 |
+
leftmost, remainder = parts[0], parts[1:]
|
| 107 |
+
|
| 108 |
+
wildcards = leftmost.count('*')
|
| 109 |
+
if wildcards > max_wildcards:
|
| 110 |
+
# Issue #17980: avoid denials of service by refusing more
|
| 111 |
+
# than one wildcard per fragment. A survey of established
|
| 112 |
+
# policy among SSL implementations showed it to be a
|
| 113 |
+
# reasonable choice.
|
| 114 |
+
raise CertificateError(
|
| 115 |
+
"too many wildcards in certificate DNS name: " + repr(dn))
|
| 116 |
+
|
| 117 |
+
# speed up common case w/o wildcards
|
| 118 |
+
if not wildcards:
|
| 119 |
+
return dn.lower() == hostname.lower()
|
| 120 |
+
|
| 121 |
+
# RFC 6125, section 6.4.3, subitem 1.
|
| 122 |
+
# The client SHOULD NOT attempt to match a presented identifier in which
|
| 123 |
+
# the wildcard character comprises a label other than the left-most label.
|
| 124 |
+
if leftmost == '*':
|
| 125 |
+
# When '*' is a fragment by itself, it matches a non-empty dotless
|
| 126 |
+
# fragment.
|
| 127 |
+
pats.append('[^.]+')
|
| 128 |
+
elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
|
| 129 |
+
# RFC 6125, section 6.4.3, subitem 3.
|
| 130 |
+
# The client SHOULD NOT attempt to match a presented identifier
|
| 131 |
+
# where the wildcard character is embedded within an A-label or
|
| 132 |
+
# U-label of an internationalized domain name.
|
| 133 |
+
pats.append(re.escape(leftmost))
|
| 134 |
+
else:
|
| 135 |
+
# Otherwise, '*' matches any dotless string, e.g. www*
|
| 136 |
+
pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
|
| 137 |
+
|
| 138 |
+
# add the remaining fragments, ignore any wildcards
|
| 139 |
+
for frag in remainder:
|
| 140 |
+
pats.append(re.escape(frag))
|
| 141 |
+
|
| 142 |
+
pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
|
| 143 |
+
return pat.match(hostname)
|
| 144 |
+
|
| 145 |
+
def match_hostname(cert, hostname):
|
| 146 |
+
"""Verify that *cert* (in decoded format as returned by
|
| 147 |
+
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
|
| 148 |
+
rules are followed, but IP addresses are not accepted for *hostname*.
|
| 149 |
+
|
| 150 |
+
CertificateError is raised on failure. On success, the function
|
| 151 |
+
returns nothing.
|
| 152 |
+
"""
|
| 153 |
+
if not cert:
|
| 154 |
+
raise ValueError("empty or no certificate, match_hostname needs a "
|
| 155 |
+
"SSL socket or SSL context with either "
|
| 156 |
+
"CERT_OPTIONAL or CERT_REQUIRED")
|
| 157 |
+
dnsnames = []
|
| 158 |
+
san = cert.get('subjectAltName', ())
|
| 159 |
+
for key, value in san:
|
| 160 |
+
if key == 'DNS':
|
| 161 |
+
if _dnsname_match(value, hostname):
|
| 162 |
+
return
|
| 163 |
+
dnsnames.append(value)
|
| 164 |
+
if not dnsnames:
|
| 165 |
+
# The subject is only checked when there is no dNSName entry
|
| 166 |
+
# in subjectAltName
|
| 167 |
+
for sub in cert.get('subject', ()):
|
| 168 |
+
for key, value in sub:
|
| 169 |
+
# XXX according to RFC 2818, the most specific Common Name
|
| 170 |
+
# must be used.
|
| 171 |
+
if key == 'commonName':
|
| 172 |
+
if _dnsname_match(value, hostname):
|
| 173 |
+
return
|
| 174 |
+
dnsnames.append(value)
|
| 175 |
+
if len(dnsnames) > 1:
|
| 176 |
+
raise CertificateError("hostname %r "
|
| 177 |
+
"doesn't match either of %s" %
|
| 178 |
+
(hostname, ', '.join(map(repr, dnsnames))))
|
| 179 |
+
elif len(dnsnames) == 1:
|
| 180 |
+
raise CertificateError("hostname %r "
|
| 181 |
+
"doesn't match %r" %
|
| 182 |
+
(hostname, dnsnames[0]))
|
| 183 |
+
else:
|
| 184 |
+
raise CertificateError("no appropriate commonName or "
|
| 185 |
+
"subjectAltName fields were found")
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
try:
|
| 189 |
+
from types import SimpleNamespace as Container
|
| 190 |
+
except ImportError: # pragma: no cover
|
| 191 |
+
|
| 192 |
+
class Container(object):
|
| 193 |
+
"""
|
| 194 |
+
A generic container for when multiple values need to be returned
|
| 195 |
+
"""
|
| 196 |
+
|
| 197 |
+
def __init__(self, **kwargs):
|
| 198 |
+
self.__dict__.update(kwargs)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
try:
|
| 202 |
+
from shutil import which
|
| 203 |
+
except ImportError: # pragma: no cover
|
| 204 |
+
# Implementation from Python 3.3
|
| 205 |
+
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
| 206 |
+
"""Given a command, mode, and a PATH string, return the path which
|
| 207 |
+
conforms to the given mode on the PATH, or None if there is no such
|
| 208 |
+
file.
|
| 209 |
+
|
| 210 |
+
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
|
| 211 |
+
of os.environ.get("PATH"), or can be overridden with a custom search
|
| 212 |
+
path.
|
| 213 |
+
|
| 214 |
+
"""
|
| 215 |
+
|
| 216 |
+
# Check that a given file can be accessed with the correct mode.
|
| 217 |
+
# Additionally check that `file` is not a directory, as on Windows
|
| 218 |
+
# directories pass the os.access check.
|
| 219 |
+
def _access_check(fn, mode):
|
| 220 |
+
return (os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn))
|
| 221 |
+
|
| 222 |
+
# If we're given a path with a directory part, look it up directly rather
|
| 223 |
+
# than referring to PATH directories. This includes checking relative to the
|
| 224 |
+
# current directory, e.g. ./script
|
| 225 |
+
if os.path.dirname(cmd):
|
| 226 |
+
if _access_check(cmd, mode):
|
| 227 |
+
return cmd
|
| 228 |
+
return None
|
| 229 |
+
|
| 230 |
+
if path is None:
|
| 231 |
+
path = os.environ.get("PATH", os.defpath)
|
| 232 |
+
if not path:
|
| 233 |
+
return None
|
| 234 |
+
path = path.split(os.pathsep)
|
| 235 |
+
|
| 236 |
+
if sys.platform == "win32":
|
| 237 |
+
# The current directory takes precedence on Windows.
|
| 238 |
+
if os.curdir not in path:
|
| 239 |
+
path.insert(0, os.curdir)
|
| 240 |
+
|
| 241 |
+
# PATHEXT is necessary to check on Windows.
|
| 242 |
+
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
|
| 243 |
+
# See if the given file matches any of the expected path extensions.
|
| 244 |
+
# This will allow us to short circuit when given "python.exe".
|
| 245 |
+
# If it does match, only test that one, otherwise we have to try
|
| 246 |
+
# others.
|
| 247 |
+
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
|
| 248 |
+
files = [cmd]
|
| 249 |
+
else:
|
| 250 |
+
files = [cmd + ext for ext in pathext]
|
| 251 |
+
else:
|
| 252 |
+
# On other platforms you don't have things like PATHEXT to tell you
|
| 253 |
+
# what file suffixes are executable, so just pass on cmd as-is.
|
| 254 |
+
files = [cmd]
|
| 255 |
+
|
| 256 |
+
seen = set()
|
| 257 |
+
for dir in path:
|
| 258 |
+
normdir = os.path.normcase(dir)
|
| 259 |
+
if normdir not in seen:
|
| 260 |
+
seen.add(normdir)
|
| 261 |
+
for thefile in files:
|
| 262 |
+
name = os.path.join(dir, thefile)
|
| 263 |
+
if _access_check(name, mode):
|
| 264 |
+
return name
|
| 265 |
+
return None
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
# ZipFile is a context manager in 2.7, but not in 2.6
|
| 269 |
+
|
| 270 |
+
from zipfile import ZipFile as BaseZipFile
|
| 271 |
+
|
| 272 |
+
if hasattr(BaseZipFile, '__enter__'): # pragma: no cover
|
| 273 |
+
ZipFile = BaseZipFile
|
| 274 |
+
else: # pragma: no cover
|
| 275 |
+
from zipfile import ZipExtFile as BaseZipExtFile
|
| 276 |
+
|
| 277 |
+
class ZipExtFile(BaseZipExtFile):
|
| 278 |
+
|
| 279 |
+
def __init__(self, base):
|
| 280 |
+
self.__dict__.update(base.__dict__)
|
| 281 |
+
|
| 282 |
+
def __enter__(self):
|
| 283 |
+
return self
|
| 284 |
+
|
| 285 |
+
def __exit__(self, *exc_info):
|
| 286 |
+
self.close()
|
| 287 |
+
# return None, so if an exception occurred, it will propagate
|
| 288 |
+
|
| 289 |
+
class ZipFile(BaseZipFile):
|
| 290 |
+
|
| 291 |
+
def __enter__(self):
|
| 292 |
+
return self
|
| 293 |
+
|
| 294 |
+
def __exit__(self, *exc_info):
|
| 295 |
+
self.close()
|
| 296 |
+
# return None, so if an exception occurred, it will propagate
|
| 297 |
+
|
| 298 |
+
def open(self, *args, **kwargs):
|
| 299 |
+
base = BaseZipFile.open(self, *args, **kwargs)
|
| 300 |
+
return ZipExtFile(base)
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
try:
|
| 304 |
+
from platform import python_implementation
|
| 305 |
+
except ImportError: # pragma: no cover
|
| 306 |
+
|
| 307 |
+
def python_implementation():
|
| 308 |
+
"""Return a string identifying the Python implementation."""
|
| 309 |
+
if 'PyPy' in sys.version:
|
| 310 |
+
return 'PyPy'
|
| 311 |
+
if os.name == 'java':
|
| 312 |
+
return 'Jython'
|
| 313 |
+
if sys.version.startswith('IronPython'):
|
| 314 |
+
return 'IronPython'
|
| 315 |
+
return 'CPython'
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
import sysconfig
|
| 319 |
+
|
| 320 |
+
try:
|
| 321 |
+
callable = callable
|
| 322 |
+
except NameError: # pragma: no cover
|
| 323 |
+
from collections.abc import Callable
|
| 324 |
+
|
| 325 |
+
def callable(obj):
|
| 326 |
+
return isinstance(obj, Callable)
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
try:
|
| 330 |
+
fsencode = os.fsencode
|
| 331 |
+
fsdecode = os.fsdecode
|
| 332 |
+
except AttributeError: # pragma: no cover
|
| 333 |
+
# Issue #99: on some systems (e.g. containerised),
|
| 334 |
+
# sys.getfilesystemencoding() returns None, and we need a real value,
|
| 335 |
+
# so fall back to utf-8. From the CPython 2.7 docs relating to Unix and
|
| 336 |
+
# sys.getfilesystemencoding(): the return value is "the user’s preference
|
| 337 |
+
# according to the result of nl_langinfo(CODESET), or None if the
|
| 338 |
+
# nl_langinfo(CODESET) failed."
|
| 339 |
+
_fsencoding = sys.getfilesystemencoding() or 'utf-8'
|
| 340 |
+
if _fsencoding == 'mbcs':
|
| 341 |
+
_fserrors = 'strict'
|
| 342 |
+
else:
|
| 343 |
+
_fserrors = 'surrogateescape'
|
| 344 |
+
|
| 345 |
+
def fsencode(filename):
|
| 346 |
+
if isinstance(filename, bytes):
|
| 347 |
+
return filename
|
| 348 |
+
elif isinstance(filename, text_type):
|
| 349 |
+
return filename.encode(_fsencoding, _fserrors)
|
| 350 |
+
else:
|
| 351 |
+
raise TypeError("expect bytes or str, not %s" %
|
| 352 |
+
type(filename).__name__)
|
| 353 |
+
|
| 354 |
+
def fsdecode(filename):
|
| 355 |
+
if isinstance(filename, text_type):
|
| 356 |
+
return filename
|
| 357 |
+
elif isinstance(filename, bytes):
|
| 358 |
+
return filename.decode(_fsencoding, _fserrors)
|
| 359 |
+
else:
|
| 360 |
+
raise TypeError("expect bytes or str, not %s" %
|
| 361 |
+
type(filename).__name__)
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
try:
|
| 365 |
+
from tokenize import detect_encoding
|
| 366 |
+
except ImportError: # pragma: no cover
|
| 367 |
+
from codecs import BOM_UTF8, lookup
|
| 368 |
+
|
| 369 |
+
cookie_re = re.compile(r"coding[:=]\s*([-\w.]+)")
|
| 370 |
+
|
| 371 |
+
def _get_normal_name(orig_enc):
|
| 372 |
+
"""Imitates get_normal_name in tokenizer.c."""
|
| 373 |
+
# Only care about the first 12 characters.
|
| 374 |
+
enc = orig_enc[:12].lower().replace("_", "-")
|
| 375 |
+
if enc == "utf-8" or enc.startswith("utf-8-"):
|
| 376 |
+
return "utf-8"
|
| 377 |
+
if enc in ("latin-1", "iso-8859-1", "iso-latin-1") or \
|
| 378 |
+
enc.startswith(("latin-1-", "iso-8859-1-", "iso-latin-1-")):
|
| 379 |
+
return "iso-8859-1"
|
| 380 |
+
return orig_enc
|
| 381 |
+
|
| 382 |
+
def detect_encoding(readline):
|
| 383 |
+
"""
|
| 384 |
+
The detect_encoding() function is used to detect the encoding that should
|
| 385 |
+
be used to decode a Python source file. It requires one argument, readline,
|
| 386 |
+
in the same way as the tokenize() generator.
|
| 387 |
+
|
| 388 |
+
It will call readline a maximum of twice, and return the encoding used
|
| 389 |
+
(as a string) and a list of any lines (left as bytes) it has read in.
|
| 390 |
+
|
| 391 |
+
It detects the encoding from the presence of a utf-8 bom or an encoding
|
| 392 |
+
cookie as specified in pep-0263. If both a bom and a cookie are present,
|
| 393 |
+
but disagree, a SyntaxError will be raised. If the encoding cookie is an
|
| 394 |
+
invalid charset, raise a SyntaxError. Note that if a utf-8 bom is found,
|
| 395 |
+
'utf-8-sig' is returned.
|
| 396 |
+
|
| 397 |
+
If no encoding is specified, then the default of 'utf-8' will be returned.
|
| 398 |
+
"""
|
| 399 |
+
try:
|
| 400 |
+
filename = readline.__self__.name
|
| 401 |
+
except AttributeError:
|
| 402 |
+
filename = None
|
| 403 |
+
bom_found = False
|
| 404 |
+
encoding = None
|
| 405 |
+
default = 'utf-8'
|
| 406 |
+
|
| 407 |
+
def read_or_stop():
|
| 408 |
+
try:
|
| 409 |
+
return readline()
|
| 410 |
+
except StopIteration:
|
| 411 |
+
return b''
|
| 412 |
+
|
| 413 |
+
def find_cookie(line):
|
| 414 |
+
try:
|
| 415 |
+
# Decode as UTF-8. Either the line is an encoding declaration,
|
| 416 |
+
# in which case it should be pure ASCII, or it must be UTF-8
|
| 417 |
+
# per default encoding.
|
| 418 |
+
line_string = line.decode('utf-8')
|
| 419 |
+
except UnicodeDecodeError:
|
| 420 |
+
msg = "invalid or missing encoding declaration"
|
| 421 |
+
if filename is not None:
|
| 422 |
+
msg = '{} for {!r}'.format(msg, filename)
|
| 423 |
+
raise SyntaxError(msg)
|
| 424 |
+
|
| 425 |
+
matches = cookie_re.findall(line_string)
|
| 426 |
+
if not matches:
|
| 427 |
+
return None
|
| 428 |
+
encoding = _get_normal_name(matches[0])
|
| 429 |
+
try:
|
| 430 |
+
codec = lookup(encoding)
|
| 431 |
+
except LookupError:
|
| 432 |
+
# This behaviour mimics the Python interpreter
|
| 433 |
+
if filename is None:
|
| 434 |
+
msg = "unknown encoding: " + encoding
|
| 435 |
+
else:
|
| 436 |
+
msg = "unknown encoding for {!r}: {}".format(
|
| 437 |
+
filename, encoding)
|
| 438 |
+
raise SyntaxError(msg)
|
| 439 |
+
|
| 440 |
+
if bom_found:
|
| 441 |
+
if codec.name != 'utf-8':
|
| 442 |
+
# This behaviour mimics the Python interpreter
|
| 443 |
+
if filename is None:
|
| 444 |
+
msg = 'encoding problem: utf-8'
|
| 445 |
+
else:
|
| 446 |
+
msg = 'encoding problem for {!r}: utf-8'.format(
|
| 447 |
+
filename)
|
| 448 |
+
raise SyntaxError(msg)
|
| 449 |
+
encoding += '-sig'
|
| 450 |
+
return encoding
|
| 451 |
+
|
| 452 |
+
first = read_or_stop()
|
| 453 |
+
if first.startswith(BOM_UTF8):
|
| 454 |
+
bom_found = True
|
| 455 |
+
first = first[3:]
|
| 456 |
+
default = 'utf-8-sig'
|
| 457 |
+
if not first:
|
| 458 |
+
return default, []
|
| 459 |
+
|
| 460 |
+
encoding = find_cookie(first)
|
| 461 |
+
if encoding:
|
| 462 |
+
return encoding, [first]
|
| 463 |
+
|
| 464 |
+
second = read_or_stop()
|
| 465 |
+
if not second:
|
| 466 |
+
return default, [first]
|
| 467 |
+
|
| 468 |
+
encoding = find_cookie(second)
|
| 469 |
+
if encoding:
|
| 470 |
+
return encoding, [first, second]
|
| 471 |
+
|
| 472 |
+
return default, [first, second]
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
# For converting & <-> & etc.
|
| 476 |
+
try:
|
| 477 |
+
from html import escape
|
| 478 |
+
except ImportError:
|
| 479 |
+
from cgi import escape
|
| 480 |
+
if sys.version_info[:2] < (3, 4):
|
| 481 |
+
unescape = HTMLParser().unescape
|
| 482 |
+
else:
|
| 483 |
+
from html import unescape
|
| 484 |
+
|
| 485 |
+
try:
|
| 486 |
+
from collections import ChainMap
|
| 487 |
+
except ImportError: # pragma: no cover
|
| 488 |
+
from collections import MutableMapping
|
| 489 |
+
|
| 490 |
+
try:
|
| 491 |
+
from reprlib import recursive_repr as _recursive_repr
|
| 492 |
+
except ImportError:
|
| 493 |
+
|
| 494 |
+
def _recursive_repr(fillvalue='...'):
|
| 495 |
+
'''
|
| 496 |
+
Decorator to make a repr function return fillvalue for a recursive
|
| 497 |
+
call
|
| 498 |
+
'''
|
| 499 |
+
|
| 500 |
+
def decorating_function(user_function):
|
| 501 |
+
repr_running = set()
|
| 502 |
+
|
| 503 |
+
def wrapper(self):
|
| 504 |
+
key = id(self), get_ident()
|
| 505 |
+
if key in repr_running:
|
| 506 |
+
return fillvalue
|
| 507 |
+
repr_running.add(key)
|
| 508 |
+
try:
|
| 509 |
+
result = user_function(self)
|
| 510 |
+
finally:
|
| 511 |
+
repr_running.discard(key)
|
| 512 |
+
return result
|
| 513 |
+
|
| 514 |
+
# Can't use functools.wraps() here because of bootstrap issues
|
| 515 |
+
wrapper.__module__ = getattr(user_function, '__module__')
|
| 516 |
+
wrapper.__doc__ = getattr(user_function, '__doc__')
|
| 517 |
+
wrapper.__name__ = getattr(user_function, '__name__')
|
| 518 |
+
wrapper.__annotations__ = getattr(user_function,
|
| 519 |
+
'__annotations__', {})
|
| 520 |
+
return wrapper
|
| 521 |
+
|
| 522 |
+
return decorating_function
|
| 523 |
+
|
| 524 |
+
class ChainMap(MutableMapping):
|
| 525 |
+
'''
|
| 526 |
+
A ChainMap groups multiple dicts (or other mappings) together
|
| 527 |
+
to create a single, updateable view.
|
| 528 |
+
|
| 529 |
+
The underlying mappings are stored in a list. That list is public and can
|
| 530 |
+
accessed or updated using the *maps* attribute. There is no other state.
|
| 531 |
+
|
| 532 |
+
Lookups search the underlying mappings successively until a key is found.
|
| 533 |
+
In contrast, writes, updates, and deletions only operate on the first
|
| 534 |
+
mapping.
|
| 535 |
+
'''
|
| 536 |
+
|
| 537 |
+
def __init__(self, *maps):
|
| 538 |
+
'''Initialize a ChainMap by setting *maps* to the given mappings.
|
| 539 |
+
If no mappings are provided, a single empty dictionary is used.
|
| 540 |
+
|
| 541 |
+
'''
|
| 542 |
+
self.maps = list(maps) or [{}] # always at least one map
|
| 543 |
+
|
| 544 |
+
def __missing__(self, key):
|
| 545 |
+
raise KeyError(key)
|
| 546 |
+
|
| 547 |
+
def __getitem__(self, key):
|
| 548 |
+
for mapping in self.maps:
|
| 549 |
+
try:
|
| 550 |
+
return mapping[
|
| 551 |
+
key] # can't use 'key in mapping' with defaultdict
|
| 552 |
+
except KeyError:
|
| 553 |
+
pass
|
| 554 |
+
return self.__missing__(
|
| 555 |
+
key) # support subclasses that define __missing__
|
| 556 |
+
|
| 557 |
+
def get(self, key, default=None):
|
| 558 |
+
return self[key] if key in self else default
|
| 559 |
+
|
| 560 |
+
def __len__(self):
|
| 561 |
+
return len(set().union(
|
| 562 |
+
*self.maps)) # reuses stored hash values if possible
|
| 563 |
+
|
| 564 |
+
def __iter__(self):
|
| 565 |
+
return iter(set().union(*self.maps))
|
| 566 |
+
|
| 567 |
+
def __contains__(self, key):
|
| 568 |
+
return any(key in m for m in self.maps)
|
| 569 |
+
|
| 570 |
+
def __bool__(self):
|
| 571 |
+
return any(self.maps)
|
| 572 |
+
|
| 573 |
+
@_recursive_repr()
|
| 574 |
+
def __repr__(self):
|
| 575 |
+
return '{0.__class__.__name__}({1})'.format(
|
| 576 |
+
self, ', '.join(map(repr, self.maps)))
|
| 577 |
+
|
| 578 |
+
@classmethod
|
| 579 |
+
def fromkeys(cls, iterable, *args):
|
| 580 |
+
'Create a ChainMap with a single dict created from the iterable.'
|
| 581 |
+
return cls(dict.fromkeys(iterable, *args))
|
| 582 |
+
|
| 583 |
+
def copy(self):
|
| 584 |
+
'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]'
|
| 585 |
+
return self.__class__(self.maps[0].copy(), *self.maps[1:])
|
| 586 |
+
|
| 587 |
+
__copy__ = copy
|
| 588 |
+
|
| 589 |
+
def new_child(self): # like Django's Context.push()
|
| 590 |
+
'New ChainMap with a new dict followed by all previous maps.'
|
| 591 |
+
return self.__class__({}, *self.maps)
|
| 592 |
+
|
| 593 |
+
@property
|
| 594 |
+
def parents(self): # like Django's Context.pop()
|
| 595 |
+
'New ChainMap from maps[1:].'
|
| 596 |
+
return self.__class__(*self.maps[1:])
|
| 597 |
+
|
| 598 |
+
def __setitem__(self, key, value):
|
| 599 |
+
self.maps[0][key] = value
|
| 600 |
+
|
| 601 |
+
def __delitem__(self, key):
|
| 602 |
+
try:
|
| 603 |
+
del self.maps[0][key]
|
| 604 |
+
except KeyError:
|
| 605 |
+
raise KeyError(
|
| 606 |
+
'Key not found in the first mapping: {!r}'.format(key))
|
| 607 |
+
|
| 608 |
+
def popitem(self):
|
| 609 |
+
'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
|
| 610 |
+
try:
|
| 611 |
+
return self.maps[0].popitem()
|
| 612 |
+
except KeyError:
|
| 613 |
+
raise KeyError('No keys found in the first mapping.')
|
| 614 |
+
|
| 615 |
+
def pop(self, key, *args):
|
| 616 |
+
'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].'
|
| 617 |
+
try:
|
| 618 |
+
return self.maps[0].pop(key, *args)
|
| 619 |
+
except KeyError:
|
| 620 |
+
raise KeyError(
|
| 621 |
+
'Key not found in the first mapping: {!r}'.format(key))
|
| 622 |
+
|
| 623 |
+
def clear(self):
|
| 624 |
+
'Clear maps[0], leaving maps[1:] intact.'
|
| 625 |
+
self.maps[0].clear()
|
| 626 |
+
|
| 627 |
+
|
| 628 |
+
try:
|
| 629 |
+
from importlib.util import cache_from_source # Python >= 3.4
|
| 630 |
+
except ImportError: # pragma: no cover
|
| 631 |
+
|
| 632 |
+
def cache_from_source(path, debug_override=None):
|
| 633 |
+
assert path.endswith('.py')
|
| 634 |
+
if debug_override is None:
|
| 635 |
+
debug_override = __debug__
|
| 636 |
+
if debug_override:
|
| 637 |
+
suffix = 'c'
|
| 638 |
+
else:
|
| 639 |
+
suffix = 'o'
|
| 640 |
+
return path + suffix
|
| 641 |
+
|
| 642 |
+
|
| 643 |
+
try:
|
| 644 |
+
from collections import OrderedDict
|
| 645 |
+
except ImportError: # pragma: no cover
|
| 646 |
+
# {{{ http://code.activestate.com/recipes/576693/ (r9)
|
| 647 |
+
# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
|
| 648 |
+
# Passes Python2.7's test suite and incorporates all the latest updates.
|
| 649 |
+
try:
|
| 650 |
+
from thread import get_ident as _get_ident
|
| 651 |
+
except ImportError:
|
| 652 |
+
from dummy_thread import get_ident as _get_ident
|
| 653 |
+
|
| 654 |
+
try:
|
| 655 |
+
from _abcoll import KeysView, ValuesView, ItemsView
|
| 656 |
+
except ImportError:
|
| 657 |
+
pass
|
| 658 |
+
|
| 659 |
+
class OrderedDict(dict):
|
| 660 |
+
'Dictionary that remembers insertion order'
|
| 661 |
+
|
| 662 |
+
# An inherited dict maps keys to values.
|
| 663 |
+
# The inherited dict provides __getitem__, __len__, __contains__, and get.
|
| 664 |
+
# The remaining methods are order-aware.
|
| 665 |
+
# Big-O running times for all methods are the same as for regular dictionaries.
|
| 666 |
+
|
| 667 |
+
# The internal self.__map dictionary maps keys to links in a doubly linked list.
|
| 668 |
+
# The circular doubly linked list starts and ends with a sentinel element.
|
| 669 |
+
# The sentinel element never gets deleted (this simplifies the algorithm).
|
| 670 |
+
# Each link is stored as a list of length three: [PREV, NEXT, KEY].
|
| 671 |
+
|
| 672 |
+
def __init__(self, *args, **kwds):
|
| 673 |
+
'''Initialize an ordered dictionary. Signature is the same as for
|
| 674 |
+
regular dictionaries, but keyword arguments are not recommended
|
| 675 |
+
because their insertion order is arbitrary.
|
| 676 |
+
|
| 677 |
+
'''
|
| 678 |
+
if len(args) > 1:
|
| 679 |
+
raise TypeError('expected at most 1 arguments, got %d' %
|
| 680 |
+
len(args))
|
| 681 |
+
try:
|
| 682 |
+
self.__root
|
| 683 |
+
except AttributeError:
|
| 684 |
+
self.__root = root = [] # sentinel node
|
| 685 |
+
root[:] = [root, root, None]
|
| 686 |
+
self.__map = {}
|
| 687 |
+
self.__update(*args, **kwds)
|
| 688 |
+
|
| 689 |
+
def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
|
| 690 |
+
'od.__setitem__(i, y) <==> od[i]=y'
|
| 691 |
+
# Setting a new item creates a new link which goes at the end of the linked
|
| 692 |
+
# list, and the inherited dictionary is updated with the new key/value pair.
|
| 693 |
+
if key not in self:
|
| 694 |
+
root = self.__root
|
| 695 |
+
last = root[0]
|
| 696 |
+
last[1] = root[0] = self.__map[key] = [last, root, key]
|
| 697 |
+
dict_setitem(self, key, value)
|
| 698 |
+
|
| 699 |
+
def __delitem__(self, key, dict_delitem=dict.__delitem__):
|
| 700 |
+
'od.__delitem__(y) <==> del od[y]'
|
| 701 |
+
# Deleting an existing item uses self.__map to find the link which is
|
| 702 |
+
# then removed by updating the links in the predecessor and successor nodes.
|
| 703 |
+
dict_delitem(self, key)
|
| 704 |
+
link_prev, link_next, key = self.__map.pop(key)
|
| 705 |
+
link_prev[1] = link_next
|
| 706 |
+
link_next[0] = link_prev
|
| 707 |
+
|
| 708 |
+
def __iter__(self):
|
| 709 |
+
'od.__iter__() <==> iter(od)'
|
| 710 |
+
root = self.__root
|
| 711 |
+
curr = root[1]
|
| 712 |
+
while curr is not root:
|
| 713 |
+
yield curr[2]
|
| 714 |
+
curr = curr[1]
|
| 715 |
+
|
| 716 |
+
def __reversed__(self):
|
| 717 |
+
'od.__reversed__() <==> reversed(od)'
|
| 718 |
+
root = self.__root
|
| 719 |
+
curr = root[0]
|
| 720 |
+
while curr is not root:
|
| 721 |
+
yield curr[2]
|
| 722 |
+
curr = curr[0]
|
| 723 |
+
|
| 724 |
+
def clear(self):
|
| 725 |
+
'od.clear() -> None. Remove all items from od.'
|
| 726 |
+
try:
|
| 727 |
+
for node in self.__map.itervalues():
|
| 728 |
+
del node[:]
|
| 729 |
+
root = self.__root
|
| 730 |
+
root[:] = [root, root, None]
|
| 731 |
+
self.__map.clear()
|
| 732 |
+
except AttributeError:
|
| 733 |
+
pass
|
| 734 |
+
dict.clear(self)
|
| 735 |
+
|
| 736 |
+
def popitem(self, last=True):
|
| 737 |
+
'''od.popitem() -> (k, v), return and remove a (key, value) pair.
|
| 738 |
+
Pairs are returned in LIFO order if last is true or FIFO order if false.
|
| 739 |
+
|
| 740 |
+
'''
|
| 741 |
+
if not self:
|
| 742 |
+
raise KeyError('dictionary is empty')
|
| 743 |
+
root = self.__root
|
| 744 |
+
if last:
|
| 745 |
+
link = root[0]
|
| 746 |
+
link_prev = link[0]
|
| 747 |
+
link_prev[1] = root
|
| 748 |
+
root[0] = link_prev
|
| 749 |
+
else:
|
| 750 |
+
link = root[1]
|
| 751 |
+
link_next = link[1]
|
| 752 |
+
root[1] = link_next
|
| 753 |
+
link_next[0] = root
|
| 754 |
+
key = link[2]
|
| 755 |
+
del self.__map[key]
|
| 756 |
+
value = dict.pop(self, key)
|
| 757 |
+
return key, value
|
| 758 |
+
|
| 759 |
+
# -- the following methods do not depend on the internal structure --
|
| 760 |
+
|
| 761 |
+
def keys(self):
|
| 762 |
+
'od.keys() -> list of keys in od'
|
| 763 |
+
return list(self)
|
| 764 |
+
|
| 765 |
+
def values(self):
|
| 766 |
+
'od.values() -> list of values in od'
|
| 767 |
+
return [self[key] for key in self]
|
| 768 |
+
|
| 769 |
+
def items(self):
|
| 770 |
+
'od.items() -> list of (key, value) pairs in od'
|
| 771 |
+
return [(key, self[key]) for key in self]
|
| 772 |
+
|
| 773 |
+
def iterkeys(self):
|
| 774 |
+
'od.iterkeys() -> an iterator over the keys in od'
|
| 775 |
+
return iter(self)
|
| 776 |
+
|
| 777 |
+
def itervalues(self):
|
| 778 |
+
'od.itervalues -> an iterator over the values in od'
|
| 779 |
+
for k in self:
|
| 780 |
+
yield self[k]
|
| 781 |
+
|
| 782 |
+
def iteritems(self):
|
| 783 |
+
'od.iteritems -> an iterator over the (key, value) items in od'
|
| 784 |
+
for k in self:
|
| 785 |
+
yield (k, self[k])
|
| 786 |
+
|
| 787 |
+
def update(*args, **kwds):
|
| 788 |
+
'''od.update(E, **F) -> None. Update od from dict/iterable E and F.
|
| 789 |
+
|
| 790 |
+
If E is a dict instance, does: for k in E: od[k] = E[k]
|
| 791 |
+
If E has a .keys() method, does: for k in E.keys(): od[k] = E[k]
|
| 792 |
+
Or if E is an iterable of items, does: for k, v in E: od[k] = v
|
| 793 |
+
In either case, this is followed by: for k, v in F.items(): od[k] = v
|
| 794 |
+
|
| 795 |
+
'''
|
| 796 |
+
if len(args) > 2:
|
| 797 |
+
raise TypeError('update() takes at most 2 positional '
|
| 798 |
+
'arguments (%d given)' % (len(args), ))
|
| 799 |
+
elif not args:
|
| 800 |
+
raise TypeError('update() takes at least 1 argument (0 given)')
|
| 801 |
+
self = args[0]
|
| 802 |
+
# Make progressively weaker assumptions about "other"
|
| 803 |
+
other = ()
|
| 804 |
+
if len(args) == 2:
|
| 805 |
+
other = args[1]
|
| 806 |
+
if isinstance(other, dict):
|
| 807 |
+
for key in other:
|
| 808 |
+
self[key] = other[key]
|
| 809 |
+
elif hasattr(other, 'keys'):
|
| 810 |
+
for key in other.keys():
|
| 811 |
+
self[key] = other[key]
|
| 812 |
+
else:
|
| 813 |
+
for key, value in other:
|
| 814 |
+
self[key] = value
|
| 815 |
+
for key, value in kwds.items():
|
| 816 |
+
self[key] = value
|
| 817 |
+
|
| 818 |
+
__update = update # let subclasses override update without breaking __init__
|
| 819 |
+
|
| 820 |
+
__marker = object()
|
| 821 |
+
|
| 822 |
+
def pop(self, key, default=__marker):
|
| 823 |
+
'''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
|
| 824 |
+
If key is not found, d is returned if given, otherwise KeyError is raised.
|
| 825 |
+
|
| 826 |
+
'''
|
| 827 |
+
if key in self:
|
| 828 |
+
result = self[key]
|
| 829 |
+
del self[key]
|
| 830 |
+
return result
|
| 831 |
+
if default is self.__marker:
|
| 832 |
+
raise KeyError(key)
|
| 833 |
+
return default
|
| 834 |
+
|
| 835 |
+
def setdefault(self, key, default=None):
|
| 836 |
+
'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
|
| 837 |
+
if key in self:
|
| 838 |
+
return self[key]
|
| 839 |
+
self[key] = default
|
| 840 |
+
return default
|
| 841 |
+
|
| 842 |
+
def __repr__(self, _repr_running=None):
|
| 843 |
+
'od.__repr__() <==> repr(od)'
|
| 844 |
+
if not _repr_running:
|
| 845 |
+
_repr_running = {}
|
| 846 |
+
call_key = id(self), _get_ident()
|
| 847 |
+
if call_key in _repr_running:
|
| 848 |
+
return '...'
|
| 849 |
+
_repr_running[call_key] = 1
|
| 850 |
+
try:
|
| 851 |
+
if not self:
|
| 852 |
+
return '%s()' % (self.__class__.__name__, )
|
| 853 |
+
return '%s(%r)' % (self.__class__.__name__, self.items())
|
| 854 |
+
finally:
|
| 855 |
+
del _repr_running[call_key]
|
| 856 |
+
|
| 857 |
+
def __reduce__(self):
|
| 858 |
+
'Return state information for pickling'
|
| 859 |
+
items = [[k, self[k]] for k in self]
|
| 860 |
+
inst_dict = vars(self).copy()
|
| 861 |
+
for k in vars(OrderedDict()):
|
| 862 |
+
inst_dict.pop(k, None)
|
| 863 |
+
if inst_dict:
|
| 864 |
+
return (self.__class__, (items, ), inst_dict)
|
| 865 |
+
return self.__class__, (items, )
|
| 866 |
+
|
| 867 |
+
def copy(self):
|
| 868 |
+
'od.copy() -> a shallow copy of od'
|
| 869 |
+
return self.__class__(self)
|
| 870 |
+
|
| 871 |
+
@classmethod
|
| 872 |
+
def fromkeys(cls, iterable, value=None):
|
| 873 |
+
'''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
|
| 874 |
+
and values equal to v (which defaults to None).
|
| 875 |
+
|
| 876 |
+
'''
|
| 877 |
+
d = cls()
|
| 878 |
+
for key in iterable:
|
| 879 |
+
d[key] = value
|
| 880 |
+
return d
|
| 881 |
+
|
| 882 |
+
def __eq__(self, other):
|
| 883 |
+
'''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
|
| 884 |
+
while comparison to a regular mapping is order-insensitive.
|
| 885 |
+
|
| 886 |
+
'''
|
| 887 |
+
if isinstance(other, OrderedDict):
|
| 888 |
+
return len(self) == len(
|
| 889 |
+
other) and self.items() == other.items()
|
| 890 |
+
return dict.__eq__(self, other)
|
| 891 |
+
|
| 892 |
+
def __ne__(self, other):
|
| 893 |
+
return not self == other
|
| 894 |
+
|
| 895 |
+
# -- the following methods are only used in Python 2.7 --
|
| 896 |
+
|
| 897 |
+
def viewkeys(self):
|
| 898 |
+
"od.viewkeys() -> a set-like object providing a view on od's keys"
|
| 899 |
+
return KeysView(self)
|
| 900 |
+
|
| 901 |
+
def viewvalues(self):
|
| 902 |
+
"od.viewvalues() -> an object providing a view on od's values"
|
| 903 |
+
return ValuesView(self)
|
| 904 |
+
|
| 905 |
+
def viewitems(self):
|
| 906 |
+
"od.viewitems() -> a set-like object providing a view on od's items"
|
| 907 |
+
return ItemsView(self)
|
| 908 |
+
|
| 909 |
+
|
| 910 |
+
try:
|
| 911 |
+
from logging.config import BaseConfigurator, valid_ident
|
| 912 |
+
except ImportError: # pragma: no cover
|
| 913 |
+
IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
|
| 914 |
+
|
| 915 |
+
def valid_ident(s):
|
| 916 |
+
m = IDENTIFIER.match(s)
|
| 917 |
+
if not m:
|
| 918 |
+
raise ValueError('Not a valid Python identifier: %r' % s)
|
| 919 |
+
return True
|
| 920 |
+
|
| 921 |
+
# The ConvertingXXX classes are wrappers around standard Python containers,
|
| 922 |
+
# and they serve to convert any suitable values in the container. The
|
| 923 |
+
# conversion converts base dicts, lists and tuples to their wrapped
|
| 924 |
+
# equivalents, whereas strings which match a conversion format are converted
|
| 925 |
+
# appropriately.
|
| 926 |
+
#
|
| 927 |
+
# Each wrapper should have a configurator attribute holding the actual
|
| 928 |
+
# configurator to use for conversion.
|
| 929 |
+
|
| 930 |
+
class ConvertingDict(dict):
|
| 931 |
+
"""A converting dictionary wrapper."""
|
| 932 |
+
|
| 933 |
+
def __getitem__(self, key):
|
| 934 |
+
value = dict.__getitem__(self, key)
|
| 935 |
+
result = self.configurator.convert(value)
|
| 936 |
+
# If the converted value is different, save for next time
|
| 937 |
+
if value is not result:
|
| 938 |
+
self[key] = result
|
| 939 |
+
if type(result) in (ConvertingDict, ConvertingList,
|
| 940 |
+
ConvertingTuple):
|
| 941 |
+
result.parent = self
|
| 942 |
+
result.key = key
|
| 943 |
+
return result
|
| 944 |
+
|
| 945 |
+
def get(self, key, default=None):
|
| 946 |
+
value = dict.get(self, key, default)
|
| 947 |
+
result = self.configurator.convert(value)
|
| 948 |
+
# If the converted value is different, save for next time
|
| 949 |
+
if value is not result:
|
| 950 |
+
self[key] = result
|
| 951 |
+
if type(result) in (ConvertingDict, ConvertingList,
|
| 952 |
+
ConvertingTuple):
|
| 953 |
+
result.parent = self
|
| 954 |
+
result.key = key
|
| 955 |
+
return result
|
| 956 |
+
|
| 957 |
+
def pop(self, key, default=None):
|
| 958 |
+
value = dict.pop(self, key, default)
|
| 959 |
+
result = self.configurator.convert(value)
|
| 960 |
+
if value is not result:
|
| 961 |
+
if type(result) in (ConvertingDict, ConvertingList,
|
| 962 |
+
ConvertingTuple):
|
| 963 |
+
result.parent = self
|
| 964 |
+
result.key = key
|
| 965 |
+
return result
|
| 966 |
+
|
| 967 |
+
class ConvertingList(list):
|
| 968 |
+
"""A converting list wrapper."""
|
| 969 |
+
|
| 970 |
+
def __getitem__(self, key):
|
| 971 |
+
value = list.__getitem__(self, key)
|
| 972 |
+
result = self.configurator.convert(value)
|
| 973 |
+
# If the converted value is different, save for next time
|
| 974 |
+
if value is not result:
|
| 975 |
+
self[key] = result
|
| 976 |
+
if type(result) in (ConvertingDict, ConvertingList,
|
| 977 |
+
ConvertingTuple):
|
| 978 |
+
result.parent = self
|
| 979 |
+
result.key = key
|
| 980 |
+
return result
|
| 981 |
+
|
| 982 |
+
def pop(self, idx=-1):
|
| 983 |
+
value = list.pop(self, idx)
|
| 984 |
+
result = self.configurator.convert(value)
|
| 985 |
+
if value is not result:
|
| 986 |
+
if type(result) in (ConvertingDict, ConvertingList,
|
| 987 |
+
ConvertingTuple):
|
| 988 |
+
result.parent = self
|
| 989 |
+
return result
|
| 990 |
+
|
| 991 |
+
class ConvertingTuple(tuple):
|
| 992 |
+
"""A converting tuple wrapper."""
|
| 993 |
+
|
| 994 |
+
def __getitem__(self, key):
|
| 995 |
+
value = tuple.__getitem__(self, key)
|
| 996 |
+
result = self.configurator.convert(value)
|
| 997 |
+
if value is not result:
|
| 998 |
+
if type(result) in (ConvertingDict, ConvertingList,
|
| 999 |
+
ConvertingTuple):
|
| 1000 |
+
result.parent = self
|
| 1001 |
+
result.key = key
|
| 1002 |
+
return result
|
| 1003 |
+
|
| 1004 |
+
class BaseConfigurator(object):
|
| 1005 |
+
"""
|
| 1006 |
+
The configurator base class which defines some useful defaults.
|
| 1007 |
+
"""
|
| 1008 |
+
|
| 1009 |
+
CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
|
| 1010 |
+
|
| 1011 |
+
WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
|
| 1012 |
+
DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
|
| 1013 |
+
INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
|
| 1014 |
+
DIGIT_PATTERN = re.compile(r'^\d+$')
|
| 1015 |
+
|
| 1016 |
+
value_converters = {
|
| 1017 |
+
'ext': 'ext_convert',
|
| 1018 |
+
'cfg': 'cfg_convert',
|
| 1019 |
+
}
|
| 1020 |
+
|
| 1021 |
+
# We might want to use a different one, e.g. importlib
|
| 1022 |
+
importer = staticmethod(__import__)
|
| 1023 |
+
|
| 1024 |
+
def __init__(self, config):
|
| 1025 |
+
self.config = ConvertingDict(config)
|
| 1026 |
+
self.config.configurator = self
|
| 1027 |
+
|
| 1028 |
+
def resolve(self, s):
|
| 1029 |
+
"""
|
| 1030 |
+
Resolve strings to objects using standard import and attribute
|
| 1031 |
+
syntax.
|
| 1032 |
+
"""
|
| 1033 |
+
name = s.split('.')
|
| 1034 |
+
used = name.pop(0)
|
| 1035 |
+
try:
|
| 1036 |
+
found = self.importer(used)
|
| 1037 |
+
for frag in name:
|
| 1038 |
+
used += '.' + frag
|
| 1039 |
+
try:
|
| 1040 |
+
found = getattr(found, frag)
|
| 1041 |
+
except AttributeError:
|
| 1042 |
+
self.importer(used)
|
| 1043 |
+
found = getattr(found, frag)
|
| 1044 |
+
return found
|
| 1045 |
+
except ImportError:
|
| 1046 |
+
e, tb = sys.exc_info()[1:]
|
| 1047 |
+
v = ValueError('Cannot resolve %r: %s' % (s, e))
|
| 1048 |
+
v.__cause__, v.__traceback__ = e, tb
|
| 1049 |
+
raise v
|
| 1050 |
+
|
| 1051 |
+
def ext_convert(self, value):
|
| 1052 |
+
"""Default converter for the ext:// protocol."""
|
| 1053 |
+
return self.resolve(value)
|
| 1054 |
+
|
| 1055 |
+
def cfg_convert(self, value):
|
| 1056 |
+
"""Default converter for the cfg:// protocol."""
|
| 1057 |
+
rest = value
|
| 1058 |
+
m = self.WORD_PATTERN.match(rest)
|
| 1059 |
+
if m is None:
|
| 1060 |
+
raise ValueError("Unable to convert %r" % value)
|
| 1061 |
+
else:
|
| 1062 |
+
rest = rest[m.end():]
|
| 1063 |
+
d = self.config[m.groups()[0]]
|
| 1064 |
+
while rest:
|
| 1065 |
+
m = self.DOT_PATTERN.match(rest)
|
| 1066 |
+
if m:
|
| 1067 |
+
d = d[m.groups()[0]]
|
| 1068 |
+
else:
|
| 1069 |
+
m = self.INDEX_PATTERN.match(rest)
|
| 1070 |
+
if m:
|
| 1071 |
+
idx = m.groups()[0]
|
| 1072 |
+
if not self.DIGIT_PATTERN.match(idx):
|
| 1073 |
+
d = d[idx]
|
| 1074 |
+
else:
|
| 1075 |
+
try:
|
| 1076 |
+
n = int(
|
| 1077 |
+
idx
|
| 1078 |
+
) # try as number first (most likely)
|
| 1079 |
+
d = d[n]
|
| 1080 |
+
except TypeError:
|
| 1081 |
+
d = d[idx]
|
| 1082 |
+
if m:
|
| 1083 |
+
rest = rest[m.end():]
|
| 1084 |
+
else:
|
| 1085 |
+
raise ValueError('Unable to convert '
|
| 1086 |
+
'%r at %r' % (value, rest))
|
| 1087 |
+
# rest should be empty
|
| 1088 |
+
return d
|
| 1089 |
+
|
| 1090 |
+
def convert(self, value):
|
| 1091 |
+
"""
|
| 1092 |
+
Convert values to an appropriate type. dicts, lists and tuples are
|
| 1093 |
+
replaced by their converting alternatives. Strings are checked to
|
| 1094 |
+
see if they have a conversion format and are converted if they do.
|
| 1095 |
+
"""
|
| 1096 |
+
if not isinstance(value, ConvertingDict) and isinstance(
|
| 1097 |
+
value, dict):
|
| 1098 |
+
value = ConvertingDict(value)
|
| 1099 |
+
value.configurator = self
|
| 1100 |
+
elif not isinstance(value, ConvertingList) and isinstance(
|
| 1101 |
+
value, list):
|
| 1102 |
+
value = ConvertingList(value)
|
| 1103 |
+
value.configurator = self
|
| 1104 |
+
elif not isinstance(value, ConvertingTuple) and isinstance(value, tuple):
|
| 1105 |
+
value = ConvertingTuple(value)
|
| 1106 |
+
value.configurator = self
|
| 1107 |
+
elif isinstance(value, string_types):
|
| 1108 |
+
m = self.CONVERT_PATTERN.match(value)
|
| 1109 |
+
if m:
|
| 1110 |
+
d = m.groupdict()
|
| 1111 |
+
prefix = d['prefix']
|
| 1112 |
+
converter = self.value_converters.get(prefix, None)
|
| 1113 |
+
if converter:
|
| 1114 |
+
suffix = d['suffix']
|
| 1115 |
+
converter = getattr(self, converter)
|
| 1116 |
+
value = converter(suffix)
|
| 1117 |
+
return value
|
| 1118 |
+
|
| 1119 |
+
def configure_custom(self, config):
|
| 1120 |
+
"""Configure an object with a user-supplied factory."""
|
| 1121 |
+
c = config.pop('()')
|
| 1122 |
+
if not callable(c):
|
| 1123 |
+
c = self.resolve(c)
|
| 1124 |
+
props = config.pop('.', None)
|
| 1125 |
+
# Check for valid identifiers
|
| 1126 |
+
kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
|
| 1127 |
+
result = c(**kwargs)
|
| 1128 |
+
if props:
|
| 1129 |
+
for name, value in props.items():
|
| 1130 |
+
setattr(result, name, value)
|
| 1131 |
+
return result
|
| 1132 |
+
|
| 1133 |
+
def as_tuple(self, value):
|
| 1134 |
+
"""Utility function which converts lists to tuples."""
|
| 1135 |
+
if isinstance(value, list):
|
| 1136 |
+
value = tuple(value)
|
| 1137 |
+
return value
|
lib/python3.12/site-packages/pip/_vendor/distlib/database.py
ADDED
|
@@ -0,0 +1,1329 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2012-2023 The Python Software Foundation.
|
| 4 |
+
# See LICENSE.txt and CONTRIBUTORS.txt.
|
| 5 |
+
#
|
| 6 |
+
"""PEP 376 implementation."""
|
| 7 |
+
|
| 8 |
+
from __future__ import unicode_literals
|
| 9 |
+
|
| 10 |
+
import base64
|
| 11 |
+
import codecs
|
| 12 |
+
import contextlib
|
| 13 |
+
import hashlib
|
| 14 |
+
import logging
|
| 15 |
+
import os
|
| 16 |
+
import posixpath
|
| 17 |
+
import sys
|
| 18 |
+
import zipimport
|
| 19 |
+
|
| 20 |
+
from . import DistlibException, resources
|
| 21 |
+
from .compat import StringIO
|
| 22 |
+
from .version import get_scheme, UnsupportedVersionError
|
| 23 |
+
from .metadata import (Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME)
|
| 24 |
+
from .util import (parse_requirement, cached_property, parse_name_and_version, read_exports, write_exports, CSVReader,
|
| 25 |
+
CSVWriter)
|
| 26 |
+
|
| 27 |
+
__all__ = [
|
| 28 |
+
'Distribution', 'BaseInstalledDistribution', 'InstalledDistribution', 'EggInfoDistribution', 'DistributionPath'
|
| 29 |
+
]
|
| 30 |
+
|
| 31 |
+
logger = logging.getLogger(__name__)
|
| 32 |
+
|
| 33 |
+
EXPORTS_FILENAME = 'pydist-exports.json'
|
| 34 |
+
COMMANDS_FILENAME = 'pydist-commands.json'
|
| 35 |
+
|
| 36 |
+
DIST_FILES = ('INSTALLER', METADATA_FILENAME, 'RECORD', 'REQUESTED', 'RESOURCES', EXPORTS_FILENAME, 'SHARED')
|
| 37 |
+
|
| 38 |
+
DISTINFO_EXT = '.dist-info'
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class _Cache(object):
|
| 42 |
+
"""
|
| 43 |
+
A simple cache mapping names and .dist-info paths to distributions
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
def __init__(self):
|
| 47 |
+
"""
|
| 48 |
+
Initialise an instance. There is normally one for each DistributionPath.
|
| 49 |
+
"""
|
| 50 |
+
self.name = {}
|
| 51 |
+
self.path = {}
|
| 52 |
+
self.generated = False
|
| 53 |
+
|
| 54 |
+
def clear(self):
|
| 55 |
+
"""
|
| 56 |
+
Clear the cache, setting it to its initial state.
|
| 57 |
+
"""
|
| 58 |
+
self.name.clear()
|
| 59 |
+
self.path.clear()
|
| 60 |
+
self.generated = False
|
| 61 |
+
|
| 62 |
+
def add(self, dist):
|
| 63 |
+
"""
|
| 64 |
+
Add a distribution to the cache.
|
| 65 |
+
:param dist: The distribution to add.
|
| 66 |
+
"""
|
| 67 |
+
if dist.path not in self.path:
|
| 68 |
+
self.path[dist.path] = dist
|
| 69 |
+
self.name.setdefault(dist.key, []).append(dist)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class DistributionPath(object):
|
| 73 |
+
"""
|
| 74 |
+
Represents a set of distributions installed on a path (typically sys.path).
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
def __init__(self, path=None, include_egg=False):
|
| 78 |
+
"""
|
| 79 |
+
Create an instance from a path, optionally including legacy (distutils/
|
| 80 |
+
setuptools/distribute) distributions.
|
| 81 |
+
:param path: The path to use, as a list of directories. If not specified,
|
| 82 |
+
sys.path is used.
|
| 83 |
+
:param include_egg: If True, this instance will look for and return legacy
|
| 84 |
+
distributions as well as those based on PEP 376.
|
| 85 |
+
"""
|
| 86 |
+
if path is None:
|
| 87 |
+
path = sys.path
|
| 88 |
+
self.path = path
|
| 89 |
+
self._include_dist = True
|
| 90 |
+
self._include_egg = include_egg
|
| 91 |
+
|
| 92 |
+
self._cache = _Cache()
|
| 93 |
+
self._cache_egg = _Cache()
|
| 94 |
+
self._cache_enabled = True
|
| 95 |
+
self._scheme = get_scheme('default')
|
| 96 |
+
|
| 97 |
+
def _get_cache_enabled(self):
|
| 98 |
+
return self._cache_enabled
|
| 99 |
+
|
| 100 |
+
def _set_cache_enabled(self, value):
|
| 101 |
+
self._cache_enabled = value
|
| 102 |
+
|
| 103 |
+
cache_enabled = property(_get_cache_enabled, _set_cache_enabled)
|
| 104 |
+
|
| 105 |
+
def clear_cache(self):
|
| 106 |
+
"""
|
| 107 |
+
Clears the internal cache.
|
| 108 |
+
"""
|
| 109 |
+
self._cache.clear()
|
| 110 |
+
self._cache_egg.clear()
|
| 111 |
+
|
| 112 |
+
def _yield_distributions(self):
|
| 113 |
+
"""
|
| 114 |
+
Yield .dist-info and/or .egg(-info) distributions.
|
| 115 |
+
"""
|
| 116 |
+
# We need to check if we've seen some resources already, because on
|
| 117 |
+
# some Linux systems (e.g. some Debian/Ubuntu variants) there are
|
| 118 |
+
# symlinks which alias other files in the environment.
|
| 119 |
+
seen = set()
|
| 120 |
+
for path in self.path:
|
| 121 |
+
finder = resources.finder_for_path(path)
|
| 122 |
+
if finder is None:
|
| 123 |
+
continue
|
| 124 |
+
r = finder.find('')
|
| 125 |
+
if not r or not r.is_container:
|
| 126 |
+
continue
|
| 127 |
+
rset = sorted(r.resources)
|
| 128 |
+
for entry in rset:
|
| 129 |
+
r = finder.find(entry)
|
| 130 |
+
if not r or r.path in seen:
|
| 131 |
+
continue
|
| 132 |
+
try:
|
| 133 |
+
if self._include_dist and entry.endswith(DISTINFO_EXT):
|
| 134 |
+
possible_filenames = [METADATA_FILENAME, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME]
|
| 135 |
+
for metadata_filename in possible_filenames:
|
| 136 |
+
metadata_path = posixpath.join(entry, metadata_filename)
|
| 137 |
+
pydist = finder.find(metadata_path)
|
| 138 |
+
if pydist:
|
| 139 |
+
break
|
| 140 |
+
else:
|
| 141 |
+
continue
|
| 142 |
+
|
| 143 |
+
with contextlib.closing(pydist.as_stream()) as stream:
|
| 144 |
+
metadata = Metadata(fileobj=stream, scheme='legacy')
|
| 145 |
+
logger.debug('Found %s', r.path)
|
| 146 |
+
seen.add(r.path)
|
| 147 |
+
yield new_dist_class(r.path, metadata=metadata, env=self)
|
| 148 |
+
elif self._include_egg and entry.endswith(('.egg-info', '.egg')):
|
| 149 |
+
logger.debug('Found %s', r.path)
|
| 150 |
+
seen.add(r.path)
|
| 151 |
+
yield old_dist_class(r.path, self)
|
| 152 |
+
except Exception as e:
|
| 153 |
+
msg = 'Unable to read distribution at %s, perhaps due to bad metadata: %s'
|
| 154 |
+
logger.warning(msg, r.path, e)
|
| 155 |
+
import warnings
|
| 156 |
+
warnings.warn(msg % (r.path, e), stacklevel=2)
|
| 157 |
+
|
| 158 |
+
def _generate_cache(self):
|
| 159 |
+
"""
|
| 160 |
+
Scan the path for distributions and populate the cache with
|
| 161 |
+
those that are found.
|
| 162 |
+
"""
|
| 163 |
+
gen_dist = not self._cache.generated
|
| 164 |
+
gen_egg = self._include_egg and not self._cache_egg.generated
|
| 165 |
+
if gen_dist or gen_egg:
|
| 166 |
+
for dist in self._yield_distributions():
|
| 167 |
+
if isinstance(dist, InstalledDistribution):
|
| 168 |
+
self._cache.add(dist)
|
| 169 |
+
else:
|
| 170 |
+
self._cache_egg.add(dist)
|
| 171 |
+
|
| 172 |
+
if gen_dist:
|
| 173 |
+
self._cache.generated = True
|
| 174 |
+
if gen_egg:
|
| 175 |
+
self._cache_egg.generated = True
|
| 176 |
+
|
| 177 |
+
@classmethod
|
| 178 |
+
def distinfo_dirname(cls, name, version):
|
| 179 |
+
"""
|
| 180 |
+
The *name* and *version* parameters are converted into their
|
| 181 |
+
filename-escaped form, i.e. any ``'-'`` characters are replaced
|
| 182 |
+
with ``'_'`` other than the one in ``'dist-info'`` and the one
|
| 183 |
+
separating the name from the version number.
|
| 184 |
+
|
| 185 |
+
:parameter name: is converted to a standard distribution name by replacing
|
| 186 |
+
any runs of non- alphanumeric characters with a single
|
| 187 |
+
``'-'``.
|
| 188 |
+
:type name: string
|
| 189 |
+
:parameter version: is converted to a standard version string. Spaces
|
| 190 |
+
become dots, and all other non-alphanumeric characters
|
| 191 |
+
(except dots) become dashes, with runs of multiple
|
| 192 |
+
dashes condensed to a single dash.
|
| 193 |
+
:type version: string
|
| 194 |
+
:returns: directory name
|
| 195 |
+
:rtype: string"""
|
| 196 |
+
name = name.replace('-', '_')
|
| 197 |
+
return '-'.join([name, version]) + DISTINFO_EXT
|
| 198 |
+
|
| 199 |
+
def get_distributions(self):
|
| 200 |
+
"""
|
| 201 |
+
Provides an iterator that looks for distributions and returns
|
| 202 |
+
:class:`InstalledDistribution` or
|
| 203 |
+
:class:`EggInfoDistribution` instances for each one of them.
|
| 204 |
+
|
| 205 |
+
:rtype: iterator of :class:`InstalledDistribution` and
|
| 206 |
+
:class:`EggInfoDistribution` instances
|
| 207 |
+
"""
|
| 208 |
+
if not self._cache_enabled:
|
| 209 |
+
for dist in self._yield_distributions():
|
| 210 |
+
yield dist
|
| 211 |
+
else:
|
| 212 |
+
self._generate_cache()
|
| 213 |
+
|
| 214 |
+
for dist in self._cache.path.values():
|
| 215 |
+
yield dist
|
| 216 |
+
|
| 217 |
+
if self._include_egg:
|
| 218 |
+
for dist in self._cache_egg.path.values():
|
| 219 |
+
yield dist
|
| 220 |
+
|
| 221 |
+
def get_distribution(self, name):
|
| 222 |
+
"""
|
| 223 |
+
Looks for a named distribution on the path.
|
| 224 |
+
|
| 225 |
+
This function only returns the first result found, as no more than one
|
| 226 |
+
value is expected. If nothing is found, ``None`` is returned.
|
| 227 |
+
|
| 228 |
+
:rtype: :class:`InstalledDistribution`, :class:`EggInfoDistribution`
|
| 229 |
+
or ``None``
|
| 230 |
+
"""
|
| 231 |
+
result = None
|
| 232 |
+
name = name.lower()
|
| 233 |
+
if not self._cache_enabled:
|
| 234 |
+
for dist in self._yield_distributions():
|
| 235 |
+
if dist.key == name:
|
| 236 |
+
result = dist
|
| 237 |
+
break
|
| 238 |
+
else:
|
| 239 |
+
self._generate_cache()
|
| 240 |
+
|
| 241 |
+
if name in self._cache.name:
|
| 242 |
+
result = self._cache.name[name][0]
|
| 243 |
+
elif self._include_egg and name in self._cache_egg.name:
|
| 244 |
+
result = self._cache_egg.name[name][0]
|
| 245 |
+
return result
|
| 246 |
+
|
| 247 |
+
def provides_distribution(self, name, version=None):
|
| 248 |
+
"""
|
| 249 |
+
Iterates over all distributions to find which distributions provide *name*.
|
| 250 |
+
If a *version* is provided, it will be used to filter the results.
|
| 251 |
+
|
| 252 |
+
This function only returns the first result found, since no more than
|
| 253 |
+
one values are expected. If the directory is not found, returns ``None``.
|
| 254 |
+
|
| 255 |
+
:parameter version: a version specifier that indicates the version
|
| 256 |
+
required, conforming to the format in ``PEP-345``
|
| 257 |
+
|
| 258 |
+
:type name: string
|
| 259 |
+
:type version: string
|
| 260 |
+
"""
|
| 261 |
+
matcher = None
|
| 262 |
+
if version is not None:
|
| 263 |
+
try:
|
| 264 |
+
matcher = self._scheme.matcher('%s (%s)' % (name, version))
|
| 265 |
+
except ValueError:
|
| 266 |
+
raise DistlibException('invalid name or version: %r, %r' % (name, version))
|
| 267 |
+
|
| 268 |
+
for dist in self.get_distributions():
|
| 269 |
+
# We hit a problem on Travis where enum34 was installed and doesn't
|
| 270 |
+
# have a provides attribute ...
|
| 271 |
+
if not hasattr(dist, 'provides'):
|
| 272 |
+
logger.debug('No "provides": %s', dist)
|
| 273 |
+
else:
|
| 274 |
+
provided = dist.provides
|
| 275 |
+
|
| 276 |
+
for p in provided:
|
| 277 |
+
p_name, p_ver = parse_name_and_version(p)
|
| 278 |
+
if matcher is None:
|
| 279 |
+
if p_name == name:
|
| 280 |
+
yield dist
|
| 281 |
+
break
|
| 282 |
+
else:
|
| 283 |
+
if p_name == name and matcher.match(p_ver):
|
| 284 |
+
yield dist
|
| 285 |
+
break
|
| 286 |
+
|
| 287 |
+
def get_file_path(self, name, relative_path):
|
| 288 |
+
"""
|
| 289 |
+
Return the path to a resource file.
|
| 290 |
+
"""
|
| 291 |
+
dist = self.get_distribution(name)
|
| 292 |
+
if dist is None:
|
| 293 |
+
raise LookupError('no distribution named %r found' % name)
|
| 294 |
+
return dist.get_resource_path(relative_path)
|
| 295 |
+
|
| 296 |
+
def get_exported_entries(self, category, name=None):
|
| 297 |
+
"""
|
| 298 |
+
Return all of the exported entries in a particular category.
|
| 299 |
+
|
| 300 |
+
:param category: The category to search for entries.
|
| 301 |
+
:param name: If specified, only entries with that name are returned.
|
| 302 |
+
"""
|
| 303 |
+
for dist in self.get_distributions():
|
| 304 |
+
r = dist.exports
|
| 305 |
+
if category in r:
|
| 306 |
+
d = r[category]
|
| 307 |
+
if name is not None:
|
| 308 |
+
if name in d:
|
| 309 |
+
yield d[name]
|
| 310 |
+
else:
|
| 311 |
+
for v in d.values():
|
| 312 |
+
yield v
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
class Distribution(object):
|
| 316 |
+
"""
|
| 317 |
+
A base class for distributions, whether installed or from indexes.
|
| 318 |
+
Either way, it must have some metadata, so that's all that's needed
|
| 319 |
+
for construction.
|
| 320 |
+
"""
|
| 321 |
+
|
| 322 |
+
build_time_dependency = False
|
| 323 |
+
"""
|
| 324 |
+
Set to True if it's known to be only a build-time dependency (i.e.
|
| 325 |
+
not needed after installation).
|
| 326 |
+
"""
|
| 327 |
+
|
| 328 |
+
requested = False
|
| 329 |
+
"""A boolean that indicates whether the ``REQUESTED`` metadata file is
|
| 330 |
+
present (in other words, whether the package was installed by user
|
| 331 |
+
request or it was installed as a dependency)."""
|
| 332 |
+
|
| 333 |
+
def __init__(self, metadata):
|
| 334 |
+
"""
|
| 335 |
+
Initialise an instance.
|
| 336 |
+
:param metadata: The instance of :class:`Metadata` describing this
|
| 337 |
+
distribution.
|
| 338 |
+
"""
|
| 339 |
+
self.metadata = metadata
|
| 340 |
+
self.name = metadata.name
|
| 341 |
+
self.key = self.name.lower() # for case-insensitive comparisons
|
| 342 |
+
self.version = metadata.version
|
| 343 |
+
self.locator = None
|
| 344 |
+
self.digest = None
|
| 345 |
+
self.extras = None # additional features requested
|
| 346 |
+
self.context = None # environment marker overrides
|
| 347 |
+
self.download_urls = set()
|
| 348 |
+
self.digests = {}
|
| 349 |
+
|
| 350 |
+
@property
|
| 351 |
+
def source_url(self):
|
| 352 |
+
"""
|
| 353 |
+
The source archive download URL for this distribution.
|
| 354 |
+
"""
|
| 355 |
+
return self.metadata.source_url
|
| 356 |
+
|
| 357 |
+
download_url = source_url # Backward compatibility
|
| 358 |
+
|
| 359 |
+
@property
|
| 360 |
+
def name_and_version(self):
|
| 361 |
+
"""
|
| 362 |
+
A utility property which displays the name and version in parentheses.
|
| 363 |
+
"""
|
| 364 |
+
return '%s (%s)' % (self.name, self.version)
|
| 365 |
+
|
| 366 |
+
@property
|
| 367 |
+
def provides(self):
|
| 368 |
+
"""
|
| 369 |
+
A set of distribution names and versions provided by this distribution.
|
| 370 |
+
:return: A set of "name (version)" strings.
|
| 371 |
+
"""
|
| 372 |
+
plist = self.metadata.provides
|
| 373 |
+
s = '%s (%s)' % (self.name, self.version)
|
| 374 |
+
if s not in plist:
|
| 375 |
+
plist.append(s)
|
| 376 |
+
return plist
|
| 377 |
+
|
| 378 |
+
def _get_requirements(self, req_attr):
|
| 379 |
+
md = self.metadata
|
| 380 |
+
reqts = getattr(md, req_attr)
|
| 381 |
+
logger.debug('%s: got requirements %r from metadata: %r', self.name, req_attr, reqts)
|
| 382 |
+
return set(md.get_requirements(reqts, extras=self.extras, env=self.context))
|
| 383 |
+
|
| 384 |
+
@property
|
| 385 |
+
def run_requires(self):
|
| 386 |
+
return self._get_requirements('run_requires')
|
| 387 |
+
|
| 388 |
+
@property
|
| 389 |
+
def meta_requires(self):
|
| 390 |
+
return self._get_requirements('meta_requires')
|
| 391 |
+
|
| 392 |
+
@property
|
| 393 |
+
def build_requires(self):
|
| 394 |
+
return self._get_requirements('build_requires')
|
| 395 |
+
|
| 396 |
+
@property
|
| 397 |
+
def test_requires(self):
|
| 398 |
+
return self._get_requirements('test_requires')
|
| 399 |
+
|
| 400 |
+
@property
|
| 401 |
+
def dev_requires(self):
|
| 402 |
+
return self._get_requirements('dev_requires')
|
| 403 |
+
|
| 404 |
+
def matches_requirement(self, req):
|
| 405 |
+
"""
|
| 406 |
+
Say if this instance matches (fulfills) a requirement.
|
| 407 |
+
:param req: The requirement to match.
|
| 408 |
+
:rtype req: str
|
| 409 |
+
:return: True if it matches, else False.
|
| 410 |
+
"""
|
| 411 |
+
# Requirement may contain extras - parse to lose those
|
| 412 |
+
# from what's passed to the matcher
|
| 413 |
+
r = parse_requirement(req)
|
| 414 |
+
scheme = get_scheme(self.metadata.scheme)
|
| 415 |
+
try:
|
| 416 |
+
matcher = scheme.matcher(r.requirement)
|
| 417 |
+
except UnsupportedVersionError:
|
| 418 |
+
# XXX compat-mode if cannot read the version
|
| 419 |
+
logger.warning('could not read version %r - using name only', req)
|
| 420 |
+
name = req.split()[0]
|
| 421 |
+
matcher = scheme.matcher(name)
|
| 422 |
+
|
| 423 |
+
name = matcher.key # case-insensitive
|
| 424 |
+
|
| 425 |
+
result = False
|
| 426 |
+
for p in self.provides:
|
| 427 |
+
p_name, p_ver = parse_name_and_version(p)
|
| 428 |
+
if p_name != name:
|
| 429 |
+
continue
|
| 430 |
+
try:
|
| 431 |
+
result = matcher.match(p_ver)
|
| 432 |
+
break
|
| 433 |
+
except UnsupportedVersionError:
|
| 434 |
+
pass
|
| 435 |
+
return result
|
| 436 |
+
|
| 437 |
+
def __repr__(self):
|
| 438 |
+
"""
|
| 439 |
+
Return a textual representation of this instance,
|
| 440 |
+
"""
|
| 441 |
+
if self.source_url:
|
| 442 |
+
suffix = ' [%s]' % self.source_url
|
| 443 |
+
else:
|
| 444 |
+
suffix = ''
|
| 445 |
+
return '<Distribution %s (%s)%s>' % (self.name, self.version, suffix)
|
| 446 |
+
|
| 447 |
+
def __eq__(self, other):
|
| 448 |
+
"""
|
| 449 |
+
See if this distribution is the same as another.
|
| 450 |
+
:param other: The distribution to compare with. To be equal to one
|
| 451 |
+
another. distributions must have the same type, name,
|
| 452 |
+
version and source_url.
|
| 453 |
+
:return: True if it is the same, else False.
|
| 454 |
+
"""
|
| 455 |
+
if type(other) is not type(self):
|
| 456 |
+
result = False
|
| 457 |
+
else:
|
| 458 |
+
result = (self.name == other.name and self.version == other.version and self.source_url == other.source_url)
|
| 459 |
+
return result
|
| 460 |
+
|
| 461 |
+
def __hash__(self):
|
| 462 |
+
"""
|
| 463 |
+
Compute hash in a way which matches the equality test.
|
| 464 |
+
"""
|
| 465 |
+
return hash(self.name) + hash(self.version) + hash(self.source_url)
|
| 466 |
+
|
| 467 |
+
|
| 468 |
+
class BaseInstalledDistribution(Distribution):
|
| 469 |
+
"""
|
| 470 |
+
This is the base class for installed distributions (whether PEP 376 or
|
| 471 |
+
legacy).
|
| 472 |
+
"""
|
| 473 |
+
|
| 474 |
+
hasher = None
|
| 475 |
+
|
| 476 |
+
def __init__(self, metadata, path, env=None):
|
| 477 |
+
"""
|
| 478 |
+
Initialise an instance.
|
| 479 |
+
:param metadata: An instance of :class:`Metadata` which describes the
|
| 480 |
+
distribution. This will normally have been initialised
|
| 481 |
+
from a metadata file in the ``path``.
|
| 482 |
+
:param path: The path of the ``.dist-info`` or ``.egg-info``
|
| 483 |
+
directory for the distribution.
|
| 484 |
+
:param env: This is normally the :class:`DistributionPath`
|
| 485 |
+
instance where this distribution was found.
|
| 486 |
+
"""
|
| 487 |
+
super(BaseInstalledDistribution, self).__init__(metadata)
|
| 488 |
+
self.path = path
|
| 489 |
+
self.dist_path = env
|
| 490 |
+
|
| 491 |
+
def get_hash(self, data, hasher=None):
|
| 492 |
+
"""
|
| 493 |
+
Get the hash of some data, using a particular hash algorithm, if
|
| 494 |
+
specified.
|
| 495 |
+
|
| 496 |
+
:param data: The data to be hashed.
|
| 497 |
+
:type data: bytes
|
| 498 |
+
:param hasher: The name of a hash implementation, supported by hashlib,
|
| 499 |
+
or ``None``. Examples of valid values are ``'sha1'``,
|
| 500 |
+
``'sha224'``, ``'sha384'``, '``sha256'``, ``'md5'`` and
|
| 501 |
+
``'sha512'``. If no hasher is specified, the ``hasher``
|
| 502 |
+
attribute of the :class:`InstalledDistribution` instance
|
| 503 |
+
is used. If the hasher is determined to be ``None``, MD5
|
| 504 |
+
is used as the hashing algorithm.
|
| 505 |
+
:returns: The hash of the data. If a hasher was explicitly specified,
|
| 506 |
+
the returned hash will be prefixed with the specified hasher
|
| 507 |
+
followed by '='.
|
| 508 |
+
:rtype: str
|
| 509 |
+
"""
|
| 510 |
+
if hasher is None:
|
| 511 |
+
hasher = self.hasher
|
| 512 |
+
if hasher is None:
|
| 513 |
+
hasher = hashlib.md5
|
| 514 |
+
prefix = ''
|
| 515 |
+
else:
|
| 516 |
+
hasher = getattr(hashlib, hasher)
|
| 517 |
+
prefix = '%s=' % self.hasher
|
| 518 |
+
digest = hasher(data).digest()
|
| 519 |
+
digest = base64.urlsafe_b64encode(digest).rstrip(b'=').decode('ascii')
|
| 520 |
+
return '%s%s' % (prefix, digest)
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
class InstalledDistribution(BaseInstalledDistribution):
|
| 524 |
+
"""
|
| 525 |
+
Created with the *path* of the ``.dist-info`` directory provided to the
|
| 526 |
+
constructor. It reads the metadata contained in ``pydist.json`` when it is
|
| 527 |
+
instantiated., or uses a passed in Metadata instance (useful for when
|
| 528 |
+
dry-run mode is being used).
|
| 529 |
+
"""
|
| 530 |
+
|
| 531 |
+
hasher = 'sha256'
|
| 532 |
+
|
| 533 |
+
def __init__(self, path, metadata=None, env=None):
|
| 534 |
+
self.modules = []
|
| 535 |
+
self.finder = finder = resources.finder_for_path(path)
|
| 536 |
+
if finder is None:
|
| 537 |
+
raise ValueError('finder unavailable for %s' % path)
|
| 538 |
+
if env and env._cache_enabled and path in env._cache.path:
|
| 539 |
+
metadata = env._cache.path[path].metadata
|
| 540 |
+
elif metadata is None:
|
| 541 |
+
r = finder.find(METADATA_FILENAME)
|
| 542 |
+
# Temporary - for Wheel 0.23 support
|
| 543 |
+
if r is None:
|
| 544 |
+
r = finder.find(WHEEL_METADATA_FILENAME)
|
| 545 |
+
# Temporary - for legacy support
|
| 546 |
+
if r is None:
|
| 547 |
+
r = finder.find(LEGACY_METADATA_FILENAME)
|
| 548 |
+
if r is None:
|
| 549 |
+
raise ValueError('no %s found in %s' % (METADATA_FILENAME, path))
|
| 550 |
+
with contextlib.closing(r.as_stream()) as stream:
|
| 551 |
+
metadata = Metadata(fileobj=stream, scheme='legacy')
|
| 552 |
+
|
| 553 |
+
super(InstalledDistribution, self).__init__(metadata, path, env)
|
| 554 |
+
|
| 555 |
+
if env and env._cache_enabled:
|
| 556 |
+
env._cache.add(self)
|
| 557 |
+
|
| 558 |
+
r = finder.find('REQUESTED')
|
| 559 |
+
self.requested = r is not None
|
| 560 |
+
p = os.path.join(path, 'top_level.txt')
|
| 561 |
+
if os.path.exists(p):
|
| 562 |
+
with open(p, 'rb') as f:
|
| 563 |
+
data = f.read().decode('utf-8')
|
| 564 |
+
self.modules = data.splitlines()
|
| 565 |
+
|
| 566 |
+
def __repr__(self):
|
| 567 |
+
return '<InstalledDistribution %r %s at %r>' % (self.name, self.version, self.path)
|
| 568 |
+
|
| 569 |
+
def __str__(self):
|
| 570 |
+
return "%s %s" % (self.name, self.version)
|
| 571 |
+
|
| 572 |
+
def _get_records(self):
|
| 573 |
+
"""
|
| 574 |
+
Get the list of installed files for the distribution
|
| 575 |
+
:return: A list of tuples of path, hash and size. Note that hash and
|
| 576 |
+
size might be ``None`` for some entries. The path is exactly
|
| 577 |
+
as stored in the file (which is as in PEP 376).
|
| 578 |
+
"""
|
| 579 |
+
results = []
|
| 580 |
+
r = self.get_distinfo_resource('RECORD')
|
| 581 |
+
with contextlib.closing(r.as_stream()) as stream:
|
| 582 |
+
with CSVReader(stream=stream) as record_reader:
|
| 583 |
+
# Base location is parent dir of .dist-info dir
|
| 584 |
+
# base_location = os.path.dirname(self.path)
|
| 585 |
+
# base_location = os.path.abspath(base_location)
|
| 586 |
+
for row in record_reader:
|
| 587 |
+
missing = [None for i in range(len(row), 3)]
|
| 588 |
+
path, checksum, size = row + missing
|
| 589 |
+
# if not os.path.isabs(path):
|
| 590 |
+
# path = path.replace('/', os.sep)
|
| 591 |
+
# path = os.path.join(base_location, path)
|
| 592 |
+
results.append((path, checksum, size))
|
| 593 |
+
return results
|
| 594 |
+
|
| 595 |
+
@cached_property
|
| 596 |
+
def exports(self):
|
| 597 |
+
"""
|
| 598 |
+
Return the information exported by this distribution.
|
| 599 |
+
:return: A dictionary of exports, mapping an export category to a dict
|
| 600 |
+
of :class:`ExportEntry` instances describing the individual
|
| 601 |
+
export entries, and keyed by name.
|
| 602 |
+
"""
|
| 603 |
+
result = {}
|
| 604 |
+
r = self.get_distinfo_resource(EXPORTS_FILENAME)
|
| 605 |
+
if r:
|
| 606 |
+
result = self.read_exports()
|
| 607 |
+
return result
|
| 608 |
+
|
| 609 |
+
def read_exports(self):
|
| 610 |
+
"""
|
| 611 |
+
Read exports data from a file in .ini format.
|
| 612 |
+
|
| 613 |
+
:return: A dictionary of exports, mapping an export category to a list
|
| 614 |
+
of :class:`ExportEntry` instances describing the individual
|
| 615 |
+
export entries.
|
| 616 |
+
"""
|
| 617 |
+
result = {}
|
| 618 |
+
r = self.get_distinfo_resource(EXPORTS_FILENAME)
|
| 619 |
+
if r:
|
| 620 |
+
with contextlib.closing(r.as_stream()) as stream:
|
| 621 |
+
result = read_exports(stream)
|
| 622 |
+
return result
|
| 623 |
+
|
| 624 |
+
def write_exports(self, exports):
|
| 625 |
+
"""
|
| 626 |
+
Write a dictionary of exports to a file in .ini format.
|
| 627 |
+
:param exports: A dictionary of exports, mapping an export category to
|
| 628 |
+
a list of :class:`ExportEntry` instances describing the
|
| 629 |
+
individual export entries.
|
| 630 |
+
"""
|
| 631 |
+
rf = self.get_distinfo_file(EXPORTS_FILENAME)
|
| 632 |
+
with open(rf, 'w') as f:
|
| 633 |
+
write_exports(exports, f)
|
| 634 |
+
|
| 635 |
+
def get_resource_path(self, relative_path):
|
| 636 |
+
"""
|
| 637 |
+
NOTE: This API may change in the future.
|
| 638 |
+
|
| 639 |
+
Return the absolute path to a resource file with the given relative
|
| 640 |
+
path.
|
| 641 |
+
|
| 642 |
+
:param relative_path: The path, relative to .dist-info, of the resource
|
| 643 |
+
of interest.
|
| 644 |
+
:return: The absolute path where the resource is to be found.
|
| 645 |
+
"""
|
| 646 |
+
r = self.get_distinfo_resource('RESOURCES')
|
| 647 |
+
with contextlib.closing(r.as_stream()) as stream:
|
| 648 |
+
with CSVReader(stream=stream) as resources_reader:
|
| 649 |
+
for relative, destination in resources_reader:
|
| 650 |
+
if relative == relative_path:
|
| 651 |
+
return destination
|
| 652 |
+
raise KeyError('no resource file with relative path %r '
|
| 653 |
+
'is installed' % relative_path)
|
| 654 |
+
|
| 655 |
+
def list_installed_files(self):
|
| 656 |
+
"""
|
| 657 |
+
Iterates over the ``RECORD`` entries and returns a tuple
|
| 658 |
+
``(path, hash, size)`` for each line.
|
| 659 |
+
|
| 660 |
+
:returns: iterator of (path, hash, size)
|
| 661 |
+
"""
|
| 662 |
+
for result in self._get_records():
|
| 663 |
+
yield result
|
| 664 |
+
|
| 665 |
+
def write_installed_files(self, paths, prefix, dry_run=False):
|
| 666 |
+
"""
|
| 667 |
+
Writes the ``RECORD`` file, using the ``paths`` iterable passed in. Any
|
| 668 |
+
existing ``RECORD`` file is silently overwritten.
|
| 669 |
+
|
| 670 |
+
prefix is used to determine when to write absolute paths.
|
| 671 |
+
"""
|
| 672 |
+
prefix = os.path.join(prefix, '')
|
| 673 |
+
base = os.path.dirname(self.path)
|
| 674 |
+
base_under_prefix = base.startswith(prefix)
|
| 675 |
+
base = os.path.join(base, '')
|
| 676 |
+
record_path = self.get_distinfo_file('RECORD')
|
| 677 |
+
logger.info('creating %s', record_path)
|
| 678 |
+
if dry_run:
|
| 679 |
+
return None
|
| 680 |
+
with CSVWriter(record_path) as writer:
|
| 681 |
+
for path in paths:
|
| 682 |
+
if os.path.isdir(path) or path.endswith(('.pyc', '.pyo')):
|
| 683 |
+
# do not put size and hash, as in PEP-376
|
| 684 |
+
hash_value = size = ''
|
| 685 |
+
else:
|
| 686 |
+
size = '%d' % os.path.getsize(path)
|
| 687 |
+
with open(path, 'rb') as fp:
|
| 688 |
+
hash_value = self.get_hash(fp.read())
|
| 689 |
+
if path.startswith(base) or (base_under_prefix and path.startswith(prefix)):
|
| 690 |
+
path = os.path.relpath(path, base)
|
| 691 |
+
writer.writerow((path, hash_value, size))
|
| 692 |
+
|
| 693 |
+
# add the RECORD file itself
|
| 694 |
+
if record_path.startswith(base):
|
| 695 |
+
record_path = os.path.relpath(record_path, base)
|
| 696 |
+
writer.writerow((record_path, '', ''))
|
| 697 |
+
return record_path
|
| 698 |
+
|
| 699 |
+
def check_installed_files(self):
|
| 700 |
+
"""
|
| 701 |
+
Checks that the hashes and sizes of the files in ``RECORD`` are
|
| 702 |
+
matched by the files themselves. Returns a (possibly empty) list of
|
| 703 |
+
mismatches. Each entry in the mismatch list will be a tuple consisting
|
| 704 |
+
of the path, 'exists', 'size' or 'hash' according to what didn't match
|
| 705 |
+
(existence is checked first, then size, then hash), the expected
|
| 706 |
+
value and the actual value.
|
| 707 |
+
"""
|
| 708 |
+
mismatches = []
|
| 709 |
+
base = os.path.dirname(self.path)
|
| 710 |
+
record_path = self.get_distinfo_file('RECORD')
|
| 711 |
+
for path, hash_value, size in self.list_installed_files():
|
| 712 |
+
if not os.path.isabs(path):
|
| 713 |
+
path = os.path.join(base, path)
|
| 714 |
+
if path == record_path:
|
| 715 |
+
continue
|
| 716 |
+
if not os.path.exists(path):
|
| 717 |
+
mismatches.append((path, 'exists', True, False))
|
| 718 |
+
elif os.path.isfile(path):
|
| 719 |
+
actual_size = str(os.path.getsize(path))
|
| 720 |
+
if size and actual_size != size:
|
| 721 |
+
mismatches.append((path, 'size', size, actual_size))
|
| 722 |
+
elif hash_value:
|
| 723 |
+
if '=' in hash_value:
|
| 724 |
+
hasher = hash_value.split('=', 1)[0]
|
| 725 |
+
else:
|
| 726 |
+
hasher = None
|
| 727 |
+
|
| 728 |
+
with open(path, 'rb') as f:
|
| 729 |
+
actual_hash = self.get_hash(f.read(), hasher)
|
| 730 |
+
if actual_hash != hash_value:
|
| 731 |
+
mismatches.append((path, 'hash', hash_value, actual_hash))
|
| 732 |
+
return mismatches
|
| 733 |
+
|
| 734 |
+
@cached_property
|
| 735 |
+
def shared_locations(self):
|
| 736 |
+
"""
|
| 737 |
+
A dictionary of shared locations whose keys are in the set 'prefix',
|
| 738 |
+
'purelib', 'platlib', 'scripts', 'headers', 'data' and 'namespace'.
|
| 739 |
+
The corresponding value is the absolute path of that category for
|
| 740 |
+
this distribution, and takes into account any paths selected by the
|
| 741 |
+
user at installation time (e.g. via command-line arguments). In the
|
| 742 |
+
case of the 'namespace' key, this would be a list of absolute paths
|
| 743 |
+
for the roots of namespace packages in this distribution.
|
| 744 |
+
|
| 745 |
+
The first time this property is accessed, the relevant information is
|
| 746 |
+
read from the SHARED file in the .dist-info directory.
|
| 747 |
+
"""
|
| 748 |
+
result = {}
|
| 749 |
+
shared_path = os.path.join(self.path, 'SHARED')
|
| 750 |
+
if os.path.isfile(shared_path):
|
| 751 |
+
with codecs.open(shared_path, 'r', encoding='utf-8') as f:
|
| 752 |
+
lines = f.read().splitlines()
|
| 753 |
+
for line in lines:
|
| 754 |
+
key, value = line.split('=', 1)
|
| 755 |
+
if key == 'namespace':
|
| 756 |
+
result.setdefault(key, []).append(value)
|
| 757 |
+
else:
|
| 758 |
+
result[key] = value
|
| 759 |
+
return result
|
| 760 |
+
|
| 761 |
+
def write_shared_locations(self, paths, dry_run=False):
|
| 762 |
+
"""
|
| 763 |
+
Write shared location information to the SHARED file in .dist-info.
|
| 764 |
+
:param paths: A dictionary as described in the documentation for
|
| 765 |
+
:meth:`shared_locations`.
|
| 766 |
+
:param dry_run: If True, the action is logged but no file is actually
|
| 767 |
+
written.
|
| 768 |
+
:return: The path of the file written to.
|
| 769 |
+
"""
|
| 770 |
+
shared_path = os.path.join(self.path, 'SHARED')
|
| 771 |
+
logger.info('creating %s', shared_path)
|
| 772 |
+
if dry_run:
|
| 773 |
+
return None
|
| 774 |
+
lines = []
|
| 775 |
+
for key in ('prefix', 'lib', 'headers', 'scripts', 'data'):
|
| 776 |
+
path = paths[key]
|
| 777 |
+
if os.path.isdir(paths[key]):
|
| 778 |
+
lines.append('%s=%s' % (key, path))
|
| 779 |
+
for ns in paths.get('namespace', ()):
|
| 780 |
+
lines.append('namespace=%s' % ns)
|
| 781 |
+
|
| 782 |
+
with codecs.open(shared_path, 'w', encoding='utf-8') as f:
|
| 783 |
+
f.write('\n'.join(lines))
|
| 784 |
+
return shared_path
|
| 785 |
+
|
| 786 |
+
def get_distinfo_resource(self, path):
|
| 787 |
+
if path not in DIST_FILES:
|
| 788 |
+
raise DistlibException('invalid path for a dist-info file: '
|
| 789 |
+
'%r at %r' % (path, self.path))
|
| 790 |
+
finder = resources.finder_for_path(self.path)
|
| 791 |
+
if finder is None:
|
| 792 |
+
raise DistlibException('Unable to get a finder for %s' % self.path)
|
| 793 |
+
return finder.find(path)
|
| 794 |
+
|
| 795 |
+
def get_distinfo_file(self, path):
|
| 796 |
+
"""
|
| 797 |
+
Returns a path located under the ``.dist-info`` directory. Returns a
|
| 798 |
+
string representing the path.
|
| 799 |
+
|
| 800 |
+
:parameter path: a ``'/'``-separated path relative to the
|
| 801 |
+
``.dist-info`` directory or an absolute path;
|
| 802 |
+
If *path* is an absolute path and doesn't start
|
| 803 |
+
with the ``.dist-info`` directory path,
|
| 804 |
+
a :class:`DistlibException` is raised
|
| 805 |
+
:type path: str
|
| 806 |
+
:rtype: str
|
| 807 |
+
"""
|
| 808 |
+
# Check if it is an absolute path # XXX use relpath, add tests
|
| 809 |
+
if path.find(os.sep) >= 0:
|
| 810 |
+
# it's an absolute path?
|
| 811 |
+
distinfo_dirname, path = path.split(os.sep)[-2:]
|
| 812 |
+
if distinfo_dirname != self.path.split(os.sep)[-1]:
|
| 813 |
+
raise DistlibException('dist-info file %r does not belong to the %r %s '
|
| 814 |
+
'distribution' % (path, self.name, self.version))
|
| 815 |
+
|
| 816 |
+
# The file must be relative
|
| 817 |
+
if path not in DIST_FILES:
|
| 818 |
+
raise DistlibException('invalid path for a dist-info file: '
|
| 819 |
+
'%r at %r' % (path, self.path))
|
| 820 |
+
|
| 821 |
+
return os.path.join(self.path, path)
|
| 822 |
+
|
| 823 |
+
def list_distinfo_files(self):
|
| 824 |
+
"""
|
| 825 |
+
Iterates over the ``RECORD`` entries and returns paths for each line if
|
| 826 |
+
the path is pointing to a file located in the ``.dist-info`` directory
|
| 827 |
+
or one of its subdirectories.
|
| 828 |
+
|
| 829 |
+
:returns: iterator of paths
|
| 830 |
+
"""
|
| 831 |
+
base = os.path.dirname(self.path)
|
| 832 |
+
for path, checksum, size in self._get_records():
|
| 833 |
+
# XXX add separator or use real relpath algo
|
| 834 |
+
if not os.path.isabs(path):
|
| 835 |
+
path = os.path.join(base, path)
|
| 836 |
+
if path.startswith(self.path):
|
| 837 |
+
yield path
|
| 838 |
+
|
| 839 |
+
def __eq__(self, other):
|
| 840 |
+
return (isinstance(other, InstalledDistribution) and self.path == other.path)
|
| 841 |
+
|
| 842 |
+
# See http://docs.python.org/reference/datamodel#object.__hash__
|
| 843 |
+
__hash__ = object.__hash__
|
| 844 |
+
|
| 845 |
+
|
| 846 |
+
class EggInfoDistribution(BaseInstalledDistribution):
|
| 847 |
+
"""Created with the *path* of the ``.egg-info`` directory or file provided
|
| 848 |
+
to the constructor. It reads the metadata contained in the file itself, or
|
| 849 |
+
if the given path happens to be a directory, the metadata is read from the
|
| 850 |
+
file ``PKG-INFO`` under that directory."""
|
| 851 |
+
|
| 852 |
+
requested = True # as we have no way of knowing, assume it was
|
| 853 |
+
shared_locations = {}
|
| 854 |
+
|
| 855 |
+
def __init__(self, path, env=None):
|
| 856 |
+
|
| 857 |
+
def set_name_and_version(s, n, v):
|
| 858 |
+
s.name = n
|
| 859 |
+
s.key = n.lower() # for case-insensitive comparisons
|
| 860 |
+
s.version = v
|
| 861 |
+
|
| 862 |
+
self.path = path
|
| 863 |
+
self.dist_path = env
|
| 864 |
+
if env and env._cache_enabled and path in env._cache_egg.path:
|
| 865 |
+
metadata = env._cache_egg.path[path].metadata
|
| 866 |
+
set_name_and_version(self, metadata.name, metadata.version)
|
| 867 |
+
else:
|
| 868 |
+
metadata = self._get_metadata(path)
|
| 869 |
+
|
| 870 |
+
# Need to be set before caching
|
| 871 |
+
set_name_and_version(self, metadata.name, metadata.version)
|
| 872 |
+
|
| 873 |
+
if env and env._cache_enabled:
|
| 874 |
+
env._cache_egg.add(self)
|
| 875 |
+
super(EggInfoDistribution, self).__init__(metadata, path, env)
|
| 876 |
+
|
| 877 |
+
def _get_metadata(self, path):
|
| 878 |
+
requires = None
|
| 879 |
+
|
| 880 |
+
def parse_requires_data(data):
|
| 881 |
+
"""Create a list of dependencies from a requires.txt file.
|
| 882 |
+
|
| 883 |
+
*data*: the contents of a setuptools-produced requires.txt file.
|
| 884 |
+
"""
|
| 885 |
+
reqs = []
|
| 886 |
+
lines = data.splitlines()
|
| 887 |
+
for line in lines:
|
| 888 |
+
line = line.strip()
|
| 889 |
+
# sectioned files have bare newlines (separating sections)
|
| 890 |
+
if not line: # pragma: no cover
|
| 891 |
+
continue
|
| 892 |
+
if line.startswith('['): # pragma: no cover
|
| 893 |
+
logger.warning('Unexpected line: quitting requirement scan: %r', line)
|
| 894 |
+
break
|
| 895 |
+
r = parse_requirement(line)
|
| 896 |
+
if not r: # pragma: no cover
|
| 897 |
+
logger.warning('Not recognised as a requirement: %r', line)
|
| 898 |
+
continue
|
| 899 |
+
if r.extras: # pragma: no cover
|
| 900 |
+
logger.warning('extra requirements in requires.txt are '
|
| 901 |
+
'not supported')
|
| 902 |
+
if not r.constraints:
|
| 903 |
+
reqs.append(r.name)
|
| 904 |
+
else:
|
| 905 |
+
cons = ', '.join('%s%s' % c for c in r.constraints)
|
| 906 |
+
reqs.append('%s (%s)' % (r.name, cons))
|
| 907 |
+
return reqs
|
| 908 |
+
|
| 909 |
+
def parse_requires_path(req_path):
|
| 910 |
+
"""Create a list of dependencies from a requires.txt file.
|
| 911 |
+
|
| 912 |
+
*req_path*: the path to a setuptools-produced requires.txt file.
|
| 913 |
+
"""
|
| 914 |
+
|
| 915 |
+
reqs = []
|
| 916 |
+
try:
|
| 917 |
+
with codecs.open(req_path, 'r', 'utf-8') as fp:
|
| 918 |
+
reqs = parse_requires_data(fp.read())
|
| 919 |
+
except IOError:
|
| 920 |
+
pass
|
| 921 |
+
return reqs
|
| 922 |
+
|
| 923 |
+
tl_path = tl_data = None
|
| 924 |
+
if path.endswith('.egg'):
|
| 925 |
+
if os.path.isdir(path):
|
| 926 |
+
p = os.path.join(path, 'EGG-INFO')
|
| 927 |
+
meta_path = os.path.join(p, 'PKG-INFO')
|
| 928 |
+
metadata = Metadata(path=meta_path, scheme='legacy')
|
| 929 |
+
req_path = os.path.join(p, 'requires.txt')
|
| 930 |
+
tl_path = os.path.join(p, 'top_level.txt')
|
| 931 |
+
requires = parse_requires_path(req_path)
|
| 932 |
+
else:
|
| 933 |
+
# FIXME handle the case where zipfile is not available
|
| 934 |
+
zipf = zipimport.zipimporter(path)
|
| 935 |
+
fileobj = StringIO(zipf.get_data('EGG-INFO/PKG-INFO').decode('utf8'))
|
| 936 |
+
metadata = Metadata(fileobj=fileobj, scheme='legacy')
|
| 937 |
+
try:
|
| 938 |
+
data = zipf.get_data('EGG-INFO/requires.txt')
|
| 939 |
+
tl_data = zipf.get_data('EGG-INFO/top_level.txt').decode('utf-8')
|
| 940 |
+
requires = parse_requires_data(data.decode('utf-8'))
|
| 941 |
+
except IOError:
|
| 942 |
+
requires = None
|
| 943 |
+
elif path.endswith('.egg-info'):
|
| 944 |
+
if os.path.isdir(path):
|
| 945 |
+
req_path = os.path.join(path, 'requires.txt')
|
| 946 |
+
requires = parse_requires_path(req_path)
|
| 947 |
+
path = os.path.join(path, 'PKG-INFO')
|
| 948 |
+
tl_path = os.path.join(path, 'top_level.txt')
|
| 949 |
+
metadata = Metadata(path=path, scheme='legacy')
|
| 950 |
+
else:
|
| 951 |
+
raise DistlibException('path must end with .egg-info or .egg, '
|
| 952 |
+
'got %r' % path)
|
| 953 |
+
|
| 954 |
+
if requires:
|
| 955 |
+
metadata.add_requirements(requires)
|
| 956 |
+
# look for top-level modules in top_level.txt, if present
|
| 957 |
+
if tl_data is None:
|
| 958 |
+
if tl_path is not None and os.path.exists(tl_path):
|
| 959 |
+
with open(tl_path, 'rb') as f:
|
| 960 |
+
tl_data = f.read().decode('utf-8')
|
| 961 |
+
if not tl_data:
|
| 962 |
+
tl_data = []
|
| 963 |
+
else:
|
| 964 |
+
tl_data = tl_data.splitlines()
|
| 965 |
+
self.modules = tl_data
|
| 966 |
+
return metadata
|
| 967 |
+
|
| 968 |
+
def __repr__(self):
|
| 969 |
+
return '<EggInfoDistribution %r %s at %r>' % (self.name, self.version, self.path)
|
| 970 |
+
|
| 971 |
+
def __str__(self):
|
| 972 |
+
return "%s %s" % (self.name, self.version)
|
| 973 |
+
|
| 974 |
+
def check_installed_files(self):
|
| 975 |
+
"""
|
| 976 |
+
Checks that the hashes and sizes of the files in ``RECORD`` are
|
| 977 |
+
matched by the files themselves. Returns a (possibly empty) list of
|
| 978 |
+
mismatches. Each entry in the mismatch list will be a tuple consisting
|
| 979 |
+
of the path, 'exists', 'size' or 'hash' according to what didn't match
|
| 980 |
+
(existence is checked first, then size, then hash), the expected
|
| 981 |
+
value and the actual value.
|
| 982 |
+
"""
|
| 983 |
+
mismatches = []
|
| 984 |
+
record_path = os.path.join(self.path, 'installed-files.txt')
|
| 985 |
+
if os.path.exists(record_path):
|
| 986 |
+
for path, _, _ in self.list_installed_files():
|
| 987 |
+
if path == record_path:
|
| 988 |
+
continue
|
| 989 |
+
if not os.path.exists(path):
|
| 990 |
+
mismatches.append((path, 'exists', True, False))
|
| 991 |
+
return mismatches
|
| 992 |
+
|
| 993 |
+
def list_installed_files(self):
|
| 994 |
+
"""
|
| 995 |
+
Iterates over the ``installed-files.txt`` entries and returns a tuple
|
| 996 |
+
``(path, hash, size)`` for each line.
|
| 997 |
+
|
| 998 |
+
:returns: a list of (path, hash, size)
|
| 999 |
+
"""
|
| 1000 |
+
|
| 1001 |
+
def _md5(path):
|
| 1002 |
+
f = open(path, 'rb')
|
| 1003 |
+
try:
|
| 1004 |
+
content = f.read()
|
| 1005 |
+
finally:
|
| 1006 |
+
f.close()
|
| 1007 |
+
return hashlib.md5(content).hexdigest()
|
| 1008 |
+
|
| 1009 |
+
def _size(path):
|
| 1010 |
+
return os.stat(path).st_size
|
| 1011 |
+
|
| 1012 |
+
record_path = os.path.join(self.path, 'installed-files.txt')
|
| 1013 |
+
result = []
|
| 1014 |
+
if os.path.exists(record_path):
|
| 1015 |
+
with codecs.open(record_path, 'r', encoding='utf-8') as f:
|
| 1016 |
+
for line in f:
|
| 1017 |
+
line = line.strip()
|
| 1018 |
+
p = os.path.normpath(os.path.join(self.path, line))
|
| 1019 |
+
# "./" is present as a marker between installed files
|
| 1020 |
+
# and installation metadata files
|
| 1021 |
+
if not os.path.exists(p):
|
| 1022 |
+
logger.warning('Non-existent file: %s', p)
|
| 1023 |
+
if p.endswith(('.pyc', '.pyo')):
|
| 1024 |
+
continue
|
| 1025 |
+
# otherwise fall through and fail
|
| 1026 |
+
if not os.path.isdir(p):
|
| 1027 |
+
result.append((p, _md5(p), _size(p)))
|
| 1028 |
+
result.append((record_path, None, None))
|
| 1029 |
+
return result
|
| 1030 |
+
|
| 1031 |
+
def list_distinfo_files(self, absolute=False):
|
| 1032 |
+
"""
|
| 1033 |
+
Iterates over the ``installed-files.txt`` entries and returns paths for
|
| 1034 |
+
each line if the path is pointing to a file located in the
|
| 1035 |
+
``.egg-info`` directory or one of its subdirectories.
|
| 1036 |
+
|
| 1037 |
+
:parameter absolute: If *absolute* is ``True``, each returned path is
|
| 1038 |
+
transformed into a local absolute path. Otherwise the
|
| 1039 |
+
raw value from ``installed-files.txt`` is returned.
|
| 1040 |
+
:type absolute: boolean
|
| 1041 |
+
:returns: iterator of paths
|
| 1042 |
+
"""
|
| 1043 |
+
record_path = os.path.join(self.path, 'installed-files.txt')
|
| 1044 |
+
if os.path.exists(record_path):
|
| 1045 |
+
skip = True
|
| 1046 |
+
with codecs.open(record_path, 'r', encoding='utf-8') as f:
|
| 1047 |
+
for line in f:
|
| 1048 |
+
line = line.strip()
|
| 1049 |
+
if line == './':
|
| 1050 |
+
skip = False
|
| 1051 |
+
continue
|
| 1052 |
+
if not skip:
|
| 1053 |
+
p = os.path.normpath(os.path.join(self.path, line))
|
| 1054 |
+
if p.startswith(self.path):
|
| 1055 |
+
if absolute:
|
| 1056 |
+
yield p
|
| 1057 |
+
else:
|
| 1058 |
+
yield line
|
| 1059 |
+
|
| 1060 |
+
def __eq__(self, other):
|
| 1061 |
+
return (isinstance(other, EggInfoDistribution) and self.path == other.path)
|
| 1062 |
+
|
| 1063 |
+
# See http://docs.python.org/reference/datamodel#object.__hash__
|
| 1064 |
+
__hash__ = object.__hash__
|
| 1065 |
+
|
| 1066 |
+
|
| 1067 |
+
new_dist_class = InstalledDistribution
|
| 1068 |
+
old_dist_class = EggInfoDistribution
|
| 1069 |
+
|
| 1070 |
+
|
| 1071 |
+
class DependencyGraph(object):
|
| 1072 |
+
"""
|
| 1073 |
+
Represents a dependency graph between distributions.
|
| 1074 |
+
|
| 1075 |
+
The dependency relationships are stored in an ``adjacency_list`` that maps
|
| 1076 |
+
distributions to a list of ``(other, label)`` tuples where ``other``
|
| 1077 |
+
is a distribution and the edge is labeled with ``label`` (i.e. the version
|
| 1078 |
+
specifier, if such was provided). Also, for more efficient traversal, for
|
| 1079 |
+
every distribution ``x``, a list of predecessors is kept in
|
| 1080 |
+
``reverse_list[x]``. An edge from distribution ``a`` to
|
| 1081 |
+
distribution ``b`` means that ``a`` depends on ``b``. If any missing
|
| 1082 |
+
dependencies are found, they are stored in ``missing``, which is a
|
| 1083 |
+
dictionary that maps distributions to a list of requirements that were not
|
| 1084 |
+
provided by any other distributions.
|
| 1085 |
+
"""
|
| 1086 |
+
|
| 1087 |
+
def __init__(self):
|
| 1088 |
+
self.adjacency_list = {}
|
| 1089 |
+
self.reverse_list = {}
|
| 1090 |
+
self.missing = {}
|
| 1091 |
+
|
| 1092 |
+
def add_distribution(self, distribution):
|
| 1093 |
+
"""Add the *distribution* to the graph.
|
| 1094 |
+
|
| 1095 |
+
:type distribution: :class:`distutils2.database.InstalledDistribution`
|
| 1096 |
+
or :class:`distutils2.database.EggInfoDistribution`
|
| 1097 |
+
"""
|
| 1098 |
+
self.adjacency_list[distribution] = []
|
| 1099 |
+
self.reverse_list[distribution] = []
|
| 1100 |
+
# self.missing[distribution] = []
|
| 1101 |
+
|
| 1102 |
+
def add_edge(self, x, y, label=None):
|
| 1103 |
+
"""Add an edge from distribution *x* to distribution *y* with the given
|
| 1104 |
+
*label*.
|
| 1105 |
+
|
| 1106 |
+
:type x: :class:`distutils2.database.InstalledDistribution` or
|
| 1107 |
+
:class:`distutils2.database.EggInfoDistribution`
|
| 1108 |
+
:type y: :class:`distutils2.database.InstalledDistribution` or
|
| 1109 |
+
:class:`distutils2.database.EggInfoDistribution`
|
| 1110 |
+
:type label: ``str`` or ``None``
|
| 1111 |
+
"""
|
| 1112 |
+
self.adjacency_list[x].append((y, label))
|
| 1113 |
+
# multiple edges are allowed, so be careful
|
| 1114 |
+
if x not in self.reverse_list[y]:
|
| 1115 |
+
self.reverse_list[y].append(x)
|
| 1116 |
+
|
| 1117 |
+
def add_missing(self, distribution, requirement):
|
| 1118 |
+
"""
|
| 1119 |
+
Add a missing *requirement* for the given *distribution*.
|
| 1120 |
+
|
| 1121 |
+
:type distribution: :class:`distutils2.database.InstalledDistribution`
|
| 1122 |
+
or :class:`distutils2.database.EggInfoDistribution`
|
| 1123 |
+
:type requirement: ``str``
|
| 1124 |
+
"""
|
| 1125 |
+
logger.debug('%s missing %r', distribution, requirement)
|
| 1126 |
+
self.missing.setdefault(distribution, []).append(requirement)
|
| 1127 |
+
|
| 1128 |
+
def _repr_dist(self, dist):
|
| 1129 |
+
return '%s %s' % (dist.name, dist.version)
|
| 1130 |
+
|
| 1131 |
+
def repr_node(self, dist, level=1):
|
| 1132 |
+
"""Prints only a subgraph"""
|
| 1133 |
+
output = [self._repr_dist(dist)]
|
| 1134 |
+
for other, label in self.adjacency_list[dist]:
|
| 1135 |
+
dist = self._repr_dist(other)
|
| 1136 |
+
if label is not None:
|
| 1137 |
+
dist = '%s [%s]' % (dist, label)
|
| 1138 |
+
output.append(' ' * level + str(dist))
|
| 1139 |
+
suboutput = self.repr_node(other, level + 1)
|
| 1140 |
+
subs = suboutput.split('\n')
|
| 1141 |
+
output.extend(subs[1:])
|
| 1142 |
+
return '\n'.join(output)
|
| 1143 |
+
|
| 1144 |
+
def to_dot(self, f, skip_disconnected=True):
|
| 1145 |
+
"""Writes a DOT output for the graph to the provided file *f*.
|
| 1146 |
+
|
| 1147 |
+
If *skip_disconnected* is set to ``True``, then all distributions
|
| 1148 |
+
that are not dependent on any other distribution are skipped.
|
| 1149 |
+
|
| 1150 |
+
:type f: has to support ``file``-like operations
|
| 1151 |
+
:type skip_disconnected: ``bool``
|
| 1152 |
+
"""
|
| 1153 |
+
disconnected = []
|
| 1154 |
+
|
| 1155 |
+
f.write("digraph dependencies {\n")
|
| 1156 |
+
for dist, adjs in self.adjacency_list.items():
|
| 1157 |
+
if len(adjs) == 0 and not skip_disconnected:
|
| 1158 |
+
disconnected.append(dist)
|
| 1159 |
+
for other, label in adjs:
|
| 1160 |
+
if label is not None:
|
| 1161 |
+
f.write('"%s" -> "%s" [label="%s"]\n' % (dist.name, other.name, label))
|
| 1162 |
+
else:
|
| 1163 |
+
f.write('"%s" -> "%s"\n' % (dist.name, other.name))
|
| 1164 |
+
if not skip_disconnected and len(disconnected) > 0:
|
| 1165 |
+
f.write('subgraph disconnected {\n')
|
| 1166 |
+
f.write('label = "Disconnected"\n')
|
| 1167 |
+
f.write('bgcolor = red\n')
|
| 1168 |
+
|
| 1169 |
+
for dist in disconnected:
|
| 1170 |
+
f.write('"%s"' % dist.name)
|
| 1171 |
+
f.write('\n')
|
| 1172 |
+
f.write('}\n')
|
| 1173 |
+
f.write('}\n')
|
| 1174 |
+
|
| 1175 |
+
def topological_sort(self):
|
| 1176 |
+
"""
|
| 1177 |
+
Perform a topological sort of the graph.
|
| 1178 |
+
:return: A tuple, the first element of which is a topologically sorted
|
| 1179 |
+
list of distributions, and the second element of which is a
|
| 1180 |
+
list of distributions that cannot be sorted because they have
|
| 1181 |
+
circular dependencies and so form a cycle.
|
| 1182 |
+
"""
|
| 1183 |
+
result = []
|
| 1184 |
+
# Make a shallow copy of the adjacency list
|
| 1185 |
+
alist = {}
|
| 1186 |
+
for k, v in self.adjacency_list.items():
|
| 1187 |
+
alist[k] = v[:]
|
| 1188 |
+
while True:
|
| 1189 |
+
# See what we can remove in this run
|
| 1190 |
+
to_remove = []
|
| 1191 |
+
for k, v in list(alist.items())[:]:
|
| 1192 |
+
if not v:
|
| 1193 |
+
to_remove.append(k)
|
| 1194 |
+
del alist[k]
|
| 1195 |
+
if not to_remove:
|
| 1196 |
+
# What's left in alist (if anything) is a cycle.
|
| 1197 |
+
break
|
| 1198 |
+
# Remove from the adjacency list of others
|
| 1199 |
+
for k, v in alist.items():
|
| 1200 |
+
alist[k] = [(d, r) for d, r in v if d not in to_remove]
|
| 1201 |
+
logger.debug('Moving to result: %s', ['%s (%s)' % (d.name, d.version) for d in to_remove])
|
| 1202 |
+
result.extend(to_remove)
|
| 1203 |
+
return result, list(alist.keys())
|
| 1204 |
+
|
| 1205 |
+
def __repr__(self):
|
| 1206 |
+
"""Representation of the graph"""
|
| 1207 |
+
output = []
|
| 1208 |
+
for dist, adjs in self.adjacency_list.items():
|
| 1209 |
+
output.append(self.repr_node(dist))
|
| 1210 |
+
return '\n'.join(output)
|
| 1211 |
+
|
| 1212 |
+
|
| 1213 |
+
def make_graph(dists, scheme='default'):
|
| 1214 |
+
"""Makes a dependency graph from the given distributions.
|
| 1215 |
+
|
| 1216 |
+
:parameter dists: a list of distributions
|
| 1217 |
+
:type dists: list of :class:`distutils2.database.InstalledDistribution` and
|
| 1218 |
+
:class:`distutils2.database.EggInfoDistribution` instances
|
| 1219 |
+
:rtype: a :class:`DependencyGraph` instance
|
| 1220 |
+
"""
|
| 1221 |
+
scheme = get_scheme(scheme)
|
| 1222 |
+
graph = DependencyGraph()
|
| 1223 |
+
provided = {} # maps names to lists of (version, dist) tuples
|
| 1224 |
+
|
| 1225 |
+
# first, build the graph and find out what's provided
|
| 1226 |
+
for dist in dists:
|
| 1227 |
+
graph.add_distribution(dist)
|
| 1228 |
+
|
| 1229 |
+
for p in dist.provides:
|
| 1230 |
+
name, version = parse_name_and_version(p)
|
| 1231 |
+
logger.debug('Add to provided: %s, %s, %s', name, version, dist)
|
| 1232 |
+
provided.setdefault(name, []).append((version, dist))
|
| 1233 |
+
|
| 1234 |
+
# now make the edges
|
| 1235 |
+
for dist in dists:
|
| 1236 |
+
requires = (dist.run_requires | dist.meta_requires | dist.build_requires | dist.dev_requires)
|
| 1237 |
+
for req in requires:
|
| 1238 |
+
try:
|
| 1239 |
+
matcher = scheme.matcher(req)
|
| 1240 |
+
except UnsupportedVersionError:
|
| 1241 |
+
# XXX compat-mode if cannot read the version
|
| 1242 |
+
logger.warning('could not read version %r - using name only', req)
|
| 1243 |
+
name = req.split()[0]
|
| 1244 |
+
matcher = scheme.matcher(name)
|
| 1245 |
+
|
| 1246 |
+
name = matcher.key # case-insensitive
|
| 1247 |
+
|
| 1248 |
+
matched = False
|
| 1249 |
+
if name in provided:
|
| 1250 |
+
for version, provider in provided[name]:
|
| 1251 |
+
try:
|
| 1252 |
+
match = matcher.match(version)
|
| 1253 |
+
except UnsupportedVersionError:
|
| 1254 |
+
match = False
|
| 1255 |
+
|
| 1256 |
+
if match:
|
| 1257 |
+
graph.add_edge(dist, provider, req)
|
| 1258 |
+
matched = True
|
| 1259 |
+
break
|
| 1260 |
+
if not matched:
|
| 1261 |
+
graph.add_missing(dist, req)
|
| 1262 |
+
return graph
|
| 1263 |
+
|
| 1264 |
+
|
| 1265 |
+
def get_dependent_dists(dists, dist):
|
| 1266 |
+
"""Recursively generate a list of distributions from *dists* that are
|
| 1267 |
+
dependent on *dist*.
|
| 1268 |
+
|
| 1269 |
+
:param dists: a list of distributions
|
| 1270 |
+
:param dist: a distribution, member of *dists* for which we are interested
|
| 1271 |
+
"""
|
| 1272 |
+
if dist not in dists:
|
| 1273 |
+
raise DistlibException('given distribution %r is not a member '
|
| 1274 |
+
'of the list' % dist.name)
|
| 1275 |
+
graph = make_graph(dists)
|
| 1276 |
+
|
| 1277 |
+
dep = [dist] # dependent distributions
|
| 1278 |
+
todo = graph.reverse_list[dist] # list of nodes we should inspect
|
| 1279 |
+
|
| 1280 |
+
while todo:
|
| 1281 |
+
d = todo.pop()
|
| 1282 |
+
dep.append(d)
|
| 1283 |
+
for succ in graph.reverse_list[d]:
|
| 1284 |
+
if succ not in dep:
|
| 1285 |
+
todo.append(succ)
|
| 1286 |
+
|
| 1287 |
+
dep.pop(0) # remove dist from dep, was there to prevent infinite loops
|
| 1288 |
+
return dep
|
| 1289 |
+
|
| 1290 |
+
|
| 1291 |
+
def get_required_dists(dists, dist):
|
| 1292 |
+
"""Recursively generate a list of distributions from *dists* that are
|
| 1293 |
+
required by *dist*.
|
| 1294 |
+
|
| 1295 |
+
:param dists: a list of distributions
|
| 1296 |
+
:param dist: a distribution, member of *dists* for which we are interested
|
| 1297 |
+
in finding the dependencies.
|
| 1298 |
+
"""
|
| 1299 |
+
if dist not in dists:
|
| 1300 |
+
raise DistlibException('given distribution %r is not a member '
|
| 1301 |
+
'of the list' % dist.name)
|
| 1302 |
+
graph = make_graph(dists)
|
| 1303 |
+
|
| 1304 |
+
req = set() # required distributions
|
| 1305 |
+
todo = graph.adjacency_list[dist] # list of nodes we should inspect
|
| 1306 |
+
seen = set(t[0] for t in todo) # already added to todo
|
| 1307 |
+
|
| 1308 |
+
while todo:
|
| 1309 |
+
d = todo.pop()[0]
|
| 1310 |
+
req.add(d)
|
| 1311 |
+
pred_list = graph.adjacency_list[d]
|
| 1312 |
+
for pred in pred_list:
|
| 1313 |
+
d = pred[0]
|
| 1314 |
+
if d not in req and d not in seen:
|
| 1315 |
+
seen.add(d)
|
| 1316 |
+
todo.append(pred)
|
| 1317 |
+
return req
|
| 1318 |
+
|
| 1319 |
+
|
| 1320 |
+
def make_dist(name, version, **kwargs):
|
| 1321 |
+
"""
|
| 1322 |
+
A convenience method for making a dist given just a name and version.
|
| 1323 |
+
"""
|
| 1324 |
+
summary = kwargs.pop('summary', 'Placeholder for summary')
|
| 1325 |
+
md = Metadata(**kwargs)
|
| 1326 |
+
md.name = name
|
| 1327 |
+
md.version = version
|
| 1328 |
+
md.summary = summary or 'Placeholder for summary'
|
| 1329 |
+
return Distribution(md)
|
lib/python3.12/site-packages/pip/_vendor/distlib/index.py
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2013-2023 Vinay Sajip.
|
| 4 |
+
# Licensed to the Python Software Foundation under a contributor agreement.
|
| 5 |
+
# See LICENSE.txt and CONTRIBUTORS.txt.
|
| 6 |
+
#
|
| 7 |
+
import hashlib
|
| 8 |
+
import logging
|
| 9 |
+
import os
|
| 10 |
+
import shutil
|
| 11 |
+
import subprocess
|
| 12 |
+
import tempfile
|
| 13 |
+
try:
|
| 14 |
+
from threading import Thread
|
| 15 |
+
except ImportError: # pragma: no cover
|
| 16 |
+
from dummy_threading import Thread
|
| 17 |
+
|
| 18 |
+
from . import DistlibException
|
| 19 |
+
from .compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr,
|
| 20 |
+
urlparse, build_opener, string_types)
|
| 21 |
+
from .util import zip_dir, ServerProxy
|
| 22 |
+
|
| 23 |
+
logger = logging.getLogger(__name__)
|
| 24 |
+
|
| 25 |
+
DEFAULT_INDEX = 'https://pypi.org/pypi'
|
| 26 |
+
DEFAULT_REALM = 'pypi'
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class PackageIndex(object):
|
| 30 |
+
"""
|
| 31 |
+
This class represents a package index compatible with PyPI, the Python
|
| 32 |
+
Package Index.
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
boundary = b'----------ThIs_Is_tHe_distlib_index_bouNdaRY_$'
|
| 36 |
+
|
| 37 |
+
def __init__(self, url=None):
|
| 38 |
+
"""
|
| 39 |
+
Initialise an instance.
|
| 40 |
+
|
| 41 |
+
:param url: The URL of the index. If not specified, the URL for PyPI is
|
| 42 |
+
used.
|
| 43 |
+
"""
|
| 44 |
+
self.url = url or DEFAULT_INDEX
|
| 45 |
+
self.read_configuration()
|
| 46 |
+
scheme, netloc, path, params, query, frag = urlparse(self.url)
|
| 47 |
+
if params or query or frag or scheme not in ('http', 'https'):
|
| 48 |
+
raise DistlibException('invalid repository: %s' % self.url)
|
| 49 |
+
self.password_handler = None
|
| 50 |
+
self.ssl_verifier = None
|
| 51 |
+
self.gpg = None
|
| 52 |
+
self.gpg_home = None
|
| 53 |
+
with open(os.devnull, 'w') as sink:
|
| 54 |
+
# Use gpg by default rather than gpg2, as gpg2 insists on
|
| 55 |
+
# prompting for passwords
|
| 56 |
+
for s in ('gpg', 'gpg2'):
|
| 57 |
+
try:
|
| 58 |
+
rc = subprocess.check_call([s, '--version'], stdout=sink,
|
| 59 |
+
stderr=sink)
|
| 60 |
+
if rc == 0:
|
| 61 |
+
self.gpg = s
|
| 62 |
+
break
|
| 63 |
+
except OSError:
|
| 64 |
+
pass
|
| 65 |
+
|
| 66 |
+
def _get_pypirc_command(self):
|
| 67 |
+
"""
|
| 68 |
+
Get the distutils command for interacting with PyPI configurations.
|
| 69 |
+
:return: the command.
|
| 70 |
+
"""
|
| 71 |
+
from .util import _get_pypirc_command as cmd
|
| 72 |
+
return cmd()
|
| 73 |
+
|
| 74 |
+
def read_configuration(self):
|
| 75 |
+
"""
|
| 76 |
+
Read the PyPI access configuration as supported by distutils. This populates
|
| 77 |
+
``username``, ``password``, ``realm`` and ``url`` attributes from the
|
| 78 |
+
configuration.
|
| 79 |
+
"""
|
| 80 |
+
from .util import _load_pypirc
|
| 81 |
+
cfg = _load_pypirc(self)
|
| 82 |
+
self.username = cfg.get('username')
|
| 83 |
+
self.password = cfg.get('password')
|
| 84 |
+
self.realm = cfg.get('realm', 'pypi')
|
| 85 |
+
self.url = cfg.get('repository', self.url)
|
| 86 |
+
|
| 87 |
+
def save_configuration(self):
|
| 88 |
+
"""
|
| 89 |
+
Save the PyPI access configuration. You must have set ``username`` and
|
| 90 |
+
``password`` attributes before calling this method.
|
| 91 |
+
"""
|
| 92 |
+
self.check_credentials()
|
| 93 |
+
from .util import _store_pypirc
|
| 94 |
+
_store_pypirc(self)
|
| 95 |
+
|
| 96 |
+
def check_credentials(self):
|
| 97 |
+
"""
|
| 98 |
+
Check that ``username`` and ``password`` have been set, and raise an
|
| 99 |
+
exception if not.
|
| 100 |
+
"""
|
| 101 |
+
if self.username is None or self.password is None:
|
| 102 |
+
raise DistlibException('username and password must be set')
|
| 103 |
+
pm = HTTPPasswordMgr()
|
| 104 |
+
_, netloc, _, _, _, _ = urlparse(self.url)
|
| 105 |
+
pm.add_password(self.realm, netloc, self.username, self.password)
|
| 106 |
+
self.password_handler = HTTPBasicAuthHandler(pm)
|
| 107 |
+
|
| 108 |
+
def register(self, metadata): # pragma: no cover
|
| 109 |
+
"""
|
| 110 |
+
Register a distribution on PyPI, using the provided metadata.
|
| 111 |
+
|
| 112 |
+
:param metadata: A :class:`Metadata` instance defining at least a name
|
| 113 |
+
and version number for the distribution to be
|
| 114 |
+
registered.
|
| 115 |
+
:return: The HTTP response received from PyPI upon submission of the
|
| 116 |
+
request.
|
| 117 |
+
"""
|
| 118 |
+
self.check_credentials()
|
| 119 |
+
metadata.validate()
|
| 120 |
+
d = metadata.todict()
|
| 121 |
+
d[':action'] = 'verify'
|
| 122 |
+
request = self.encode_request(d.items(), [])
|
| 123 |
+
self.send_request(request)
|
| 124 |
+
d[':action'] = 'submit'
|
| 125 |
+
request = self.encode_request(d.items(), [])
|
| 126 |
+
return self.send_request(request)
|
| 127 |
+
|
| 128 |
+
def _reader(self, name, stream, outbuf):
|
| 129 |
+
"""
|
| 130 |
+
Thread runner for reading lines of from a subprocess into a buffer.
|
| 131 |
+
|
| 132 |
+
:param name: The logical name of the stream (used for logging only).
|
| 133 |
+
:param stream: The stream to read from. This will typically a pipe
|
| 134 |
+
connected to the output stream of a subprocess.
|
| 135 |
+
:param outbuf: The list to append the read lines to.
|
| 136 |
+
"""
|
| 137 |
+
while True:
|
| 138 |
+
s = stream.readline()
|
| 139 |
+
if not s:
|
| 140 |
+
break
|
| 141 |
+
s = s.decode('utf-8').rstrip()
|
| 142 |
+
outbuf.append(s)
|
| 143 |
+
logger.debug('%s: %s' % (name, s))
|
| 144 |
+
stream.close()
|
| 145 |
+
|
| 146 |
+
def get_sign_command(self, filename, signer, sign_password, keystore=None): # pragma: no cover
|
| 147 |
+
"""
|
| 148 |
+
Return a suitable command for signing a file.
|
| 149 |
+
|
| 150 |
+
:param filename: The pathname to the file to be signed.
|
| 151 |
+
:param signer: The identifier of the signer of the file.
|
| 152 |
+
:param sign_password: The passphrase for the signer's
|
| 153 |
+
private key used for signing.
|
| 154 |
+
:param keystore: The path to a directory which contains the keys
|
| 155 |
+
used in verification. If not specified, the
|
| 156 |
+
instance's ``gpg_home`` attribute is used instead.
|
| 157 |
+
:return: The signing command as a list suitable to be
|
| 158 |
+
passed to :class:`subprocess.Popen`.
|
| 159 |
+
"""
|
| 160 |
+
cmd = [self.gpg, '--status-fd', '2', '--no-tty']
|
| 161 |
+
if keystore is None:
|
| 162 |
+
keystore = self.gpg_home
|
| 163 |
+
if keystore:
|
| 164 |
+
cmd.extend(['--homedir', keystore])
|
| 165 |
+
if sign_password is not None:
|
| 166 |
+
cmd.extend(['--batch', '--passphrase-fd', '0'])
|
| 167 |
+
td = tempfile.mkdtemp()
|
| 168 |
+
sf = os.path.join(td, os.path.basename(filename) + '.asc')
|
| 169 |
+
cmd.extend(['--detach-sign', '--armor', '--local-user',
|
| 170 |
+
signer, '--output', sf, filename])
|
| 171 |
+
logger.debug('invoking: %s', ' '.join(cmd))
|
| 172 |
+
return cmd, sf
|
| 173 |
+
|
| 174 |
+
def run_command(self, cmd, input_data=None):
|
| 175 |
+
"""
|
| 176 |
+
Run a command in a child process , passing it any input data specified.
|
| 177 |
+
|
| 178 |
+
:param cmd: The command to run.
|
| 179 |
+
:param input_data: If specified, this must be a byte string containing
|
| 180 |
+
data to be sent to the child process.
|
| 181 |
+
:return: A tuple consisting of the subprocess' exit code, a list of
|
| 182 |
+
lines read from the subprocess' ``stdout``, and a list of
|
| 183 |
+
lines read from the subprocess' ``stderr``.
|
| 184 |
+
"""
|
| 185 |
+
kwargs = {
|
| 186 |
+
'stdout': subprocess.PIPE,
|
| 187 |
+
'stderr': subprocess.PIPE,
|
| 188 |
+
}
|
| 189 |
+
if input_data is not None:
|
| 190 |
+
kwargs['stdin'] = subprocess.PIPE
|
| 191 |
+
stdout = []
|
| 192 |
+
stderr = []
|
| 193 |
+
p = subprocess.Popen(cmd, **kwargs)
|
| 194 |
+
# We don't use communicate() here because we may need to
|
| 195 |
+
# get clever with interacting with the command
|
| 196 |
+
t1 = Thread(target=self._reader, args=('stdout', p.stdout, stdout))
|
| 197 |
+
t1.start()
|
| 198 |
+
t2 = Thread(target=self._reader, args=('stderr', p.stderr, stderr))
|
| 199 |
+
t2.start()
|
| 200 |
+
if input_data is not None:
|
| 201 |
+
p.stdin.write(input_data)
|
| 202 |
+
p.stdin.close()
|
| 203 |
+
|
| 204 |
+
p.wait()
|
| 205 |
+
t1.join()
|
| 206 |
+
t2.join()
|
| 207 |
+
return p.returncode, stdout, stderr
|
| 208 |
+
|
| 209 |
+
def sign_file(self, filename, signer, sign_password, keystore=None): # pragma: no cover
|
| 210 |
+
"""
|
| 211 |
+
Sign a file.
|
| 212 |
+
|
| 213 |
+
:param filename: The pathname to the file to be signed.
|
| 214 |
+
:param signer: The identifier of the signer of the file.
|
| 215 |
+
:param sign_password: The passphrase for the signer's
|
| 216 |
+
private key used for signing.
|
| 217 |
+
:param keystore: The path to a directory which contains the keys
|
| 218 |
+
used in signing. If not specified, the instance's
|
| 219 |
+
``gpg_home`` attribute is used instead.
|
| 220 |
+
:return: The absolute pathname of the file where the signature is
|
| 221 |
+
stored.
|
| 222 |
+
"""
|
| 223 |
+
cmd, sig_file = self.get_sign_command(filename, signer, sign_password,
|
| 224 |
+
keystore)
|
| 225 |
+
rc, stdout, stderr = self.run_command(cmd,
|
| 226 |
+
sign_password.encode('utf-8'))
|
| 227 |
+
if rc != 0:
|
| 228 |
+
raise DistlibException('sign command failed with error '
|
| 229 |
+
'code %s' % rc)
|
| 230 |
+
return sig_file
|
| 231 |
+
|
| 232 |
+
def upload_file(self, metadata, filename, signer=None, sign_password=None,
|
| 233 |
+
filetype='sdist', pyversion='source', keystore=None):
|
| 234 |
+
"""
|
| 235 |
+
Upload a release file to the index.
|
| 236 |
+
|
| 237 |
+
:param metadata: A :class:`Metadata` instance defining at least a name
|
| 238 |
+
and version number for the file to be uploaded.
|
| 239 |
+
:param filename: The pathname of the file to be uploaded.
|
| 240 |
+
:param signer: The identifier of the signer of the file.
|
| 241 |
+
:param sign_password: The passphrase for the signer's
|
| 242 |
+
private key used for signing.
|
| 243 |
+
:param filetype: The type of the file being uploaded. This is the
|
| 244 |
+
distutils command which produced that file, e.g.
|
| 245 |
+
``sdist`` or ``bdist_wheel``.
|
| 246 |
+
:param pyversion: The version of Python which the release relates
|
| 247 |
+
to. For code compatible with any Python, this would
|
| 248 |
+
be ``source``, otherwise it would be e.g. ``3.2``.
|
| 249 |
+
:param keystore: The path to a directory which contains the keys
|
| 250 |
+
used in signing. If not specified, the instance's
|
| 251 |
+
``gpg_home`` attribute is used instead.
|
| 252 |
+
:return: The HTTP response received from PyPI upon submission of the
|
| 253 |
+
request.
|
| 254 |
+
"""
|
| 255 |
+
self.check_credentials()
|
| 256 |
+
if not os.path.exists(filename):
|
| 257 |
+
raise DistlibException('not found: %s' % filename)
|
| 258 |
+
metadata.validate()
|
| 259 |
+
d = metadata.todict()
|
| 260 |
+
sig_file = None
|
| 261 |
+
if signer:
|
| 262 |
+
if not self.gpg:
|
| 263 |
+
logger.warning('no signing program available - not signed')
|
| 264 |
+
else:
|
| 265 |
+
sig_file = self.sign_file(filename, signer, sign_password,
|
| 266 |
+
keystore)
|
| 267 |
+
with open(filename, 'rb') as f:
|
| 268 |
+
file_data = f.read()
|
| 269 |
+
md5_digest = hashlib.md5(file_data).hexdigest()
|
| 270 |
+
sha256_digest = hashlib.sha256(file_data).hexdigest()
|
| 271 |
+
d.update({
|
| 272 |
+
':action': 'file_upload',
|
| 273 |
+
'protocol_version': '1',
|
| 274 |
+
'filetype': filetype,
|
| 275 |
+
'pyversion': pyversion,
|
| 276 |
+
'md5_digest': md5_digest,
|
| 277 |
+
'sha256_digest': sha256_digest,
|
| 278 |
+
})
|
| 279 |
+
files = [('content', os.path.basename(filename), file_data)]
|
| 280 |
+
if sig_file:
|
| 281 |
+
with open(sig_file, 'rb') as f:
|
| 282 |
+
sig_data = f.read()
|
| 283 |
+
files.append(('gpg_signature', os.path.basename(sig_file),
|
| 284 |
+
sig_data))
|
| 285 |
+
shutil.rmtree(os.path.dirname(sig_file))
|
| 286 |
+
request = self.encode_request(d.items(), files)
|
| 287 |
+
return self.send_request(request)
|
| 288 |
+
|
| 289 |
+
def upload_documentation(self, metadata, doc_dir): # pragma: no cover
|
| 290 |
+
"""
|
| 291 |
+
Upload documentation to the index.
|
| 292 |
+
|
| 293 |
+
:param metadata: A :class:`Metadata` instance defining at least a name
|
| 294 |
+
and version number for the documentation to be
|
| 295 |
+
uploaded.
|
| 296 |
+
:param doc_dir: The pathname of the directory which contains the
|
| 297 |
+
documentation. This should be the directory that
|
| 298 |
+
contains the ``index.html`` for the documentation.
|
| 299 |
+
:return: The HTTP response received from PyPI upon submission of the
|
| 300 |
+
request.
|
| 301 |
+
"""
|
| 302 |
+
self.check_credentials()
|
| 303 |
+
if not os.path.isdir(doc_dir):
|
| 304 |
+
raise DistlibException('not a directory: %r' % doc_dir)
|
| 305 |
+
fn = os.path.join(doc_dir, 'index.html')
|
| 306 |
+
if not os.path.exists(fn):
|
| 307 |
+
raise DistlibException('not found: %r' % fn)
|
| 308 |
+
metadata.validate()
|
| 309 |
+
name, version = metadata.name, metadata.version
|
| 310 |
+
zip_data = zip_dir(doc_dir).getvalue()
|
| 311 |
+
fields = [(':action', 'doc_upload'),
|
| 312 |
+
('name', name), ('version', version)]
|
| 313 |
+
files = [('content', name, zip_data)]
|
| 314 |
+
request = self.encode_request(fields, files)
|
| 315 |
+
return self.send_request(request)
|
| 316 |
+
|
| 317 |
+
def get_verify_command(self, signature_filename, data_filename,
|
| 318 |
+
keystore=None):
|
| 319 |
+
"""
|
| 320 |
+
Return a suitable command for verifying a file.
|
| 321 |
+
|
| 322 |
+
:param signature_filename: The pathname to the file containing the
|
| 323 |
+
signature.
|
| 324 |
+
:param data_filename: The pathname to the file containing the
|
| 325 |
+
signed data.
|
| 326 |
+
:param keystore: The path to a directory which contains the keys
|
| 327 |
+
used in verification. If not specified, the
|
| 328 |
+
instance's ``gpg_home`` attribute is used instead.
|
| 329 |
+
:return: The verifying command as a list suitable to be
|
| 330 |
+
passed to :class:`subprocess.Popen`.
|
| 331 |
+
"""
|
| 332 |
+
cmd = [self.gpg, '--status-fd', '2', '--no-tty']
|
| 333 |
+
if keystore is None:
|
| 334 |
+
keystore = self.gpg_home
|
| 335 |
+
if keystore:
|
| 336 |
+
cmd.extend(['--homedir', keystore])
|
| 337 |
+
cmd.extend(['--verify', signature_filename, data_filename])
|
| 338 |
+
logger.debug('invoking: %s', ' '.join(cmd))
|
| 339 |
+
return cmd
|
| 340 |
+
|
| 341 |
+
def verify_signature(self, signature_filename, data_filename,
|
| 342 |
+
keystore=None):
|
| 343 |
+
"""
|
| 344 |
+
Verify a signature for a file.
|
| 345 |
+
|
| 346 |
+
:param signature_filename: The pathname to the file containing the
|
| 347 |
+
signature.
|
| 348 |
+
:param data_filename: The pathname to the file containing the
|
| 349 |
+
signed data.
|
| 350 |
+
:param keystore: The path to a directory which contains the keys
|
| 351 |
+
used in verification. If not specified, the
|
| 352 |
+
instance's ``gpg_home`` attribute is used instead.
|
| 353 |
+
:return: True if the signature was verified, else False.
|
| 354 |
+
"""
|
| 355 |
+
if not self.gpg:
|
| 356 |
+
raise DistlibException('verification unavailable because gpg '
|
| 357 |
+
'unavailable')
|
| 358 |
+
cmd = self.get_verify_command(signature_filename, data_filename,
|
| 359 |
+
keystore)
|
| 360 |
+
rc, stdout, stderr = self.run_command(cmd)
|
| 361 |
+
if rc not in (0, 1):
|
| 362 |
+
raise DistlibException('verify command failed with error code %s' % rc)
|
| 363 |
+
return rc == 0
|
| 364 |
+
|
| 365 |
+
def download_file(self, url, destfile, digest=None, reporthook=None):
|
| 366 |
+
"""
|
| 367 |
+
This is a convenience method for downloading a file from an URL.
|
| 368 |
+
Normally, this will be a file from the index, though currently
|
| 369 |
+
no check is made for this (i.e. a file can be downloaded from
|
| 370 |
+
anywhere).
|
| 371 |
+
|
| 372 |
+
The method is just like the :func:`urlretrieve` function in the
|
| 373 |
+
standard library, except that it allows digest computation to be
|
| 374 |
+
done during download and checking that the downloaded data
|
| 375 |
+
matched any expected value.
|
| 376 |
+
|
| 377 |
+
:param url: The URL of the file to be downloaded (assumed to be
|
| 378 |
+
available via an HTTP GET request).
|
| 379 |
+
:param destfile: The pathname where the downloaded file is to be
|
| 380 |
+
saved.
|
| 381 |
+
:param digest: If specified, this must be a (hasher, value)
|
| 382 |
+
tuple, where hasher is the algorithm used (e.g.
|
| 383 |
+
``'md5'``) and ``value`` is the expected value.
|
| 384 |
+
:param reporthook: The same as for :func:`urlretrieve` in the
|
| 385 |
+
standard library.
|
| 386 |
+
"""
|
| 387 |
+
if digest is None:
|
| 388 |
+
digester = None
|
| 389 |
+
logger.debug('No digest specified')
|
| 390 |
+
else:
|
| 391 |
+
if isinstance(digest, (list, tuple)):
|
| 392 |
+
hasher, digest = digest
|
| 393 |
+
else:
|
| 394 |
+
hasher = 'md5'
|
| 395 |
+
digester = getattr(hashlib, hasher)()
|
| 396 |
+
logger.debug('Digest specified: %s' % digest)
|
| 397 |
+
# The following code is equivalent to urlretrieve.
|
| 398 |
+
# We need to do it this way so that we can compute the
|
| 399 |
+
# digest of the file as we go.
|
| 400 |
+
with open(destfile, 'wb') as dfp:
|
| 401 |
+
# addinfourl is not a context manager on 2.x
|
| 402 |
+
# so we have to use try/finally
|
| 403 |
+
sfp = self.send_request(Request(url))
|
| 404 |
+
try:
|
| 405 |
+
headers = sfp.info()
|
| 406 |
+
blocksize = 8192
|
| 407 |
+
size = -1
|
| 408 |
+
read = 0
|
| 409 |
+
blocknum = 0
|
| 410 |
+
if "content-length" in headers:
|
| 411 |
+
size = int(headers["Content-Length"])
|
| 412 |
+
if reporthook:
|
| 413 |
+
reporthook(blocknum, blocksize, size)
|
| 414 |
+
while True:
|
| 415 |
+
block = sfp.read(blocksize)
|
| 416 |
+
if not block:
|
| 417 |
+
break
|
| 418 |
+
read += len(block)
|
| 419 |
+
dfp.write(block)
|
| 420 |
+
if digester:
|
| 421 |
+
digester.update(block)
|
| 422 |
+
blocknum += 1
|
| 423 |
+
if reporthook:
|
| 424 |
+
reporthook(blocknum, blocksize, size)
|
| 425 |
+
finally:
|
| 426 |
+
sfp.close()
|
| 427 |
+
|
| 428 |
+
# check that we got the whole file, if we can
|
| 429 |
+
if size >= 0 and read < size:
|
| 430 |
+
raise DistlibException(
|
| 431 |
+
'retrieval incomplete: got only %d out of %d bytes'
|
| 432 |
+
% (read, size))
|
| 433 |
+
# if we have a digest, it must match.
|
| 434 |
+
if digester:
|
| 435 |
+
actual = digester.hexdigest()
|
| 436 |
+
if digest != actual:
|
| 437 |
+
raise DistlibException('%s digest mismatch for %s: expected '
|
| 438 |
+
'%s, got %s' % (hasher, destfile,
|
| 439 |
+
digest, actual))
|
| 440 |
+
logger.debug('Digest verified: %s', digest)
|
| 441 |
+
|
| 442 |
+
def send_request(self, req):
|
| 443 |
+
"""
|
| 444 |
+
Send a standard library :class:`Request` to PyPI and return its
|
| 445 |
+
response.
|
| 446 |
+
|
| 447 |
+
:param req: The request to send.
|
| 448 |
+
:return: The HTTP response from PyPI (a standard library HTTPResponse).
|
| 449 |
+
"""
|
| 450 |
+
handlers = []
|
| 451 |
+
if self.password_handler:
|
| 452 |
+
handlers.append(self.password_handler)
|
| 453 |
+
if self.ssl_verifier:
|
| 454 |
+
handlers.append(self.ssl_verifier)
|
| 455 |
+
opener = build_opener(*handlers)
|
| 456 |
+
return opener.open(req)
|
| 457 |
+
|
| 458 |
+
def encode_request(self, fields, files):
|
| 459 |
+
"""
|
| 460 |
+
Encode fields and files for posting to an HTTP server.
|
| 461 |
+
|
| 462 |
+
:param fields: The fields to send as a list of (fieldname, value)
|
| 463 |
+
tuples.
|
| 464 |
+
:param files: The files to send as a list of (fieldname, filename,
|
| 465 |
+
file_bytes) tuple.
|
| 466 |
+
"""
|
| 467 |
+
# Adapted from packaging, which in turn was adapted from
|
| 468 |
+
# http://code.activestate.com/recipes/146306
|
| 469 |
+
|
| 470 |
+
parts = []
|
| 471 |
+
boundary = self.boundary
|
| 472 |
+
for k, values in fields:
|
| 473 |
+
if not isinstance(values, (list, tuple)):
|
| 474 |
+
values = [values]
|
| 475 |
+
|
| 476 |
+
for v in values:
|
| 477 |
+
parts.extend((
|
| 478 |
+
b'--' + boundary,
|
| 479 |
+
('Content-Disposition: form-data; name="%s"' %
|
| 480 |
+
k).encode('utf-8'),
|
| 481 |
+
b'',
|
| 482 |
+
v.encode('utf-8')))
|
| 483 |
+
for key, filename, value in files:
|
| 484 |
+
parts.extend((
|
| 485 |
+
b'--' + boundary,
|
| 486 |
+
('Content-Disposition: form-data; name="%s"; filename="%s"' %
|
| 487 |
+
(key, filename)).encode('utf-8'),
|
| 488 |
+
b'',
|
| 489 |
+
value))
|
| 490 |
+
|
| 491 |
+
parts.extend((b'--' + boundary + b'--', b''))
|
| 492 |
+
|
| 493 |
+
body = b'\r\n'.join(parts)
|
| 494 |
+
ct = b'multipart/form-data; boundary=' + boundary
|
| 495 |
+
headers = {
|
| 496 |
+
'Content-type': ct,
|
| 497 |
+
'Content-length': str(len(body))
|
| 498 |
+
}
|
| 499 |
+
return Request(self.url, body, headers)
|
| 500 |
+
|
| 501 |
+
def search(self, terms, operator=None): # pragma: no cover
|
| 502 |
+
if isinstance(terms, string_types):
|
| 503 |
+
terms = {'name': terms}
|
| 504 |
+
rpc_proxy = ServerProxy(self.url, timeout=3.0)
|
| 505 |
+
try:
|
| 506 |
+
return rpc_proxy.search(terms, operator or 'and')
|
| 507 |
+
finally:
|
| 508 |
+
rpc_proxy('close')()
|
lib/python3.12/site-packages/pip/_vendor/distlib/locators.py
ADDED
|
@@ -0,0 +1,1295 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2012-2023 Vinay Sajip.
|
| 4 |
+
# Licensed to the Python Software Foundation under a contributor agreement.
|
| 5 |
+
# See LICENSE.txt and CONTRIBUTORS.txt.
|
| 6 |
+
#
|
| 7 |
+
|
| 8 |
+
import gzip
|
| 9 |
+
from io import BytesIO
|
| 10 |
+
import json
|
| 11 |
+
import logging
|
| 12 |
+
import os
|
| 13 |
+
import posixpath
|
| 14 |
+
import re
|
| 15 |
+
try:
|
| 16 |
+
import threading
|
| 17 |
+
except ImportError: # pragma: no cover
|
| 18 |
+
import dummy_threading as threading
|
| 19 |
+
import zlib
|
| 20 |
+
|
| 21 |
+
from . import DistlibException
|
| 22 |
+
from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url, queue, quote, unescape, build_opener,
|
| 23 |
+
HTTPRedirectHandler as BaseRedirectHandler, text_type, Request, HTTPError, URLError)
|
| 24 |
+
from .database import Distribution, DistributionPath, make_dist
|
| 25 |
+
from .metadata import Metadata, MetadataInvalidError
|
| 26 |
+
from .util import (cached_property, ensure_slash, split_filename, get_project_data, parse_requirement,
|
| 27 |
+
parse_name_and_version, ServerProxy, normalize_name)
|
| 28 |
+
from .version import get_scheme, UnsupportedVersionError
|
| 29 |
+
from .wheel import Wheel, is_compatible
|
| 30 |
+
|
| 31 |
+
logger = logging.getLogger(__name__)
|
| 32 |
+
|
| 33 |
+
HASHER_HASH = re.compile(r'^(\w+)=([a-f0-9]+)')
|
| 34 |
+
CHARSET = re.compile(r';\s*charset\s*=\s*(.*)\s*$', re.I)
|
| 35 |
+
HTML_CONTENT_TYPE = re.compile('text/html|application/x(ht)?ml')
|
| 36 |
+
DEFAULT_INDEX = 'https://pypi.org/pypi'
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def get_all_distribution_names(url=None):
|
| 40 |
+
"""
|
| 41 |
+
Return all distribution names known by an index.
|
| 42 |
+
:param url: The URL of the index.
|
| 43 |
+
:return: A list of all known distribution names.
|
| 44 |
+
"""
|
| 45 |
+
if url is None:
|
| 46 |
+
url = DEFAULT_INDEX
|
| 47 |
+
client = ServerProxy(url, timeout=3.0)
|
| 48 |
+
try:
|
| 49 |
+
return client.list_packages()
|
| 50 |
+
finally:
|
| 51 |
+
client('close')()
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class RedirectHandler(BaseRedirectHandler):
|
| 55 |
+
"""
|
| 56 |
+
A class to work around a bug in some Python 3.2.x releases.
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
# There's a bug in the base version for some 3.2.x
|
| 60 |
+
# (e.g. 3.2.2 on Ubuntu Oneiric). If a Location header
|
| 61 |
+
# returns e.g. /abc, it bails because it says the scheme ''
|
| 62 |
+
# is bogus, when actually it should use the request's
|
| 63 |
+
# URL for the scheme. See Python issue #13696.
|
| 64 |
+
def http_error_302(self, req, fp, code, msg, headers):
|
| 65 |
+
# Some servers (incorrectly) return multiple Location headers
|
| 66 |
+
# (so probably same goes for URI). Use first header.
|
| 67 |
+
newurl = None
|
| 68 |
+
for key in ('location', 'uri'):
|
| 69 |
+
if key in headers:
|
| 70 |
+
newurl = headers[key]
|
| 71 |
+
break
|
| 72 |
+
if newurl is None: # pragma: no cover
|
| 73 |
+
return
|
| 74 |
+
urlparts = urlparse(newurl)
|
| 75 |
+
if urlparts.scheme == '':
|
| 76 |
+
newurl = urljoin(req.get_full_url(), newurl)
|
| 77 |
+
if hasattr(headers, 'replace_header'):
|
| 78 |
+
headers.replace_header(key, newurl)
|
| 79 |
+
else:
|
| 80 |
+
headers[key] = newurl
|
| 81 |
+
return BaseRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
|
| 82 |
+
|
| 83 |
+
http_error_301 = http_error_303 = http_error_307 = http_error_302
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class Locator(object):
|
| 87 |
+
"""
|
| 88 |
+
A base class for locators - things that locate distributions.
|
| 89 |
+
"""
|
| 90 |
+
source_extensions = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz')
|
| 91 |
+
binary_extensions = ('.egg', '.exe', '.whl')
|
| 92 |
+
excluded_extensions = ('.pdf', )
|
| 93 |
+
|
| 94 |
+
# A list of tags indicating which wheels you want to match. The default
|
| 95 |
+
# value of None matches against the tags compatible with the running
|
| 96 |
+
# Python. If you want to match other values, set wheel_tags on a locator
|
| 97 |
+
# instance to a list of tuples (pyver, abi, arch) which you want to match.
|
| 98 |
+
wheel_tags = None
|
| 99 |
+
|
| 100 |
+
downloadable_extensions = source_extensions + ('.whl', )
|
| 101 |
+
|
| 102 |
+
def __init__(self, scheme='default'):
|
| 103 |
+
"""
|
| 104 |
+
Initialise an instance.
|
| 105 |
+
:param scheme: Because locators look for most recent versions, they
|
| 106 |
+
need to know the version scheme to use. This specifies
|
| 107 |
+
the current PEP-recommended scheme - use ``'legacy'``
|
| 108 |
+
if you need to support existing distributions on PyPI.
|
| 109 |
+
"""
|
| 110 |
+
self._cache = {}
|
| 111 |
+
self.scheme = scheme
|
| 112 |
+
# Because of bugs in some of the handlers on some of the platforms,
|
| 113 |
+
# we use our own opener rather than just using urlopen.
|
| 114 |
+
self.opener = build_opener(RedirectHandler())
|
| 115 |
+
# If get_project() is called from locate(), the matcher instance
|
| 116 |
+
# is set from the requirement passed to locate(). See issue #18 for
|
| 117 |
+
# why this can be useful to know.
|
| 118 |
+
self.matcher = None
|
| 119 |
+
self.errors = queue.Queue()
|
| 120 |
+
|
| 121 |
+
def get_errors(self):
|
| 122 |
+
"""
|
| 123 |
+
Return any errors which have occurred.
|
| 124 |
+
"""
|
| 125 |
+
result = []
|
| 126 |
+
while not self.errors.empty(): # pragma: no cover
|
| 127 |
+
try:
|
| 128 |
+
e = self.errors.get(False)
|
| 129 |
+
result.append(e)
|
| 130 |
+
except self.errors.Empty:
|
| 131 |
+
continue
|
| 132 |
+
self.errors.task_done()
|
| 133 |
+
return result
|
| 134 |
+
|
| 135 |
+
def clear_errors(self):
|
| 136 |
+
"""
|
| 137 |
+
Clear any errors which may have been logged.
|
| 138 |
+
"""
|
| 139 |
+
# Just get the errors and throw them away
|
| 140 |
+
self.get_errors()
|
| 141 |
+
|
| 142 |
+
def clear_cache(self):
|
| 143 |
+
self._cache.clear()
|
| 144 |
+
|
| 145 |
+
def _get_scheme(self):
|
| 146 |
+
return self._scheme
|
| 147 |
+
|
| 148 |
+
def _set_scheme(self, value):
|
| 149 |
+
self._scheme = value
|
| 150 |
+
|
| 151 |
+
scheme = property(_get_scheme, _set_scheme)
|
| 152 |
+
|
| 153 |
+
def _get_project(self, name):
|
| 154 |
+
"""
|
| 155 |
+
For a given project, get a dictionary mapping available versions to Distribution
|
| 156 |
+
instances.
|
| 157 |
+
|
| 158 |
+
This should be implemented in subclasses.
|
| 159 |
+
|
| 160 |
+
If called from a locate() request, self.matcher will be set to a
|
| 161 |
+
matcher for the requirement to satisfy, otherwise it will be None.
|
| 162 |
+
"""
|
| 163 |
+
raise NotImplementedError('Please implement in the subclass')
|
| 164 |
+
|
| 165 |
+
def get_distribution_names(self):
|
| 166 |
+
"""
|
| 167 |
+
Return all the distribution names known to this locator.
|
| 168 |
+
"""
|
| 169 |
+
raise NotImplementedError('Please implement in the subclass')
|
| 170 |
+
|
| 171 |
+
def get_project(self, name):
|
| 172 |
+
"""
|
| 173 |
+
For a given project, get a dictionary mapping available versions to Distribution
|
| 174 |
+
instances.
|
| 175 |
+
|
| 176 |
+
This calls _get_project to do all the work, and just implements a caching layer on top.
|
| 177 |
+
"""
|
| 178 |
+
if self._cache is None: # pragma: no cover
|
| 179 |
+
result = self._get_project(name)
|
| 180 |
+
elif name in self._cache:
|
| 181 |
+
result = self._cache[name]
|
| 182 |
+
else:
|
| 183 |
+
self.clear_errors()
|
| 184 |
+
result = self._get_project(name)
|
| 185 |
+
self._cache[name] = result
|
| 186 |
+
return result
|
| 187 |
+
|
| 188 |
+
def score_url(self, url):
|
| 189 |
+
"""
|
| 190 |
+
Give an url a score which can be used to choose preferred URLs
|
| 191 |
+
for a given project release.
|
| 192 |
+
"""
|
| 193 |
+
t = urlparse(url)
|
| 194 |
+
basename = posixpath.basename(t.path)
|
| 195 |
+
compatible = True
|
| 196 |
+
is_wheel = basename.endswith('.whl')
|
| 197 |
+
is_downloadable = basename.endswith(self.downloadable_extensions)
|
| 198 |
+
if is_wheel:
|
| 199 |
+
compatible = is_compatible(Wheel(basename), self.wheel_tags)
|
| 200 |
+
return (t.scheme == 'https', 'pypi.org' in t.netloc, is_downloadable, is_wheel, compatible, basename)
|
| 201 |
+
|
| 202 |
+
def prefer_url(self, url1, url2):
|
| 203 |
+
"""
|
| 204 |
+
Choose one of two URLs where both are candidates for distribution
|
| 205 |
+
archives for the same version of a distribution (for example,
|
| 206 |
+
.tar.gz vs. zip).
|
| 207 |
+
|
| 208 |
+
The current implementation favours https:// URLs over http://, archives
|
| 209 |
+
from PyPI over those from other locations, wheel compatibility (if a
|
| 210 |
+
wheel) and then the archive name.
|
| 211 |
+
"""
|
| 212 |
+
result = url2
|
| 213 |
+
if url1:
|
| 214 |
+
s1 = self.score_url(url1)
|
| 215 |
+
s2 = self.score_url(url2)
|
| 216 |
+
if s1 > s2:
|
| 217 |
+
result = url1
|
| 218 |
+
if result != url2:
|
| 219 |
+
logger.debug('Not replacing %r with %r', url1, url2)
|
| 220 |
+
else:
|
| 221 |
+
logger.debug('Replacing %r with %r', url1, url2)
|
| 222 |
+
return result
|
| 223 |
+
|
| 224 |
+
def split_filename(self, filename, project_name):
|
| 225 |
+
"""
|
| 226 |
+
Attempt to split a filename in project name, version and Python version.
|
| 227 |
+
"""
|
| 228 |
+
return split_filename(filename, project_name)
|
| 229 |
+
|
| 230 |
+
def convert_url_to_download_info(self, url, project_name):
|
| 231 |
+
"""
|
| 232 |
+
See if a URL is a candidate for a download URL for a project (the URL
|
| 233 |
+
has typically been scraped from an HTML page).
|
| 234 |
+
|
| 235 |
+
If it is, a dictionary is returned with keys "name", "version",
|
| 236 |
+
"filename" and "url"; otherwise, None is returned.
|
| 237 |
+
"""
|
| 238 |
+
|
| 239 |
+
def same_project(name1, name2):
|
| 240 |
+
return normalize_name(name1) == normalize_name(name2)
|
| 241 |
+
|
| 242 |
+
result = None
|
| 243 |
+
scheme, netloc, path, params, query, frag = urlparse(url)
|
| 244 |
+
if frag.lower().startswith('egg='): # pragma: no cover
|
| 245 |
+
logger.debug('%s: version hint in fragment: %r', project_name, frag)
|
| 246 |
+
m = HASHER_HASH.match(frag)
|
| 247 |
+
if m:
|
| 248 |
+
algo, digest = m.groups()
|
| 249 |
+
else:
|
| 250 |
+
algo, digest = None, None
|
| 251 |
+
origpath = path
|
| 252 |
+
if path and path[-1] == '/': # pragma: no cover
|
| 253 |
+
path = path[:-1]
|
| 254 |
+
if path.endswith('.whl'):
|
| 255 |
+
try:
|
| 256 |
+
wheel = Wheel(path)
|
| 257 |
+
if not is_compatible(wheel, self.wheel_tags):
|
| 258 |
+
logger.debug('Wheel not compatible: %s', path)
|
| 259 |
+
else:
|
| 260 |
+
if project_name is None:
|
| 261 |
+
include = True
|
| 262 |
+
else:
|
| 263 |
+
include = same_project(wheel.name, project_name)
|
| 264 |
+
if include:
|
| 265 |
+
result = {
|
| 266 |
+
'name': wheel.name,
|
| 267 |
+
'version': wheel.version,
|
| 268 |
+
'filename': wheel.filename,
|
| 269 |
+
'url': urlunparse((scheme, netloc, origpath, params, query, '')),
|
| 270 |
+
'python-version': ', '.join(['.'.join(list(v[2:])) for v in wheel.pyver]),
|
| 271 |
+
}
|
| 272 |
+
except Exception: # pragma: no cover
|
| 273 |
+
logger.warning('invalid path for wheel: %s', path)
|
| 274 |
+
elif not path.endswith(self.downloadable_extensions): # pragma: no cover
|
| 275 |
+
logger.debug('Not downloadable: %s', path)
|
| 276 |
+
else: # downloadable extension
|
| 277 |
+
path = filename = posixpath.basename(path)
|
| 278 |
+
for ext in self.downloadable_extensions:
|
| 279 |
+
if path.endswith(ext):
|
| 280 |
+
path = path[:-len(ext)]
|
| 281 |
+
t = self.split_filename(path, project_name)
|
| 282 |
+
if not t: # pragma: no cover
|
| 283 |
+
logger.debug('No match for project/version: %s', path)
|
| 284 |
+
else:
|
| 285 |
+
name, version, pyver = t
|
| 286 |
+
if not project_name or same_project(project_name, name):
|
| 287 |
+
result = {
|
| 288 |
+
'name': name,
|
| 289 |
+
'version': version,
|
| 290 |
+
'filename': filename,
|
| 291 |
+
'url': urlunparse((scheme, netloc, origpath, params, query, '')),
|
| 292 |
+
}
|
| 293 |
+
if pyver: # pragma: no cover
|
| 294 |
+
result['python-version'] = pyver
|
| 295 |
+
break
|
| 296 |
+
if result and algo:
|
| 297 |
+
result['%s_digest' % algo] = digest
|
| 298 |
+
return result
|
| 299 |
+
|
| 300 |
+
def _get_digest(self, info):
|
| 301 |
+
"""
|
| 302 |
+
Get a digest from a dictionary by looking at a "digests" dictionary
|
| 303 |
+
or keys of the form 'algo_digest'.
|
| 304 |
+
|
| 305 |
+
Returns a 2-tuple (algo, digest) if found, else None. Currently
|
| 306 |
+
looks only for SHA256, then MD5.
|
| 307 |
+
"""
|
| 308 |
+
result = None
|
| 309 |
+
if 'digests' in info:
|
| 310 |
+
digests = info['digests']
|
| 311 |
+
for algo in ('sha256', 'md5'):
|
| 312 |
+
if algo in digests:
|
| 313 |
+
result = (algo, digests[algo])
|
| 314 |
+
break
|
| 315 |
+
if not result:
|
| 316 |
+
for algo in ('sha256', 'md5'):
|
| 317 |
+
key = '%s_digest' % algo
|
| 318 |
+
if key in info:
|
| 319 |
+
result = (algo, info[key])
|
| 320 |
+
break
|
| 321 |
+
return result
|
| 322 |
+
|
| 323 |
+
def _update_version_data(self, result, info):
|
| 324 |
+
"""
|
| 325 |
+
Update a result dictionary (the final result from _get_project) with a
|
| 326 |
+
dictionary for a specific version, which typically holds information
|
| 327 |
+
gleaned from a filename or URL for an archive for the distribution.
|
| 328 |
+
"""
|
| 329 |
+
name = info.pop('name')
|
| 330 |
+
version = info.pop('version')
|
| 331 |
+
if version in result:
|
| 332 |
+
dist = result[version]
|
| 333 |
+
md = dist.metadata
|
| 334 |
+
else:
|
| 335 |
+
dist = make_dist(name, version, scheme=self.scheme)
|
| 336 |
+
md = dist.metadata
|
| 337 |
+
dist.digest = digest = self._get_digest(info)
|
| 338 |
+
url = info['url']
|
| 339 |
+
result['digests'][url] = digest
|
| 340 |
+
if md.source_url != info['url']:
|
| 341 |
+
md.source_url = self.prefer_url(md.source_url, url)
|
| 342 |
+
result['urls'].setdefault(version, set()).add(url)
|
| 343 |
+
dist.locator = self
|
| 344 |
+
result[version] = dist
|
| 345 |
+
|
| 346 |
+
def locate(self, requirement, prereleases=False):
|
| 347 |
+
"""
|
| 348 |
+
Find the most recent distribution which matches the given
|
| 349 |
+
requirement.
|
| 350 |
+
|
| 351 |
+
:param requirement: A requirement of the form 'foo (1.0)' or perhaps
|
| 352 |
+
'foo (>= 1.0, < 2.0, != 1.3)'
|
| 353 |
+
:param prereleases: If ``True``, allow pre-release versions
|
| 354 |
+
to be located. Otherwise, pre-release versions
|
| 355 |
+
are not returned.
|
| 356 |
+
:return: A :class:`Distribution` instance, or ``None`` if no such
|
| 357 |
+
distribution could be located.
|
| 358 |
+
"""
|
| 359 |
+
result = None
|
| 360 |
+
r = parse_requirement(requirement)
|
| 361 |
+
if r is None: # pragma: no cover
|
| 362 |
+
raise DistlibException('Not a valid requirement: %r' % requirement)
|
| 363 |
+
scheme = get_scheme(self.scheme)
|
| 364 |
+
self.matcher = matcher = scheme.matcher(r.requirement)
|
| 365 |
+
logger.debug('matcher: %s (%s)', matcher, type(matcher).__name__)
|
| 366 |
+
versions = self.get_project(r.name)
|
| 367 |
+
if len(versions) > 2: # urls and digests keys are present
|
| 368 |
+
# sometimes, versions are invalid
|
| 369 |
+
slist = []
|
| 370 |
+
vcls = matcher.version_class
|
| 371 |
+
for k in versions:
|
| 372 |
+
if k in ('urls', 'digests'):
|
| 373 |
+
continue
|
| 374 |
+
try:
|
| 375 |
+
if not matcher.match(k):
|
| 376 |
+
pass # logger.debug('%s did not match %r', matcher, k)
|
| 377 |
+
else:
|
| 378 |
+
if prereleases or not vcls(k).is_prerelease:
|
| 379 |
+
slist.append(k)
|
| 380 |
+
except Exception: # pragma: no cover
|
| 381 |
+
logger.warning('error matching %s with %r', matcher, k)
|
| 382 |
+
pass # slist.append(k)
|
| 383 |
+
if len(slist) > 1:
|
| 384 |
+
slist = sorted(slist, key=scheme.key)
|
| 385 |
+
if slist:
|
| 386 |
+
logger.debug('sorted list: %s', slist)
|
| 387 |
+
version = slist[-1]
|
| 388 |
+
result = versions[version]
|
| 389 |
+
if result:
|
| 390 |
+
if r.extras:
|
| 391 |
+
result.extras = r.extras
|
| 392 |
+
result.download_urls = versions.get('urls', {}).get(version, set())
|
| 393 |
+
d = {}
|
| 394 |
+
sd = versions.get('digests', {})
|
| 395 |
+
for url in result.download_urls:
|
| 396 |
+
if url in sd: # pragma: no cover
|
| 397 |
+
d[url] = sd[url]
|
| 398 |
+
result.digests = d
|
| 399 |
+
self.matcher = None
|
| 400 |
+
return result
|
| 401 |
+
|
| 402 |
+
|
| 403 |
+
class PyPIRPCLocator(Locator):
|
| 404 |
+
"""
|
| 405 |
+
This locator uses XML-RPC to locate distributions. It therefore
|
| 406 |
+
cannot be used with simple mirrors (that only mirror file content).
|
| 407 |
+
"""
|
| 408 |
+
|
| 409 |
+
def __init__(self, url, **kwargs):
|
| 410 |
+
"""
|
| 411 |
+
Initialise an instance.
|
| 412 |
+
|
| 413 |
+
:param url: The URL to use for XML-RPC.
|
| 414 |
+
:param kwargs: Passed to the superclass constructor.
|
| 415 |
+
"""
|
| 416 |
+
super(PyPIRPCLocator, self).__init__(**kwargs)
|
| 417 |
+
self.base_url = url
|
| 418 |
+
self.client = ServerProxy(url, timeout=3.0)
|
| 419 |
+
|
| 420 |
+
def get_distribution_names(self):
|
| 421 |
+
"""
|
| 422 |
+
Return all the distribution names known to this locator.
|
| 423 |
+
"""
|
| 424 |
+
return set(self.client.list_packages())
|
| 425 |
+
|
| 426 |
+
def _get_project(self, name):
|
| 427 |
+
result = {'urls': {}, 'digests': {}}
|
| 428 |
+
versions = self.client.package_releases(name, True)
|
| 429 |
+
for v in versions:
|
| 430 |
+
urls = self.client.release_urls(name, v)
|
| 431 |
+
data = self.client.release_data(name, v)
|
| 432 |
+
metadata = Metadata(scheme=self.scheme)
|
| 433 |
+
metadata.name = data['name']
|
| 434 |
+
metadata.version = data['version']
|
| 435 |
+
metadata.license = data.get('license')
|
| 436 |
+
metadata.keywords = data.get('keywords', [])
|
| 437 |
+
metadata.summary = data.get('summary')
|
| 438 |
+
dist = Distribution(metadata)
|
| 439 |
+
if urls:
|
| 440 |
+
info = urls[0]
|
| 441 |
+
metadata.source_url = info['url']
|
| 442 |
+
dist.digest = self._get_digest(info)
|
| 443 |
+
dist.locator = self
|
| 444 |
+
result[v] = dist
|
| 445 |
+
for info in urls:
|
| 446 |
+
url = info['url']
|
| 447 |
+
digest = self._get_digest(info)
|
| 448 |
+
result['urls'].setdefault(v, set()).add(url)
|
| 449 |
+
result['digests'][url] = digest
|
| 450 |
+
return result
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
class PyPIJSONLocator(Locator):
|
| 454 |
+
"""
|
| 455 |
+
This locator uses PyPI's JSON interface. It's very limited in functionality
|
| 456 |
+
and probably not worth using.
|
| 457 |
+
"""
|
| 458 |
+
|
| 459 |
+
def __init__(self, url, **kwargs):
|
| 460 |
+
super(PyPIJSONLocator, self).__init__(**kwargs)
|
| 461 |
+
self.base_url = ensure_slash(url)
|
| 462 |
+
|
| 463 |
+
def get_distribution_names(self):
|
| 464 |
+
"""
|
| 465 |
+
Return all the distribution names known to this locator.
|
| 466 |
+
"""
|
| 467 |
+
raise NotImplementedError('Not available from this locator')
|
| 468 |
+
|
| 469 |
+
def _get_project(self, name):
|
| 470 |
+
result = {'urls': {}, 'digests': {}}
|
| 471 |
+
url = urljoin(self.base_url, '%s/json' % quote(name))
|
| 472 |
+
try:
|
| 473 |
+
resp = self.opener.open(url)
|
| 474 |
+
data = resp.read().decode() # for now
|
| 475 |
+
d = json.loads(data)
|
| 476 |
+
md = Metadata(scheme=self.scheme)
|
| 477 |
+
data = d['info']
|
| 478 |
+
md.name = data['name']
|
| 479 |
+
md.version = data['version']
|
| 480 |
+
md.license = data.get('license')
|
| 481 |
+
md.keywords = data.get('keywords', [])
|
| 482 |
+
md.summary = data.get('summary')
|
| 483 |
+
dist = Distribution(md)
|
| 484 |
+
dist.locator = self
|
| 485 |
+
# urls = d['urls']
|
| 486 |
+
result[md.version] = dist
|
| 487 |
+
for info in d['urls']:
|
| 488 |
+
url = info['url']
|
| 489 |
+
dist.download_urls.add(url)
|
| 490 |
+
dist.digests[url] = self._get_digest(info)
|
| 491 |
+
result['urls'].setdefault(md.version, set()).add(url)
|
| 492 |
+
result['digests'][url] = self._get_digest(info)
|
| 493 |
+
# Now get other releases
|
| 494 |
+
for version, infos in d['releases'].items():
|
| 495 |
+
if version == md.version:
|
| 496 |
+
continue # already done
|
| 497 |
+
omd = Metadata(scheme=self.scheme)
|
| 498 |
+
omd.name = md.name
|
| 499 |
+
omd.version = version
|
| 500 |
+
odist = Distribution(omd)
|
| 501 |
+
odist.locator = self
|
| 502 |
+
result[version] = odist
|
| 503 |
+
for info in infos:
|
| 504 |
+
url = info['url']
|
| 505 |
+
odist.download_urls.add(url)
|
| 506 |
+
odist.digests[url] = self._get_digest(info)
|
| 507 |
+
result['urls'].setdefault(version, set()).add(url)
|
| 508 |
+
result['digests'][url] = self._get_digest(info)
|
| 509 |
+
|
| 510 |
+
|
| 511 |
+
# for info in urls:
|
| 512 |
+
# md.source_url = info['url']
|
| 513 |
+
# dist.digest = self._get_digest(info)
|
| 514 |
+
# dist.locator = self
|
| 515 |
+
# for info in urls:
|
| 516 |
+
# url = info['url']
|
| 517 |
+
# result['urls'].setdefault(md.version, set()).add(url)
|
| 518 |
+
# result['digests'][url] = self._get_digest(info)
|
| 519 |
+
except Exception as e:
|
| 520 |
+
self.errors.put(text_type(e))
|
| 521 |
+
logger.exception('JSON fetch failed: %s', e)
|
| 522 |
+
return result
|
| 523 |
+
|
| 524 |
+
|
| 525 |
+
class Page(object):
|
| 526 |
+
"""
|
| 527 |
+
This class represents a scraped HTML page.
|
| 528 |
+
"""
|
| 529 |
+
# The following slightly hairy-looking regex just looks for the contents of
|
| 530 |
+
# an anchor link, which has an attribute "href" either immediately preceded
|
| 531 |
+
# or immediately followed by a "rel" attribute. The attribute values can be
|
| 532 |
+
# declared with double quotes, single quotes or no quotes - which leads to
|
| 533 |
+
# the length of the expression.
|
| 534 |
+
_href = re.compile(
|
| 535 |
+
"""
|
| 536 |
+
(rel\\s*=\\s*(?:"(?P<rel1>[^"]*)"|'(?P<rel2>[^']*)'|(?P<rel3>[^>\\s\n]*))\\s+)?
|
| 537 |
+
href\\s*=\\s*(?:"(?P<url1>[^"]*)"|'(?P<url2>[^']*)'|(?P<url3>[^>\\s\n]*))
|
| 538 |
+
(\\s+rel\\s*=\\s*(?:"(?P<rel4>[^"]*)"|'(?P<rel5>[^']*)'|(?P<rel6>[^>\\s\n]*)))?
|
| 539 |
+
""", re.I | re.S | re.X)
|
| 540 |
+
_base = re.compile(r"""<base\s+href\s*=\s*['"]?([^'">]+)""", re.I | re.S)
|
| 541 |
+
|
| 542 |
+
def __init__(self, data, url):
|
| 543 |
+
"""
|
| 544 |
+
Initialise an instance with the Unicode page contents and the URL they
|
| 545 |
+
came from.
|
| 546 |
+
"""
|
| 547 |
+
self.data = data
|
| 548 |
+
self.base_url = self.url = url
|
| 549 |
+
m = self._base.search(self.data)
|
| 550 |
+
if m:
|
| 551 |
+
self.base_url = m.group(1)
|
| 552 |
+
|
| 553 |
+
_clean_re = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I)
|
| 554 |
+
|
| 555 |
+
@cached_property
|
| 556 |
+
def links(self):
|
| 557 |
+
"""
|
| 558 |
+
Return the URLs of all the links on a page together with information
|
| 559 |
+
about their "rel" attribute, for determining which ones to treat as
|
| 560 |
+
downloads and which ones to queue for further scraping.
|
| 561 |
+
"""
|
| 562 |
+
|
| 563 |
+
def clean(url):
|
| 564 |
+
"Tidy up an URL."
|
| 565 |
+
scheme, netloc, path, params, query, frag = urlparse(url)
|
| 566 |
+
return urlunparse((scheme, netloc, quote(path), params, query, frag))
|
| 567 |
+
|
| 568 |
+
result = set()
|
| 569 |
+
for match in self._href.finditer(self.data):
|
| 570 |
+
d = match.groupdict('')
|
| 571 |
+
rel = (d['rel1'] or d['rel2'] or d['rel3'] or d['rel4'] or d['rel5'] or d['rel6'])
|
| 572 |
+
url = d['url1'] or d['url2'] or d['url3']
|
| 573 |
+
url = urljoin(self.base_url, url)
|
| 574 |
+
url = unescape(url)
|
| 575 |
+
url = self._clean_re.sub(lambda m: '%%%2x' % ord(m.group(0)), url)
|
| 576 |
+
result.add((url, rel))
|
| 577 |
+
# We sort the result, hoping to bring the most recent versions
|
| 578 |
+
# to the front
|
| 579 |
+
result = sorted(result, key=lambda t: t[0], reverse=True)
|
| 580 |
+
return result
|
| 581 |
+
|
| 582 |
+
|
| 583 |
+
class SimpleScrapingLocator(Locator):
|
| 584 |
+
"""
|
| 585 |
+
A locator which scrapes HTML pages to locate downloads for a distribution.
|
| 586 |
+
This runs multiple threads to do the I/O; performance is at least as good
|
| 587 |
+
as pip's PackageFinder, which works in an analogous fashion.
|
| 588 |
+
"""
|
| 589 |
+
|
| 590 |
+
# These are used to deal with various Content-Encoding schemes.
|
| 591 |
+
decoders = {
|
| 592 |
+
'deflate': zlib.decompress,
|
| 593 |
+
'gzip': lambda b: gzip.GzipFile(fileobj=BytesIO(b)).read(),
|
| 594 |
+
'none': lambda b: b,
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
def __init__(self, url, timeout=None, num_workers=10, **kwargs):
|
| 598 |
+
"""
|
| 599 |
+
Initialise an instance.
|
| 600 |
+
:param url: The root URL to use for scraping.
|
| 601 |
+
:param timeout: The timeout, in seconds, to be applied to requests.
|
| 602 |
+
This defaults to ``None`` (no timeout specified).
|
| 603 |
+
:param num_workers: The number of worker threads you want to do I/O,
|
| 604 |
+
This defaults to 10.
|
| 605 |
+
:param kwargs: Passed to the superclass.
|
| 606 |
+
"""
|
| 607 |
+
super(SimpleScrapingLocator, self).__init__(**kwargs)
|
| 608 |
+
self.base_url = ensure_slash(url)
|
| 609 |
+
self.timeout = timeout
|
| 610 |
+
self._page_cache = {}
|
| 611 |
+
self._seen = set()
|
| 612 |
+
self._to_fetch = queue.Queue()
|
| 613 |
+
self._bad_hosts = set()
|
| 614 |
+
self.skip_externals = False
|
| 615 |
+
self.num_workers = num_workers
|
| 616 |
+
self._lock = threading.RLock()
|
| 617 |
+
# See issue #45: we need to be resilient when the locator is used
|
| 618 |
+
# in a thread, e.g. with concurrent.futures. We can't use self._lock
|
| 619 |
+
# as it is for coordinating our internal threads - the ones created
|
| 620 |
+
# in _prepare_threads.
|
| 621 |
+
self._gplock = threading.RLock()
|
| 622 |
+
self.platform_check = False # See issue #112
|
| 623 |
+
|
| 624 |
+
def _prepare_threads(self):
|
| 625 |
+
"""
|
| 626 |
+
Threads are created only when get_project is called, and terminate
|
| 627 |
+
before it returns. They are there primarily to parallelise I/O (i.e.
|
| 628 |
+
fetching web pages).
|
| 629 |
+
"""
|
| 630 |
+
self._threads = []
|
| 631 |
+
for i in range(self.num_workers):
|
| 632 |
+
t = threading.Thread(target=self._fetch)
|
| 633 |
+
t.daemon = True
|
| 634 |
+
t.start()
|
| 635 |
+
self._threads.append(t)
|
| 636 |
+
|
| 637 |
+
def _wait_threads(self):
|
| 638 |
+
"""
|
| 639 |
+
Tell all the threads to terminate (by sending a sentinel value) and
|
| 640 |
+
wait for them to do so.
|
| 641 |
+
"""
|
| 642 |
+
# Note that you need two loops, since you can't say which
|
| 643 |
+
# thread will get each sentinel
|
| 644 |
+
for t in self._threads:
|
| 645 |
+
self._to_fetch.put(None) # sentinel
|
| 646 |
+
for t in self._threads:
|
| 647 |
+
t.join()
|
| 648 |
+
self._threads = []
|
| 649 |
+
|
| 650 |
+
def _get_project(self, name):
|
| 651 |
+
result = {'urls': {}, 'digests': {}}
|
| 652 |
+
with self._gplock:
|
| 653 |
+
self.result = result
|
| 654 |
+
self.project_name = name
|
| 655 |
+
url = urljoin(self.base_url, '%s/' % quote(name))
|
| 656 |
+
self._seen.clear()
|
| 657 |
+
self._page_cache.clear()
|
| 658 |
+
self._prepare_threads()
|
| 659 |
+
try:
|
| 660 |
+
logger.debug('Queueing %s', url)
|
| 661 |
+
self._to_fetch.put(url)
|
| 662 |
+
self._to_fetch.join()
|
| 663 |
+
finally:
|
| 664 |
+
self._wait_threads()
|
| 665 |
+
del self.result
|
| 666 |
+
return result
|
| 667 |
+
|
| 668 |
+
platform_dependent = re.compile(r'\b(linux_(i\d86|x86_64|arm\w+)|'
|
| 669 |
+
r'win(32|_amd64)|macosx_?\d+)\b', re.I)
|
| 670 |
+
|
| 671 |
+
def _is_platform_dependent(self, url):
|
| 672 |
+
"""
|
| 673 |
+
Does an URL refer to a platform-specific download?
|
| 674 |
+
"""
|
| 675 |
+
return self.platform_dependent.search(url)
|
| 676 |
+
|
| 677 |
+
def _process_download(self, url):
|
| 678 |
+
"""
|
| 679 |
+
See if an URL is a suitable download for a project.
|
| 680 |
+
|
| 681 |
+
If it is, register information in the result dictionary (for
|
| 682 |
+
_get_project) about the specific version it's for.
|
| 683 |
+
|
| 684 |
+
Note that the return value isn't actually used other than as a boolean
|
| 685 |
+
value.
|
| 686 |
+
"""
|
| 687 |
+
if self.platform_check and self._is_platform_dependent(url):
|
| 688 |
+
info = None
|
| 689 |
+
else:
|
| 690 |
+
info = self.convert_url_to_download_info(url, self.project_name)
|
| 691 |
+
logger.debug('process_download: %s -> %s', url, info)
|
| 692 |
+
if info:
|
| 693 |
+
with self._lock: # needed because self.result is shared
|
| 694 |
+
self._update_version_data(self.result, info)
|
| 695 |
+
return info
|
| 696 |
+
|
| 697 |
+
def _should_queue(self, link, referrer, rel):
|
| 698 |
+
"""
|
| 699 |
+
Determine whether a link URL from a referring page and with a
|
| 700 |
+
particular "rel" attribute should be queued for scraping.
|
| 701 |
+
"""
|
| 702 |
+
scheme, netloc, path, _, _, _ = urlparse(link)
|
| 703 |
+
if path.endswith(self.source_extensions + self.binary_extensions + self.excluded_extensions):
|
| 704 |
+
result = False
|
| 705 |
+
elif self.skip_externals and not link.startswith(self.base_url):
|
| 706 |
+
result = False
|
| 707 |
+
elif not referrer.startswith(self.base_url):
|
| 708 |
+
result = False
|
| 709 |
+
elif rel not in ('homepage', 'download'):
|
| 710 |
+
result = False
|
| 711 |
+
elif scheme not in ('http', 'https', 'ftp'):
|
| 712 |
+
result = False
|
| 713 |
+
elif self._is_platform_dependent(link):
|
| 714 |
+
result = False
|
| 715 |
+
else:
|
| 716 |
+
host = netloc.split(':', 1)[0]
|
| 717 |
+
if host.lower() == 'localhost':
|
| 718 |
+
result = False
|
| 719 |
+
else:
|
| 720 |
+
result = True
|
| 721 |
+
logger.debug('should_queue: %s (%s) from %s -> %s', link, rel, referrer, result)
|
| 722 |
+
return result
|
| 723 |
+
|
| 724 |
+
def _fetch(self):
|
| 725 |
+
"""
|
| 726 |
+
Get a URL to fetch from the work queue, get the HTML page, examine its
|
| 727 |
+
links for download candidates and candidates for further scraping.
|
| 728 |
+
|
| 729 |
+
This is a handy method to run in a thread.
|
| 730 |
+
"""
|
| 731 |
+
while True:
|
| 732 |
+
url = self._to_fetch.get()
|
| 733 |
+
try:
|
| 734 |
+
if url:
|
| 735 |
+
page = self.get_page(url)
|
| 736 |
+
if page is None: # e.g. after an error
|
| 737 |
+
continue
|
| 738 |
+
for link, rel in page.links:
|
| 739 |
+
if link not in self._seen:
|
| 740 |
+
try:
|
| 741 |
+
self._seen.add(link)
|
| 742 |
+
if (not self._process_download(link) and self._should_queue(link, url, rel)):
|
| 743 |
+
logger.debug('Queueing %s from %s', link, url)
|
| 744 |
+
self._to_fetch.put(link)
|
| 745 |
+
except MetadataInvalidError: # e.g. invalid versions
|
| 746 |
+
pass
|
| 747 |
+
except Exception as e: # pragma: no cover
|
| 748 |
+
self.errors.put(text_type(e))
|
| 749 |
+
finally:
|
| 750 |
+
# always do this, to avoid hangs :-)
|
| 751 |
+
self._to_fetch.task_done()
|
| 752 |
+
if not url:
|
| 753 |
+
# logger.debug('Sentinel seen, quitting.')
|
| 754 |
+
break
|
| 755 |
+
|
| 756 |
+
def get_page(self, url):
|
| 757 |
+
"""
|
| 758 |
+
Get the HTML for an URL, possibly from an in-memory cache.
|
| 759 |
+
|
| 760 |
+
XXX TODO Note: this cache is never actually cleared. It's assumed that
|
| 761 |
+
the data won't get stale over the lifetime of a locator instance (not
|
| 762 |
+
necessarily true for the default_locator).
|
| 763 |
+
"""
|
| 764 |
+
# http://peak.telecommunity.com/DevCenter/EasyInstall#package-index-api
|
| 765 |
+
scheme, netloc, path, _, _, _ = urlparse(url)
|
| 766 |
+
if scheme == 'file' and os.path.isdir(url2pathname(path)):
|
| 767 |
+
url = urljoin(ensure_slash(url), 'index.html')
|
| 768 |
+
|
| 769 |
+
if url in self._page_cache:
|
| 770 |
+
result = self._page_cache[url]
|
| 771 |
+
logger.debug('Returning %s from cache: %s', url, result)
|
| 772 |
+
else:
|
| 773 |
+
host = netloc.split(':', 1)[0]
|
| 774 |
+
result = None
|
| 775 |
+
if host in self._bad_hosts:
|
| 776 |
+
logger.debug('Skipping %s due to bad host %s', url, host)
|
| 777 |
+
else:
|
| 778 |
+
req = Request(url, headers={'Accept-encoding': 'identity'})
|
| 779 |
+
try:
|
| 780 |
+
logger.debug('Fetching %s', url)
|
| 781 |
+
resp = self.opener.open(req, timeout=self.timeout)
|
| 782 |
+
logger.debug('Fetched %s', url)
|
| 783 |
+
headers = resp.info()
|
| 784 |
+
content_type = headers.get('Content-Type', '')
|
| 785 |
+
if HTML_CONTENT_TYPE.match(content_type):
|
| 786 |
+
final_url = resp.geturl()
|
| 787 |
+
data = resp.read()
|
| 788 |
+
encoding = headers.get('Content-Encoding')
|
| 789 |
+
if encoding:
|
| 790 |
+
decoder = self.decoders[encoding] # fail if not found
|
| 791 |
+
data = decoder(data)
|
| 792 |
+
encoding = 'utf-8'
|
| 793 |
+
m = CHARSET.search(content_type)
|
| 794 |
+
if m:
|
| 795 |
+
encoding = m.group(1)
|
| 796 |
+
try:
|
| 797 |
+
data = data.decode(encoding)
|
| 798 |
+
except UnicodeError: # pragma: no cover
|
| 799 |
+
data = data.decode('latin-1') # fallback
|
| 800 |
+
result = Page(data, final_url)
|
| 801 |
+
self._page_cache[final_url] = result
|
| 802 |
+
except HTTPError as e:
|
| 803 |
+
if e.code != 404:
|
| 804 |
+
logger.exception('Fetch failed: %s: %s', url, e)
|
| 805 |
+
except URLError as e: # pragma: no cover
|
| 806 |
+
logger.exception('Fetch failed: %s: %s', url, e)
|
| 807 |
+
with self._lock:
|
| 808 |
+
self._bad_hosts.add(host)
|
| 809 |
+
except Exception as e: # pragma: no cover
|
| 810 |
+
logger.exception('Fetch failed: %s: %s', url, e)
|
| 811 |
+
finally:
|
| 812 |
+
self._page_cache[url] = result # even if None (failure)
|
| 813 |
+
return result
|
| 814 |
+
|
| 815 |
+
_distname_re = re.compile('<a href=[^>]*>([^<]+)<')
|
| 816 |
+
|
| 817 |
+
def get_distribution_names(self):
|
| 818 |
+
"""
|
| 819 |
+
Return all the distribution names known to this locator.
|
| 820 |
+
"""
|
| 821 |
+
result = set()
|
| 822 |
+
page = self.get_page(self.base_url)
|
| 823 |
+
if not page:
|
| 824 |
+
raise DistlibException('Unable to get %s' % self.base_url)
|
| 825 |
+
for match in self._distname_re.finditer(page.data):
|
| 826 |
+
result.add(match.group(1))
|
| 827 |
+
return result
|
| 828 |
+
|
| 829 |
+
|
| 830 |
+
class DirectoryLocator(Locator):
|
| 831 |
+
"""
|
| 832 |
+
This class locates distributions in a directory tree.
|
| 833 |
+
"""
|
| 834 |
+
|
| 835 |
+
def __init__(self, path, **kwargs):
|
| 836 |
+
"""
|
| 837 |
+
Initialise an instance.
|
| 838 |
+
:param path: The root of the directory tree to search.
|
| 839 |
+
:param kwargs: Passed to the superclass constructor,
|
| 840 |
+
except for:
|
| 841 |
+
* recursive - if True (the default), subdirectories are
|
| 842 |
+
recursed into. If False, only the top-level directory
|
| 843 |
+
is searched,
|
| 844 |
+
"""
|
| 845 |
+
self.recursive = kwargs.pop('recursive', True)
|
| 846 |
+
super(DirectoryLocator, self).__init__(**kwargs)
|
| 847 |
+
path = os.path.abspath(path)
|
| 848 |
+
if not os.path.isdir(path): # pragma: no cover
|
| 849 |
+
raise DistlibException('Not a directory: %r' % path)
|
| 850 |
+
self.base_dir = path
|
| 851 |
+
|
| 852 |
+
def should_include(self, filename, parent):
|
| 853 |
+
"""
|
| 854 |
+
Should a filename be considered as a candidate for a distribution
|
| 855 |
+
archive? As well as the filename, the directory which contains it
|
| 856 |
+
is provided, though not used by the current implementation.
|
| 857 |
+
"""
|
| 858 |
+
return filename.endswith(self.downloadable_extensions)
|
| 859 |
+
|
| 860 |
+
def _get_project(self, name):
|
| 861 |
+
result = {'urls': {}, 'digests': {}}
|
| 862 |
+
for root, dirs, files in os.walk(self.base_dir):
|
| 863 |
+
for fn in files:
|
| 864 |
+
if self.should_include(fn, root):
|
| 865 |
+
fn = os.path.join(root, fn)
|
| 866 |
+
url = urlunparse(('file', '', pathname2url(os.path.abspath(fn)), '', '', ''))
|
| 867 |
+
info = self.convert_url_to_download_info(url, name)
|
| 868 |
+
if info:
|
| 869 |
+
self._update_version_data(result, info)
|
| 870 |
+
if not self.recursive:
|
| 871 |
+
break
|
| 872 |
+
return result
|
| 873 |
+
|
| 874 |
+
def get_distribution_names(self):
|
| 875 |
+
"""
|
| 876 |
+
Return all the distribution names known to this locator.
|
| 877 |
+
"""
|
| 878 |
+
result = set()
|
| 879 |
+
for root, dirs, files in os.walk(self.base_dir):
|
| 880 |
+
for fn in files:
|
| 881 |
+
if self.should_include(fn, root):
|
| 882 |
+
fn = os.path.join(root, fn)
|
| 883 |
+
url = urlunparse(('file', '', pathname2url(os.path.abspath(fn)), '', '', ''))
|
| 884 |
+
info = self.convert_url_to_download_info(url, None)
|
| 885 |
+
if info:
|
| 886 |
+
result.add(info['name'])
|
| 887 |
+
if not self.recursive:
|
| 888 |
+
break
|
| 889 |
+
return result
|
| 890 |
+
|
| 891 |
+
|
| 892 |
+
class JSONLocator(Locator):
|
| 893 |
+
"""
|
| 894 |
+
This locator uses special extended metadata (not available on PyPI) and is
|
| 895 |
+
the basis of performant dependency resolution in distlib. Other locators
|
| 896 |
+
require archive downloads before dependencies can be determined! As you
|
| 897 |
+
might imagine, that can be slow.
|
| 898 |
+
"""
|
| 899 |
+
|
| 900 |
+
def get_distribution_names(self):
|
| 901 |
+
"""
|
| 902 |
+
Return all the distribution names known to this locator.
|
| 903 |
+
"""
|
| 904 |
+
raise NotImplementedError('Not available from this locator')
|
| 905 |
+
|
| 906 |
+
def _get_project(self, name):
|
| 907 |
+
result = {'urls': {}, 'digests': {}}
|
| 908 |
+
data = get_project_data(name)
|
| 909 |
+
if data:
|
| 910 |
+
for info in data.get('files', []):
|
| 911 |
+
if info['ptype'] != 'sdist' or info['pyversion'] != 'source':
|
| 912 |
+
continue
|
| 913 |
+
# We don't store summary in project metadata as it makes
|
| 914 |
+
# the data bigger for no benefit during dependency
|
| 915 |
+
# resolution
|
| 916 |
+
dist = make_dist(data['name'],
|
| 917 |
+
info['version'],
|
| 918 |
+
summary=data.get('summary', 'Placeholder for summary'),
|
| 919 |
+
scheme=self.scheme)
|
| 920 |
+
md = dist.metadata
|
| 921 |
+
md.source_url = info['url']
|
| 922 |
+
# TODO SHA256 digest
|
| 923 |
+
if 'digest' in info and info['digest']:
|
| 924 |
+
dist.digest = ('md5', info['digest'])
|
| 925 |
+
md.dependencies = info.get('requirements', {})
|
| 926 |
+
dist.exports = info.get('exports', {})
|
| 927 |
+
result[dist.version] = dist
|
| 928 |
+
result['urls'].setdefault(dist.version, set()).add(info['url'])
|
| 929 |
+
return result
|
| 930 |
+
|
| 931 |
+
|
| 932 |
+
class DistPathLocator(Locator):
|
| 933 |
+
"""
|
| 934 |
+
This locator finds installed distributions in a path. It can be useful for
|
| 935 |
+
adding to an :class:`AggregatingLocator`.
|
| 936 |
+
"""
|
| 937 |
+
|
| 938 |
+
def __init__(self, distpath, **kwargs):
|
| 939 |
+
"""
|
| 940 |
+
Initialise an instance.
|
| 941 |
+
|
| 942 |
+
:param distpath: A :class:`DistributionPath` instance to search.
|
| 943 |
+
"""
|
| 944 |
+
super(DistPathLocator, self).__init__(**kwargs)
|
| 945 |
+
assert isinstance(distpath, DistributionPath)
|
| 946 |
+
self.distpath = distpath
|
| 947 |
+
|
| 948 |
+
def _get_project(self, name):
|
| 949 |
+
dist = self.distpath.get_distribution(name)
|
| 950 |
+
if dist is None:
|
| 951 |
+
result = {'urls': {}, 'digests': {}}
|
| 952 |
+
else:
|
| 953 |
+
result = {
|
| 954 |
+
dist.version: dist,
|
| 955 |
+
'urls': {
|
| 956 |
+
dist.version: set([dist.source_url])
|
| 957 |
+
},
|
| 958 |
+
'digests': {
|
| 959 |
+
dist.version: set([None])
|
| 960 |
+
}
|
| 961 |
+
}
|
| 962 |
+
return result
|
| 963 |
+
|
| 964 |
+
|
| 965 |
+
class AggregatingLocator(Locator):
|
| 966 |
+
"""
|
| 967 |
+
This class allows you to chain and/or merge a list of locators.
|
| 968 |
+
"""
|
| 969 |
+
|
| 970 |
+
def __init__(self, *locators, **kwargs):
|
| 971 |
+
"""
|
| 972 |
+
Initialise an instance.
|
| 973 |
+
|
| 974 |
+
:param locators: The list of locators to search.
|
| 975 |
+
:param kwargs: Passed to the superclass constructor,
|
| 976 |
+
except for:
|
| 977 |
+
* merge - if False (the default), the first successful
|
| 978 |
+
search from any of the locators is returned. If True,
|
| 979 |
+
the results from all locators are merged (this can be
|
| 980 |
+
slow).
|
| 981 |
+
"""
|
| 982 |
+
self.merge = kwargs.pop('merge', False)
|
| 983 |
+
self.locators = locators
|
| 984 |
+
super(AggregatingLocator, self).__init__(**kwargs)
|
| 985 |
+
|
| 986 |
+
def clear_cache(self):
|
| 987 |
+
super(AggregatingLocator, self).clear_cache()
|
| 988 |
+
for locator in self.locators:
|
| 989 |
+
locator.clear_cache()
|
| 990 |
+
|
| 991 |
+
def _set_scheme(self, value):
|
| 992 |
+
self._scheme = value
|
| 993 |
+
for locator in self.locators:
|
| 994 |
+
locator.scheme = value
|
| 995 |
+
|
| 996 |
+
scheme = property(Locator.scheme.fget, _set_scheme)
|
| 997 |
+
|
| 998 |
+
def _get_project(self, name):
|
| 999 |
+
result = {}
|
| 1000 |
+
for locator in self.locators:
|
| 1001 |
+
d = locator.get_project(name)
|
| 1002 |
+
if d:
|
| 1003 |
+
if self.merge:
|
| 1004 |
+
files = result.get('urls', {})
|
| 1005 |
+
digests = result.get('digests', {})
|
| 1006 |
+
# next line could overwrite result['urls'], result['digests']
|
| 1007 |
+
result.update(d)
|
| 1008 |
+
df = result.get('urls')
|
| 1009 |
+
if files and df:
|
| 1010 |
+
for k, v in files.items():
|
| 1011 |
+
if k in df:
|
| 1012 |
+
df[k] |= v
|
| 1013 |
+
else:
|
| 1014 |
+
df[k] = v
|
| 1015 |
+
dd = result.get('digests')
|
| 1016 |
+
if digests and dd:
|
| 1017 |
+
dd.update(digests)
|
| 1018 |
+
else:
|
| 1019 |
+
# See issue #18. If any dists are found and we're looking
|
| 1020 |
+
# for specific constraints, we only return something if
|
| 1021 |
+
# a match is found. For example, if a DirectoryLocator
|
| 1022 |
+
# returns just foo (1.0) while we're looking for
|
| 1023 |
+
# foo (>= 2.0), we'll pretend there was nothing there so
|
| 1024 |
+
# that subsequent locators can be queried. Otherwise we
|
| 1025 |
+
# would just return foo (1.0) which would then lead to a
|
| 1026 |
+
# failure to find foo (>= 2.0), because other locators
|
| 1027 |
+
# weren't searched. Note that this only matters when
|
| 1028 |
+
# merge=False.
|
| 1029 |
+
if self.matcher is None:
|
| 1030 |
+
found = True
|
| 1031 |
+
else:
|
| 1032 |
+
found = False
|
| 1033 |
+
for k in d:
|
| 1034 |
+
if self.matcher.match(k):
|
| 1035 |
+
found = True
|
| 1036 |
+
break
|
| 1037 |
+
if found:
|
| 1038 |
+
result = d
|
| 1039 |
+
break
|
| 1040 |
+
return result
|
| 1041 |
+
|
| 1042 |
+
def get_distribution_names(self):
|
| 1043 |
+
"""
|
| 1044 |
+
Return all the distribution names known to this locator.
|
| 1045 |
+
"""
|
| 1046 |
+
result = set()
|
| 1047 |
+
for locator in self.locators:
|
| 1048 |
+
try:
|
| 1049 |
+
result |= locator.get_distribution_names()
|
| 1050 |
+
except NotImplementedError:
|
| 1051 |
+
pass
|
| 1052 |
+
return result
|
| 1053 |
+
|
| 1054 |
+
|
| 1055 |
+
# We use a legacy scheme simply because most of the dists on PyPI use legacy
|
| 1056 |
+
# versions which don't conform to PEP 440.
|
| 1057 |
+
default_locator = AggregatingLocator(
|
| 1058 |
+
# JSONLocator(), # don't use as PEP 426 is withdrawn
|
| 1059 |
+
SimpleScrapingLocator('https://pypi.org/simple/', timeout=3.0),
|
| 1060 |
+
scheme='legacy')
|
| 1061 |
+
|
| 1062 |
+
locate = default_locator.locate
|
| 1063 |
+
|
| 1064 |
+
|
| 1065 |
+
class DependencyFinder(object):
|
| 1066 |
+
"""
|
| 1067 |
+
Locate dependencies for distributions.
|
| 1068 |
+
"""
|
| 1069 |
+
|
| 1070 |
+
def __init__(self, locator=None):
|
| 1071 |
+
"""
|
| 1072 |
+
Initialise an instance, using the specified locator
|
| 1073 |
+
to locate distributions.
|
| 1074 |
+
"""
|
| 1075 |
+
self.locator = locator or default_locator
|
| 1076 |
+
self.scheme = get_scheme(self.locator.scheme)
|
| 1077 |
+
|
| 1078 |
+
def add_distribution(self, dist):
|
| 1079 |
+
"""
|
| 1080 |
+
Add a distribution to the finder. This will update internal information
|
| 1081 |
+
about who provides what.
|
| 1082 |
+
:param dist: The distribution to add.
|
| 1083 |
+
"""
|
| 1084 |
+
logger.debug('adding distribution %s', dist)
|
| 1085 |
+
name = dist.key
|
| 1086 |
+
self.dists_by_name[name] = dist
|
| 1087 |
+
self.dists[(name, dist.version)] = dist
|
| 1088 |
+
for p in dist.provides:
|
| 1089 |
+
name, version = parse_name_and_version(p)
|
| 1090 |
+
logger.debug('Add to provided: %s, %s, %s', name, version, dist)
|
| 1091 |
+
self.provided.setdefault(name, set()).add((version, dist))
|
| 1092 |
+
|
| 1093 |
+
def remove_distribution(self, dist):
|
| 1094 |
+
"""
|
| 1095 |
+
Remove a distribution from the finder. This will update internal
|
| 1096 |
+
information about who provides what.
|
| 1097 |
+
:param dist: The distribution to remove.
|
| 1098 |
+
"""
|
| 1099 |
+
logger.debug('removing distribution %s', dist)
|
| 1100 |
+
name = dist.key
|
| 1101 |
+
del self.dists_by_name[name]
|
| 1102 |
+
del self.dists[(name, dist.version)]
|
| 1103 |
+
for p in dist.provides:
|
| 1104 |
+
name, version = parse_name_and_version(p)
|
| 1105 |
+
logger.debug('Remove from provided: %s, %s, %s', name, version, dist)
|
| 1106 |
+
s = self.provided[name]
|
| 1107 |
+
s.remove((version, dist))
|
| 1108 |
+
if not s:
|
| 1109 |
+
del self.provided[name]
|
| 1110 |
+
|
| 1111 |
+
def get_matcher(self, reqt):
|
| 1112 |
+
"""
|
| 1113 |
+
Get a version matcher for a requirement.
|
| 1114 |
+
:param reqt: The requirement
|
| 1115 |
+
:type reqt: str
|
| 1116 |
+
:return: A version matcher (an instance of
|
| 1117 |
+
:class:`distlib.version.Matcher`).
|
| 1118 |
+
"""
|
| 1119 |
+
try:
|
| 1120 |
+
matcher = self.scheme.matcher(reqt)
|
| 1121 |
+
except UnsupportedVersionError: # pragma: no cover
|
| 1122 |
+
# XXX compat-mode if cannot read the version
|
| 1123 |
+
name = reqt.split()[0]
|
| 1124 |
+
matcher = self.scheme.matcher(name)
|
| 1125 |
+
return matcher
|
| 1126 |
+
|
| 1127 |
+
def find_providers(self, reqt):
|
| 1128 |
+
"""
|
| 1129 |
+
Find the distributions which can fulfill a requirement.
|
| 1130 |
+
|
| 1131 |
+
:param reqt: The requirement.
|
| 1132 |
+
:type reqt: str
|
| 1133 |
+
:return: A set of distribution which can fulfill the requirement.
|
| 1134 |
+
"""
|
| 1135 |
+
matcher = self.get_matcher(reqt)
|
| 1136 |
+
name = matcher.key # case-insensitive
|
| 1137 |
+
result = set()
|
| 1138 |
+
provided = self.provided
|
| 1139 |
+
if name in provided:
|
| 1140 |
+
for version, provider in provided[name]:
|
| 1141 |
+
try:
|
| 1142 |
+
match = matcher.match(version)
|
| 1143 |
+
except UnsupportedVersionError:
|
| 1144 |
+
match = False
|
| 1145 |
+
|
| 1146 |
+
if match:
|
| 1147 |
+
result.add(provider)
|
| 1148 |
+
break
|
| 1149 |
+
return result
|
| 1150 |
+
|
| 1151 |
+
def try_to_replace(self, provider, other, problems):
|
| 1152 |
+
"""
|
| 1153 |
+
Attempt to replace one provider with another. This is typically used
|
| 1154 |
+
when resolving dependencies from multiple sources, e.g. A requires
|
| 1155 |
+
(B >= 1.0) while C requires (B >= 1.1).
|
| 1156 |
+
|
| 1157 |
+
For successful replacement, ``provider`` must meet all the requirements
|
| 1158 |
+
which ``other`` fulfills.
|
| 1159 |
+
|
| 1160 |
+
:param provider: The provider we are trying to replace with.
|
| 1161 |
+
:param other: The provider we're trying to replace.
|
| 1162 |
+
:param problems: If False is returned, this will contain what
|
| 1163 |
+
problems prevented replacement. This is currently
|
| 1164 |
+
a tuple of the literal string 'cantreplace',
|
| 1165 |
+
``provider``, ``other`` and the set of requirements
|
| 1166 |
+
that ``provider`` couldn't fulfill.
|
| 1167 |
+
:return: True if we can replace ``other`` with ``provider``, else
|
| 1168 |
+
False.
|
| 1169 |
+
"""
|
| 1170 |
+
rlist = self.reqts[other]
|
| 1171 |
+
unmatched = set()
|
| 1172 |
+
for s in rlist:
|
| 1173 |
+
matcher = self.get_matcher(s)
|
| 1174 |
+
if not matcher.match(provider.version):
|
| 1175 |
+
unmatched.add(s)
|
| 1176 |
+
if unmatched:
|
| 1177 |
+
# can't replace other with provider
|
| 1178 |
+
problems.add(('cantreplace', provider, other, frozenset(unmatched)))
|
| 1179 |
+
result = False
|
| 1180 |
+
else:
|
| 1181 |
+
# can replace other with provider
|
| 1182 |
+
self.remove_distribution(other)
|
| 1183 |
+
del self.reqts[other]
|
| 1184 |
+
for s in rlist:
|
| 1185 |
+
self.reqts.setdefault(provider, set()).add(s)
|
| 1186 |
+
self.add_distribution(provider)
|
| 1187 |
+
result = True
|
| 1188 |
+
return result
|
| 1189 |
+
|
| 1190 |
+
def find(self, requirement, meta_extras=None, prereleases=False):
|
| 1191 |
+
"""
|
| 1192 |
+
Find a distribution and all distributions it depends on.
|
| 1193 |
+
|
| 1194 |
+
:param requirement: The requirement specifying the distribution to
|
| 1195 |
+
find, or a Distribution instance.
|
| 1196 |
+
:param meta_extras: A list of meta extras such as :test:, :build: and
|
| 1197 |
+
so on.
|
| 1198 |
+
:param prereleases: If ``True``, allow pre-release versions to be
|
| 1199 |
+
returned - otherwise, don't return prereleases
|
| 1200 |
+
unless they're all that's available.
|
| 1201 |
+
|
| 1202 |
+
Return a set of :class:`Distribution` instances and a set of
|
| 1203 |
+
problems.
|
| 1204 |
+
|
| 1205 |
+
The distributions returned should be such that they have the
|
| 1206 |
+
:attr:`required` attribute set to ``True`` if they were
|
| 1207 |
+
from the ``requirement`` passed to ``find()``, and they have the
|
| 1208 |
+
:attr:`build_time_dependency` attribute set to ``True`` unless they
|
| 1209 |
+
are post-installation dependencies of the ``requirement``.
|
| 1210 |
+
|
| 1211 |
+
The problems should be a tuple consisting of the string
|
| 1212 |
+
``'unsatisfied'`` and the requirement which couldn't be satisfied
|
| 1213 |
+
by any distribution known to the locator.
|
| 1214 |
+
"""
|
| 1215 |
+
|
| 1216 |
+
self.provided = {}
|
| 1217 |
+
self.dists = {}
|
| 1218 |
+
self.dists_by_name = {}
|
| 1219 |
+
self.reqts = {}
|
| 1220 |
+
|
| 1221 |
+
meta_extras = set(meta_extras or [])
|
| 1222 |
+
if ':*:' in meta_extras:
|
| 1223 |
+
meta_extras.remove(':*:')
|
| 1224 |
+
# :meta: and :run: are implicitly included
|
| 1225 |
+
meta_extras |= set([':test:', ':build:', ':dev:'])
|
| 1226 |
+
|
| 1227 |
+
if isinstance(requirement, Distribution):
|
| 1228 |
+
dist = odist = requirement
|
| 1229 |
+
logger.debug('passed %s as requirement', odist)
|
| 1230 |
+
else:
|
| 1231 |
+
dist = odist = self.locator.locate(requirement, prereleases=prereleases)
|
| 1232 |
+
if dist is None:
|
| 1233 |
+
raise DistlibException('Unable to locate %r' % requirement)
|
| 1234 |
+
logger.debug('located %s', odist)
|
| 1235 |
+
dist.requested = True
|
| 1236 |
+
problems = set()
|
| 1237 |
+
todo = set([dist])
|
| 1238 |
+
install_dists = set([odist])
|
| 1239 |
+
while todo:
|
| 1240 |
+
dist = todo.pop()
|
| 1241 |
+
name = dist.key # case-insensitive
|
| 1242 |
+
if name not in self.dists_by_name:
|
| 1243 |
+
self.add_distribution(dist)
|
| 1244 |
+
else:
|
| 1245 |
+
# import pdb; pdb.set_trace()
|
| 1246 |
+
other = self.dists_by_name[name]
|
| 1247 |
+
if other != dist:
|
| 1248 |
+
self.try_to_replace(dist, other, problems)
|
| 1249 |
+
|
| 1250 |
+
ireqts = dist.run_requires | dist.meta_requires
|
| 1251 |
+
sreqts = dist.build_requires
|
| 1252 |
+
ereqts = set()
|
| 1253 |
+
if meta_extras and dist in install_dists:
|
| 1254 |
+
for key in ('test', 'build', 'dev'):
|
| 1255 |
+
e = ':%s:' % key
|
| 1256 |
+
if e in meta_extras:
|
| 1257 |
+
ereqts |= getattr(dist, '%s_requires' % key)
|
| 1258 |
+
all_reqts = ireqts | sreqts | ereqts
|
| 1259 |
+
for r in all_reqts:
|
| 1260 |
+
providers = self.find_providers(r)
|
| 1261 |
+
if not providers:
|
| 1262 |
+
logger.debug('No providers found for %r', r)
|
| 1263 |
+
provider = self.locator.locate(r, prereleases=prereleases)
|
| 1264 |
+
# If no provider is found and we didn't consider
|
| 1265 |
+
# prereleases, consider them now.
|
| 1266 |
+
if provider is None and not prereleases:
|
| 1267 |
+
provider = self.locator.locate(r, prereleases=True)
|
| 1268 |
+
if provider is None:
|
| 1269 |
+
logger.debug('Cannot satisfy %r', r)
|
| 1270 |
+
problems.add(('unsatisfied', r))
|
| 1271 |
+
else:
|
| 1272 |
+
n, v = provider.key, provider.version
|
| 1273 |
+
if (n, v) not in self.dists:
|
| 1274 |
+
todo.add(provider)
|
| 1275 |
+
providers.add(provider)
|
| 1276 |
+
if r in ireqts and dist in install_dists:
|
| 1277 |
+
install_dists.add(provider)
|
| 1278 |
+
logger.debug('Adding %s to install_dists', provider.name_and_version)
|
| 1279 |
+
for p in providers:
|
| 1280 |
+
name = p.key
|
| 1281 |
+
if name not in self.dists_by_name:
|
| 1282 |
+
self.reqts.setdefault(p, set()).add(r)
|
| 1283 |
+
else:
|
| 1284 |
+
other = self.dists_by_name[name]
|
| 1285 |
+
if other != p:
|
| 1286 |
+
# see if other can be replaced by p
|
| 1287 |
+
self.try_to_replace(p, other, problems)
|
| 1288 |
+
|
| 1289 |
+
dists = set(self.dists.values())
|
| 1290 |
+
for dist in dists:
|
| 1291 |
+
dist.build_time_dependency = dist not in install_dists
|
| 1292 |
+
if dist.build_time_dependency:
|
| 1293 |
+
logger.debug('%s is a build-time dependency only.', dist.name_and_version)
|
| 1294 |
+
logger.debug('find done for %s', odist)
|
| 1295 |
+
return dists, problems
|
lib/python3.12/site-packages/pip/_vendor/distlib/manifest.py
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2012-2023 Python Software Foundation.
|
| 4 |
+
# See LICENSE.txt and CONTRIBUTORS.txt.
|
| 5 |
+
#
|
| 6 |
+
"""
|
| 7 |
+
Class representing the list of files in a distribution.
|
| 8 |
+
|
| 9 |
+
Equivalent to distutils.filelist, but fixes some problems.
|
| 10 |
+
"""
|
| 11 |
+
import fnmatch
|
| 12 |
+
import logging
|
| 13 |
+
import os
|
| 14 |
+
import re
|
| 15 |
+
import sys
|
| 16 |
+
|
| 17 |
+
from . import DistlibException
|
| 18 |
+
from .compat import fsdecode
|
| 19 |
+
from .util import convert_path
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
__all__ = ['Manifest']
|
| 23 |
+
|
| 24 |
+
logger = logging.getLogger(__name__)
|
| 25 |
+
|
| 26 |
+
# a \ followed by some spaces + EOL
|
| 27 |
+
_COLLAPSE_PATTERN = re.compile('\\\\w*\n', re.M)
|
| 28 |
+
_COMMENTED_LINE = re.compile('#.*?(?=\n)|\n(?=$)', re.M | re.S)
|
| 29 |
+
|
| 30 |
+
#
|
| 31 |
+
# Due to the different results returned by fnmatch.translate, we need
|
| 32 |
+
# to do slightly different processing for Python 2.7 and 3.2 ... this needed
|
| 33 |
+
# to be brought in for Python 3.6 onwards.
|
| 34 |
+
#
|
| 35 |
+
_PYTHON_VERSION = sys.version_info[:2]
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class Manifest(object):
|
| 39 |
+
"""
|
| 40 |
+
A list of files built by exploring the filesystem and filtered by applying various
|
| 41 |
+
patterns to what we find there.
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
def __init__(self, base=None):
|
| 45 |
+
"""
|
| 46 |
+
Initialise an instance.
|
| 47 |
+
|
| 48 |
+
:param base: The base directory to explore under.
|
| 49 |
+
"""
|
| 50 |
+
self.base = os.path.abspath(os.path.normpath(base or os.getcwd()))
|
| 51 |
+
self.prefix = self.base + os.sep
|
| 52 |
+
self.allfiles = None
|
| 53 |
+
self.files = set()
|
| 54 |
+
|
| 55 |
+
#
|
| 56 |
+
# Public API
|
| 57 |
+
#
|
| 58 |
+
|
| 59 |
+
def findall(self):
|
| 60 |
+
"""Find all files under the base and set ``allfiles`` to the absolute
|
| 61 |
+
pathnames of files found.
|
| 62 |
+
"""
|
| 63 |
+
from stat import S_ISREG, S_ISDIR, S_ISLNK
|
| 64 |
+
|
| 65 |
+
self.allfiles = allfiles = []
|
| 66 |
+
root = self.base
|
| 67 |
+
stack = [root]
|
| 68 |
+
pop = stack.pop
|
| 69 |
+
push = stack.append
|
| 70 |
+
|
| 71 |
+
while stack:
|
| 72 |
+
root = pop()
|
| 73 |
+
names = os.listdir(root)
|
| 74 |
+
|
| 75 |
+
for name in names:
|
| 76 |
+
fullname = os.path.join(root, name)
|
| 77 |
+
|
| 78 |
+
# Avoid excess stat calls -- just one will do, thank you!
|
| 79 |
+
stat = os.stat(fullname)
|
| 80 |
+
mode = stat.st_mode
|
| 81 |
+
if S_ISREG(mode):
|
| 82 |
+
allfiles.append(fsdecode(fullname))
|
| 83 |
+
elif S_ISDIR(mode) and not S_ISLNK(mode):
|
| 84 |
+
push(fullname)
|
| 85 |
+
|
| 86 |
+
def add(self, item):
|
| 87 |
+
"""
|
| 88 |
+
Add a file to the manifest.
|
| 89 |
+
|
| 90 |
+
:param item: The pathname to add. This can be relative to the base.
|
| 91 |
+
"""
|
| 92 |
+
if not item.startswith(self.prefix):
|
| 93 |
+
item = os.path.join(self.base, item)
|
| 94 |
+
self.files.add(os.path.normpath(item))
|
| 95 |
+
|
| 96 |
+
def add_many(self, items):
|
| 97 |
+
"""
|
| 98 |
+
Add a list of files to the manifest.
|
| 99 |
+
|
| 100 |
+
:param items: The pathnames to add. These can be relative to the base.
|
| 101 |
+
"""
|
| 102 |
+
for item in items:
|
| 103 |
+
self.add(item)
|
| 104 |
+
|
| 105 |
+
def sorted(self, wantdirs=False):
|
| 106 |
+
"""
|
| 107 |
+
Return sorted files in directory order
|
| 108 |
+
"""
|
| 109 |
+
|
| 110 |
+
def add_dir(dirs, d):
|
| 111 |
+
dirs.add(d)
|
| 112 |
+
logger.debug('add_dir added %s', d)
|
| 113 |
+
if d != self.base:
|
| 114 |
+
parent, _ = os.path.split(d)
|
| 115 |
+
assert parent not in ('', '/')
|
| 116 |
+
add_dir(dirs, parent)
|
| 117 |
+
|
| 118 |
+
result = set(self.files) # make a copy!
|
| 119 |
+
if wantdirs:
|
| 120 |
+
dirs = set()
|
| 121 |
+
for f in result:
|
| 122 |
+
add_dir(dirs, os.path.dirname(f))
|
| 123 |
+
result |= dirs
|
| 124 |
+
return [os.path.join(*path_tuple) for path_tuple in
|
| 125 |
+
sorted(os.path.split(path) for path in result)]
|
| 126 |
+
|
| 127 |
+
def clear(self):
|
| 128 |
+
"""Clear all collected files."""
|
| 129 |
+
self.files = set()
|
| 130 |
+
self.allfiles = []
|
| 131 |
+
|
| 132 |
+
def process_directive(self, directive):
|
| 133 |
+
"""
|
| 134 |
+
Process a directive which either adds some files from ``allfiles`` to
|
| 135 |
+
``files``, or removes some files from ``files``.
|
| 136 |
+
|
| 137 |
+
:param directive: The directive to process. This should be in a format
|
| 138 |
+
compatible with distutils ``MANIFEST.in`` files:
|
| 139 |
+
|
| 140 |
+
http://docs.python.org/distutils/sourcedist.html#commands
|
| 141 |
+
"""
|
| 142 |
+
# Parse the line: split it up, make sure the right number of words
|
| 143 |
+
# is there, and return the relevant words. 'action' is always
|
| 144 |
+
# defined: it's the first word of the line. Which of the other
|
| 145 |
+
# three are defined depends on the action; it'll be either
|
| 146 |
+
# patterns, (dir and patterns), or (dirpattern).
|
| 147 |
+
action, patterns, thedir, dirpattern = self._parse_directive(directive)
|
| 148 |
+
|
| 149 |
+
# OK, now we know that the action is valid and we have the
|
| 150 |
+
# right number of words on the line for that action -- so we
|
| 151 |
+
# can proceed with minimal error-checking.
|
| 152 |
+
if action == 'include':
|
| 153 |
+
for pattern in patterns:
|
| 154 |
+
if not self._include_pattern(pattern, anchor=True):
|
| 155 |
+
logger.warning('no files found matching %r', pattern)
|
| 156 |
+
|
| 157 |
+
elif action == 'exclude':
|
| 158 |
+
for pattern in patterns:
|
| 159 |
+
self._exclude_pattern(pattern, anchor=True)
|
| 160 |
+
|
| 161 |
+
elif action == 'global-include':
|
| 162 |
+
for pattern in patterns:
|
| 163 |
+
if not self._include_pattern(pattern, anchor=False):
|
| 164 |
+
logger.warning('no files found matching %r '
|
| 165 |
+
'anywhere in distribution', pattern)
|
| 166 |
+
|
| 167 |
+
elif action == 'global-exclude':
|
| 168 |
+
for pattern in patterns:
|
| 169 |
+
self._exclude_pattern(pattern, anchor=False)
|
| 170 |
+
|
| 171 |
+
elif action == 'recursive-include':
|
| 172 |
+
for pattern in patterns:
|
| 173 |
+
if not self._include_pattern(pattern, prefix=thedir):
|
| 174 |
+
logger.warning('no files found matching %r '
|
| 175 |
+
'under directory %r', pattern, thedir)
|
| 176 |
+
|
| 177 |
+
elif action == 'recursive-exclude':
|
| 178 |
+
for pattern in patterns:
|
| 179 |
+
self._exclude_pattern(pattern, prefix=thedir)
|
| 180 |
+
|
| 181 |
+
elif action == 'graft':
|
| 182 |
+
if not self._include_pattern(None, prefix=dirpattern):
|
| 183 |
+
logger.warning('no directories found matching %r',
|
| 184 |
+
dirpattern)
|
| 185 |
+
|
| 186 |
+
elif action == 'prune':
|
| 187 |
+
if not self._exclude_pattern(None, prefix=dirpattern):
|
| 188 |
+
logger.warning('no previously-included directories found '
|
| 189 |
+
'matching %r', dirpattern)
|
| 190 |
+
else: # pragma: no cover
|
| 191 |
+
# This should never happen, as it should be caught in
|
| 192 |
+
# _parse_template_line
|
| 193 |
+
raise DistlibException(
|
| 194 |
+
'invalid action %r' % action)
|
| 195 |
+
|
| 196 |
+
#
|
| 197 |
+
# Private API
|
| 198 |
+
#
|
| 199 |
+
|
| 200 |
+
def _parse_directive(self, directive):
|
| 201 |
+
"""
|
| 202 |
+
Validate a directive.
|
| 203 |
+
:param directive: The directive to validate.
|
| 204 |
+
:return: A tuple of action, patterns, thedir, dir_patterns
|
| 205 |
+
"""
|
| 206 |
+
words = directive.split()
|
| 207 |
+
if len(words) == 1 and words[0] not in ('include', 'exclude',
|
| 208 |
+
'global-include',
|
| 209 |
+
'global-exclude',
|
| 210 |
+
'recursive-include',
|
| 211 |
+
'recursive-exclude',
|
| 212 |
+
'graft', 'prune'):
|
| 213 |
+
# no action given, let's use the default 'include'
|
| 214 |
+
words.insert(0, 'include')
|
| 215 |
+
|
| 216 |
+
action = words[0]
|
| 217 |
+
patterns = thedir = dir_pattern = None
|
| 218 |
+
|
| 219 |
+
if action in ('include', 'exclude',
|
| 220 |
+
'global-include', 'global-exclude'):
|
| 221 |
+
if len(words) < 2:
|
| 222 |
+
raise DistlibException(
|
| 223 |
+
'%r expects <pattern1> <pattern2> ...' % action)
|
| 224 |
+
|
| 225 |
+
patterns = [convert_path(word) for word in words[1:]]
|
| 226 |
+
|
| 227 |
+
elif action in ('recursive-include', 'recursive-exclude'):
|
| 228 |
+
if len(words) < 3:
|
| 229 |
+
raise DistlibException(
|
| 230 |
+
'%r expects <dir> <pattern1> <pattern2> ...' % action)
|
| 231 |
+
|
| 232 |
+
thedir = convert_path(words[1])
|
| 233 |
+
patterns = [convert_path(word) for word in words[2:]]
|
| 234 |
+
|
| 235 |
+
elif action in ('graft', 'prune'):
|
| 236 |
+
if len(words) != 2:
|
| 237 |
+
raise DistlibException(
|
| 238 |
+
'%r expects a single <dir_pattern>' % action)
|
| 239 |
+
|
| 240 |
+
dir_pattern = convert_path(words[1])
|
| 241 |
+
|
| 242 |
+
else:
|
| 243 |
+
raise DistlibException('unknown action %r' % action)
|
| 244 |
+
|
| 245 |
+
return action, patterns, thedir, dir_pattern
|
| 246 |
+
|
| 247 |
+
def _include_pattern(self, pattern, anchor=True, prefix=None,
|
| 248 |
+
is_regex=False):
|
| 249 |
+
"""Select strings (presumably filenames) from 'self.files' that
|
| 250 |
+
match 'pattern', a Unix-style wildcard (glob) pattern.
|
| 251 |
+
|
| 252 |
+
Patterns are not quite the same as implemented by the 'fnmatch'
|
| 253 |
+
module: '*' and '?' match non-special characters, where "special"
|
| 254 |
+
is platform-dependent: slash on Unix; colon, slash, and backslash on
|
| 255 |
+
DOS/Windows; and colon on Mac OS.
|
| 256 |
+
|
| 257 |
+
If 'anchor' is true (the default), then the pattern match is more
|
| 258 |
+
stringent: "*.py" will match "foo.py" but not "foo/bar.py". If
|
| 259 |
+
'anchor' is false, both of these will match.
|
| 260 |
+
|
| 261 |
+
If 'prefix' is supplied, then only filenames starting with 'prefix'
|
| 262 |
+
(itself a pattern) and ending with 'pattern', with anything in between
|
| 263 |
+
them, will match. 'anchor' is ignored in this case.
|
| 264 |
+
|
| 265 |
+
If 'is_regex' is true, 'anchor' and 'prefix' are ignored, and
|
| 266 |
+
'pattern' is assumed to be either a string containing a regex or a
|
| 267 |
+
regex object -- no translation is done, the regex is just compiled
|
| 268 |
+
and used as-is.
|
| 269 |
+
|
| 270 |
+
Selected strings will be added to self.files.
|
| 271 |
+
|
| 272 |
+
Return True if files are found.
|
| 273 |
+
"""
|
| 274 |
+
# XXX docstring lying about what the special chars are?
|
| 275 |
+
found = False
|
| 276 |
+
pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex)
|
| 277 |
+
|
| 278 |
+
# delayed loading of allfiles list
|
| 279 |
+
if self.allfiles is None:
|
| 280 |
+
self.findall()
|
| 281 |
+
|
| 282 |
+
for name in self.allfiles:
|
| 283 |
+
if pattern_re.search(name):
|
| 284 |
+
self.files.add(name)
|
| 285 |
+
found = True
|
| 286 |
+
return found
|
| 287 |
+
|
| 288 |
+
def _exclude_pattern(self, pattern, anchor=True, prefix=None,
|
| 289 |
+
is_regex=False):
|
| 290 |
+
"""Remove strings (presumably filenames) from 'files' that match
|
| 291 |
+
'pattern'.
|
| 292 |
+
|
| 293 |
+
Other parameters are the same as for 'include_pattern()', above.
|
| 294 |
+
The list 'self.files' is modified in place. Return True if files are
|
| 295 |
+
found.
|
| 296 |
+
|
| 297 |
+
This API is public to allow e.g. exclusion of SCM subdirs, e.g. when
|
| 298 |
+
packaging source distributions
|
| 299 |
+
"""
|
| 300 |
+
found = False
|
| 301 |
+
pattern_re = self._translate_pattern(pattern, anchor, prefix, is_regex)
|
| 302 |
+
for f in list(self.files):
|
| 303 |
+
if pattern_re.search(f):
|
| 304 |
+
self.files.remove(f)
|
| 305 |
+
found = True
|
| 306 |
+
return found
|
| 307 |
+
|
| 308 |
+
def _translate_pattern(self, pattern, anchor=True, prefix=None,
|
| 309 |
+
is_regex=False):
|
| 310 |
+
"""Translate a shell-like wildcard pattern to a compiled regular
|
| 311 |
+
expression.
|
| 312 |
+
|
| 313 |
+
Return the compiled regex. If 'is_regex' true,
|
| 314 |
+
then 'pattern' is directly compiled to a regex (if it's a string)
|
| 315 |
+
or just returned as-is (assumes it's a regex object).
|
| 316 |
+
"""
|
| 317 |
+
if is_regex:
|
| 318 |
+
if isinstance(pattern, str):
|
| 319 |
+
return re.compile(pattern)
|
| 320 |
+
else:
|
| 321 |
+
return pattern
|
| 322 |
+
|
| 323 |
+
if _PYTHON_VERSION > (3, 2):
|
| 324 |
+
# ditch start and end characters
|
| 325 |
+
start, _, end = self._glob_to_re('_').partition('_')
|
| 326 |
+
|
| 327 |
+
if pattern:
|
| 328 |
+
pattern_re = self._glob_to_re(pattern)
|
| 329 |
+
if _PYTHON_VERSION > (3, 2):
|
| 330 |
+
assert pattern_re.startswith(start) and pattern_re.endswith(end)
|
| 331 |
+
else:
|
| 332 |
+
pattern_re = ''
|
| 333 |
+
|
| 334 |
+
base = re.escape(os.path.join(self.base, ''))
|
| 335 |
+
if prefix is not None:
|
| 336 |
+
# ditch end of pattern character
|
| 337 |
+
if _PYTHON_VERSION <= (3, 2):
|
| 338 |
+
empty_pattern = self._glob_to_re('')
|
| 339 |
+
prefix_re = self._glob_to_re(prefix)[:-len(empty_pattern)]
|
| 340 |
+
else:
|
| 341 |
+
prefix_re = self._glob_to_re(prefix)
|
| 342 |
+
assert prefix_re.startswith(start) and prefix_re.endswith(end)
|
| 343 |
+
prefix_re = prefix_re[len(start): len(prefix_re) - len(end)]
|
| 344 |
+
sep = os.sep
|
| 345 |
+
if os.sep == '\\':
|
| 346 |
+
sep = r'\\'
|
| 347 |
+
if _PYTHON_VERSION <= (3, 2):
|
| 348 |
+
pattern_re = '^' + base + sep.join((prefix_re,
|
| 349 |
+
'.*' + pattern_re))
|
| 350 |
+
else:
|
| 351 |
+
pattern_re = pattern_re[len(start): len(pattern_re) - len(end)]
|
| 352 |
+
pattern_re = r'%s%s%s%s.*%s%s' % (start, base, prefix_re, sep,
|
| 353 |
+
pattern_re, end)
|
| 354 |
+
else: # no prefix -- respect anchor flag
|
| 355 |
+
if anchor:
|
| 356 |
+
if _PYTHON_VERSION <= (3, 2):
|
| 357 |
+
pattern_re = '^' + base + pattern_re
|
| 358 |
+
else:
|
| 359 |
+
pattern_re = r'%s%s%s' % (start, base, pattern_re[len(start):])
|
| 360 |
+
|
| 361 |
+
return re.compile(pattern_re)
|
| 362 |
+
|
| 363 |
+
def _glob_to_re(self, pattern):
|
| 364 |
+
"""Translate a shell-like glob pattern to a regular expression.
|
| 365 |
+
|
| 366 |
+
Return a string containing the regex. Differs from
|
| 367 |
+
'fnmatch.translate()' in that '*' does not match "special characters"
|
| 368 |
+
(which are platform-specific).
|
| 369 |
+
"""
|
| 370 |
+
pattern_re = fnmatch.translate(pattern)
|
| 371 |
+
|
| 372 |
+
# '?' and '*' in the glob pattern become '.' and '.*' in the RE, which
|
| 373 |
+
# IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix,
|
| 374 |
+
# and by extension they shouldn't match such "special characters" under
|
| 375 |
+
# any OS. So change all non-escaped dots in the RE to match any
|
| 376 |
+
# character except the special characters (currently: just os.sep).
|
| 377 |
+
sep = os.sep
|
| 378 |
+
if os.sep == '\\':
|
| 379 |
+
# we're using a regex to manipulate a regex, so we need
|
| 380 |
+
# to escape the backslash twice
|
| 381 |
+
sep = r'\\\\'
|
| 382 |
+
escaped = r'\1[^%s]' % sep
|
| 383 |
+
pattern_re = re.sub(r'((?<!\\)(\\\\)*)\.', escaped, pattern_re)
|
| 384 |
+
return pattern_re
|
lib/python3.12/site-packages/pip/_vendor/distlib/metadata.py
ADDED
|
@@ -0,0 +1,1031 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2012 The Python Software Foundation.
|
| 4 |
+
# See LICENSE.txt and CONTRIBUTORS.txt.
|
| 5 |
+
#
|
| 6 |
+
"""Implementation of the Metadata for Python packages PEPs.
|
| 7 |
+
|
| 8 |
+
Supports all metadata formats (1.0, 1.1, 1.2, 1.3/2.1 and 2.2).
|
| 9 |
+
"""
|
| 10 |
+
from __future__ import unicode_literals
|
| 11 |
+
|
| 12 |
+
import codecs
|
| 13 |
+
from email import message_from_file
|
| 14 |
+
import json
|
| 15 |
+
import logging
|
| 16 |
+
import re
|
| 17 |
+
|
| 18 |
+
from . import DistlibException, __version__
|
| 19 |
+
from .compat import StringIO, string_types, text_type
|
| 20 |
+
from .markers import interpret
|
| 21 |
+
from .util import extract_by_key, get_extras
|
| 22 |
+
from .version import get_scheme, PEP440_VERSION_RE
|
| 23 |
+
|
| 24 |
+
logger = logging.getLogger(__name__)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class MetadataMissingError(DistlibException):
|
| 28 |
+
"""A required metadata is missing"""
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class MetadataConflictError(DistlibException):
|
| 32 |
+
"""Attempt to read or write metadata fields that are conflictual."""
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class MetadataUnrecognizedVersionError(DistlibException):
|
| 36 |
+
"""Unknown metadata version number."""
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class MetadataInvalidError(DistlibException):
|
| 40 |
+
"""A metadata value is invalid"""
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
# public API of this module
|
| 44 |
+
__all__ = ['Metadata', 'PKG_INFO_ENCODING', 'PKG_INFO_PREFERRED_VERSION']
|
| 45 |
+
|
| 46 |
+
# Encoding used for the PKG-INFO files
|
| 47 |
+
PKG_INFO_ENCODING = 'utf-8'
|
| 48 |
+
|
| 49 |
+
# preferred version. Hopefully will be changed
|
| 50 |
+
# to 1.2 once PEP 345 is supported everywhere
|
| 51 |
+
PKG_INFO_PREFERRED_VERSION = '1.1'
|
| 52 |
+
|
| 53 |
+
_LINE_PREFIX_1_2 = re.compile('\n \\|')
|
| 54 |
+
_LINE_PREFIX_PRE_1_2 = re.compile('\n ')
|
| 55 |
+
_241_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Summary', 'Description', 'Keywords', 'Home-page',
|
| 56 |
+
'Author', 'Author-email', 'License')
|
| 57 |
+
|
| 58 |
+
_314_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description',
|
| 59 |
+
'Keywords', 'Home-page', 'Author', 'Author-email', 'License', 'Classifier', 'Download-URL', 'Obsoletes',
|
| 60 |
+
'Provides', 'Requires')
|
| 61 |
+
|
| 62 |
+
_314_MARKERS = ('Obsoletes', 'Provides', 'Requires', 'Classifier', 'Download-URL')
|
| 63 |
+
|
| 64 |
+
_345_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description',
|
| 65 |
+
'Keywords', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License',
|
| 66 |
+
'Classifier', 'Download-URL', 'Obsoletes-Dist', 'Project-URL', 'Provides-Dist', 'Requires-Dist',
|
| 67 |
+
'Requires-Python', 'Requires-External')
|
| 68 |
+
|
| 69 |
+
_345_MARKERS = ('Provides-Dist', 'Requires-Dist', 'Requires-Python', 'Obsoletes-Dist', 'Requires-External',
|
| 70 |
+
'Maintainer', 'Maintainer-email', 'Project-URL')
|
| 71 |
+
|
| 72 |
+
_426_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description',
|
| 73 |
+
'Keywords', 'Home-page', 'Author', 'Author-email', 'Maintainer', 'Maintainer-email', 'License',
|
| 74 |
+
'Classifier', 'Download-URL', 'Obsoletes-Dist', 'Project-URL', 'Provides-Dist', 'Requires-Dist',
|
| 75 |
+
'Requires-Python', 'Requires-External', 'Private-Version', 'Obsoleted-By', 'Setup-Requires-Dist',
|
| 76 |
+
'Extension', 'Provides-Extra')
|
| 77 |
+
|
| 78 |
+
_426_MARKERS = ('Private-Version', 'Provides-Extra', 'Obsoleted-By', 'Setup-Requires-Dist', 'Extension')
|
| 79 |
+
|
| 80 |
+
# See issue #106: Sometimes 'Requires' and 'Provides' occur wrongly in
|
| 81 |
+
# the metadata. Include them in the tuple literal below to allow them
|
| 82 |
+
# (for now).
|
| 83 |
+
# Ditto for Obsoletes - see issue #140.
|
| 84 |
+
_566_FIELDS = _426_FIELDS + ('Description-Content-Type', 'Requires', 'Provides', 'Obsoletes')
|
| 85 |
+
|
| 86 |
+
_566_MARKERS = ('Description-Content-Type', )
|
| 87 |
+
|
| 88 |
+
_643_MARKERS = ('Dynamic', 'License-File')
|
| 89 |
+
|
| 90 |
+
_643_FIELDS = _566_FIELDS + _643_MARKERS
|
| 91 |
+
|
| 92 |
+
_ALL_FIELDS = set()
|
| 93 |
+
_ALL_FIELDS.update(_241_FIELDS)
|
| 94 |
+
_ALL_FIELDS.update(_314_FIELDS)
|
| 95 |
+
_ALL_FIELDS.update(_345_FIELDS)
|
| 96 |
+
_ALL_FIELDS.update(_426_FIELDS)
|
| 97 |
+
_ALL_FIELDS.update(_566_FIELDS)
|
| 98 |
+
_ALL_FIELDS.update(_643_FIELDS)
|
| 99 |
+
|
| 100 |
+
EXTRA_RE = re.compile(r'''extra\s*==\s*("([^"]+)"|'([^']+)')''')
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def _version2fieldlist(version):
|
| 104 |
+
if version == '1.0':
|
| 105 |
+
return _241_FIELDS
|
| 106 |
+
elif version == '1.1':
|
| 107 |
+
return _314_FIELDS
|
| 108 |
+
elif version == '1.2':
|
| 109 |
+
return _345_FIELDS
|
| 110 |
+
elif version in ('1.3', '2.1'):
|
| 111 |
+
# avoid adding field names if already there
|
| 112 |
+
return _345_FIELDS + tuple(f for f in _566_FIELDS if f not in _345_FIELDS)
|
| 113 |
+
elif version == '2.0':
|
| 114 |
+
raise ValueError('Metadata 2.0 is withdrawn and not supported')
|
| 115 |
+
# return _426_FIELDS
|
| 116 |
+
elif version == '2.2':
|
| 117 |
+
return _643_FIELDS
|
| 118 |
+
raise MetadataUnrecognizedVersionError(version)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def _best_version(fields):
|
| 122 |
+
"""Detect the best version depending on the fields used."""
|
| 123 |
+
|
| 124 |
+
def _has_marker(keys, markers):
|
| 125 |
+
return any(marker in keys for marker in markers)
|
| 126 |
+
|
| 127 |
+
keys = [key for key, value in fields.items() if value not in ([], 'UNKNOWN', None)]
|
| 128 |
+
possible_versions = ['1.0', '1.1', '1.2', '1.3', '2.1', '2.2'] # 2.0 removed
|
| 129 |
+
|
| 130 |
+
# first let's try to see if a field is not part of one of the version
|
| 131 |
+
for key in keys:
|
| 132 |
+
if key not in _241_FIELDS and '1.0' in possible_versions:
|
| 133 |
+
possible_versions.remove('1.0')
|
| 134 |
+
logger.debug('Removed 1.0 due to %s', key)
|
| 135 |
+
if key not in _314_FIELDS and '1.1' in possible_versions:
|
| 136 |
+
possible_versions.remove('1.1')
|
| 137 |
+
logger.debug('Removed 1.1 due to %s', key)
|
| 138 |
+
if key not in _345_FIELDS and '1.2' in possible_versions:
|
| 139 |
+
possible_versions.remove('1.2')
|
| 140 |
+
logger.debug('Removed 1.2 due to %s', key)
|
| 141 |
+
if key not in _566_FIELDS and '1.3' in possible_versions:
|
| 142 |
+
possible_versions.remove('1.3')
|
| 143 |
+
logger.debug('Removed 1.3 due to %s', key)
|
| 144 |
+
if key not in _566_FIELDS and '2.1' in possible_versions:
|
| 145 |
+
if key != 'Description': # In 2.1, description allowed after headers
|
| 146 |
+
possible_versions.remove('2.1')
|
| 147 |
+
logger.debug('Removed 2.1 due to %s', key)
|
| 148 |
+
if key not in _643_FIELDS and '2.2' in possible_versions:
|
| 149 |
+
possible_versions.remove('2.2')
|
| 150 |
+
logger.debug('Removed 2.2 due to %s', key)
|
| 151 |
+
# if key not in _426_FIELDS and '2.0' in possible_versions:
|
| 152 |
+
# possible_versions.remove('2.0')
|
| 153 |
+
# logger.debug('Removed 2.0 due to %s', key)
|
| 154 |
+
|
| 155 |
+
# possible_version contains qualified versions
|
| 156 |
+
if len(possible_versions) == 1:
|
| 157 |
+
return possible_versions[0] # found !
|
| 158 |
+
elif len(possible_versions) == 0:
|
| 159 |
+
logger.debug('Out of options - unknown metadata set: %s', fields)
|
| 160 |
+
raise MetadataConflictError('Unknown metadata set')
|
| 161 |
+
|
| 162 |
+
# let's see if one unique marker is found
|
| 163 |
+
is_1_1 = '1.1' in possible_versions and _has_marker(keys, _314_MARKERS)
|
| 164 |
+
is_1_2 = '1.2' in possible_versions and _has_marker(keys, _345_MARKERS)
|
| 165 |
+
is_2_1 = '2.1' in possible_versions and _has_marker(keys, _566_MARKERS)
|
| 166 |
+
# is_2_0 = '2.0' in possible_versions and _has_marker(keys, _426_MARKERS)
|
| 167 |
+
is_2_2 = '2.2' in possible_versions and _has_marker(keys, _643_MARKERS)
|
| 168 |
+
if int(is_1_1) + int(is_1_2) + int(is_2_1) + int(is_2_2) > 1:
|
| 169 |
+
raise MetadataConflictError('You used incompatible 1.1/1.2/2.1/2.2 fields')
|
| 170 |
+
|
| 171 |
+
# we have the choice, 1.0, or 1.2, 2.1 or 2.2
|
| 172 |
+
# - 1.0 has a broken Summary field but works with all tools
|
| 173 |
+
# - 1.1 is to avoid
|
| 174 |
+
# - 1.2 fixes Summary but has little adoption
|
| 175 |
+
# - 2.1 adds more features
|
| 176 |
+
# - 2.2 is the latest
|
| 177 |
+
if not is_1_1 and not is_1_2 and not is_2_1 and not is_2_2:
|
| 178 |
+
# we couldn't find any specific marker
|
| 179 |
+
if PKG_INFO_PREFERRED_VERSION in possible_versions:
|
| 180 |
+
return PKG_INFO_PREFERRED_VERSION
|
| 181 |
+
if is_1_1:
|
| 182 |
+
return '1.1'
|
| 183 |
+
if is_1_2:
|
| 184 |
+
return '1.2'
|
| 185 |
+
if is_2_1:
|
| 186 |
+
return '2.1'
|
| 187 |
+
# if is_2_2:
|
| 188 |
+
# return '2.2'
|
| 189 |
+
|
| 190 |
+
return '2.2'
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
# This follows the rules about transforming keys as described in
|
| 194 |
+
# https://www.python.org/dev/peps/pep-0566/#id17
|
| 195 |
+
_ATTR2FIELD = {name.lower().replace("-", "_"): name for name in _ALL_FIELDS}
|
| 196 |
+
_FIELD2ATTR = {field: attr for attr, field in _ATTR2FIELD.items()}
|
| 197 |
+
|
| 198 |
+
_PREDICATE_FIELDS = ('Requires-Dist', 'Obsoletes-Dist', 'Provides-Dist')
|
| 199 |
+
_VERSIONS_FIELDS = ('Requires-Python', )
|
| 200 |
+
_VERSION_FIELDS = ('Version', )
|
| 201 |
+
_LISTFIELDS = ('Platform', 'Classifier', 'Obsoletes', 'Requires', 'Provides', 'Obsoletes-Dist', 'Provides-Dist',
|
| 202 |
+
'Requires-Dist', 'Requires-External', 'Project-URL', 'Supported-Platform', 'Setup-Requires-Dist',
|
| 203 |
+
'Provides-Extra', 'Extension', 'License-File')
|
| 204 |
+
_LISTTUPLEFIELDS = ('Project-URL', )
|
| 205 |
+
|
| 206 |
+
_ELEMENTSFIELD = ('Keywords', )
|
| 207 |
+
|
| 208 |
+
_UNICODEFIELDS = ('Author', 'Maintainer', 'Summary', 'Description')
|
| 209 |
+
|
| 210 |
+
_MISSING = object()
|
| 211 |
+
|
| 212 |
+
_FILESAFE = re.compile('[^A-Za-z0-9.]+')
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def _get_name_and_version(name, version, for_filename=False):
|
| 216 |
+
"""Return the distribution name with version.
|
| 217 |
+
|
| 218 |
+
If for_filename is true, return a filename-escaped form."""
|
| 219 |
+
if for_filename:
|
| 220 |
+
# For both name and version any runs of non-alphanumeric or '.'
|
| 221 |
+
# characters are replaced with a single '-'. Additionally any
|
| 222 |
+
# spaces in the version string become '.'
|
| 223 |
+
name = _FILESAFE.sub('-', name)
|
| 224 |
+
version = _FILESAFE.sub('-', version.replace(' ', '.'))
|
| 225 |
+
return '%s-%s' % (name, version)
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
class LegacyMetadata(object):
|
| 229 |
+
"""The legacy metadata of a release.
|
| 230 |
+
|
| 231 |
+
Supports versions 1.0, 1.1, 1.2, 2.0 and 1.3/2.1 (auto-detected). You can
|
| 232 |
+
instantiate the class with one of these arguments (or none):
|
| 233 |
+
- *path*, the path to a metadata file
|
| 234 |
+
- *fileobj* give a file-like object with metadata as content
|
| 235 |
+
- *mapping* is a dict-like object
|
| 236 |
+
- *scheme* is a version scheme name
|
| 237 |
+
"""
|
| 238 |
+
|
| 239 |
+
# TODO document the mapping API and UNKNOWN default key
|
| 240 |
+
|
| 241 |
+
def __init__(self, path=None, fileobj=None, mapping=None, scheme='default'):
|
| 242 |
+
if [path, fileobj, mapping].count(None) < 2:
|
| 243 |
+
raise TypeError('path, fileobj and mapping are exclusive')
|
| 244 |
+
self._fields = {}
|
| 245 |
+
self.requires_files = []
|
| 246 |
+
self._dependencies = None
|
| 247 |
+
self.scheme = scheme
|
| 248 |
+
if path is not None:
|
| 249 |
+
self.read(path)
|
| 250 |
+
elif fileobj is not None:
|
| 251 |
+
self.read_file(fileobj)
|
| 252 |
+
elif mapping is not None:
|
| 253 |
+
self.update(mapping)
|
| 254 |
+
self.set_metadata_version()
|
| 255 |
+
|
| 256 |
+
def set_metadata_version(self):
|
| 257 |
+
self._fields['Metadata-Version'] = _best_version(self._fields)
|
| 258 |
+
|
| 259 |
+
def _write_field(self, fileobj, name, value):
|
| 260 |
+
fileobj.write('%s: %s\n' % (name, value))
|
| 261 |
+
|
| 262 |
+
def __getitem__(self, name):
|
| 263 |
+
return self.get(name)
|
| 264 |
+
|
| 265 |
+
def __setitem__(self, name, value):
|
| 266 |
+
return self.set(name, value)
|
| 267 |
+
|
| 268 |
+
def __delitem__(self, name):
|
| 269 |
+
field_name = self._convert_name(name)
|
| 270 |
+
try:
|
| 271 |
+
del self._fields[field_name]
|
| 272 |
+
except KeyError:
|
| 273 |
+
raise KeyError(name)
|
| 274 |
+
|
| 275 |
+
def __contains__(self, name):
|
| 276 |
+
return (name in self._fields or self._convert_name(name) in self._fields)
|
| 277 |
+
|
| 278 |
+
def _convert_name(self, name):
|
| 279 |
+
if name in _ALL_FIELDS:
|
| 280 |
+
return name
|
| 281 |
+
name = name.replace('-', '_').lower()
|
| 282 |
+
return _ATTR2FIELD.get(name, name)
|
| 283 |
+
|
| 284 |
+
def _default_value(self, name):
|
| 285 |
+
if name in _LISTFIELDS or name in _ELEMENTSFIELD:
|
| 286 |
+
return []
|
| 287 |
+
return 'UNKNOWN'
|
| 288 |
+
|
| 289 |
+
def _remove_line_prefix(self, value):
|
| 290 |
+
if self.metadata_version in ('1.0', '1.1'):
|
| 291 |
+
return _LINE_PREFIX_PRE_1_2.sub('\n', value)
|
| 292 |
+
else:
|
| 293 |
+
return _LINE_PREFIX_1_2.sub('\n', value)
|
| 294 |
+
|
| 295 |
+
def __getattr__(self, name):
|
| 296 |
+
if name in _ATTR2FIELD:
|
| 297 |
+
return self[name]
|
| 298 |
+
raise AttributeError(name)
|
| 299 |
+
|
| 300 |
+
#
|
| 301 |
+
# Public API
|
| 302 |
+
#
|
| 303 |
+
|
| 304 |
+
def get_fullname(self, filesafe=False):
|
| 305 |
+
"""
|
| 306 |
+
Return the distribution name with version.
|
| 307 |
+
|
| 308 |
+
If filesafe is true, return a filename-escaped form.
|
| 309 |
+
"""
|
| 310 |
+
return _get_name_and_version(self['Name'], self['Version'], filesafe)
|
| 311 |
+
|
| 312 |
+
def is_field(self, name):
|
| 313 |
+
"""return True if name is a valid metadata key"""
|
| 314 |
+
name = self._convert_name(name)
|
| 315 |
+
return name in _ALL_FIELDS
|
| 316 |
+
|
| 317 |
+
def is_multi_field(self, name):
|
| 318 |
+
name = self._convert_name(name)
|
| 319 |
+
return name in _LISTFIELDS
|
| 320 |
+
|
| 321 |
+
def read(self, filepath):
|
| 322 |
+
"""Read the metadata values from a file path."""
|
| 323 |
+
fp = codecs.open(filepath, 'r', encoding='utf-8')
|
| 324 |
+
try:
|
| 325 |
+
self.read_file(fp)
|
| 326 |
+
finally:
|
| 327 |
+
fp.close()
|
| 328 |
+
|
| 329 |
+
def read_file(self, fileob):
|
| 330 |
+
"""Read the metadata values from a file object."""
|
| 331 |
+
msg = message_from_file(fileob)
|
| 332 |
+
self._fields['Metadata-Version'] = msg['metadata-version']
|
| 333 |
+
|
| 334 |
+
# When reading, get all the fields we can
|
| 335 |
+
for field in _ALL_FIELDS:
|
| 336 |
+
if field not in msg:
|
| 337 |
+
continue
|
| 338 |
+
if field in _LISTFIELDS:
|
| 339 |
+
# we can have multiple lines
|
| 340 |
+
values = msg.get_all(field)
|
| 341 |
+
if field in _LISTTUPLEFIELDS and values is not None:
|
| 342 |
+
values = [tuple(value.split(',')) for value in values]
|
| 343 |
+
self.set(field, values)
|
| 344 |
+
else:
|
| 345 |
+
# single line
|
| 346 |
+
value = msg[field]
|
| 347 |
+
if value is not None and value != 'UNKNOWN':
|
| 348 |
+
self.set(field, value)
|
| 349 |
+
|
| 350 |
+
# PEP 566 specifies that the body be used for the description, if
|
| 351 |
+
# available
|
| 352 |
+
body = msg.get_payload()
|
| 353 |
+
self["Description"] = body if body else self["Description"]
|
| 354 |
+
# logger.debug('Attempting to set metadata for %s', self)
|
| 355 |
+
# self.set_metadata_version()
|
| 356 |
+
|
| 357 |
+
def write(self, filepath, skip_unknown=False):
|
| 358 |
+
"""Write the metadata fields to filepath."""
|
| 359 |
+
fp = codecs.open(filepath, 'w', encoding='utf-8')
|
| 360 |
+
try:
|
| 361 |
+
self.write_file(fp, skip_unknown)
|
| 362 |
+
finally:
|
| 363 |
+
fp.close()
|
| 364 |
+
|
| 365 |
+
def write_file(self, fileobject, skip_unknown=False):
|
| 366 |
+
"""Write the PKG-INFO format data to a file object."""
|
| 367 |
+
self.set_metadata_version()
|
| 368 |
+
|
| 369 |
+
for field in _version2fieldlist(self['Metadata-Version']):
|
| 370 |
+
values = self.get(field)
|
| 371 |
+
if skip_unknown and values in ('UNKNOWN', [], ['UNKNOWN']):
|
| 372 |
+
continue
|
| 373 |
+
if field in _ELEMENTSFIELD:
|
| 374 |
+
self._write_field(fileobject, field, ','.join(values))
|
| 375 |
+
continue
|
| 376 |
+
if field not in _LISTFIELDS:
|
| 377 |
+
if field == 'Description':
|
| 378 |
+
if self.metadata_version in ('1.0', '1.1'):
|
| 379 |
+
values = values.replace('\n', '\n ')
|
| 380 |
+
else:
|
| 381 |
+
values = values.replace('\n', '\n |')
|
| 382 |
+
values = [values]
|
| 383 |
+
|
| 384 |
+
if field in _LISTTUPLEFIELDS:
|
| 385 |
+
values = [','.join(value) for value in values]
|
| 386 |
+
|
| 387 |
+
for value in values:
|
| 388 |
+
self._write_field(fileobject, field, value)
|
| 389 |
+
|
| 390 |
+
def update(self, other=None, **kwargs):
|
| 391 |
+
"""Set metadata values from the given iterable `other` and kwargs.
|
| 392 |
+
|
| 393 |
+
Behavior is like `dict.update`: If `other` has a ``keys`` method,
|
| 394 |
+
they are looped over and ``self[key]`` is assigned ``other[key]``.
|
| 395 |
+
Else, ``other`` is an iterable of ``(key, value)`` iterables.
|
| 396 |
+
|
| 397 |
+
Keys that don't match a metadata field or that have an empty value are
|
| 398 |
+
dropped.
|
| 399 |
+
"""
|
| 400 |
+
|
| 401 |
+
def _set(key, value):
|
| 402 |
+
if key in _ATTR2FIELD and value:
|
| 403 |
+
self.set(self._convert_name(key), value)
|
| 404 |
+
|
| 405 |
+
if not other:
|
| 406 |
+
# other is None or empty container
|
| 407 |
+
pass
|
| 408 |
+
elif hasattr(other, 'keys'):
|
| 409 |
+
for k in other.keys():
|
| 410 |
+
_set(k, other[k])
|
| 411 |
+
else:
|
| 412 |
+
for k, v in other:
|
| 413 |
+
_set(k, v)
|
| 414 |
+
|
| 415 |
+
if kwargs:
|
| 416 |
+
for k, v in kwargs.items():
|
| 417 |
+
_set(k, v)
|
| 418 |
+
|
| 419 |
+
def set(self, name, value):
|
| 420 |
+
"""Control then set a metadata field."""
|
| 421 |
+
name = self._convert_name(name)
|
| 422 |
+
|
| 423 |
+
if ((name in _ELEMENTSFIELD or name == 'Platform') and not isinstance(value, (list, tuple))):
|
| 424 |
+
if isinstance(value, string_types):
|
| 425 |
+
value = [v.strip() for v in value.split(',')]
|
| 426 |
+
else:
|
| 427 |
+
value = []
|
| 428 |
+
elif (name in _LISTFIELDS and not isinstance(value, (list, tuple))):
|
| 429 |
+
if isinstance(value, string_types):
|
| 430 |
+
value = [value]
|
| 431 |
+
else:
|
| 432 |
+
value = []
|
| 433 |
+
|
| 434 |
+
if logger.isEnabledFor(logging.WARNING):
|
| 435 |
+
project_name = self['Name']
|
| 436 |
+
|
| 437 |
+
scheme = get_scheme(self.scheme)
|
| 438 |
+
if name in _PREDICATE_FIELDS and value is not None:
|
| 439 |
+
for v in value:
|
| 440 |
+
# check that the values are valid
|
| 441 |
+
if not scheme.is_valid_matcher(v.split(';')[0]):
|
| 442 |
+
logger.warning("'%s': '%s' is not valid (field '%s')", project_name, v, name)
|
| 443 |
+
# FIXME this rejects UNKNOWN, is that right?
|
| 444 |
+
elif name in _VERSIONS_FIELDS and value is not None:
|
| 445 |
+
if not scheme.is_valid_constraint_list(value):
|
| 446 |
+
logger.warning("'%s': '%s' is not a valid version (field '%s')", project_name, value, name)
|
| 447 |
+
elif name in _VERSION_FIELDS and value is not None:
|
| 448 |
+
if not scheme.is_valid_version(value):
|
| 449 |
+
logger.warning("'%s': '%s' is not a valid version (field '%s')", project_name, value, name)
|
| 450 |
+
|
| 451 |
+
if name in _UNICODEFIELDS:
|
| 452 |
+
if name == 'Description':
|
| 453 |
+
value = self._remove_line_prefix(value)
|
| 454 |
+
|
| 455 |
+
self._fields[name] = value
|
| 456 |
+
|
| 457 |
+
def get(self, name, default=_MISSING):
|
| 458 |
+
"""Get a metadata field."""
|
| 459 |
+
name = self._convert_name(name)
|
| 460 |
+
if name not in self._fields:
|
| 461 |
+
if default is _MISSING:
|
| 462 |
+
default = self._default_value(name)
|
| 463 |
+
return default
|
| 464 |
+
if name in _UNICODEFIELDS:
|
| 465 |
+
value = self._fields[name]
|
| 466 |
+
return value
|
| 467 |
+
elif name in _LISTFIELDS:
|
| 468 |
+
value = self._fields[name]
|
| 469 |
+
if value is None:
|
| 470 |
+
return []
|
| 471 |
+
res = []
|
| 472 |
+
for val in value:
|
| 473 |
+
if name not in _LISTTUPLEFIELDS:
|
| 474 |
+
res.append(val)
|
| 475 |
+
else:
|
| 476 |
+
# That's for Project-URL
|
| 477 |
+
res.append((val[0], val[1]))
|
| 478 |
+
return res
|
| 479 |
+
|
| 480 |
+
elif name in _ELEMENTSFIELD:
|
| 481 |
+
value = self._fields[name]
|
| 482 |
+
if isinstance(value, string_types):
|
| 483 |
+
return value.split(',')
|
| 484 |
+
return self._fields[name]
|
| 485 |
+
|
| 486 |
+
def check(self, strict=False):
|
| 487 |
+
"""Check if the metadata is compliant. If strict is True then raise if
|
| 488 |
+
no Name or Version are provided"""
|
| 489 |
+
self.set_metadata_version()
|
| 490 |
+
|
| 491 |
+
# XXX should check the versions (if the file was loaded)
|
| 492 |
+
missing, warnings = [], []
|
| 493 |
+
|
| 494 |
+
for attr in ('Name', 'Version'): # required by PEP 345
|
| 495 |
+
if attr not in self:
|
| 496 |
+
missing.append(attr)
|
| 497 |
+
|
| 498 |
+
if strict and missing != []:
|
| 499 |
+
msg = 'missing required metadata: %s' % ', '.join(missing)
|
| 500 |
+
raise MetadataMissingError(msg)
|
| 501 |
+
|
| 502 |
+
for attr in ('Home-page', 'Author'):
|
| 503 |
+
if attr not in self:
|
| 504 |
+
missing.append(attr)
|
| 505 |
+
|
| 506 |
+
# checking metadata 1.2 (XXX needs to check 1.1, 1.0)
|
| 507 |
+
if self['Metadata-Version'] != '1.2':
|
| 508 |
+
return missing, warnings
|
| 509 |
+
|
| 510 |
+
scheme = get_scheme(self.scheme)
|
| 511 |
+
|
| 512 |
+
def are_valid_constraints(value):
|
| 513 |
+
for v in value:
|
| 514 |
+
if not scheme.is_valid_matcher(v.split(';')[0]):
|
| 515 |
+
return False
|
| 516 |
+
return True
|
| 517 |
+
|
| 518 |
+
for fields, controller in ((_PREDICATE_FIELDS, are_valid_constraints),
|
| 519 |
+
(_VERSIONS_FIELDS, scheme.is_valid_constraint_list), (_VERSION_FIELDS,
|
| 520 |
+
scheme.is_valid_version)):
|
| 521 |
+
for field in fields:
|
| 522 |
+
value = self.get(field, None)
|
| 523 |
+
if value is not None and not controller(value):
|
| 524 |
+
warnings.append("Wrong value for '%s': %s" % (field, value))
|
| 525 |
+
|
| 526 |
+
return missing, warnings
|
| 527 |
+
|
| 528 |
+
def todict(self, skip_missing=False):
|
| 529 |
+
"""Return fields as a dict.
|
| 530 |
+
|
| 531 |
+
Field names will be converted to use the underscore-lowercase style
|
| 532 |
+
instead of hyphen-mixed case (i.e. home_page instead of Home-page).
|
| 533 |
+
This is as per https://www.python.org/dev/peps/pep-0566/#id17.
|
| 534 |
+
"""
|
| 535 |
+
self.set_metadata_version()
|
| 536 |
+
|
| 537 |
+
fields = _version2fieldlist(self['Metadata-Version'])
|
| 538 |
+
|
| 539 |
+
data = {}
|
| 540 |
+
|
| 541 |
+
for field_name in fields:
|
| 542 |
+
if not skip_missing or field_name in self._fields:
|
| 543 |
+
key = _FIELD2ATTR[field_name]
|
| 544 |
+
if key != 'project_url':
|
| 545 |
+
data[key] = self[field_name]
|
| 546 |
+
else:
|
| 547 |
+
data[key] = [','.join(u) for u in self[field_name]]
|
| 548 |
+
|
| 549 |
+
return data
|
| 550 |
+
|
| 551 |
+
def add_requirements(self, requirements):
|
| 552 |
+
if self['Metadata-Version'] == '1.1':
|
| 553 |
+
# we can't have 1.1 metadata *and* Setuptools requires
|
| 554 |
+
for field in ('Obsoletes', 'Requires', 'Provides'):
|
| 555 |
+
if field in self:
|
| 556 |
+
del self[field]
|
| 557 |
+
self['Requires-Dist'] += requirements
|
| 558 |
+
|
| 559 |
+
# Mapping API
|
| 560 |
+
# TODO could add iter* variants
|
| 561 |
+
|
| 562 |
+
def keys(self):
|
| 563 |
+
return list(_version2fieldlist(self['Metadata-Version']))
|
| 564 |
+
|
| 565 |
+
def __iter__(self):
|
| 566 |
+
for key in self.keys():
|
| 567 |
+
yield key
|
| 568 |
+
|
| 569 |
+
def values(self):
|
| 570 |
+
return [self[key] for key in self.keys()]
|
| 571 |
+
|
| 572 |
+
def items(self):
|
| 573 |
+
return [(key, self[key]) for key in self.keys()]
|
| 574 |
+
|
| 575 |
+
def __repr__(self):
|
| 576 |
+
return '<%s %s %s>' % (self.__class__.__name__, self.name, self.version)
|
| 577 |
+
|
| 578 |
+
|
| 579 |
+
METADATA_FILENAME = 'pydist.json'
|
| 580 |
+
WHEEL_METADATA_FILENAME = 'metadata.json'
|
| 581 |
+
LEGACY_METADATA_FILENAME = 'METADATA'
|
| 582 |
+
|
| 583 |
+
|
| 584 |
+
class Metadata(object):
|
| 585 |
+
"""
|
| 586 |
+
The metadata of a release. This implementation uses 2.1
|
| 587 |
+
metadata where possible. If not possible, it wraps a LegacyMetadata
|
| 588 |
+
instance which handles the key-value metadata format.
|
| 589 |
+
"""
|
| 590 |
+
|
| 591 |
+
METADATA_VERSION_MATCHER = re.compile(r'^\d+(\.\d+)*$')
|
| 592 |
+
|
| 593 |
+
NAME_MATCHER = re.compile('^[0-9A-Z]([0-9A-Z_.-]*[0-9A-Z])?$', re.I)
|
| 594 |
+
|
| 595 |
+
FIELDNAME_MATCHER = re.compile('^[A-Z]([0-9A-Z-]*[0-9A-Z])?$', re.I)
|
| 596 |
+
|
| 597 |
+
VERSION_MATCHER = PEP440_VERSION_RE
|
| 598 |
+
|
| 599 |
+
SUMMARY_MATCHER = re.compile('.{1,2047}')
|
| 600 |
+
|
| 601 |
+
METADATA_VERSION = '2.0'
|
| 602 |
+
|
| 603 |
+
GENERATOR = 'distlib (%s)' % __version__
|
| 604 |
+
|
| 605 |
+
MANDATORY_KEYS = {
|
| 606 |
+
'name': (),
|
| 607 |
+
'version': (),
|
| 608 |
+
'summary': ('legacy', ),
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
INDEX_KEYS = ('name version license summary description author '
|
| 612 |
+
'author_email keywords platform home_page classifiers '
|
| 613 |
+
'download_url')
|
| 614 |
+
|
| 615 |
+
DEPENDENCY_KEYS = ('extras run_requires test_requires build_requires '
|
| 616 |
+
'dev_requires provides meta_requires obsoleted_by '
|
| 617 |
+
'supports_environments')
|
| 618 |
+
|
| 619 |
+
SYNTAX_VALIDATORS = {
|
| 620 |
+
'metadata_version': (METADATA_VERSION_MATCHER, ()),
|
| 621 |
+
'name': (NAME_MATCHER, ('legacy', )),
|
| 622 |
+
'version': (VERSION_MATCHER, ('legacy', )),
|
| 623 |
+
'summary': (SUMMARY_MATCHER, ('legacy', )),
|
| 624 |
+
'dynamic': (FIELDNAME_MATCHER, ('legacy', )),
|
| 625 |
+
}
|
| 626 |
+
|
| 627 |
+
__slots__ = ('_legacy', '_data', 'scheme')
|
| 628 |
+
|
| 629 |
+
def __init__(self, path=None, fileobj=None, mapping=None, scheme='default'):
|
| 630 |
+
if [path, fileobj, mapping].count(None) < 2:
|
| 631 |
+
raise TypeError('path, fileobj and mapping are exclusive')
|
| 632 |
+
self._legacy = None
|
| 633 |
+
self._data = None
|
| 634 |
+
self.scheme = scheme
|
| 635 |
+
# import pdb; pdb.set_trace()
|
| 636 |
+
if mapping is not None:
|
| 637 |
+
try:
|
| 638 |
+
self._validate_mapping(mapping, scheme)
|
| 639 |
+
self._data = mapping
|
| 640 |
+
except MetadataUnrecognizedVersionError:
|
| 641 |
+
self._legacy = LegacyMetadata(mapping=mapping, scheme=scheme)
|
| 642 |
+
self.validate()
|
| 643 |
+
else:
|
| 644 |
+
data = None
|
| 645 |
+
if path:
|
| 646 |
+
with open(path, 'rb') as f:
|
| 647 |
+
data = f.read()
|
| 648 |
+
elif fileobj:
|
| 649 |
+
data = fileobj.read()
|
| 650 |
+
if data is None:
|
| 651 |
+
# Initialised with no args - to be added
|
| 652 |
+
self._data = {
|
| 653 |
+
'metadata_version': self.METADATA_VERSION,
|
| 654 |
+
'generator': self.GENERATOR,
|
| 655 |
+
}
|
| 656 |
+
else:
|
| 657 |
+
if not isinstance(data, text_type):
|
| 658 |
+
data = data.decode('utf-8')
|
| 659 |
+
try:
|
| 660 |
+
self._data = json.loads(data)
|
| 661 |
+
self._validate_mapping(self._data, scheme)
|
| 662 |
+
except ValueError:
|
| 663 |
+
# Note: MetadataUnrecognizedVersionError does not
|
| 664 |
+
# inherit from ValueError (it's a DistlibException,
|
| 665 |
+
# which should not inherit from ValueError).
|
| 666 |
+
# The ValueError comes from the json.load - if that
|
| 667 |
+
# succeeds and we get a validation error, we want
|
| 668 |
+
# that to propagate
|
| 669 |
+
self._legacy = LegacyMetadata(fileobj=StringIO(data), scheme=scheme)
|
| 670 |
+
self.validate()
|
| 671 |
+
|
| 672 |
+
common_keys = set(('name', 'version', 'license', 'keywords', 'summary'))
|
| 673 |
+
|
| 674 |
+
none_list = (None, list)
|
| 675 |
+
none_dict = (None, dict)
|
| 676 |
+
|
| 677 |
+
mapped_keys = {
|
| 678 |
+
'run_requires': ('Requires-Dist', list),
|
| 679 |
+
'build_requires': ('Setup-Requires-Dist', list),
|
| 680 |
+
'dev_requires': none_list,
|
| 681 |
+
'test_requires': none_list,
|
| 682 |
+
'meta_requires': none_list,
|
| 683 |
+
'extras': ('Provides-Extra', list),
|
| 684 |
+
'modules': none_list,
|
| 685 |
+
'namespaces': none_list,
|
| 686 |
+
'exports': none_dict,
|
| 687 |
+
'commands': none_dict,
|
| 688 |
+
'classifiers': ('Classifier', list),
|
| 689 |
+
'source_url': ('Download-URL', None),
|
| 690 |
+
'metadata_version': ('Metadata-Version', None),
|
| 691 |
+
}
|
| 692 |
+
|
| 693 |
+
del none_list, none_dict
|
| 694 |
+
|
| 695 |
+
def __getattribute__(self, key):
|
| 696 |
+
common = object.__getattribute__(self, 'common_keys')
|
| 697 |
+
mapped = object.__getattribute__(self, 'mapped_keys')
|
| 698 |
+
if key in mapped:
|
| 699 |
+
lk, maker = mapped[key]
|
| 700 |
+
if self._legacy:
|
| 701 |
+
if lk is None:
|
| 702 |
+
result = None if maker is None else maker()
|
| 703 |
+
else:
|
| 704 |
+
result = self._legacy.get(lk)
|
| 705 |
+
else:
|
| 706 |
+
value = None if maker is None else maker()
|
| 707 |
+
if key not in ('commands', 'exports', 'modules', 'namespaces', 'classifiers'):
|
| 708 |
+
result = self._data.get(key, value)
|
| 709 |
+
else:
|
| 710 |
+
# special cases for PEP 459
|
| 711 |
+
sentinel = object()
|
| 712 |
+
result = sentinel
|
| 713 |
+
d = self._data.get('extensions')
|
| 714 |
+
if d:
|
| 715 |
+
if key == 'commands':
|
| 716 |
+
result = d.get('python.commands', value)
|
| 717 |
+
elif key == 'classifiers':
|
| 718 |
+
d = d.get('python.details')
|
| 719 |
+
if d:
|
| 720 |
+
result = d.get(key, value)
|
| 721 |
+
else:
|
| 722 |
+
d = d.get('python.exports')
|
| 723 |
+
if not d:
|
| 724 |
+
d = self._data.get('python.exports')
|
| 725 |
+
if d:
|
| 726 |
+
result = d.get(key, value)
|
| 727 |
+
if result is sentinel:
|
| 728 |
+
result = value
|
| 729 |
+
elif key not in common:
|
| 730 |
+
result = object.__getattribute__(self, key)
|
| 731 |
+
elif self._legacy:
|
| 732 |
+
result = self._legacy.get(key)
|
| 733 |
+
else:
|
| 734 |
+
result = self._data.get(key)
|
| 735 |
+
return result
|
| 736 |
+
|
| 737 |
+
def _validate_value(self, key, value, scheme=None):
|
| 738 |
+
if key in self.SYNTAX_VALIDATORS:
|
| 739 |
+
pattern, exclusions = self.SYNTAX_VALIDATORS[key]
|
| 740 |
+
if (scheme or self.scheme) not in exclusions:
|
| 741 |
+
m = pattern.match(value)
|
| 742 |
+
if not m:
|
| 743 |
+
raise MetadataInvalidError("'%s' is an invalid value for "
|
| 744 |
+
"the '%s' property" % (value, key))
|
| 745 |
+
|
| 746 |
+
def __setattr__(self, key, value):
|
| 747 |
+
self._validate_value(key, value)
|
| 748 |
+
common = object.__getattribute__(self, 'common_keys')
|
| 749 |
+
mapped = object.__getattribute__(self, 'mapped_keys')
|
| 750 |
+
if key in mapped:
|
| 751 |
+
lk, _ = mapped[key]
|
| 752 |
+
if self._legacy:
|
| 753 |
+
if lk is None:
|
| 754 |
+
raise NotImplementedError
|
| 755 |
+
self._legacy[lk] = value
|
| 756 |
+
elif key not in ('commands', 'exports', 'modules', 'namespaces', 'classifiers'):
|
| 757 |
+
self._data[key] = value
|
| 758 |
+
else:
|
| 759 |
+
# special cases for PEP 459
|
| 760 |
+
d = self._data.setdefault('extensions', {})
|
| 761 |
+
if key == 'commands':
|
| 762 |
+
d['python.commands'] = value
|
| 763 |
+
elif key == 'classifiers':
|
| 764 |
+
d = d.setdefault('python.details', {})
|
| 765 |
+
d[key] = value
|
| 766 |
+
else:
|
| 767 |
+
d = d.setdefault('python.exports', {})
|
| 768 |
+
d[key] = value
|
| 769 |
+
elif key not in common:
|
| 770 |
+
object.__setattr__(self, key, value)
|
| 771 |
+
else:
|
| 772 |
+
if key == 'keywords':
|
| 773 |
+
if isinstance(value, string_types):
|
| 774 |
+
value = value.strip()
|
| 775 |
+
if value:
|
| 776 |
+
value = value.split()
|
| 777 |
+
else:
|
| 778 |
+
value = []
|
| 779 |
+
if self._legacy:
|
| 780 |
+
self._legacy[key] = value
|
| 781 |
+
else:
|
| 782 |
+
self._data[key] = value
|
| 783 |
+
|
| 784 |
+
@property
|
| 785 |
+
def name_and_version(self):
|
| 786 |
+
return _get_name_and_version(self.name, self.version, True)
|
| 787 |
+
|
| 788 |
+
@property
|
| 789 |
+
def provides(self):
|
| 790 |
+
if self._legacy:
|
| 791 |
+
result = self._legacy['Provides-Dist']
|
| 792 |
+
else:
|
| 793 |
+
result = self._data.setdefault('provides', [])
|
| 794 |
+
s = '%s (%s)' % (self.name, self.version)
|
| 795 |
+
if s not in result:
|
| 796 |
+
result.append(s)
|
| 797 |
+
return result
|
| 798 |
+
|
| 799 |
+
@provides.setter
|
| 800 |
+
def provides(self, value):
|
| 801 |
+
if self._legacy:
|
| 802 |
+
self._legacy['Provides-Dist'] = value
|
| 803 |
+
else:
|
| 804 |
+
self._data['provides'] = value
|
| 805 |
+
|
| 806 |
+
def get_requirements(self, reqts, extras=None, env=None):
|
| 807 |
+
"""
|
| 808 |
+
Base method to get dependencies, given a set of extras
|
| 809 |
+
to satisfy and an optional environment context.
|
| 810 |
+
:param reqts: A list of sometimes-wanted dependencies,
|
| 811 |
+
perhaps dependent on extras and environment.
|
| 812 |
+
:param extras: A list of optional components being requested.
|
| 813 |
+
:param env: An optional environment for marker evaluation.
|
| 814 |
+
"""
|
| 815 |
+
if self._legacy:
|
| 816 |
+
result = reqts
|
| 817 |
+
else:
|
| 818 |
+
result = []
|
| 819 |
+
extras = get_extras(extras or [], self.extras)
|
| 820 |
+
for d in reqts:
|
| 821 |
+
if 'extra' not in d and 'environment' not in d:
|
| 822 |
+
# unconditional
|
| 823 |
+
include = True
|
| 824 |
+
else:
|
| 825 |
+
if 'extra' not in d:
|
| 826 |
+
# Not extra-dependent - only environment-dependent
|
| 827 |
+
include = True
|
| 828 |
+
else:
|
| 829 |
+
include = d.get('extra') in extras
|
| 830 |
+
if include:
|
| 831 |
+
# Not excluded because of extras, check environment
|
| 832 |
+
marker = d.get('environment')
|
| 833 |
+
if marker:
|
| 834 |
+
include = interpret(marker, env)
|
| 835 |
+
if include:
|
| 836 |
+
result.extend(d['requires'])
|
| 837 |
+
for key in ('build', 'dev', 'test'):
|
| 838 |
+
e = ':%s:' % key
|
| 839 |
+
if e in extras:
|
| 840 |
+
extras.remove(e)
|
| 841 |
+
# A recursive call, but it should terminate since 'test'
|
| 842 |
+
# has been removed from the extras
|
| 843 |
+
reqts = self._data.get('%s_requires' % key, [])
|
| 844 |
+
result.extend(self.get_requirements(reqts, extras=extras, env=env))
|
| 845 |
+
return result
|
| 846 |
+
|
| 847 |
+
@property
|
| 848 |
+
def dictionary(self):
|
| 849 |
+
if self._legacy:
|
| 850 |
+
return self._from_legacy()
|
| 851 |
+
return self._data
|
| 852 |
+
|
| 853 |
+
@property
|
| 854 |
+
def dependencies(self):
|
| 855 |
+
if self._legacy:
|
| 856 |
+
raise NotImplementedError
|
| 857 |
+
else:
|
| 858 |
+
return extract_by_key(self._data, self.DEPENDENCY_KEYS)
|
| 859 |
+
|
| 860 |
+
@dependencies.setter
|
| 861 |
+
def dependencies(self, value):
|
| 862 |
+
if self._legacy:
|
| 863 |
+
raise NotImplementedError
|
| 864 |
+
else:
|
| 865 |
+
self._data.update(value)
|
| 866 |
+
|
| 867 |
+
def _validate_mapping(self, mapping, scheme):
|
| 868 |
+
if mapping.get('metadata_version') != self.METADATA_VERSION:
|
| 869 |
+
raise MetadataUnrecognizedVersionError()
|
| 870 |
+
missing = []
|
| 871 |
+
for key, exclusions in self.MANDATORY_KEYS.items():
|
| 872 |
+
if key not in mapping:
|
| 873 |
+
if scheme not in exclusions:
|
| 874 |
+
missing.append(key)
|
| 875 |
+
if missing:
|
| 876 |
+
msg = 'Missing metadata items: %s' % ', '.join(missing)
|
| 877 |
+
raise MetadataMissingError(msg)
|
| 878 |
+
for k, v in mapping.items():
|
| 879 |
+
self._validate_value(k, v, scheme)
|
| 880 |
+
|
| 881 |
+
def validate(self):
|
| 882 |
+
if self._legacy:
|
| 883 |
+
missing, warnings = self._legacy.check(True)
|
| 884 |
+
if missing or warnings:
|
| 885 |
+
logger.warning('Metadata: missing: %s, warnings: %s', missing, warnings)
|
| 886 |
+
else:
|
| 887 |
+
self._validate_mapping(self._data, self.scheme)
|
| 888 |
+
|
| 889 |
+
def todict(self):
|
| 890 |
+
if self._legacy:
|
| 891 |
+
return self._legacy.todict(True)
|
| 892 |
+
else:
|
| 893 |
+
result = extract_by_key(self._data, self.INDEX_KEYS)
|
| 894 |
+
return result
|
| 895 |
+
|
| 896 |
+
def _from_legacy(self):
|
| 897 |
+
assert self._legacy and not self._data
|
| 898 |
+
result = {
|
| 899 |
+
'metadata_version': self.METADATA_VERSION,
|
| 900 |
+
'generator': self.GENERATOR,
|
| 901 |
+
}
|
| 902 |
+
lmd = self._legacy.todict(True) # skip missing ones
|
| 903 |
+
for k in ('name', 'version', 'license', 'summary', 'description', 'classifier'):
|
| 904 |
+
if k in lmd:
|
| 905 |
+
if k == 'classifier':
|
| 906 |
+
nk = 'classifiers'
|
| 907 |
+
else:
|
| 908 |
+
nk = k
|
| 909 |
+
result[nk] = lmd[k]
|
| 910 |
+
kw = lmd.get('Keywords', [])
|
| 911 |
+
if kw == ['']:
|
| 912 |
+
kw = []
|
| 913 |
+
result['keywords'] = kw
|
| 914 |
+
keys = (('requires_dist', 'run_requires'), ('setup_requires_dist', 'build_requires'))
|
| 915 |
+
for ok, nk in keys:
|
| 916 |
+
if ok in lmd and lmd[ok]:
|
| 917 |
+
result[nk] = [{'requires': lmd[ok]}]
|
| 918 |
+
result['provides'] = self.provides
|
| 919 |
+
# author = {}
|
| 920 |
+
# maintainer = {}
|
| 921 |
+
return result
|
| 922 |
+
|
| 923 |
+
LEGACY_MAPPING = {
|
| 924 |
+
'name': 'Name',
|
| 925 |
+
'version': 'Version',
|
| 926 |
+
('extensions', 'python.details', 'license'): 'License',
|
| 927 |
+
'summary': 'Summary',
|
| 928 |
+
'description': 'Description',
|
| 929 |
+
('extensions', 'python.project', 'project_urls', 'Home'): 'Home-page',
|
| 930 |
+
('extensions', 'python.project', 'contacts', 0, 'name'): 'Author',
|
| 931 |
+
('extensions', 'python.project', 'contacts', 0, 'email'): 'Author-email',
|
| 932 |
+
'source_url': 'Download-URL',
|
| 933 |
+
('extensions', 'python.details', 'classifiers'): 'Classifier',
|
| 934 |
+
}
|
| 935 |
+
|
| 936 |
+
def _to_legacy(self):
|
| 937 |
+
|
| 938 |
+
def process_entries(entries):
|
| 939 |
+
reqts = set()
|
| 940 |
+
for e in entries:
|
| 941 |
+
extra = e.get('extra')
|
| 942 |
+
env = e.get('environment')
|
| 943 |
+
rlist = e['requires']
|
| 944 |
+
for r in rlist:
|
| 945 |
+
if not env and not extra:
|
| 946 |
+
reqts.add(r)
|
| 947 |
+
else:
|
| 948 |
+
marker = ''
|
| 949 |
+
if extra:
|
| 950 |
+
marker = 'extra == "%s"' % extra
|
| 951 |
+
if env:
|
| 952 |
+
if marker:
|
| 953 |
+
marker = '(%s) and %s' % (env, marker)
|
| 954 |
+
else:
|
| 955 |
+
marker = env
|
| 956 |
+
reqts.add(';'.join((r, marker)))
|
| 957 |
+
return reqts
|
| 958 |
+
|
| 959 |
+
assert self._data and not self._legacy
|
| 960 |
+
result = LegacyMetadata()
|
| 961 |
+
nmd = self._data
|
| 962 |
+
# import pdb; pdb.set_trace()
|
| 963 |
+
for nk, ok in self.LEGACY_MAPPING.items():
|
| 964 |
+
if not isinstance(nk, tuple):
|
| 965 |
+
if nk in nmd:
|
| 966 |
+
result[ok] = nmd[nk]
|
| 967 |
+
else:
|
| 968 |
+
d = nmd
|
| 969 |
+
found = True
|
| 970 |
+
for k in nk:
|
| 971 |
+
try:
|
| 972 |
+
d = d[k]
|
| 973 |
+
except (KeyError, IndexError):
|
| 974 |
+
found = False
|
| 975 |
+
break
|
| 976 |
+
if found:
|
| 977 |
+
result[ok] = d
|
| 978 |
+
r1 = process_entries(self.run_requires + self.meta_requires)
|
| 979 |
+
r2 = process_entries(self.build_requires + self.dev_requires)
|
| 980 |
+
if self.extras:
|
| 981 |
+
result['Provides-Extra'] = sorted(self.extras)
|
| 982 |
+
result['Requires-Dist'] = sorted(r1)
|
| 983 |
+
result['Setup-Requires-Dist'] = sorted(r2)
|
| 984 |
+
# TODO: any other fields wanted
|
| 985 |
+
return result
|
| 986 |
+
|
| 987 |
+
def write(self, path=None, fileobj=None, legacy=False, skip_unknown=True):
|
| 988 |
+
if [path, fileobj].count(None) != 1:
|
| 989 |
+
raise ValueError('Exactly one of path and fileobj is needed')
|
| 990 |
+
self.validate()
|
| 991 |
+
if legacy:
|
| 992 |
+
if self._legacy:
|
| 993 |
+
legacy_md = self._legacy
|
| 994 |
+
else:
|
| 995 |
+
legacy_md = self._to_legacy()
|
| 996 |
+
if path:
|
| 997 |
+
legacy_md.write(path, skip_unknown=skip_unknown)
|
| 998 |
+
else:
|
| 999 |
+
legacy_md.write_file(fileobj, skip_unknown=skip_unknown)
|
| 1000 |
+
else:
|
| 1001 |
+
if self._legacy:
|
| 1002 |
+
d = self._from_legacy()
|
| 1003 |
+
else:
|
| 1004 |
+
d = self._data
|
| 1005 |
+
if fileobj:
|
| 1006 |
+
json.dump(d, fileobj, ensure_ascii=True, indent=2, sort_keys=True)
|
| 1007 |
+
else:
|
| 1008 |
+
with codecs.open(path, 'w', 'utf-8') as f:
|
| 1009 |
+
json.dump(d, f, ensure_ascii=True, indent=2, sort_keys=True)
|
| 1010 |
+
|
| 1011 |
+
def add_requirements(self, requirements):
|
| 1012 |
+
if self._legacy:
|
| 1013 |
+
self._legacy.add_requirements(requirements)
|
| 1014 |
+
else:
|
| 1015 |
+
run_requires = self._data.setdefault('run_requires', [])
|
| 1016 |
+
always = None
|
| 1017 |
+
for entry in run_requires:
|
| 1018 |
+
if 'environment' not in entry and 'extra' not in entry:
|
| 1019 |
+
always = entry
|
| 1020 |
+
break
|
| 1021 |
+
if always is None:
|
| 1022 |
+
always = {'requires': requirements}
|
| 1023 |
+
run_requires.insert(0, always)
|
| 1024 |
+
else:
|
| 1025 |
+
rset = set(always['requires']) | set(requirements)
|
| 1026 |
+
always['requires'] = sorted(rset)
|
| 1027 |
+
|
| 1028 |
+
def __repr__(self):
|
| 1029 |
+
name = self.name or '(no name)'
|
| 1030 |
+
version = self.version or 'no version'
|
| 1031 |
+
return '<%s %s %s (%s)>' % (self.__class__.__name__, self.metadata_version, name, version)
|
lib/python3.12/site-packages/pip/_vendor/distlib/resources.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
#
|
| 3 |
+
# Copyright (C) 2013-2017 Vinay Sajip.
|
| 4 |
+
# Licensed to the Python Software Foundation under a contributor agreement.
|
| 5 |
+
# See LICENSE.txt and CONTRIBUTORS.txt.
|
| 6 |
+
#
|
| 7 |
+
from __future__ import unicode_literals
|
| 8 |
+
|
| 9 |
+
import bisect
|
| 10 |
+
import io
|
| 11 |
+
import logging
|
| 12 |
+
import os
|
| 13 |
+
import pkgutil
|
| 14 |
+
import sys
|
| 15 |
+
import types
|
| 16 |
+
import zipimport
|
| 17 |
+
|
| 18 |
+
from . import DistlibException
|
| 19 |
+
from .util import cached_property, get_cache_base, Cache
|
| 20 |
+
|
| 21 |
+
logger = logging.getLogger(__name__)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
cache = None # created when needed
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class ResourceCache(Cache):
|
| 28 |
+
def __init__(self, base=None):
|
| 29 |
+
if base is None:
|
| 30 |
+
# Use native string to avoid issues on 2.x: see Python #20140.
|
| 31 |
+
base = os.path.join(get_cache_base(), str('resource-cache'))
|
| 32 |
+
super(ResourceCache, self).__init__(base)
|
| 33 |
+
|
| 34 |
+
def is_stale(self, resource, path):
|
| 35 |
+
"""
|
| 36 |
+
Is the cache stale for the given resource?
|
| 37 |
+
|
| 38 |
+
:param resource: The :class:`Resource` being cached.
|
| 39 |
+
:param path: The path of the resource in the cache.
|
| 40 |
+
:return: True if the cache is stale.
|
| 41 |
+
"""
|
| 42 |
+
# Cache invalidation is a hard problem :-)
|
| 43 |
+
return True
|
| 44 |
+
|
| 45 |
+
def get(self, resource):
|
| 46 |
+
"""
|
| 47 |
+
Get a resource into the cache,
|
| 48 |
+
|
| 49 |
+
:param resource: A :class:`Resource` instance.
|
| 50 |
+
:return: The pathname of the resource in the cache.
|
| 51 |
+
"""
|
| 52 |
+
prefix, path = resource.finder.get_cache_info(resource)
|
| 53 |
+
if prefix is None:
|
| 54 |
+
result = path
|
| 55 |
+
else:
|
| 56 |
+
result = os.path.join(self.base, self.prefix_to_dir(prefix), path)
|
| 57 |
+
dirname = os.path.dirname(result)
|
| 58 |
+
if not os.path.isdir(dirname):
|
| 59 |
+
os.makedirs(dirname)
|
| 60 |
+
if not os.path.exists(result):
|
| 61 |
+
stale = True
|
| 62 |
+
else:
|
| 63 |
+
stale = self.is_stale(resource, path)
|
| 64 |
+
if stale:
|
| 65 |
+
# write the bytes of the resource to the cache location
|
| 66 |
+
with open(result, 'wb') as f:
|
| 67 |
+
f.write(resource.bytes)
|
| 68 |
+
return result
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class ResourceBase(object):
|
| 72 |
+
def __init__(self, finder, name):
|
| 73 |
+
self.finder = finder
|
| 74 |
+
self.name = name
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class Resource(ResourceBase):
|
| 78 |
+
"""
|
| 79 |
+
A class representing an in-package resource, such as a data file. This is
|
| 80 |
+
not normally instantiated by user code, but rather by a
|
| 81 |
+
:class:`ResourceFinder` which manages the resource.
|
| 82 |
+
"""
|
| 83 |
+
is_container = False # Backwards compatibility
|
| 84 |
+
|
| 85 |
+
def as_stream(self):
|
| 86 |
+
"""
|
| 87 |
+
Get the resource as a stream.
|
| 88 |
+
|
| 89 |
+
This is not a property to make it obvious that it returns a new stream
|
| 90 |
+
each time.
|
| 91 |
+
"""
|
| 92 |
+
return self.finder.get_stream(self)
|
| 93 |
+
|
| 94 |
+
@cached_property
|
| 95 |
+
def file_path(self):
|
| 96 |
+
global cache
|
| 97 |
+
if cache is None:
|
| 98 |
+
cache = ResourceCache()
|
| 99 |
+
return cache.get(self)
|
| 100 |
+
|
| 101 |
+
@cached_property
|
| 102 |
+
def bytes(self):
|
| 103 |
+
return self.finder.get_bytes(self)
|
| 104 |
+
|
| 105 |
+
@cached_property
|
| 106 |
+
def size(self):
|
| 107 |
+
return self.finder.get_size(self)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
class ResourceContainer(ResourceBase):
|
| 111 |
+
is_container = True # Backwards compatibility
|
| 112 |
+
|
| 113 |
+
@cached_property
|
| 114 |
+
def resources(self):
|
| 115 |
+
return self.finder.get_resources(self)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
class ResourceFinder(object):
|
| 119 |
+
"""
|
| 120 |
+
Resource finder for file system resources.
|
| 121 |
+
"""
|
| 122 |
+
|
| 123 |
+
if sys.platform.startswith('java'):
|
| 124 |
+
skipped_extensions = ('.pyc', '.pyo', '.class')
|
| 125 |
+
else:
|
| 126 |
+
skipped_extensions = ('.pyc', '.pyo')
|
| 127 |
+
|
| 128 |
+
def __init__(self, module):
|
| 129 |
+
self.module = module
|
| 130 |
+
self.loader = getattr(module, '__loader__', None)
|
| 131 |
+
self.base = os.path.dirname(getattr(module, '__file__', ''))
|
| 132 |
+
|
| 133 |
+
def _adjust_path(self, path):
|
| 134 |
+
return os.path.realpath(path)
|
| 135 |
+
|
| 136 |
+
def _make_path(self, resource_name):
|
| 137 |
+
# Issue #50: need to preserve type of path on Python 2.x
|
| 138 |
+
# like os.path._get_sep
|
| 139 |
+
if isinstance(resource_name, bytes): # should only happen on 2.x
|
| 140 |
+
sep = b'/'
|
| 141 |
+
else:
|
| 142 |
+
sep = '/'
|
| 143 |
+
parts = resource_name.split(sep)
|
| 144 |
+
parts.insert(0, self.base)
|
| 145 |
+
result = os.path.join(*parts)
|
| 146 |
+
return self._adjust_path(result)
|
| 147 |
+
|
| 148 |
+
def _find(self, path):
|
| 149 |
+
return os.path.exists(path)
|
| 150 |
+
|
| 151 |
+
def get_cache_info(self, resource):
|
| 152 |
+
return None, resource.path
|
| 153 |
+
|
| 154 |
+
def find(self, resource_name):
|
| 155 |
+
path = self._make_path(resource_name)
|
| 156 |
+
if not self._find(path):
|
| 157 |
+
result = None
|
| 158 |
+
else:
|
| 159 |
+
if self._is_directory(path):
|
| 160 |
+
result = ResourceContainer(self, resource_name)
|
| 161 |
+
else:
|
| 162 |
+
result = Resource(self, resource_name)
|
| 163 |
+
result.path = path
|
| 164 |
+
return result
|
| 165 |
+
|
| 166 |
+
def get_stream(self, resource):
|
| 167 |
+
return open(resource.path, 'rb')
|
| 168 |
+
|
| 169 |
+
def get_bytes(self, resource):
|
| 170 |
+
with open(resource.path, 'rb') as f:
|
| 171 |
+
return f.read()
|
| 172 |
+
|
| 173 |
+
def get_size(self, resource):
|
| 174 |
+
return os.path.getsize(resource.path)
|
| 175 |
+
|
| 176 |
+
def get_resources(self, resource):
|
| 177 |
+
def allowed(f):
|
| 178 |
+
return (f != '__pycache__' and not
|
| 179 |
+
f.endswith(self.skipped_extensions))
|
| 180 |
+
return set([f for f in os.listdir(resource.path) if allowed(f)])
|
| 181 |
+
|
| 182 |
+
def is_container(self, resource):
|
| 183 |
+
return self._is_directory(resource.path)
|
| 184 |
+
|
| 185 |
+
_is_directory = staticmethod(os.path.isdir)
|
| 186 |
+
|
| 187 |
+
def iterator(self, resource_name):
|
| 188 |
+
resource = self.find(resource_name)
|
| 189 |
+
if resource is not None:
|
| 190 |
+
todo = [resource]
|
| 191 |
+
while todo:
|
| 192 |
+
resource = todo.pop(0)
|
| 193 |
+
yield resource
|
| 194 |
+
if resource.is_container:
|
| 195 |
+
rname = resource.name
|
| 196 |
+
for name in resource.resources:
|
| 197 |
+
if not rname:
|
| 198 |
+
new_name = name
|
| 199 |
+
else:
|
| 200 |
+
new_name = '/'.join([rname, name])
|
| 201 |
+
child = self.find(new_name)
|
| 202 |
+
if child.is_container:
|
| 203 |
+
todo.append(child)
|
| 204 |
+
else:
|
| 205 |
+
yield child
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
class ZipResourceFinder(ResourceFinder):
|
| 209 |
+
"""
|
| 210 |
+
Resource finder for resources in .zip files.
|
| 211 |
+
"""
|
| 212 |
+
def __init__(self, module):
|
| 213 |
+
super(ZipResourceFinder, self).__init__(module)
|
| 214 |
+
archive = self.loader.archive
|
| 215 |
+
self.prefix_len = 1 + len(archive)
|
| 216 |
+
# PyPy doesn't have a _files attr on zipimporter, and you can't set one
|
| 217 |
+
if hasattr(self.loader, '_files'):
|
| 218 |
+
self._files = self.loader._files
|
| 219 |
+
else:
|
| 220 |
+
self._files = zipimport._zip_directory_cache[archive]
|
| 221 |
+
self.index = sorted(self._files)
|
| 222 |
+
|
| 223 |
+
def _adjust_path(self, path):
|
| 224 |
+
return path
|
| 225 |
+
|
| 226 |
+
def _find(self, path):
|
| 227 |
+
path = path[self.prefix_len:]
|
| 228 |
+
if path in self._files:
|
| 229 |
+
result = True
|
| 230 |
+
else:
|
| 231 |
+
if path and path[-1] != os.sep:
|
| 232 |
+
path = path + os.sep
|
| 233 |
+
i = bisect.bisect(self.index, path)
|
| 234 |
+
try:
|
| 235 |
+
result = self.index[i].startswith(path)
|
| 236 |
+
except IndexError:
|
| 237 |
+
result = False
|
| 238 |
+
if not result:
|
| 239 |
+
logger.debug('_find failed: %r %r', path, self.loader.prefix)
|
| 240 |
+
else:
|
| 241 |
+
logger.debug('_find worked: %r %r', path, self.loader.prefix)
|
| 242 |
+
return result
|
| 243 |
+
|
| 244 |
+
def get_cache_info(self, resource):
|
| 245 |
+
prefix = self.loader.archive
|
| 246 |
+
path = resource.path[1 + len(prefix):]
|
| 247 |
+
return prefix, path
|
| 248 |
+
|
| 249 |
+
def get_bytes(self, resource):
|
| 250 |
+
return self.loader.get_data(resource.path)
|
| 251 |
+
|
| 252 |
+
def get_stream(self, resource):
|
| 253 |
+
return io.BytesIO(self.get_bytes(resource))
|
| 254 |
+
|
| 255 |
+
def get_size(self, resource):
|
| 256 |
+
path = resource.path[self.prefix_len:]
|
| 257 |
+
return self._files[path][3]
|
| 258 |
+
|
| 259 |
+
def get_resources(self, resource):
|
| 260 |
+
path = resource.path[self.prefix_len:]
|
| 261 |
+
if path and path[-1] != os.sep:
|
| 262 |
+
path += os.sep
|
| 263 |
+
plen = len(path)
|
| 264 |
+
result = set()
|
| 265 |
+
i = bisect.bisect(self.index, path)
|
| 266 |
+
while i < len(self.index):
|
| 267 |
+
if not self.index[i].startswith(path):
|
| 268 |
+
break
|
| 269 |
+
s = self.index[i][plen:]
|
| 270 |
+
result.add(s.split(os.sep, 1)[0]) # only immediate children
|
| 271 |
+
i += 1
|
| 272 |
+
return result
|
| 273 |
+
|
| 274 |
+
def _is_directory(self, path):
|
| 275 |
+
path = path[self.prefix_len:]
|
| 276 |
+
if path and path[-1] != os.sep:
|
| 277 |
+
path += os.sep
|
| 278 |
+
i = bisect.bisect(self.index, path)
|
| 279 |
+
try:
|
| 280 |
+
result = self.index[i].startswith(path)
|
| 281 |
+
except IndexError:
|
| 282 |
+
result = False
|
| 283 |
+
return result
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
_finder_registry = {
|
| 287 |
+
type(None): ResourceFinder,
|
| 288 |
+
zipimport.zipimporter: ZipResourceFinder
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
try:
|
| 292 |
+
# In Python 3.6, _frozen_importlib -> _frozen_importlib_external
|
| 293 |
+
try:
|
| 294 |
+
import _frozen_importlib_external as _fi
|
| 295 |
+
except ImportError:
|
| 296 |
+
import _frozen_importlib as _fi
|
| 297 |
+
_finder_registry[_fi.SourceFileLoader] = ResourceFinder
|
| 298 |
+
_finder_registry[_fi.FileFinder] = ResourceFinder
|
| 299 |
+
# See issue #146
|
| 300 |
+
_finder_registry[_fi.SourcelessFileLoader] = ResourceFinder
|
| 301 |
+
del _fi
|
| 302 |
+
except (ImportError, AttributeError):
|
| 303 |
+
pass
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
def register_finder(loader, finder_maker):
|
| 307 |
+
_finder_registry[type(loader)] = finder_maker
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
_finder_cache = {}
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
def finder(package):
|
| 314 |
+
"""
|
| 315 |
+
Return a resource finder for a package.
|
| 316 |
+
:param package: The name of the package.
|
| 317 |
+
:return: A :class:`ResourceFinder` instance for the package.
|
| 318 |
+
"""
|
| 319 |
+
if package in _finder_cache:
|
| 320 |
+
result = _finder_cache[package]
|
| 321 |
+
else:
|
| 322 |
+
if package not in sys.modules:
|
| 323 |
+
__import__(package)
|
| 324 |
+
module = sys.modules[package]
|
| 325 |
+
path = getattr(module, '__path__', None)
|
| 326 |
+
if path is None:
|
| 327 |
+
raise DistlibException('You cannot get a finder for a module, '
|
| 328 |
+
'only for a package')
|
| 329 |
+
loader = getattr(module, '__loader__', None)
|
| 330 |
+
finder_maker = _finder_registry.get(type(loader))
|
| 331 |
+
if finder_maker is None:
|
| 332 |
+
raise DistlibException('Unable to locate finder for %r' % package)
|
| 333 |
+
result = finder_maker(module)
|
| 334 |
+
_finder_cache[package] = result
|
| 335 |
+
return result
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
_dummy_module = types.ModuleType(str('__dummy__'))
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
def finder_for_path(path):
|
| 342 |
+
"""
|
| 343 |
+
Return a resource finder for a path, which should represent a container.
|
| 344 |
+
|
| 345 |
+
:param path: The path.
|
| 346 |
+
:return: A :class:`ResourceFinder` instance for the path.
|
| 347 |
+
"""
|
| 348 |
+
result = None
|
| 349 |
+
# calls any path hooks, gets importer into cache
|
| 350 |
+
pkgutil.get_importer(path)
|
| 351 |
+
loader = sys.path_importer_cache.get(path)
|
| 352 |
+
finder = _finder_registry.get(type(loader))
|
| 353 |
+
if finder:
|
| 354 |
+
module = _dummy_module
|
| 355 |
+
module.__file__ = os.path.join(path, '')
|
| 356 |
+
module.__loader__ = loader
|
| 357 |
+
result = finder(module)
|
| 358 |
+
return result
|
lib/python3.12/site-packages/pip/_vendor/distlib/util.py
ADDED
|
@@ -0,0 +1,1984 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# Copyright (C) 2012-2023 The Python Software Foundation.
|
| 3 |
+
# See LICENSE.txt and CONTRIBUTORS.txt.
|
| 4 |
+
#
|
| 5 |
+
import codecs
|
| 6 |
+
from collections import deque
|
| 7 |
+
import contextlib
|
| 8 |
+
import csv
|
| 9 |
+
from glob import iglob as std_iglob
|
| 10 |
+
import io
|
| 11 |
+
import json
|
| 12 |
+
import logging
|
| 13 |
+
import os
|
| 14 |
+
import py_compile
|
| 15 |
+
import re
|
| 16 |
+
import socket
|
| 17 |
+
try:
|
| 18 |
+
import ssl
|
| 19 |
+
except ImportError: # pragma: no cover
|
| 20 |
+
ssl = None
|
| 21 |
+
import subprocess
|
| 22 |
+
import sys
|
| 23 |
+
import tarfile
|
| 24 |
+
import tempfile
|
| 25 |
+
import textwrap
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
import threading
|
| 29 |
+
except ImportError: # pragma: no cover
|
| 30 |
+
import dummy_threading as threading
|
| 31 |
+
import time
|
| 32 |
+
|
| 33 |
+
from . import DistlibException
|
| 34 |
+
from .compat import (string_types, text_type, shutil, raw_input, StringIO, cache_from_source, urlopen, urljoin, httplib,
|
| 35 |
+
xmlrpclib, HTTPHandler, BaseConfigurator, valid_ident, Container, configparser, URLError, ZipFile,
|
| 36 |
+
fsdecode, unquote, urlparse)
|
| 37 |
+
|
| 38 |
+
logger = logging.getLogger(__name__)
|
| 39 |
+
|
| 40 |
+
#
|
| 41 |
+
# Requirement parsing code as per PEP 508
|
| 42 |
+
#
|
| 43 |
+
|
| 44 |
+
IDENTIFIER = re.compile(r'^([\w\.-]+)\s*')
|
| 45 |
+
VERSION_IDENTIFIER = re.compile(r'^([\w\.*+-]+)\s*')
|
| 46 |
+
COMPARE_OP = re.compile(r'^(<=?|>=?|={2,3}|[~!]=)\s*')
|
| 47 |
+
MARKER_OP = re.compile(r'^((<=?)|(>=?)|={2,3}|[~!]=|in|not\s+in)\s*')
|
| 48 |
+
OR = re.compile(r'^or\b\s*')
|
| 49 |
+
AND = re.compile(r'^and\b\s*')
|
| 50 |
+
NON_SPACE = re.compile(r'(\S+)\s*')
|
| 51 |
+
STRING_CHUNK = re.compile(r'([\s\w\.{}()*+#:;,/?!~`@$%^&=|<>\[\]-]+)')
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def parse_marker(marker_string):
|
| 55 |
+
"""
|
| 56 |
+
Parse a marker string and return a dictionary containing a marker expression.
|
| 57 |
+
|
| 58 |
+
The dictionary will contain keys "op", "lhs" and "rhs" for non-terminals in
|
| 59 |
+
the expression grammar, or strings. A string contained in quotes is to be
|
| 60 |
+
interpreted as a literal string, and a string not contained in quotes is a
|
| 61 |
+
variable (such as os_name).
|
| 62 |
+
"""
|
| 63 |
+
|
| 64 |
+
def marker_var(remaining):
|
| 65 |
+
# either identifier, or literal string
|
| 66 |
+
m = IDENTIFIER.match(remaining)
|
| 67 |
+
if m:
|
| 68 |
+
result = m.groups()[0]
|
| 69 |
+
remaining = remaining[m.end():]
|
| 70 |
+
elif not remaining:
|
| 71 |
+
raise SyntaxError('unexpected end of input')
|
| 72 |
+
else:
|
| 73 |
+
q = remaining[0]
|
| 74 |
+
if q not in '\'"':
|
| 75 |
+
raise SyntaxError('invalid expression: %s' % remaining)
|
| 76 |
+
oq = '\'"'.replace(q, '')
|
| 77 |
+
remaining = remaining[1:]
|
| 78 |
+
parts = [q]
|
| 79 |
+
while remaining:
|
| 80 |
+
# either a string chunk, or oq, or q to terminate
|
| 81 |
+
if remaining[0] == q:
|
| 82 |
+
break
|
| 83 |
+
elif remaining[0] == oq:
|
| 84 |
+
parts.append(oq)
|
| 85 |
+
remaining = remaining[1:]
|
| 86 |
+
else:
|
| 87 |
+
m = STRING_CHUNK.match(remaining)
|
| 88 |
+
if not m:
|
| 89 |
+
raise SyntaxError('error in string literal: %s' % remaining)
|
| 90 |
+
parts.append(m.groups()[0])
|
| 91 |
+
remaining = remaining[m.end():]
|
| 92 |
+
else:
|
| 93 |
+
s = ''.join(parts)
|
| 94 |
+
raise SyntaxError('unterminated string: %s' % s)
|
| 95 |
+
parts.append(q)
|
| 96 |
+
result = ''.join(parts)
|
| 97 |
+
remaining = remaining[1:].lstrip() # skip past closing quote
|
| 98 |
+
return result, remaining
|
| 99 |
+
|
| 100 |
+
def marker_expr(remaining):
|
| 101 |
+
if remaining and remaining[0] == '(':
|
| 102 |
+
result, remaining = marker(remaining[1:].lstrip())
|
| 103 |
+
if remaining[0] != ')':
|
| 104 |
+
raise SyntaxError('unterminated parenthesis: %s' % remaining)
|
| 105 |
+
remaining = remaining[1:].lstrip()
|
| 106 |
+
else:
|
| 107 |
+
lhs, remaining = marker_var(remaining)
|
| 108 |
+
while remaining:
|
| 109 |
+
m = MARKER_OP.match(remaining)
|
| 110 |
+
if not m:
|
| 111 |
+
break
|
| 112 |
+
op = m.groups()[0]
|
| 113 |
+
remaining = remaining[m.end():]
|
| 114 |
+
rhs, remaining = marker_var(remaining)
|
| 115 |
+
lhs = {'op': op, 'lhs': lhs, 'rhs': rhs}
|
| 116 |
+
result = lhs
|
| 117 |
+
return result, remaining
|
| 118 |
+
|
| 119 |
+
def marker_and(remaining):
|
| 120 |
+
lhs, remaining = marker_expr(remaining)
|
| 121 |
+
while remaining:
|
| 122 |
+
m = AND.match(remaining)
|
| 123 |
+
if not m:
|
| 124 |
+
break
|
| 125 |
+
remaining = remaining[m.end():]
|
| 126 |
+
rhs, remaining = marker_expr(remaining)
|
| 127 |
+
lhs = {'op': 'and', 'lhs': lhs, 'rhs': rhs}
|
| 128 |
+
return lhs, remaining
|
| 129 |
+
|
| 130 |
+
def marker(remaining):
|
| 131 |
+
lhs, remaining = marker_and(remaining)
|
| 132 |
+
while remaining:
|
| 133 |
+
m = OR.match(remaining)
|
| 134 |
+
if not m:
|
| 135 |
+
break
|
| 136 |
+
remaining = remaining[m.end():]
|
| 137 |
+
rhs, remaining = marker_and(remaining)
|
| 138 |
+
lhs = {'op': 'or', 'lhs': lhs, 'rhs': rhs}
|
| 139 |
+
return lhs, remaining
|
| 140 |
+
|
| 141 |
+
return marker(marker_string)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def parse_requirement(req):
|
| 145 |
+
"""
|
| 146 |
+
Parse a requirement passed in as a string. Return a Container
|
| 147 |
+
whose attributes contain the various parts of the requirement.
|
| 148 |
+
"""
|
| 149 |
+
remaining = req.strip()
|
| 150 |
+
if not remaining or remaining.startswith('#'):
|
| 151 |
+
return None
|
| 152 |
+
m = IDENTIFIER.match(remaining)
|
| 153 |
+
if not m:
|
| 154 |
+
raise SyntaxError('name expected: %s' % remaining)
|
| 155 |
+
distname = m.groups()[0]
|
| 156 |
+
remaining = remaining[m.end():]
|
| 157 |
+
extras = mark_expr = versions = uri = None
|
| 158 |
+
if remaining and remaining[0] == '[':
|
| 159 |
+
i = remaining.find(']', 1)
|
| 160 |
+
if i < 0:
|
| 161 |
+
raise SyntaxError('unterminated extra: %s' % remaining)
|
| 162 |
+
s = remaining[1:i]
|
| 163 |
+
remaining = remaining[i + 1:].lstrip()
|
| 164 |
+
extras = []
|
| 165 |
+
while s:
|
| 166 |
+
m = IDENTIFIER.match(s)
|
| 167 |
+
if not m:
|
| 168 |
+
raise SyntaxError('malformed extra: %s' % s)
|
| 169 |
+
extras.append(m.groups()[0])
|
| 170 |
+
s = s[m.end():]
|
| 171 |
+
if not s:
|
| 172 |
+
break
|
| 173 |
+
if s[0] != ',':
|
| 174 |
+
raise SyntaxError('comma expected in extras: %s' % s)
|
| 175 |
+
s = s[1:].lstrip()
|
| 176 |
+
if not extras:
|
| 177 |
+
extras = None
|
| 178 |
+
if remaining:
|
| 179 |
+
if remaining[0] == '@':
|
| 180 |
+
# it's a URI
|
| 181 |
+
remaining = remaining[1:].lstrip()
|
| 182 |
+
m = NON_SPACE.match(remaining)
|
| 183 |
+
if not m:
|
| 184 |
+
raise SyntaxError('invalid URI: %s' % remaining)
|
| 185 |
+
uri = m.groups()[0]
|
| 186 |
+
t = urlparse(uri)
|
| 187 |
+
# there are issues with Python and URL parsing, so this test
|
| 188 |
+
# is a bit crude. See bpo-20271, bpo-23505. Python doesn't
|
| 189 |
+
# always parse invalid URLs correctly - it should raise
|
| 190 |
+
# exceptions for malformed URLs
|
| 191 |
+
if not (t.scheme and t.netloc):
|
| 192 |
+
raise SyntaxError('Invalid URL: %s' % uri)
|
| 193 |
+
remaining = remaining[m.end():].lstrip()
|
| 194 |
+
else:
|
| 195 |
+
|
| 196 |
+
def get_versions(ver_remaining):
|
| 197 |
+
"""
|
| 198 |
+
Return a list of operator, version tuples if any are
|
| 199 |
+
specified, else None.
|
| 200 |
+
"""
|
| 201 |
+
m = COMPARE_OP.match(ver_remaining)
|
| 202 |
+
versions = None
|
| 203 |
+
if m:
|
| 204 |
+
versions = []
|
| 205 |
+
while True:
|
| 206 |
+
op = m.groups()[0]
|
| 207 |
+
ver_remaining = ver_remaining[m.end():]
|
| 208 |
+
m = VERSION_IDENTIFIER.match(ver_remaining)
|
| 209 |
+
if not m:
|
| 210 |
+
raise SyntaxError('invalid version: %s' % ver_remaining)
|
| 211 |
+
v = m.groups()[0]
|
| 212 |
+
versions.append((op, v))
|
| 213 |
+
ver_remaining = ver_remaining[m.end():]
|
| 214 |
+
if not ver_remaining or ver_remaining[0] != ',':
|
| 215 |
+
break
|
| 216 |
+
ver_remaining = ver_remaining[1:].lstrip()
|
| 217 |
+
# Some packages have a trailing comma which would break things
|
| 218 |
+
# See issue #148
|
| 219 |
+
if not ver_remaining:
|
| 220 |
+
break
|
| 221 |
+
m = COMPARE_OP.match(ver_remaining)
|
| 222 |
+
if not m:
|
| 223 |
+
raise SyntaxError('invalid constraint: %s' % ver_remaining)
|
| 224 |
+
if not versions:
|
| 225 |
+
versions = None
|
| 226 |
+
return versions, ver_remaining
|
| 227 |
+
|
| 228 |
+
if remaining[0] != '(':
|
| 229 |
+
versions, remaining = get_versions(remaining)
|
| 230 |
+
else:
|
| 231 |
+
i = remaining.find(')', 1)
|
| 232 |
+
if i < 0:
|
| 233 |
+
raise SyntaxError('unterminated parenthesis: %s' % remaining)
|
| 234 |
+
s = remaining[1:i]
|
| 235 |
+
remaining = remaining[i + 1:].lstrip()
|
| 236 |
+
# As a special diversion from PEP 508, allow a version number
|
| 237 |
+
# a.b.c in parentheses as a synonym for ~= a.b.c (because this
|
| 238 |
+
# is allowed in earlier PEPs)
|
| 239 |
+
if COMPARE_OP.match(s):
|
| 240 |
+
versions, _ = get_versions(s)
|
| 241 |
+
else:
|
| 242 |
+
m = VERSION_IDENTIFIER.match(s)
|
| 243 |
+
if not m:
|
| 244 |
+
raise SyntaxError('invalid constraint: %s' % s)
|
| 245 |
+
v = m.groups()[0]
|
| 246 |
+
s = s[m.end():].lstrip()
|
| 247 |
+
if s:
|
| 248 |
+
raise SyntaxError('invalid constraint: %s' % s)
|
| 249 |
+
versions = [('~=', v)]
|
| 250 |
+
|
| 251 |
+
if remaining:
|
| 252 |
+
if remaining[0] != ';':
|
| 253 |
+
raise SyntaxError('invalid requirement: %s' % remaining)
|
| 254 |
+
remaining = remaining[1:].lstrip()
|
| 255 |
+
|
| 256 |
+
mark_expr, remaining = parse_marker(remaining)
|
| 257 |
+
|
| 258 |
+
if remaining and remaining[0] != '#':
|
| 259 |
+
raise SyntaxError('unexpected trailing data: %s' % remaining)
|
| 260 |
+
|
| 261 |
+
if not versions:
|
| 262 |
+
rs = distname
|
| 263 |
+
else:
|
| 264 |
+
rs = '%s %s' % (distname, ', '.join(['%s %s' % con for con in versions]))
|
| 265 |
+
return Container(name=distname, extras=extras, constraints=versions, marker=mark_expr, url=uri, requirement=rs)
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def get_resources_dests(resources_root, rules):
|
| 269 |
+
"""Find destinations for resources files"""
|
| 270 |
+
|
| 271 |
+
def get_rel_path(root, path):
|
| 272 |
+
# normalizes and returns a lstripped-/-separated path
|
| 273 |
+
root = root.replace(os.path.sep, '/')
|
| 274 |
+
path = path.replace(os.path.sep, '/')
|
| 275 |
+
assert path.startswith(root)
|
| 276 |
+
return path[len(root):].lstrip('/')
|
| 277 |
+
|
| 278 |
+
destinations = {}
|
| 279 |
+
for base, suffix, dest in rules:
|
| 280 |
+
prefix = os.path.join(resources_root, base)
|
| 281 |
+
for abs_base in iglob(prefix):
|
| 282 |
+
abs_glob = os.path.join(abs_base, suffix)
|
| 283 |
+
for abs_path in iglob(abs_glob):
|
| 284 |
+
resource_file = get_rel_path(resources_root, abs_path)
|
| 285 |
+
if dest is None: # remove the entry if it was here
|
| 286 |
+
destinations.pop(resource_file, None)
|
| 287 |
+
else:
|
| 288 |
+
rel_path = get_rel_path(abs_base, abs_path)
|
| 289 |
+
rel_dest = dest.replace(os.path.sep, '/').rstrip('/')
|
| 290 |
+
destinations[resource_file] = rel_dest + '/' + rel_path
|
| 291 |
+
return destinations
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def in_venv():
|
| 295 |
+
if hasattr(sys, 'real_prefix'):
|
| 296 |
+
# virtualenv venvs
|
| 297 |
+
result = True
|
| 298 |
+
else:
|
| 299 |
+
# PEP 405 venvs
|
| 300 |
+
result = sys.prefix != getattr(sys, 'base_prefix', sys.prefix)
|
| 301 |
+
return result
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def get_executable():
|
| 305 |
+
# The __PYVENV_LAUNCHER__ dance is apparently no longer needed, as
|
| 306 |
+
# changes to the stub launcher mean that sys.executable always points
|
| 307 |
+
# to the stub on OS X
|
| 308 |
+
# if sys.platform == 'darwin' and ('__PYVENV_LAUNCHER__'
|
| 309 |
+
# in os.environ):
|
| 310 |
+
# result = os.environ['__PYVENV_LAUNCHER__']
|
| 311 |
+
# else:
|
| 312 |
+
# result = sys.executable
|
| 313 |
+
# return result
|
| 314 |
+
# Avoid normcasing: see issue #143
|
| 315 |
+
# result = os.path.normcase(sys.executable)
|
| 316 |
+
result = sys.executable
|
| 317 |
+
if not isinstance(result, text_type):
|
| 318 |
+
result = fsdecode(result)
|
| 319 |
+
return result
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def proceed(prompt, allowed_chars, error_prompt=None, default=None):
|
| 323 |
+
p = prompt
|
| 324 |
+
while True:
|
| 325 |
+
s = raw_input(p)
|
| 326 |
+
p = prompt
|
| 327 |
+
if not s and default:
|
| 328 |
+
s = default
|
| 329 |
+
if s:
|
| 330 |
+
c = s[0].lower()
|
| 331 |
+
if c in allowed_chars:
|
| 332 |
+
break
|
| 333 |
+
if error_prompt:
|
| 334 |
+
p = '%c: %s\n%s' % (c, error_prompt, prompt)
|
| 335 |
+
return c
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
def extract_by_key(d, keys):
|
| 339 |
+
if isinstance(keys, string_types):
|
| 340 |
+
keys = keys.split()
|
| 341 |
+
result = {}
|
| 342 |
+
for key in keys:
|
| 343 |
+
if key in d:
|
| 344 |
+
result[key] = d[key]
|
| 345 |
+
return result
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def read_exports(stream):
|
| 349 |
+
if sys.version_info[0] >= 3:
|
| 350 |
+
# needs to be a text stream
|
| 351 |
+
stream = codecs.getreader('utf-8')(stream)
|
| 352 |
+
# Try to load as JSON, falling back on legacy format
|
| 353 |
+
data = stream.read()
|
| 354 |
+
stream = StringIO(data)
|
| 355 |
+
try:
|
| 356 |
+
jdata = json.load(stream)
|
| 357 |
+
result = jdata['extensions']['python.exports']['exports']
|
| 358 |
+
for group, entries in result.items():
|
| 359 |
+
for k, v in entries.items():
|
| 360 |
+
s = '%s = %s' % (k, v)
|
| 361 |
+
entry = get_export_entry(s)
|
| 362 |
+
assert entry is not None
|
| 363 |
+
entries[k] = entry
|
| 364 |
+
return result
|
| 365 |
+
except Exception:
|
| 366 |
+
stream.seek(0, 0)
|
| 367 |
+
|
| 368 |
+
def read_stream(cp, stream):
|
| 369 |
+
if hasattr(cp, 'read_file'):
|
| 370 |
+
cp.read_file(stream)
|
| 371 |
+
else:
|
| 372 |
+
cp.readfp(stream)
|
| 373 |
+
|
| 374 |
+
cp = configparser.ConfigParser()
|
| 375 |
+
try:
|
| 376 |
+
read_stream(cp, stream)
|
| 377 |
+
except configparser.MissingSectionHeaderError:
|
| 378 |
+
stream.close()
|
| 379 |
+
data = textwrap.dedent(data)
|
| 380 |
+
stream = StringIO(data)
|
| 381 |
+
read_stream(cp, stream)
|
| 382 |
+
|
| 383 |
+
result = {}
|
| 384 |
+
for key in cp.sections():
|
| 385 |
+
result[key] = entries = {}
|
| 386 |
+
for name, value in cp.items(key):
|
| 387 |
+
s = '%s = %s' % (name, value)
|
| 388 |
+
entry = get_export_entry(s)
|
| 389 |
+
assert entry is not None
|
| 390 |
+
# entry.dist = self
|
| 391 |
+
entries[name] = entry
|
| 392 |
+
return result
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
def write_exports(exports, stream):
|
| 396 |
+
if sys.version_info[0] >= 3:
|
| 397 |
+
# needs to be a text stream
|
| 398 |
+
stream = codecs.getwriter('utf-8')(stream)
|
| 399 |
+
cp = configparser.ConfigParser()
|
| 400 |
+
for k, v in exports.items():
|
| 401 |
+
# TODO check k, v for valid values
|
| 402 |
+
cp.add_section(k)
|
| 403 |
+
for entry in v.values():
|
| 404 |
+
if entry.suffix is None:
|
| 405 |
+
s = entry.prefix
|
| 406 |
+
else:
|
| 407 |
+
s = '%s:%s' % (entry.prefix, entry.suffix)
|
| 408 |
+
if entry.flags:
|
| 409 |
+
s = '%s [%s]' % (s, ', '.join(entry.flags))
|
| 410 |
+
cp.set(k, entry.name, s)
|
| 411 |
+
cp.write(stream)
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
@contextlib.contextmanager
|
| 415 |
+
def tempdir():
|
| 416 |
+
td = tempfile.mkdtemp()
|
| 417 |
+
try:
|
| 418 |
+
yield td
|
| 419 |
+
finally:
|
| 420 |
+
shutil.rmtree(td)
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
@contextlib.contextmanager
|
| 424 |
+
def chdir(d):
|
| 425 |
+
cwd = os.getcwd()
|
| 426 |
+
try:
|
| 427 |
+
os.chdir(d)
|
| 428 |
+
yield
|
| 429 |
+
finally:
|
| 430 |
+
os.chdir(cwd)
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
@contextlib.contextmanager
|
| 434 |
+
def socket_timeout(seconds=15):
|
| 435 |
+
cto = socket.getdefaulttimeout()
|
| 436 |
+
try:
|
| 437 |
+
socket.setdefaulttimeout(seconds)
|
| 438 |
+
yield
|
| 439 |
+
finally:
|
| 440 |
+
socket.setdefaulttimeout(cto)
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
class cached_property(object):
|
| 444 |
+
|
| 445 |
+
def __init__(self, func):
|
| 446 |
+
self.func = func
|
| 447 |
+
# for attr in ('__name__', '__module__', '__doc__'):
|
| 448 |
+
# setattr(self, attr, getattr(func, attr, None))
|
| 449 |
+
|
| 450 |
+
def __get__(self, obj, cls=None):
|
| 451 |
+
if obj is None:
|
| 452 |
+
return self
|
| 453 |
+
value = self.func(obj)
|
| 454 |
+
object.__setattr__(obj, self.func.__name__, value)
|
| 455 |
+
# obj.__dict__[self.func.__name__] = value = self.func(obj)
|
| 456 |
+
return value
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
def convert_path(pathname):
|
| 460 |
+
"""Return 'pathname' as a name that will work on the native filesystem.
|
| 461 |
+
|
| 462 |
+
The path is split on '/' and put back together again using the current
|
| 463 |
+
directory separator. Needed because filenames in the setup script are
|
| 464 |
+
always supplied in Unix style, and have to be converted to the local
|
| 465 |
+
convention before we can actually use them in the filesystem. Raises
|
| 466 |
+
ValueError on non-Unix-ish systems if 'pathname' either starts or
|
| 467 |
+
ends with a slash.
|
| 468 |
+
"""
|
| 469 |
+
if os.sep == '/':
|
| 470 |
+
return pathname
|
| 471 |
+
if not pathname:
|
| 472 |
+
return pathname
|
| 473 |
+
if pathname[0] == '/':
|
| 474 |
+
raise ValueError("path '%s' cannot be absolute" % pathname)
|
| 475 |
+
if pathname[-1] == '/':
|
| 476 |
+
raise ValueError("path '%s' cannot end with '/'" % pathname)
|
| 477 |
+
|
| 478 |
+
paths = pathname.split('/')
|
| 479 |
+
while os.curdir in paths:
|
| 480 |
+
paths.remove(os.curdir)
|
| 481 |
+
if not paths:
|
| 482 |
+
return os.curdir
|
| 483 |
+
return os.path.join(*paths)
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
class FileOperator(object):
|
| 487 |
+
|
| 488 |
+
def __init__(self, dry_run=False):
|
| 489 |
+
self.dry_run = dry_run
|
| 490 |
+
self.ensured = set()
|
| 491 |
+
self._init_record()
|
| 492 |
+
|
| 493 |
+
def _init_record(self):
|
| 494 |
+
self.record = False
|
| 495 |
+
self.files_written = set()
|
| 496 |
+
self.dirs_created = set()
|
| 497 |
+
|
| 498 |
+
def record_as_written(self, path):
|
| 499 |
+
if self.record:
|
| 500 |
+
self.files_written.add(path)
|
| 501 |
+
|
| 502 |
+
def newer(self, source, target):
|
| 503 |
+
"""Tell if the target is newer than the source.
|
| 504 |
+
|
| 505 |
+
Returns true if 'source' exists and is more recently modified than
|
| 506 |
+
'target', or if 'source' exists and 'target' doesn't.
|
| 507 |
+
|
| 508 |
+
Returns false if both exist and 'target' is the same age or younger
|
| 509 |
+
than 'source'. Raise PackagingFileError if 'source' does not exist.
|
| 510 |
+
|
| 511 |
+
Note that this test is not very accurate: files created in the same
|
| 512 |
+
second will have the same "age".
|
| 513 |
+
"""
|
| 514 |
+
if not os.path.exists(source):
|
| 515 |
+
raise DistlibException("file '%r' does not exist" % os.path.abspath(source))
|
| 516 |
+
if not os.path.exists(target):
|
| 517 |
+
return True
|
| 518 |
+
|
| 519 |
+
return os.stat(source).st_mtime > os.stat(target).st_mtime
|
| 520 |
+
|
| 521 |
+
def copy_file(self, infile, outfile, check=True):
|
| 522 |
+
"""Copy a file respecting dry-run and force flags.
|
| 523 |
+
"""
|
| 524 |
+
self.ensure_dir(os.path.dirname(outfile))
|
| 525 |
+
logger.info('Copying %s to %s', infile, outfile)
|
| 526 |
+
if not self.dry_run:
|
| 527 |
+
msg = None
|
| 528 |
+
if check:
|
| 529 |
+
if os.path.islink(outfile):
|
| 530 |
+
msg = '%s is a symlink' % outfile
|
| 531 |
+
elif os.path.exists(outfile) and not os.path.isfile(outfile):
|
| 532 |
+
msg = '%s is a non-regular file' % outfile
|
| 533 |
+
if msg:
|
| 534 |
+
raise ValueError(msg + ' which would be overwritten')
|
| 535 |
+
shutil.copyfile(infile, outfile)
|
| 536 |
+
self.record_as_written(outfile)
|
| 537 |
+
|
| 538 |
+
def copy_stream(self, instream, outfile, encoding=None):
|
| 539 |
+
assert not os.path.isdir(outfile)
|
| 540 |
+
self.ensure_dir(os.path.dirname(outfile))
|
| 541 |
+
logger.info('Copying stream %s to %s', instream, outfile)
|
| 542 |
+
if not self.dry_run:
|
| 543 |
+
if encoding is None:
|
| 544 |
+
outstream = open(outfile, 'wb')
|
| 545 |
+
else:
|
| 546 |
+
outstream = codecs.open(outfile, 'w', encoding=encoding)
|
| 547 |
+
try:
|
| 548 |
+
shutil.copyfileobj(instream, outstream)
|
| 549 |
+
finally:
|
| 550 |
+
outstream.close()
|
| 551 |
+
self.record_as_written(outfile)
|
| 552 |
+
|
| 553 |
+
def write_binary_file(self, path, data):
|
| 554 |
+
self.ensure_dir(os.path.dirname(path))
|
| 555 |
+
if not self.dry_run:
|
| 556 |
+
if os.path.exists(path):
|
| 557 |
+
os.remove(path)
|
| 558 |
+
with open(path, 'wb') as f:
|
| 559 |
+
f.write(data)
|
| 560 |
+
self.record_as_written(path)
|
| 561 |
+
|
| 562 |
+
def write_text_file(self, path, data, encoding):
|
| 563 |
+
self.write_binary_file(path, data.encode(encoding))
|
| 564 |
+
|
| 565 |
+
def set_mode(self, bits, mask, files):
|
| 566 |
+
if os.name == 'posix' or (os.name == 'java' and os._name == 'posix'):
|
| 567 |
+
# Set the executable bits (owner, group, and world) on
|
| 568 |
+
# all the files specified.
|
| 569 |
+
for f in files:
|
| 570 |
+
if self.dry_run:
|
| 571 |
+
logger.info("changing mode of %s", f)
|
| 572 |
+
else:
|
| 573 |
+
mode = (os.stat(f).st_mode | bits) & mask
|
| 574 |
+
logger.info("changing mode of %s to %o", f, mode)
|
| 575 |
+
os.chmod(f, mode)
|
| 576 |
+
|
| 577 |
+
set_executable_mode = lambda s, f: s.set_mode(0o555, 0o7777, f)
|
| 578 |
+
|
| 579 |
+
def ensure_dir(self, path):
|
| 580 |
+
path = os.path.abspath(path)
|
| 581 |
+
if path not in self.ensured and not os.path.exists(path):
|
| 582 |
+
self.ensured.add(path)
|
| 583 |
+
d, f = os.path.split(path)
|
| 584 |
+
self.ensure_dir(d)
|
| 585 |
+
logger.info('Creating %s' % path)
|
| 586 |
+
if not self.dry_run:
|
| 587 |
+
os.mkdir(path)
|
| 588 |
+
if self.record:
|
| 589 |
+
self.dirs_created.add(path)
|
| 590 |
+
|
| 591 |
+
def byte_compile(self, path, optimize=False, force=False, prefix=None, hashed_invalidation=False):
|
| 592 |
+
dpath = cache_from_source(path, not optimize)
|
| 593 |
+
logger.info('Byte-compiling %s to %s', path, dpath)
|
| 594 |
+
if not self.dry_run:
|
| 595 |
+
if force or self.newer(path, dpath):
|
| 596 |
+
if not prefix:
|
| 597 |
+
diagpath = None
|
| 598 |
+
else:
|
| 599 |
+
assert path.startswith(prefix)
|
| 600 |
+
diagpath = path[len(prefix):]
|
| 601 |
+
compile_kwargs = {}
|
| 602 |
+
if hashed_invalidation and hasattr(py_compile, 'PycInvalidationMode'):
|
| 603 |
+
if not isinstance(hashed_invalidation, py_compile.PycInvalidationMode):
|
| 604 |
+
hashed_invalidation = py_compile.PycInvalidationMode.CHECKED_HASH
|
| 605 |
+
compile_kwargs['invalidation_mode'] = hashed_invalidation
|
| 606 |
+
py_compile.compile(path, dpath, diagpath, True, **compile_kwargs) # raise error
|
| 607 |
+
self.record_as_written(dpath)
|
| 608 |
+
return dpath
|
| 609 |
+
|
| 610 |
+
def ensure_removed(self, path):
|
| 611 |
+
if os.path.exists(path):
|
| 612 |
+
if os.path.isdir(path) and not os.path.islink(path):
|
| 613 |
+
logger.debug('Removing directory tree at %s', path)
|
| 614 |
+
if not self.dry_run:
|
| 615 |
+
shutil.rmtree(path)
|
| 616 |
+
if self.record:
|
| 617 |
+
if path in self.dirs_created:
|
| 618 |
+
self.dirs_created.remove(path)
|
| 619 |
+
else:
|
| 620 |
+
if os.path.islink(path):
|
| 621 |
+
s = 'link'
|
| 622 |
+
else:
|
| 623 |
+
s = 'file'
|
| 624 |
+
logger.debug('Removing %s %s', s, path)
|
| 625 |
+
if not self.dry_run:
|
| 626 |
+
os.remove(path)
|
| 627 |
+
if self.record:
|
| 628 |
+
if path in self.files_written:
|
| 629 |
+
self.files_written.remove(path)
|
| 630 |
+
|
| 631 |
+
def is_writable(self, path):
|
| 632 |
+
result = False
|
| 633 |
+
while not result:
|
| 634 |
+
if os.path.exists(path):
|
| 635 |
+
result = os.access(path, os.W_OK)
|
| 636 |
+
break
|
| 637 |
+
parent = os.path.dirname(path)
|
| 638 |
+
if parent == path:
|
| 639 |
+
break
|
| 640 |
+
path = parent
|
| 641 |
+
return result
|
| 642 |
+
|
| 643 |
+
def commit(self):
|
| 644 |
+
"""
|
| 645 |
+
Commit recorded changes, turn off recording, return
|
| 646 |
+
changes.
|
| 647 |
+
"""
|
| 648 |
+
assert self.record
|
| 649 |
+
result = self.files_written, self.dirs_created
|
| 650 |
+
self._init_record()
|
| 651 |
+
return result
|
| 652 |
+
|
| 653 |
+
def rollback(self):
|
| 654 |
+
if not self.dry_run:
|
| 655 |
+
for f in list(self.files_written):
|
| 656 |
+
if os.path.exists(f):
|
| 657 |
+
os.remove(f)
|
| 658 |
+
# dirs should all be empty now, except perhaps for
|
| 659 |
+
# __pycache__ subdirs
|
| 660 |
+
# reverse so that subdirs appear before their parents
|
| 661 |
+
dirs = sorted(self.dirs_created, reverse=True)
|
| 662 |
+
for d in dirs:
|
| 663 |
+
flist = os.listdir(d)
|
| 664 |
+
if flist:
|
| 665 |
+
assert flist == ['__pycache__']
|
| 666 |
+
sd = os.path.join(d, flist[0])
|
| 667 |
+
os.rmdir(sd)
|
| 668 |
+
os.rmdir(d) # should fail if non-empty
|
| 669 |
+
self._init_record()
|
| 670 |
+
|
| 671 |
+
|
| 672 |
+
def resolve(module_name, dotted_path):
|
| 673 |
+
if module_name in sys.modules:
|
| 674 |
+
mod = sys.modules[module_name]
|
| 675 |
+
else:
|
| 676 |
+
mod = __import__(module_name)
|
| 677 |
+
if dotted_path is None:
|
| 678 |
+
result = mod
|
| 679 |
+
else:
|
| 680 |
+
parts = dotted_path.split('.')
|
| 681 |
+
result = getattr(mod, parts.pop(0))
|
| 682 |
+
for p in parts:
|
| 683 |
+
result = getattr(result, p)
|
| 684 |
+
return result
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
class ExportEntry(object):
|
| 688 |
+
|
| 689 |
+
def __init__(self, name, prefix, suffix, flags):
|
| 690 |
+
self.name = name
|
| 691 |
+
self.prefix = prefix
|
| 692 |
+
self.suffix = suffix
|
| 693 |
+
self.flags = flags
|
| 694 |
+
|
| 695 |
+
@cached_property
|
| 696 |
+
def value(self):
|
| 697 |
+
return resolve(self.prefix, self.suffix)
|
| 698 |
+
|
| 699 |
+
def __repr__(self): # pragma: no cover
|
| 700 |
+
return '<ExportEntry %s = %s:%s %s>' % (self.name, self.prefix, self.suffix, self.flags)
|
| 701 |
+
|
| 702 |
+
def __eq__(self, other):
|
| 703 |
+
if not isinstance(other, ExportEntry):
|
| 704 |
+
result = False
|
| 705 |
+
else:
|
| 706 |
+
result = (self.name == other.name and self.prefix == other.prefix and self.suffix == other.suffix and
|
| 707 |
+
self.flags == other.flags)
|
| 708 |
+
return result
|
| 709 |
+
|
| 710 |
+
__hash__ = object.__hash__
|
| 711 |
+
|
| 712 |
+
|
| 713 |
+
ENTRY_RE = re.compile(
|
| 714 |
+
r'''(?P<name>([^\[]\S*))
|
| 715 |
+
\s*=\s*(?P<callable>(\w+)([:\.]\w+)*)
|
| 716 |
+
\s*(\[\s*(?P<flags>[\w-]+(=\w+)?(,\s*\w+(=\w+)?)*)\s*\])?
|
| 717 |
+
''', re.VERBOSE)
|
| 718 |
+
|
| 719 |
+
|
| 720 |
+
def get_export_entry(specification):
|
| 721 |
+
m = ENTRY_RE.search(specification)
|
| 722 |
+
if not m:
|
| 723 |
+
result = None
|
| 724 |
+
if '[' in specification or ']' in specification:
|
| 725 |
+
raise DistlibException("Invalid specification "
|
| 726 |
+
"'%s'" % specification)
|
| 727 |
+
else:
|
| 728 |
+
d = m.groupdict()
|
| 729 |
+
name = d['name']
|
| 730 |
+
path = d['callable']
|
| 731 |
+
colons = path.count(':')
|
| 732 |
+
if colons == 0:
|
| 733 |
+
prefix, suffix = path, None
|
| 734 |
+
else:
|
| 735 |
+
if colons != 1:
|
| 736 |
+
raise DistlibException("Invalid specification "
|
| 737 |
+
"'%s'" % specification)
|
| 738 |
+
prefix, suffix = path.split(':')
|
| 739 |
+
flags = d['flags']
|
| 740 |
+
if flags is None:
|
| 741 |
+
if '[' in specification or ']' in specification:
|
| 742 |
+
raise DistlibException("Invalid specification "
|
| 743 |
+
"'%s'" % specification)
|
| 744 |
+
flags = []
|
| 745 |
+
else:
|
| 746 |
+
flags = [f.strip() for f in flags.split(',')]
|
| 747 |
+
result = ExportEntry(name, prefix, suffix, flags)
|
| 748 |
+
return result
|
| 749 |
+
|
| 750 |
+
|
| 751 |
+
def get_cache_base(suffix=None):
|
| 752 |
+
"""
|
| 753 |
+
Return the default base location for distlib caches. If the directory does
|
| 754 |
+
not exist, it is created. Use the suffix provided for the base directory,
|
| 755 |
+
and default to '.distlib' if it isn't provided.
|
| 756 |
+
|
| 757 |
+
On Windows, if LOCALAPPDATA is defined in the environment, then it is
|
| 758 |
+
assumed to be a directory, and will be the parent directory of the result.
|
| 759 |
+
On POSIX, and on Windows if LOCALAPPDATA is not defined, the user's home
|
| 760 |
+
directory - using os.expanduser('~') - will be the parent directory of
|
| 761 |
+
the result.
|
| 762 |
+
|
| 763 |
+
The result is just the directory '.distlib' in the parent directory as
|
| 764 |
+
determined above, or with the name specified with ``suffix``.
|
| 765 |
+
"""
|
| 766 |
+
if suffix is None:
|
| 767 |
+
suffix = '.distlib'
|
| 768 |
+
if os.name == 'nt' and 'LOCALAPPDATA' in os.environ:
|
| 769 |
+
result = os.path.expandvars('$localappdata')
|
| 770 |
+
else:
|
| 771 |
+
# Assume posix, or old Windows
|
| 772 |
+
result = os.path.expanduser('~')
|
| 773 |
+
# we use 'isdir' instead of 'exists', because we want to
|
| 774 |
+
# fail if there's a file with that name
|
| 775 |
+
if os.path.isdir(result):
|
| 776 |
+
usable = os.access(result, os.W_OK)
|
| 777 |
+
if not usable:
|
| 778 |
+
logger.warning('Directory exists but is not writable: %s', result)
|
| 779 |
+
else:
|
| 780 |
+
try:
|
| 781 |
+
os.makedirs(result)
|
| 782 |
+
usable = True
|
| 783 |
+
except OSError:
|
| 784 |
+
logger.warning('Unable to create %s', result, exc_info=True)
|
| 785 |
+
usable = False
|
| 786 |
+
if not usable:
|
| 787 |
+
result = tempfile.mkdtemp()
|
| 788 |
+
logger.warning('Default location unusable, using %s', result)
|
| 789 |
+
return os.path.join(result, suffix)
|
| 790 |
+
|
| 791 |
+
|
| 792 |
+
def path_to_cache_dir(path, use_abspath=True):
|
| 793 |
+
"""
|
| 794 |
+
Convert an absolute path to a directory name for use in a cache.
|
| 795 |
+
|
| 796 |
+
The algorithm used is:
|
| 797 |
+
|
| 798 |
+
#. On Windows, any ``':'`` in the drive is replaced with ``'---'``.
|
| 799 |
+
#. Any occurrence of ``os.sep`` is replaced with ``'--'``.
|
| 800 |
+
#. ``'.cache'`` is appended.
|
| 801 |
+
"""
|
| 802 |
+
d, p = os.path.splitdrive(os.path.abspath(path) if use_abspath else path)
|
| 803 |
+
if d:
|
| 804 |
+
d = d.replace(':', '---')
|
| 805 |
+
p = p.replace(os.sep, '--')
|
| 806 |
+
return d + p + '.cache'
|
| 807 |
+
|
| 808 |
+
|
| 809 |
+
def ensure_slash(s):
|
| 810 |
+
if not s.endswith('/'):
|
| 811 |
+
return s + '/'
|
| 812 |
+
return s
|
| 813 |
+
|
| 814 |
+
|
| 815 |
+
def parse_credentials(netloc):
|
| 816 |
+
username = password = None
|
| 817 |
+
if '@' in netloc:
|
| 818 |
+
prefix, netloc = netloc.rsplit('@', 1)
|
| 819 |
+
if ':' not in prefix:
|
| 820 |
+
username = prefix
|
| 821 |
+
else:
|
| 822 |
+
username, password = prefix.split(':', 1)
|
| 823 |
+
if username:
|
| 824 |
+
username = unquote(username)
|
| 825 |
+
if password:
|
| 826 |
+
password = unquote(password)
|
| 827 |
+
return username, password, netloc
|
| 828 |
+
|
| 829 |
+
|
| 830 |
+
def get_process_umask():
|
| 831 |
+
result = os.umask(0o22)
|
| 832 |
+
os.umask(result)
|
| 833 |
+
return result
|
| 834 |
+
|
| 835 |
+
|
| 836 |
+
def is_string_sequence(seq):
|
| 837 |
+
result = True
|
| 838 |
+
i = None
|
| 839 |
+
for i, s in enumerate(seq):
|
| 840 |
+
if not isinstance(s, string_types):
|
| 841 |
+
result = False
|
| 842 |
+
break
|
| 843 |
+
assert i is not None
|
| 844 |
+
return result
|
| 845 |
+
|
| 846 |
+
|
| 847 |
+
PROJECT_NAME_AND_VERSION = re.compile('([a-z0-9_]+([.-][a-z_][a-z0-9_]*)*)-'
|
| 848 |
+
'([a-z0-9_.+-]+)', re.I)
|
| 849 |
+
PYTHON_VERSION = re.compile(r'-py(\d\.?\d?)')
|
| 850 |
+
|
| 851 |
+
|
| 852 |
+
def split_filename(filename, project_name=None):
|
| 853 |
+
"""
|
| 854 |
+
Extract name, version, python version from a filename (no extension)
|
| 855 |
+
|
| 856 |
+
Return name, version, pyver or None
|
| 857 |
+
"""
|
| 858 |
+
result = None
|
| 859 |
+
pyver = None
|
| 860 |
+
filename = unquote(filename).replace(' ', '-')
|
| 861 |
+
m = PYTHON_VERSION.search(filename)
|
| 862 |
+
if m:
|
| 863 |
+
pyver = m.group(1)
|
| 864 |
+
filename = filename[:m.start()]
|
| 865 |
+
if project_name and len(filename) > len(project_name) + 1:
|
| 866 |
+
m = re.match(re.escape(project_name) + r'\b', filename)
|
| 867 |
+
if m:
|
| 868 |
+
n = m.end()
|
| 869 |
+
result = filename[:n], filename[n + 1:], pyver
|
| 870 |
+
if result is None:
|
| 871 |
+
m = PROJECT_NAME_AND_VERSION.match(filename)
|
| 872 |
+
if m:
|
| 873 |
+
result = m.group(1), m.group(3), pyver
|
| 874 |
+
return result
|
| 875 |
+
|
| 876 |
+
|
| 877 |
+
# Allow spaces in name because of legacy dists like "Twisted Core"
|
| 878 |
+
NAME_VERSION_RE = re.compile(r'(?P<name>[\w .-]+)\s*'
|
| 879 |
+
r'\(\s*(?P<ver>[^\s)]+)\)$')
|
| 880 |
+
|
| 881 |
+
|
| 882 |
+
def parse_name_and_version(p):
|
| 883 |
+
"""
|
| 884 |
+
A utility method used to get name and version from a string.
|
| 885 |
+
|
| 886 |
+
From e.g. a Provides-Dist value.
|
| 887 |
+
|
| 888 |
+
:param p: A value in a form 'foo (1.0)'
|
| 889 |
+
:return: The name and version as a tuple.
|
| 890 |
+
"""
|
| 891 |
+
m = NAME_VERSION_RE.match(p)
|
| 892 |
+
if not m:
|
| 893 |
+
raise DistlibException('Ill-formed name/version string: \'%s\'' % p)
|
| 894 |
+
d = m.groupdict()
|
| 895 |
+
return d['name'].strip().lower(), d['ver']
|
| 896 |
+
|
| 897 |
+
|
| 898 |
+
def get_extras(requested, available):
|
| 899 |
+
result = set()
|
| 900 |
+
requested = set(requested or [])
|
| 901 |
+
available = set(available or [])
|
| 902 |
+
if '*' in requested:
|
| 903 |
+
requested.remove('*')
|
| 904 |
+
result |= available
|
| 905 |
+
for r in requested:
|
| 906 |
+
if r == '-':
|
| 907 |
+
result.add(r)
|
| 908 |
+
elif r.startswith('-'):
|
| 909 |
+
unwanted = r[1:]
|
| 910 |
+
if unwanted not in available:
|
| 911 |
+
logger.warning('undeclared extra: %s' % unwanted)
|
| 912 |
+
if unwanted in result:
|
| 913 |
+
result.remove(unwanted)
|
| 914 |
+
else:
|
| 915 |
+
if r not in available:
|
| 916 |
+
logger.warning('undeclared extra: %s' % r)
|
| 917 |
+
result.add(r)
|
| 918 |
+
return result
|
| 919 |
+
|
| 920 |
+
|
| 921 |
+
#
|
| 922 |
+
# Extended metadata functionality
|
| 923 |
+
#
|
| 924 |
+
|
| 925 |
+
|
| 926 |
+
def _get_external_data(url):
|
| 927 |
+
result = {}
|
| 928 |
+
try:
|
| 929 |
+
# urlopen might fail if it runs into redirections,
|
| 930 |
+
# because of Python issue #13696. Fixed in locators
|
| 931 |
+
# using a custom redirect handler.
|
| 932 |
+
resp = urlopen(url)
|
| 933 |
+
headers = resp.info()
|
| 934 |
+
ct = headers.get('Content-Type')
|
| 935 |
+
if not ct.startswith('application/json'):
|
| 936 |
+
logger.debug('Unexpected response for JSON request: %s', ct)
|
| 937 |
+
else:
|
| 938 |
+
reader = codecs.getreader('utf-8')(resp)
|
| 939 |
+
# data = reader.read().decode('utf-8')
|
| 940 |
+
# result = json.loads(data)
|
| 941 |
+
result = json.load(reader)
|
| 942 |
+
except Exception as e:
|
| 943 |
+
logger.exception('Failed to get external data for %s: %s', url, e)
|
| 944 |
+
return result
|
| 945 |
+
|
| 946 |
+
|
| 947 |
+
_external_data_base_url = 'https://www.red-dove.com/pypi/projects/'
|
| 948 |
+
|
| 949 |
+
|
| 950 |
+
def get_project_data(name):
|
| 951 |
+
url = '%s/%s/project.json' % (name[0].upper(), name)
|
| 952 |
+
url = urljoin(_external_data_base_url, url)
|
| 953 |
+
result = _get_external_data(url)
|
| 954 |
+
return result
|
| 955 |
+
|
| 956 |
+
|
| 957 |
+
def get_package_data(name, version):
|
| 958 |
+
url = '%s/%s/package-%s.json' % (name[0].upper(), name, version)
|
| 959 |
+
url = urljoin(_external_data_base_url, url)
|
| 960 |
+
return _get_external_data(url)
|
| 961 |
+
|
| 962 |
+
|
| 963 |
+
class Cache(object):
|
| 964 |
+
"""
|
| 965 |
+
A class implementing a cache for resources that need to live in the file system
|
| 966 |
+
e.g. shared libraries. This class was moved from resources to here because it
|
| 967 |
+
could be used by other modules, e.g. the wheel module.
|
| 968 |
+
"""
|
| 969 |
+
|
| 970 |
+
def __init__(self, base):
|
| 971 |
+
"""
|
| 972 |
+
Initialise an instance.
|
| 973 |
+
|
| 974 |
+
:param base: The base directory where the cache should be located.
|
| 975 |
+
"""
|
| 976 |
+
# we use 'isdir' instead of 'exists', because we want to
|
| 977 |
+
# fail if there's a file with that name
|
| 978 |
+
if not os.path.isdir(base): # pragma: no cover
|
| 979 |
+
os.makedirs(base)
|
| 980 |
+
if (os.stat(base).st_mode & 0o77) != 0:
|
| 981 |
+
logger.warning('Directory \'%s\' is not private', base)
|
| 982 |
+
self.base = os.path.abspath(os.path.normpath(base))
|
| 983 |
+
|
| 984 |
+
def prefix_to_dir(self, prefix, use_abspath=True):
|
| 985 |
+
"""
|
| 986 |
+
Converts a resource prefix to a directory name in the cache.
|
| 987 |
+
"""
|
| 988 |
+
return path_to_cache_dir(prefix, use_abspath=use_abspath)
|
| 989 |
+
|
| 990 |
+
def clear(self):
|
| 991 |
+
"""
|
| 992 |
+
Clear the cache.
|
| 993 |
+
"""
|
| 994 |
+
not_removed = []
|
| 995 |
+
for fn in os.listdir(self.base):
|
| 996 |
+
fn = os.path.join(self.base, fn)
|
| 997 |
+
try:
|
| 998 |
+
if os.path.islink(fn) or os.path.isfile(fn):
|
| 999 |
+
os.remove(fn)
|
| 1000 |
+
elif os.path.isdir(fn):
|
| 1001 |
+
shutil.rmtree(fn)
|
| 1002 |
+
except Exception:
|
| 1003 |
+
not_removed.append(fn)
|
| 1004 |
+
return not_removed
|
| 1005 |
+
|
| 1006 |
+
|
| 1007 |
+
class EventMixin(object):
|
| 1008 |
+
"""
|
| 1009 |
+
A very simple publish/subscribe system.
|
| 1010 |
+
"""
|
| 1011 |
+
|
| 1012 |
+
def __init__(self):
|
| 1013 |
+
self._subscribers = {}
|
| 1014 |
+
|
| 1015 |
+
def add(self, event, subscriber, append=True):
|
| 1016 |
+
"""
|
| 1017 |
+
Add a subscriber for an event.
|
| 1018 |
+
|
| 1019 |
+
:param event: The name of an event.
|
| 1020 |
+
:param subscriber: The subscriber to be added (and called when the
|
| 1021 |
+
event is published).
|
| 1022 |
+
:param append: Whether to append or prepend the subscriber to an
|
| 1023 |
+
existing subscriber list for the event.
|
| 1024 |
+
"""
|
| 1025 |
+
subs = self._subscribers
|
| 1026 |
+
if event not in subs:
|
| 1027 |
+
subs[event] = deque([subscriber])
|
| 1028 |
+
else:
|
| 1029 |
+
sq = subs[event]
|
| 1030 |
+
if append:
|
| 1031 |
+
sq.append(subscriber)
|
| 1032 |
+
else:
|
| 1033 |
+
sq.appendleft(subscriber)
|
| 1034 |
+
|
| 1035 |
+
def remove(self, event, subscriber):
|
| 1036 |
+
"""
|
| 1037 |
+
Remove a subscriber for an event.
|
| 1038 |
+
|
| 1039 |
+
:param event: The name of an event.
|
| 1040 |
+
:param subscriber: The subscriber to be removed.
|
| 1041 |
+
"""
|
| 1042 |
+
subs = self._subscribers
|
| 1043 |
+
if event not in subs:
|
| 1044 |
+
raise ValueError('No subscribers: %r' % event)
|
| 1045 |
+
subs[event].remove(subscriber)
|
| 1046 |
+
|
| 1047 |
+
def get_subscribers(self, event):
|
| 1048 |
+
"""
|
| 1049 |
+
Return an iterator for the subscribers for an event.
|
| 1050 |
+
:param event: The event to return subscribers for.
|
| 1051 |
+
"""
|
| 1052 |
+
return iter(self._subscribers.get(event, ()))
|
| 1053 |
+
|
| 1054 |
+
def publish(self, event, *args, **kwargs):
|
| 1055 |
+
"""
|
| 1056 |
+
Publish a event and return a list of values returned by its
|
| 1057 |
+
subscribers.
|
| 1058 |
+
|
| 1059 |
+
:param event: The event to publish.
|
| 1060 |
+
:param args: The positional arguments to pass to the event's
|
| 1061 |
+
subscribers.
|
| 1062 |
+
:param kwargs: The keyword arguments to pass to the event's
|
| 1063 |
+
subscribers.
|
| 1064 |
+
"""
|
| 1065 |
+
result = []
|
| 1066 |
+
for subscriber in self.get_subscribers(event):
|
| 1067 |
+
try:
|
| 1068 |
+
value = subscriber(event, *args, **kwargs)
|
| 1069 |
+
except Exception:
|
| 1070 |
+
logger.exception('Exception during event publication')
|
| 1071 |
+
value = None
|
| 1072 |
+
result.append(value)
|
| 1073 |
+
logger.debug('publish %s: args = %s, kwargs = %s, result = %s', event, args, kwargs, result)
|
| 1074 |
+
return result
|
| 1075 |
+
|
| 1076 |
+
|
| 1077 |
+
#
|
| 1078 |
+
# Simple sequencing
|
| 1079 |
+
#
|
| 1080 |
+
class Sequencer(object):
|
| 1081 |
+
|
| 1082 |
+
def __init__(self):
|
| 1083 |
+
self._preds = {}
|
| 1084 |
+
self._succs = {}
|
| 1085 |
+
self._nodes = set() # nodes with no preds/succs
|
| 1086 |
+
|
| 1087 |
+
def add_node(self, node):
|
| 1088 |
+
self._nodes.add(node)
|
| 1089 |
+
|
| 1090 |
+
def remove_node(self, node, edges=False):
|
| 1091 |
+
if node in self._nodes:
|
| 1092 |
+
self._nodes.remove(node)
|
| 1093 |
+
if edges:
|
| 1094 |
+
for p in set(self._preds.get(node, ())):
|
| 1095 |
+
self.remove(p, node)
|
| 1096 |
+
for s in set(self._succs.get(node, ())):
|
| 1097 |
+
self.remove(node, s)
|
| 1098 |
+
# Remove empties
|
| 1099 |
+
for k, v in list(self._preds.items()):
|
| 1100 |
+
if not v:
|
| 1101 |
+
del self._preds[k]
|
| 1102 |
+
for k, v in list(self._succs.items()):
|
| 1103 |
+
if not v:
|
| 1104 |
+
del self._succs[k]
|
| 1105 |
+
|
| 1106 |
+
def add(self, pred, succ):
|
| 1107 |
+
assert pred != succ
|
| 1108 |
+
self._preds.setdefault(succ, set()).add(pred)
|
| 1109 |
+
self._succs.setdefault(pred, set()).add(succ)
|
| 1110 |
+
|
| 1111 |
+
def remove(self, pred, succ):
|
| 1112 |
+
assert pred != succ
|
| 1113 |
+
try:
|
| 1114 |
+
preds = self._preds[succ]
|
| 1115 |
+
succs = self._succs[pred]
|
| 1116 |
+
except KeyError: # pragma: no cover
|
| 1117 |
+
raise ValueError('%r not a successor of anything' % succ)
|
| 1118 |
+
try:
|
| 1119 |
+
preds.remove(pred)
|
| 1120 |
+
succs.remove(succ)
|
| 1121 |
+
except KeyError: # pragma: no cover
|
| 1122 |
+
raise ValueError('%r not a successor of %r' % (succ, pred))
|
| 1123 |
+
|
| 1124 |
+
def is_step(self, step):
|
| 1125 |
+
return (step in self._preds or step in self._succs or step in self._nodes)
|
| 1126 |
+
|
| 1127 |
+
def get_steps(self, final):
|
| 1128 |
+
if not self.is_step(final):
|
| 1129 |
+
raise ValueError('Unknown: %r' % final)
|
| 1130 |
+
result = []
|
| 1131 |
+
todo = []
|
| 1132 |
+
seen = set()
|
| 1133 |
+
todo.append(final)
|
| 1134 |
+
while todo:
|
| 1135 |
+
step = todo.pop(0)
|
| 1136 |
+
if step in seen:
|
| 1137 |
+
# if a step was already seen,
|
| 1138 |
+
# move it to the end (so it will appear earlier
|
| 1139 |
+
# when reversed on return) ... but not for the
|
| 1140 |
+
# final step, as that would be confusing for
|
| 1141 |
+
# users
|
| 1142 |
+
if step != final:
|
| 1143 |
+
result.remove(step)
|
| 1144 |
+
result.append(step)
|
| 1145 |
+
else:
|
| 1146 |
+
seen.add(step)
|
| 1147 |
+
result.append(step)
|
| 1148 |
+
preds = self._preds.get(step, ())
|
| 1149 |
+
todo.extend(preds)
|
| 1150 |
+
return reversed(result)
|
| 1151 |
+
|
| 1152 |
+
@property
|
| 1153 |
+
def strong_connections(self):
|
| 1154 |
+
# http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
|
| 1155 |
+
index_counter = [0]
|
| 1156 |
+
stack = []
|
| 1157 |
+
lowlinks = {}
|
| 1158 |
+
index = {}
|
| 1159 |
+
result = []
|
| 1160 |
+
|
| 1161 |
+
graph = self._succs
|
| 1162 |
+
|
| 1163 |
+
def strongconnect(node):
|
| 1164 |
+
# set the depth index for this node to the smallest unused index
|
| 1165 |
+
index[node] = index_counter[0]
|
| 1166 |
+
lowlinks[node] = index_counter[0]
|
| 1167 |
+
index_counter[0] += 1
|
| 1168 |
+
stack.append(node)
|
| 1169 |
+
|
| 1170 |
+
# Consider successors
|
| 1171 |
+
try:
|
| 1172 |
+
successors = graph[node]
|
| 1173 |
+
except Exception:
|
| 1174 |
+
successors = []
|
| 1175 |
+
for successor in successors:
|
| 1176 |
+
if successor not in lowlinks:
|
| 1177 |
+
# Successor has not yet been visited
|
| 1178 |
+
strongconnect(successor)
|
| 1179 |
+
lowlinks[node] = min(lowlinks[node], lowlinks[successor])
|
| 1180 |
+
elif successor in stack:
|
| 1181 |
+
# the successor is in the stack and hence in the current
|
| 1182 |
+
# strongly connected component (SCC)
|
| 1183 |
+
lowlinks[node] = min(lowlinks[node], index[successor])
|
| 1184 |
+
|
| 1185 |
+
# If `node` is a root node, pop the stack and generate an SCC
|
| 1186 |
+
if lowlinks[node] == index[node]:
|
| 1187 |
+
connected_component = []
|
| 1188 |
+
|
| 1189 |
+
while True:
|
| 1190 |
+
successor = stack.pop()
|
| 1191 |
+
connected_component.append(successor)
|
| 1192 |
+
if successor == node:
|
| 1193 |
+
break
|
| 1194 |
+
component = tuple(connected_component)
|
| 1195 |
+
# storing the result
|
| 1196 |
+
result.append(component)
|
| 1197 |
+
|
| 1198 |
+
for node in graph:
|
| 1199 |
+
if node not in lowlinks:
|
| 1200 |
+
strongconnect(node)
|
| 1201 |
+
|
| 1202 |
+
return result
|
| 1203 |
+
|
| 1204 |
+
@property
|
| 1205 |
+
def dot(self):
|
| 1206 |
+
result = ['digraph G {']
|
| 1207 |
+
for succ in self._preds:
|
| 1208 |
+
preds = self._preds[succ]
|
| 1209 |
+
for pred in preds:
|
| 1210 |
+
result.append(' %s -> %s;' % (pred, succ))
|
| 1211 |
+
for node in self._nodes:
|
| 1212 |
+
result.append(' %s;' % node)
|
| 1213 |
+
result.append('}')
|
| 1214 |
+
return '\n'.join(result)
|
| 1215 |
+
|
| 1216 |
+
|
| 1217 |
+
#
|
| 1218 |
+
# Unarchiving functionality for zip, tar, tgz, tbz, whl
|
| 1219 |
+
#
|
| 1220 |
+
|
| 1221 |
+
ARCHIVE_EXTENSIONS = ('.tar.gz', '.tar.bz2', '.tar', '.zip', '.tgz', '.tbz', '.whl')
|
| 1222 |
+
|
| 1223 |
+
|
| 1224 |
+
def unarchive(archive_filename, dest_dir, format=None, check=True):
|
| 1225 |
+
|
| 1226 |
+
def check_path(path):
|
| 1227 |
+
if not isinstance(path, text_type):
|
| 1228 |
+
path = path.decode('utf-8')
|
| 1229 |
+
p = os.path.abspath(os.path.join(dest_dir, path))
|
| 1230 |
+
if not p.startswith(dest_dir) or p[plen] != os.sep:
|
| 1231 |
+
raise ValueError('path outside destination: %r' % p)
|
| 1232 |
+
|
| 1233 |
+
dest_dir = os.path.abspath(dest_dir)
|
| 1234 |
+
plen = len(dest_dir)
|
| 1235 |
+
archive = None
|
| 1236 |
+
if format is None:
|
| 1237 |
+
if archive_filename.endswith(('.zip', '.whl')):
|
| 1238 |
+
format = 'zip'
|
| 1239 |
+
elif archive_filename.endswith(('.tar.gz', '.tgz')):
|
| 1240 |
+
format = 'tgz'
|
| 1241 |
+
mode = 'r:gz'
|
| 1242 |
+
elif archive_filename.endswith(('.tar.bz2', '.tbz')):
|
| 1243 |
+
format = 'tbz'
|
| 1244 |
+
mode = 'r:bz2'
|
| 1245 |
+
elif archive_filename.endswith('.tar'):
|
| 1246 |
+
format = 'tar'
|
| 1247 |
+
mode = 'r'
|
| 1248 |
+
else: # pragma: no cover
|
| 1249 |
+
raise ValueError('Unknown format for %r' % archive_filename)
|
| 1250 |
+
try:
|
| 1251 |
+
if format == 'zip':
|
| 1252 |
+
archive = ZipFile(archive_filename, 'r')
|
| 1253 |
+
if check:
|
| 1254 |
+
names = archive.namelist()
|
| 1255 |
+
for name in names:
|
| 1256 |
+
check_path(name)
|
| 1257 |
+
else:
|
| 1258 |
+
archive = tarfile.open(archive_filename, mode)
|
| 1259 |
+
if check:
|
| 1260 |
+
names = archive.getnames()
|
| 1261 |
+
for name in names:
|
| 1262 |
+
check_path(name)
|
| 1263 |
+
if format != 'zip' and sys.version_info[0] < 3:
|
| 1264 |
+
# See Python issue 17153. If the dest path contains Unicode,
|
| 1265 |
+
# tarfile extraction fails on Python 2.x if a member path name
|
| 1266 |
+
# contains non-ASCII characters - it leads to an implicit
|
| 1267 |
+
# bytes -> unicode conversion using ASCII to decode.
|
| 1268 |
+
for tarinfo in archive.getmembers():
|
| 1269 |
+
if not isinstance(tarinfo.name, text_type):
|
| 1270 |
+
tarinfo.name = tarinfo.name.decode('utf-8')
|
| 1271 |
+
|
| 1272 |
+
# Limit extraction of dangerous items, if this Python
|
| 1273 |
+
# allows it easily. If not, just trust the input.
|
| 1274 |
+
# See: https://docs.python.org/3/library/tarfile.html#extraction-filters
|
| 1275 |
+
def extraction_filter(member, path):
|
| 1276 |
+
"""Run tarfile.tar_filter, but raise the expected ValueError"""
|
| 1277 |
+
# This is only called if the current Python has tarfile filters
|
| 1278 |
+
try:
|
| 1279 |
+
return tarfile.tar_filter(member, path)
|
| 1280 |
+
except tarfile.FilterError as exc:
|
| 1281 |
+
raise ValueError(str(exc))
|
| 1282 |
+
|
| 1283 |
+
archive.extraction_filter = extraction_filter
|
| 1284 |
+
|
| 1285 |
+
archive.extractall(dest_dir)
|
| 1286 |
+
|
| 1287 |
+
finally:
|
| 1288 |
+
if archive:
|
| 1289 |
+
archive.close()
|
| 1290 |
+
|
| 1291 |
+
|
| 1292 |
+
def zip_dir(directory):
|
| 1293 |
+
"""zip a directory tree into a BytesIO object"""
|
| 1294 |
+
result = io.BytesIO()
|
| 1295 |
+
dlen = len(directory)
|
| 1296 |
+
with ZipFile(result, "w") as zf:
|
| 1297 |
+
for root, dirs, files in os.walk(directory):
|
| 1298 |
+
for name in files:
|
| 1299 |
+
full = os.path.join(root, name)
|
| 1300 |
+
rel = root[dlen:]
|
| 1301 |
+
dest = os.path.join(rel, name)
|
| 1302 |
+
zf.write(full, dest)
|
| 1303 |
+
return result
|
| 1304 |
+
|
| 1305 |
+
|
| 1306 |
+
#
|
| 1307 |
+
# Simple progress bar
|
| 1308 |
+
#
|
| 1309 |
+
|
| 1310 |
+
UNITS = ('', 'K', 'M', 'G', 'T', 'P')
|
| 1311 |
+
|
| 1312 |
+
|
| 1313 |
+
class Progress(object):
|
| 1314 |
+
unknown = 'UNKNOWN'
|
| 1315 |
+
|
| 1316 |
+
def __init__(self, minval=0, maxval=100):
|
| 1317 |
+
assert maxval is None or maxval >= minval
|
| 1318 |
+
self.min = self.cur = minval
|
| 1319 |
+
self.max = maxval
|
| 1320 |
+
self.started = None
|
| 1321 |
+
self.elapsed = 0
|
| 1322 |
+
self.done = False
|
| 1323 |
+
|
| 1324 |
+
def update(self, curval):
|
| 1325 |
+
assert self.min <= curval
|
| 1326 |
+
assert self.max is None or curval <= self.max
|
| 1327 |
+
self.cur = curval
|
| 1328 |
+
now = time.time()
|
| 1329 |
+
if self.started is None:
|
| 1330 |
+
self.started = now
|
| 1331 |
+
else:
|
| 1332 |
+
self.elapsed = now - self.started
|
| 1333 |
+
|
| 1334 |
+
def increment(self, incr):
|
| 1335 |
+
assert incr >= 0
|
| 1336 |
+
self.update(self.cur + incr)
|
| 1337 |
+
|
| 1338 |
+
def start(self):
|
| 1339 |
+
self.update(self.min)
|
| 1340 |
+
return self
|
| 1341 |
+
|
| 1342 |
+
def stop(self):
|
| 1343 |
+
if self.max is not None:
|
| 1344 |
+
self.update(self.max)
|
| 1345 |
+
self.done = True
|
| 1346 |
+
|
| 1347 |
+
@property
|
| 1348 |
+
def maximum(self):
|
| 1349 |
+
return self.unknown if self.max is None else self.max
|
| 1350 |
+
|
| 1351 |
+
@property
|
| 1352 |
+
def percentage(self):
|
| 1353 |
+
if self.done:
|
| 1354 |
+
result = '100 %'
|
| 1355 |
+
elif self.max is None:
|
| 1356 |
+
result = ' ?? %'
|
| 1357 |
+
else:
|
| 1358 |
+
v = 100.0 * (self.cur - self.min) / (self.max - self.min)
|
| 1359 |
+
result = '%3d %%' % v
|
| 1360 |
+
return result
|
| 1361 |
+
|
| 1362 |
+
def format_duration(self, duration):
|
| 1363 |
+
if (duration <= 0) and self.max is None or self.cur == self.min:
|
| 1364 |
+
result = '??:??:??'
|
| 1365 |
+
# elif duration < 1:
|
| 1366 |
+
# result = '--:--:--'
|
| 1367 |
+
else:
|
| 1368 |
+
result = time.strftime('%H:%M:%S', time.gmtime(duration))
|
| 1369 |
+
return result
|
| 1370 |
+
|
| 1371 |
+
@property
|
| 1372 |
+
def ETA(self):
|
| 1373 |
+
if self.done:
|
| 1374 |
+
prefix = 'Done'
|
| 1375 |
+
t = self.elapsed
|
| 1376 |
+
# import pdb; pdb.set_trace()
|
| 1377 |
+
else:
|
| 1378 |
+
prefix = 'ETA '
|
| 1379 |
+
if self.max is None:
|
| 1380 |
+
t = -1
|
| 1381 |
+
elif self.elapsed == 0 or (self.cur == self.min):
|
| 1382 |
+
t = 0
|
| 1383 |
+
else:
|
| 1384 |
+
# import pdb; pdb.set_trace()
|
| 1385 |
+
t = float(self.max - self.min)
|
| 1386 |
+
t /= self.cur - self.min
|
| 1387 |
+
t = (t - 1) * self.elapsed
|
| 1388 |
+
return '%s: %s' % (prefix, self.format_duration(t))
|
| 1389 |
+
|
| 1390 |
+
@property
|
| 1391 |
+
def speed(self):
|
| 1392 |
+
if self.elapsed == 0:
|
| 1393 |
+
result = 0.0
|
| 1394 |
+
else:
|
| 1395 |
+
result = (self.cur - self.min) / self.elapsed
|
| 1396 |
+
for unit in UNITS:
|
| 1397 |
+
if result < 1000:
|
| 1398 |
+
break
|
| 1399 |
+
result /= 1000.0
|
| 1400 |
+
return '%d %sB/s' % (result, unit)
|
| 1401 |
+
|
| 1402 |
+
|
| 1403 |
+
#
|
| 1404 |
+
# Glob functionality
|
| 1405 |
+
#
|
| 1406 |
+
|
| 1407 |
+
RICH_GLOB = re.compile(r'\{([^}]*)\}')
|
| 1408 |
+
_CHECK_RECURSIVE_GLOB = re.compile(r'[^/\\,{]\*\*|\*\*[^/\\,}]')
|
| 1409 |
+
_CHECK_MISMATCH_SET = re.compile(r'^[^{]*\}|\{[^}]*$')
|
| 1410 |
+
|
| 1411 |
+
|
| 1412 |
+
def iglob(path_glob):
|
| 1413 |
+
"""Extended globbing function that supports ** and {opt1,opt2,opt3}."""
|
| 1414 |
+
if _CHECK_RECURSIVE_GLOB.search(path_glob):
|
| 1415 |
+
msg = """invalid glob %r: recursive glob "**" must be used alone"""
|
| 1416 |
+
raise ValueError(msg % path_glob)
|
| 1417 |
+
if _CHECK_MISMATCH_SET.search(path_glob):
|
| 1418 |
+
msg = """invalid glob %r: mismatching set marker '{' or '}'"""
|
| 1419 |
+
raise ValueError(msg % path_glob)
|
| 1420 |
+
return _iglob(path_glob)
|
| 1421 |
+
|
| 1422 |
+
|
| 1423 |
+
def _iglob(path_glob):
|
| 1424 |
+
rich_path_glob = RICH_GLOB.split(path_glob, 1)
|
| 1425 |
+
if len(rich_path_glob) > 1:
|
| 1426 |
+
assert len(rich_path_glob) == 3, rich_path_glob
|
| 1427 |
+
prefix, set, suffix = rich_path_glob
|
| 1428 |
+
for item in set.split(','):
|
| 1429 |
+
for path in _iglob(''.join((prefix, item, suffix))):
|
| 1430 |
+
yield path
|
| 1431 |
+
else:
|
| 1432 |
+
if '**' not in path_glob:
|
| 1433 |
+
for item in std_iglob(path_glob):
|
| 1434 |
+
yield item
|
| 1435 |
+
else:
|
| 1436 |
+
prefix, radical = path_glob.split('**', 1)
|
| 1437 |
+
if prefix == '':
|
| 1438 |
+
prefix = '.'
|
| 1439 |
+
if radical == '':
|
| 1440 |
+
radical = '*'
|
| 1441 |
+
else:
|
| 1442 |
+
# we support both
|
| 1443 |
+
radical = radical.lstrip('/')
|
| 1444 |
+
radical = radical.lstrip('\\')
|
| 1445 |
+
for path, dir, files in os.walk(prefix):
|
| 1446 |
+
path = os.path.normpath(path)
|
| 1447 |
+
for fn in _iglob(os.path.join(path, radical)):
|
| 1448 |
+
yield fn
|
| 1449 |
+
|
| 1450 |
+
|
| 1451 |
+
if ssl:
|
| 1452 |
+
from .compat import (HTTPSHandler as BaseHTTPSHandler, match_hostname, CertificateError)
|
| 1453 |
+
|
| 1454 |
+
#
|
| 1455 |
+
# HTTPSConnection which verifies certificates/matches domains
|
| 1456 |
+
#
|
| 1457 |
+
|
| 1458 |
+
class HTTPSConnection(httplib.HTTPSConnection):
|
| 1459 |
+
ca_certs = None # set this to the path to the certs file (.pem)
|
| 1460 |
+
check_domain = True # only used if ca_certs is not None
|
| 1461 |
+
|
| 1462 |
+
# noinspection PyPropertyAccess
|
| 1463 |
+
def connect(self):
|
| 1464 |
+
sock = socket.create_connection((self.host, self.port), self.timeout)
|
| 1465 |
+
if getattr(self, '_tunnel_host', False):
|
| 1466 |
+
self.sock = sock
|
| 1467 |
+
self._tunnel()
|
| 1468 |
+
|
| 1469 |
+
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
| 1470 |
+
if hasattr(ssl, 'OP_NO_SSLv2'):
|
| 1471 |
+
context.options |= ssl.OP_NO_SSLv2
|
| 1472 |
+
if getattr(self, 'cert_file', None):
|
| 1473 |
+
context.load_cert_chain(self.cert_file, self.key_file)
|
| 1474 |
+
kwargs = {}
|
| 1475 |
+
if self.ca_certs:
|
| 1476 |
+
context.verify_mode = ssl.CERT_REQUIRED
|
| 1477 |
+
context.load_verify_locations(cafile=self.ca_certs)
|
| 1478 |
+
if getattr(ssl, 'HAS_SNI', False):
|
| 1479 |
+
kwargs['server_hostname'] = self.host
|
| 1480 |
+
|
| 1481 |
+
self.sock = context.wrap_socket(sock, **kwargs)
|
| 1482 |
+
if self.ca_certs and self.check_domain:
|
| 1483 |
+
try:
|
| 1484 |
+
match_hostname(self.sock.getpeercert(), self.host)
|
| 1485 |
+
logger.debug('Host verified: %s', self.host)
|
| 1486 |
+
except CertificateError: # pragma: no cover
|
| 1487 |
+
self.sock.shutdown(socket.SHUT_RDWR)
|
| 1488 |
+
self.sock.close()
|
| 1489 |
+
raise
|
| 1490 |
+
|
| 1491 |
+
class HTTPSHandler(BaseHTTPSHandler):
|
| 1492 |
+
|
| 1493 |
+
def __init__(self, ca_certs, check_domain=True):
|
| 1494 |
+
BaseHTTPSHandler.__init__(self)
|
| 1495 |
+
self.ca_certs = ca_certs
|
| 1496 |
+
self.check_domain = check_domain
|
| 1497 |
+
|
| 1498 |
+
def _conn_maker(self, *args, **kwargs):
|
| 1499 |
+
"""
|
| 1500 |
+
This is called to create a connection instance. Normally you'd
|
| 1501 |
+
pass a connection class to do_open, but it doesn't actually check for
|
| 1502 |
+
a class, and just expects a callable. As long as we behave just as a
|
| 1503 |
+
constructor would have, we should be OK. If it ever changes so that
|
| 1504 |
+
we *must* pass a class, we'll create an UnsafeHTTPSConnection class
|
| 1505 |
+
which just sets check_domain to False in the class definition, and
|
| 1506 |
+
choose which one to pass to do_open.
|
| 1507 |
+
"""
|
| 1508 |
+
result = HTTPSConnection(*args, **kwargs)
|
| 1509 |
+
if self.ca_certs:
|
| 1510 |
+
result.ca_certs = self.ca_certs
|
| 1511 |
+
result.check_domain = self.check_domain
|
| 1512 |
+
return result
|
| 1513 |
+
|
| 1514 |
+
def https_open(self, req):
|
| 1515 |
+
try:
|
| 1516 |
+
return self.do_open(self._conn_maker, req)
|
| 1517 |
+
except URLError as e:
|
| 1518 |
+
if 'certificate verify failed' in str(e.reason):
|
| 1519 |
+
raise CertificateError('Unable to verify server certificate '
|
| 1520 |
+
'for %s' % req.host)
|
| 1521 |
+
else:
|
| 1522 |
+
raise
|
| 1523 |
+
|
| 1524 |
+
#
|
| 1525 |
+
# To prevent against mixing HTTP traffic with HTTPS (examples: A Man-In-The-
|
| 1526 |
+
# Middle proxy using HTTP listens on port 443, or an index mistakenly serves
|
| 1527 |
+
# HTML containing a http://xyz link when it should be https://xyz),
|
| 1528 |
+
# you can use the following handler class, which does not allow HTTP traffic.
|
| 1529 |
+
#
|
| 1530 |
+
# It works by inheriting from HTTPHandler - so build_opener won't add a
|
| 1531 |
+
# handler for HTTP itself.
|
| 1532 |
+
#
|
| 1533 |
+
class HTTPSOnlyHandler(HTTPSHandler, HTTPHandler):
|
| 1534 |
+
|
| 1535 |
+
def http_open(self, req):
|
| 1536 |
+
raise URLError('Unexpected HTTP request on what should be a secure '
|
| 1537 |
+
'connection: %s' % req)
|
| 1538 |
+
|
| 1539 |
+
|
| 1540 |
+
#
|
| 1541 |
+
# XML-RPC with timeouts
|
| 1542 |
+
#
|
| 1543 |
+
class Transport(xmlrpclib.Transport):
|
| 1544 |
+
|
| 1545 |
+
def __init__(self, timeout, use_datetime=0):
|
| 1546 |
+
self.timeout = timeout
|
| 1547 |
+
xmlrpclib.Transport.__init__(self, use_datetime)
|
| 1548 |
+
|
| 1549 |
+
def make_connection(self, host):
|
| 1550 |
+
h, eh, x509 = self.get_host_info(host)
|
| 1551 |
+
if not self._connection or host != self._connection[0]:
|
| 1552 |
+
self._extra_headers = eh
|
| 1553 |
+
self._connection = host, httplib.HTTPConnection(h)
|
| 1554 |
+
return self._connection[1]
|
| 1555 |
+
|
| 1556 |
+
|
| 1557 |
+
if ssl:
|
| 1558 |
+
|
| 1559 |
+
class SafeTransport(xmlrpclib.SafeTransport):
|
| 1560 |
+
|
| 1561 |
+
def __init__(self, timeout, use_datetime=0):
|
| 1562 |
+
self.timeout = timeout
|
| 1563 |
+
xmlrpclib.SafeTransport.__init__(self, use_datetime)
|
| 1564 |
+
|
| 1565 |
+
def make_connection(self, host):
|
| 1566 |
+
h, eh, kwargs = self.get_host_info(host)
|
| 1567 |
+
if not kwargs:
|
| 1568 |
+
kwargs = {}
|
| 1569 |
+
kwargs['timeout'] = self.timeout
|
| 1570 |
+
if not self._connection or host != self._connection[0]:
|
| 1571 |
+
self._extra_headers = eh
|
| 1572 |
+
self._connection = host, httplib.HTTPSConnection(h, None, **kwargs)
|
| 1573 |
+
return self._connection[1]
|
| 1574 |
+
|
| 1575 |
+
|
| 1576 |
+
class ServerProxy(xmlrpclib.ServerProxy):
|
| 1577 |
+
|
| 1578 |
+
def __init__(self, uri, **kwargs):
|
| 1579 |
+
self.timeout = timeout = kwargs.pop('timeout', None)
|
| 1580 |
+
# The above classes only come into play if a timeout
|
| 1581 |
+
# is specified
|
| 1582 |
+
if timeout is not None:
|
| 1583 |
+
# scheme = splittype(uri) # deprecated as of Python 3.8
|
| 1584 |
+
scheme = urlparse(uri)[0]
|
| 1585 |
+
use_datetime = kwargs.get('use_datetime', 0)
|
| 1586 |
+
if scheme == 'https':
|
| 1587 |
+
tcls = SafeTransport
|
| 1588 |
+
else:
|
| 1589 |
+
tcls = Transport
|
| 1590 |
+
kwargs['transport'] = t = tcls(timeout, use_datetime=use_datetime)
|
| 1591 |
+
self.transport = t
|
| 1592 |
+
xmlrpclib.ServerProxy.__init__(self, uri, **kwargs)
|
| 1593 |
+
|
| 1594 |
+
|
| 1595 |
+
#
|
| 1596 |
+
# CSV functionality. This is provided because on 2.x, the csv module can't
|
| 1597 |
+
# handle Unicode. However, we need to deal with Unicode in e.g. RECORD files.
|
| 1598 |
+
#
|
| 1599 |
+
|
| 1600 |
+
|
| 1601 |
+
def _csv_open(fn, mode, **kwargs):
|
| 1602 |
+
if sys.version_info[0] < 3:
|
| 1603 |
+
mode += 'b'
|
| 1604 |
+
else:
|
| 1605 |
+
kwargs['newline'] = ''
|
| 1606 |
+
# Python 3 determines encoding from locale. Force 'utf-8'
|
| 1607 |
+
# file encoding to match other forced utf-8 encoding
|
| 1608 |
+
kwargs['encoding'] = 'utf-8'
|
| 1609 |
+
return open(fn, mode, **kwargs)
|
| 1610 |
+
|
| 1611 |
+
|
| 1612 |
+
class CSVBase(object):
|
| 1613 |
+
defaults = {
|
| 1614 |
+
'delimiter': str(','), # The strs are used because we need native
|
| 1615 |
+
'quotechar': str('"'), # str in the csv API (2.x won't take
|
| 1616 |
+
'lineterminator': str('\n') # Unicode)
|
| 1617 |
+
}
|
| 1618 |
+
|
| 1619 |
+
def __enter__(self):
|
| 1620 |
+
return self
|
| 1621 |
+
|
| 1622 |
+
def __exit__(self, *exc_info):
|
| 1623 |
+
self.stream.close()
|
| 1624 |
+
|
| 1625 |
+
|
| 1626 |
+
class CSVReader(CSVBase):
|
| 1627 |
+
|
| 1628 |
+
def __init__(self, **kwargs):
|
| 1629 |
+
if 'stream' in kwargs:
|
| 1630 |
+
stream = kwargs['stream']
|
| 1631 |
+
if sys.version_info[0] >= 3:
|
| 1632 |
+
# needs to be a text stream
|
| 1633 |
+
stream = codecs.getreader('utf-8')(stream)
|
| 1634 |
+
self.stream = stream
|
| 1635 |
+
else:
|
| 1636 |
+
self.stream = _csv_open(kwargs['path'], 'r')
|
| 1637 |
+
self.reader = csv.reader(self.stream, **self.defaults)
|
| 1638 |
+
|
| 1639 |
+
def __iter__(self):
|
| 1640 |
+
return self
|
| 1641 |
+
|
| 1642 |
+
def next(self):
|
| 1643 |
+
result = next(self.reader)
|
| 1644 |
+
if sys.version_info[0] < 3:
|
| 1645 |
+
for i, item in enumerate(result):
|
| 1646 |
+
if not isinstance(item, text_type):
|
| 1647 |
+
result[i] = item.decode('utf-8')
|
| 1648 |
+
return result
|
| 1649 |
+
|
| 1650 |
+
__next__ = next
|
| 1651 |
+
|
| 1652 |
+
|
| 1653 |
+
class CSVWriter(CSVBase):
|
| 1654 |
+
|
| 1655 |
+
def __init__(self, fn, **kwargs):
|
| 1656 |
+
self.stream = _csv_open(fn, 'w')
|
| 1657 |
+
self.writer = csv.writer(self.stream, **self.defaults)
|
| 1658 |
+
|
| 1659 |
+
def writerow(self, row):
|
| 1660 |
+
if sys.version_info[0] < 3:
|
| 1661 |
+
r = []
|
| 1662 |
+
for item in row:
|
| 1663 |
+
if isinstance(item, text_type):
|
| 1664 |
+
item = item.encode('utf-8')
|
| 1665 |
+
r.append(item)
|
| 1666 |
+
row = r
|
| 1667 |
+
self.writer.writerow(row)
|
| 1668 |
+
|
| 1669 |
+
|
| 1670 |
+
#
|
| 1671 |
+
# Configurator functionality
|
| 1672 |
+
#
|
| 1673 |
+
|
| 1674 |
+
|
| 1675 |
+
class Configurator(BaseConfigurator):
|
| 1676 |
+
|
| 1677 |
+
value_converters = dict(BaseConfigurator.value_converters)
|
| 1678 |
+
value_converters['inc'] = 'inc_convert'
|
| 1679 |
+
|
| 1680 |
+
def __init__(self, config, base=None):
|
| 1681 |
+
super(Configurator, self).__init__(config)
|
| 1682 |
+
self.base = base or os.getcwd()
|
| 1683 |
+
|
| 1684 |
+
def configure_custom(self, config):
|
| 1685 |
+
|
| 1686 |
+
def convert(o):
|
| 1687 |
+
if isinstance(o, (list, tuple)):
|
| 1688 |
+
result = type(o)([convert(i) for i in o])
|
| 1689 |
+
elif isinstance(o, dict):
|
| 1690 |
+
if '()' in o:
|
| 1691 |
+
result = self.configure_custom(o)
|
| 1692 |
+
else:
|
| 1693 |
+
result = {}
|
| 1694 |
+
for k in o:
|
| 1695 |
+
result[k] = convert(o[k])
|
| 1696 |
+
else:
|
| 1697 |
+
result = self.convert(o)
|
| 1698 |
+
return result
|
| 1699 |
+
|
| 1700 |
+
c = config.pop('()')
|
| 1701 |
+
if not callable(c):
|
| 1702 |
+
c = self.resolve(c)
|
| 1703 |
+
props = config.pop('.', None)
|
| 1704 |
+
# Check for valid identifiers
|
| 1705 |
+
args = config.pop('[]', ())
|
| 1706 |
+
if args:
|
| 1707 |
+
args = tuple([convert(o) for o in args])
|
| 1708 |
+
items = [(k, convert(config[k])) for k in config if valid_ident(k)]
|
| 1709 |
+
kwargs = dict(items)
|
| 1710 |
+
result = c(*args, **kwargs)
|
| 1711 |
+
if props:
|
| 1712 |
+
for n, v in props.items():
|
| 1713 |
+
setattr(result, n, convert(v))
|
| 1714 |
+
return result
|
| 1715 |
+
|
| 1716 |
+
def __getitem__(self, key):
|
| 1717 |
+
result = self.config[key]
|
| 1718 |
+
if isinstance(result, dict) and '()' in result:
|
| 1719 |
+
self.config[key] = result = self.configure_custom(result)
|
| 1720 |
+
return result
|
| 1721 |
+
|
| 1722 |
+
def inc_convert(self, value):
|
| 1723 |
+
"""Default converter for the inc:// protocol."""
|
| 1724 |
+
if not os.path.isabs(value):
|
| 1725 |
+
value = os.path.join(self.base, value)
|
| 1726 |
+
with codecs.open(value, 'r', encoding='utf-8') as f:
|
| 1727 |
+
result = json.load(f)
|
| 1728 |
+
return result
|
| 1729 |
+
|
| 1730 |
+
|
| 1731 |
+
class SubprocessMixin(object):
|
| 1732 |
+
"""
|
| 1733 |
+
Mixin for running subprocesses and capturing their output
|
| 1734 |
+
"""
|
| 1735 |
+
|
| 1736 |
+
def __init__(self, verbose=False, progress=None):
|
| 1737 |
+
self.verbose = verbose
|
| 1738 |
+
self.progress = progress
|
| 1739 |
+
|
| 1740 |
+
def reader(self, stream, context):
|
| 1741 |
+
"""
|
| 1742 |
+
Read lines from a subprocess' output stream and either pass to a progress
|
| 1743 |
+
callable (if specified) or write progress information to sys.stderr.
|
| 1744 |
+
"""
|
| 1745 |
+
progress = self.progress
|
| 1746 |
+
verbose = self.verbose
|
| 1747 |
+
while True:
|
| 1748 |
+
s = stream.readline()
|
| 1749 |
+
if not s:
|
| 1750 |
+
break
|
| 1751 |
+
if progress is not None:
|
| 1752 |
+
progress(s, context)
|
| 1753 |
+
else:
|
| 1754 |
+
if not verbose:
|
| 1755 |
+
sys.stderr.write('.')
|
| 1756 |
+
else:
|
| 1757 |
+
sys.stderr.write(s.decode('utf-8'))
|
| 1758 |
+
sys.stderr.flush()
|
| 1759 |
+
stream.close()
|
| 1760 |
+
|
| 1761 |
+
def run_command(self, cmd, **kwargs):
|
| 1762 |
+
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
|
| 1763 |
+
t1 = threading.Thread(target=self.reader, args=(p.stdout, 'stdout'))
|
| 1764 |
+
t1.start()
|
| 1765 |
+
t2 = threading.Thread(target=self.reader, args=(p.stderr, 'stderr'))
|
| 1766 |
+
t2.start()
|
| 1767 |
+
p.wait()
|
| 1768 |
+
t1.join()
|
| 1769 |
+
t2.join()
|
| 1770 |
+
if self.progress is not None:
|
| 1771 |
+
self.progress('done.', 'main')
|
| 1772 |
+
elif self.verbose:
|
| 1773 |
+
sys.stderr.write('done.\n')
|
| 1774 |
+
return p
|
| 1775 |
+
|
| 1776 |
+
|
| 1777 |
+
def normalize_name(name):
|
| 1778 |
+
"""Normalize a python package name a la PEP 503"""
|
| 1779 |
+
# https://www.python.org/dev/peps/pep-0503/#normalized-names
|
| 1780 |
+
return re.sub('[-_.]+', '-', name).lower()
|
| 1781 |
+
|
| 1782 |
+
|
| 1783 |
+
# def _get_pypirc_command():
|
| 1784 |
+
# """
|
| 1785 |
+
# Get the distutils command for interacting with PyPI configurations.
|
| 1786 |
+
# :return: the command.
|
| 1787 |
+
# """
|
| 1788 |
+
# from distutils.core import Distribution
|
| 1789 |
+
# from distutils.config import PyPIRCCommand
|
| 1790 |
+
# d = Distribution()
|
| 1791 |
+
# return PyPIRCCommand(d)
|
| 1792 |
+
|
| 1793 |
+
|
| 1794 |
+
class PyPIRCFile(object):
|
| 1795 |
+
|
| 1796 |
+
DEFAULT_REPOSITORY = 'https://upload.pypi.org/legacy/'
|
| 1797 |
+
DEFAULT_REALM = 'pypi'
|
| 1798 |
+
|
| 1799 |
+
def __init__(self, fn=None, url=None):
|
| 1800 |
+
if fn is None:
|
| 1801 |
+
fn = os.path.join(os.path.expanduser('~'), '.pypirc')
|
| 1802 |
+
self.filename = fn
|
| 1803 |
+
self.url = url
|
| 1804 |
+
|
| 1805 |
+
def read(self):
|
| 1806 |
+
result = {}
|
| 1807 |
+
|
| 1808 |
+
if os.path.exists(self.filename):
|
| 1809 |
+
repository = self.url or self.DEFAULT_REPOSITORY
|
| 1810 |
+
|
| 1811 |
+
config = configparser.RawConfigParser()
|
| 1812 |
+
config.read(self.filename)
|
| 1813 |
+
sections = config.sections()
|
| 1814 |
+
if 'distutils' in sections:
|
| 1815 |
+
# let's get the list of servers
|
| 1816 |
+
index_servers = config.get('distutils', 'index-servers')
|
| 1817 |
+
_servers = [server.strip() for server in index_servers.split('\n') if server.strip() != '']
|
| 1818 |
+
if _servers == []:
|
| 1819 |
+
# nothing set, let's try to get the default pypi
|
| 1820 |
+
if 'pypi' in sections:
|
| 1821 |
+
_servers = ['pypi']
|
| 1822 |
+
else:
|
| 1823 |
+
for server in _servers:
|
| 1824 |
+
result = {'server': server}
|
| 1825 |
+
result['username'] = config.get(server, 'username')
|
| 1826 |
+
|
| 1827 |
+
# optional params
|
| 1828 |
+
for key, default in (('repository', self.DEFAULT_REPOSITORY), ('realm', self.DEFAULT_REALM),
|
| 1829 |
+
('password', None)):
|
| 1830 |
+
if config.has_option(server, key):
|
| 1831 |
+
result[key] = config.get(server, key)
|
| 1832 |
+
else:
|
| 1833 |
+
result[key] = default
|
| 1834 |
+
|
| 1835 |
+
# work around people having "repository" for the "pypi"
|
| 1836 |
+
# section of their config set to the HTTP (rather than
|
| 1837 |
+
# HTTPS) URL
|
| 1838 |
+
if (server == 'pypi' and repository in (self.DEFAULT_REPOSITORY, 'pypi')):
|
| 1839 |
+
result['repository'] = self.DEFAULT_REPOSITORY
|
| 1840 |
+
elif (result['server'] != repository and result['repository'] != repository):
|
| 1841 |
+
result = {}
|
| 1842 |
+
elif 'server-login' in sections:
|
| 1843 |
+
# old format
|
| 1844 |
+
server = 'server-login'
|
| 1845 |
+
if config.has_option(server, 'repository'):
|
| 1846 |
+
repository = config.get(server, 'repository')
|
| 1847 |
+
else:
|
| 1848 |
+
repository = self.DEFAULT_REPOSITORY
|
| 1849 |
+
result = {
|
| 1850 |
+
'username': config.get(server, 'username'),
|
| 1851 |
+
'password': config.get(server, 'password'),
|
| 1852 |
+
'repository': repository,
|
| 1853 |
+
'server': server,
|
| 1854 |
+
'realm': self.DEFAULT_REALM
|
| 1855 |
+
}
|
| 1856 |
+
return result
|
| 1857 |
+
|
| 1858 |
+
def update(self, username, password):
|
| 1859 |
+
# import pdb; pdb.set_trace()
|
| 1860 |
+
config = configparser.RawConfigParser()
|
| 1861 |
+
fn = self.filename
|
| 1862 |
+
config.read(fn)
|
| 1863 |
+
if not config.has_section('pypi'):
|
| 1864 |
+
config.add_section('pypi')
|
| 1865 |
+
config.set('pypi', 'username', username)
|
| 1866 |
+
config.set('pypi', 'password', password)
|
| 1867 |
+
with open(fn, 'w') as f:
|
| 1868 |
+
config.write(f)
|
| 1869 |
+
|
| 1870 |
+
|
| 1871 |
+
def _load_pypirc(index):
|
| 1872 |
+
"""
|
| 1873 |
+
Read the PyPI access configuration as supported by distutils.
|
| 1874 |
+
"""
|
| 1875 |
+
return PyPIRCFile(url=index.url).read()
|
| 1876 |
+
|
| 1877 |
+
|
| 1878 |
+
def _store_pypirc(index):
|
| 1879 |
+
PyPIRCFile().update(index.username, index.password)
|
| 1880 |
+
|
| 1881 |
+
|
| 1882 |
+
#
|
| 1883 |
+
# get_platform()/get_host_platform() copied from Python 3.10.a0 source, with some minor
|
| 1884 |
+
# tweaks
|
| 1885 |
+
#
|
| 1886 |
+
|
| 1887 |
+
|
| 1888 |
+
def get_host_platform():
|
| 1889 |
+
"""Return a string that identifies the current platform. This is used mainly to
|
| 1890 |
+
distinguish platform-specific build directories and platform-specific built
|
| 1891 |
+
distributions. Typically includes the OS name and version and the
|
| 1892 |
+
architecture (as supplied by 'os.uname()'), although the exact information
|
| 1893 |
+
included depends on the OS; eg. on Linux, the kernel version isn't
|
| 1894 |
+
particularly important.
|
| 1895 |
+
|
| 1896 |
+
Examples of returned values:
|
| 1897 |
+
linux-i586
|
| 1898 |
+
linux-alpha (?)
|
| 1899 |
+
solaris-2.6-sun4u
|
| 1900 |
+
|
| 1901 |
+
Windows will return one of:
|
| 1902 |
+
win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
|
| 1903 |
+
win32 (all others - specifically, sys.platform is returned)
|
| 1904 |
+
|
| 1905 |
+
For other non-POSIX platforms, currently just returns 'sys.platform'.
|
| 1906 |
+
|
| 1907 |
+
"""
|
| 1908 |
+
if os.name == 'nt':
|
| 1909 |
+
if 'amd64' in sys.version.lower():
|
| 1910 |
+
return 'win-amd64'
|
| 1911 |
+
if '(arm)' in sys.version.lower():
|
| 1912 |
+
return 'win-arm32'
|
| 1913 |
+
if '(arm64)' in sys.version.lower():
|
| 1914 |
+
return 'win-arm64'
|
| 1915 |
+
return sys.platform
|
| 1916 |
+
|
| 1917 |
+
# Set for cross builds explicitly
|
| 1918 |
+
if "_PYTHON_HOST_PLATFORM" in os.environ:
|
| 1919 |
+
return os.environ["_PYTHON_HOST_PLATFORM"]
|
| 1920 |
+
|
| 1921 |
+
if os.name != 'posix' or not hasattr(os, 'uname'):
|
| 1922 |
+
# XXX what about the architecture? NT is Intel or Alpha,
|
| 1923 |
+
# Mac OS is M68k or PPC, etc.
|
| 1924 |
+
return sys.platform
|
| 1925 |
+
|
| 1926 |
+
# Try to distinguish various flavours of Unix
|
| 1927 |
+
|
| 1928 |
+
(osname, host, release, version, machine) = os.uname()
|
| 1929 |
+
|
| 1930 |
+
# Convert the OS name to lowercase, remove '/' characters, and translate
|
| 1931 |
+
# spaces (for "Power Macintosh")
|
| 1932 |
+
osname = osname.lower().replace('/', '')
|
| 1933 |
+
machine = machine.replace(' ', '_').replace('/', '-')
|
| 1934 |
+
|
| 1935 |
+
if osname[:5] == 'linux':
|
| 1936 |
+
# At least on Linux/Intel, 'machine' is the processor --
|
| 1937 |
+
# i386, etc.
|
| 1938 |
+
# XXX what about Alpha, SPARC, etc?
|
| 1939 |
+
return "%s-%s" % (osname, machine)
|
| 1940 |
+
|
| 1941 |
+
elif osname[:5] == 'sunos':
|
| 1942 |
+
if release[0] >= '5': # SunOS 5 == Solaris 2
|
| 1943 |
+
osname = 'solaris'
|
| 1944 |
+
release = '%d.%s' % (int(release[0]) - 3, release[2:])
|
| 1945 |
+
# We can't use 'platform.architecture()[0]' because a
|
| 1946 |
+
# bootstrap problem. We use a dict to get an error
|
| 1947 |
+
# if some suspicious happens.
|
| 1948 |
+
bitness = {2147483647: '32bit', 9223372036854775807: '64bit'}
|
| 1949 |
+
machine += '.%s' % bitness[sys.maxsize]
|
| 1950 |
+
# fall through to standard osname-release-machine representation
|
| 1951 |
+
elif osname[:3] == 'aix':
|
| 1952 |
+
from _aix_support import aix_platform
|
| 1953 |
+
return aix_platform()
|
| 1954 |
+
elif osname[:6] == 'cygwin':
|
| 1955 |
+
osname = 'cygwin'
|
| 1956 |
+
rel_re = re.compile(r'[\d.]+', re.ASCII)
|
| 1957 |
+
m = rel_re.match(release)
|
| 1958 |
+
if m:
|
| 1959 |
+
release = m.group()
|
| 1960 |
+
elif osname[:6] == 'darwin':
|
| 1961 |
+
import _osx_support
|
| 1962 |
+
try:
|
| 1963 |
+
from distutils import sysconfig
|
| 1964 |
+
except ImportError:
|
| 1965 |
+
import sysconfig
|
| 1966 |
+
osname, release, machine = _osx_support.get_platform_osx(sysconfig.get_config_vars(), osname, release, machine)
|
| 1967 |
+
|
| 1968 |
+
return '%s-%s-%s' % (osname, release, machine)
|
| 1969 |
+
|
| 1970 |
+
|
| 1971 |
+
_TARGET_TO_PLAT = {
|
| 1972 |
+
'x86': 'win32',
|
| 1973 |
+
'x64': 'win-amd64',
|
| 1974 |
+
'arm': 'win-arm32',
|
| 1975 |
+
}
|
| 1976 |
+
|
| 1977 |
+
|
| 1978 |
+
def get_platform():
|
| 1979 |
+
if os.name != 'nt':
|
| 1980 |
+
return get_host_platform()
|
| 1981 |
+
cross_compilation_target = os.environ.get('VSCMD_ARG_TGT_ARCH')
|
| 1982 |
+
if cross_compilation_target not in _TARGET_TO_PLAT:
|
| 1983 |
+
return get_host_platform()
|
| 1984 |
+
return _TARGET_TO_PLAT[cross_compilation_target]
|
lib/python3.12/site-packages/pip/_vendor/distro/__init__.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .distro import (
|
| 2 |
+
NORMALIZED_DISTRO_ID,
|
| 3 |
+
NORMALIZED_LSB_ID,
|
| 4 |
+
NORMALIZED_OS_ID,
|
| 5 |
+
LinuxDistribution,
|
| 6 |
+
__version__,
|
| 7 |
+
build_number,
|
| 8 |
+
codename,
|
| 9 |
+
distro_release_attr,
|
| 10 |
+
distro_release_info,
|
| 11 |
+
id,
|
| 12 |
+
info,
|
| 13 |
+
like,
|
| 14 |
+
linux_distribution,
|
| 15 |
+
lsb_release_attr,
|
| 16 |
+
lsb_release_info,
|
| 17 |
+
major_version,
|
| 18 |
+
minor_version,
|
| 19 |
+
name,
|
| 20 |
+
os_release_attr,
|
| 21 |
+
os_release_info,
|
| 22 |
+
uname_attr,
|
| 23 |
+
uname_info,
|
| 24 |
+
version,
|
| 25 |
+
version_parts,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
__all__ = [
|
| 29 |
+
"NORMALIZED_DISTRO_ID",
|
| 30 |
+
"NORMALIZED_LSB_ID",
|
| 31 |
+
"NORMALIZED_OS_ID",
|
| 32 |
+
"LinuxDistribution",
|
| 33 |
+
"build_number",
|
| 34 |
+
"codename",
|
| 35 |
+
"distro_release_attr",
|
| 36 |
+
"distro_release_info",
|
| 37 |
+
"id",
|
| 38 |
+
"info",
|
| 39 |
+
"like",
|
| 40 |
+
"linux_distribution",
|
| 41 |
+
"lsb_release_attr",
|
| 42 |
+
"lsb_release_info",
|
| 43 |
+
"major_version",
|
| 44 |
+
"minor_version",
|
| 45 |
+
"name",
|
| 46 |
+
"os_release_attr",
|
| 47 |
+
"os_release_info",
|
| 48 |
+
"uname_attr",
|
| 49 |
+
"uname_info",
|
| 50 |
+
"version",
|
| 51 |
+
"version_parts",
|
| 52 |
+
]
|
| 53 |
+
|
| 54 |
+
__version__ = __version__
|
lib/python3.12/site-packages/pip/_vendor/distro/__main__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .distro import main
|
| 2 |
+
|
| 3 |
+
if __name__ == "__main__":
|
| 4 |
+
main()
|
lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (967 Bytes). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc
ADDED
|
Binary file (299 Bytes). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-312.pyc
ADDED
|
Binary file (53.8 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/distro/distro.py
ADDED
|
@@ -0,0 +1,1403 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
# Copyright 2015-2021 Nir Cohen
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
"""
|
| 17 |
+
The ``distro`` package (``distro`` stands for Linux Distribution) provides
|
| 18 |
+
information about the Linux distribution it runs on, such as a reliable
|
| 19 |
+
machine-readable distro ID, or version information.
|
| 20 |
+
|
| 21 |
+
It is the recommended replacement for Python's original
|
| 22 |
+
:py:func:`platform.linux_distribution` function, but it provides much more
|
| 23 |
+
functionality. An alternative implementation became necessary because Python
|
| 24 |
+
3.5 deprecated this function, and Python 3.8 removed it altogether. Its
|
| 25 |
+
predecessor function :py:func:`platform.dist` was already deprecated since
|
| 26 |
+
Python 2.6 and removed in Python 3.8. Still, there are many cases in which
|
| 27 |
+
access to OS distribution information is needed. See `Python issue 1322
|
| 28 |
+
<https://bugs.python.org/issue1322>`_ for more information.
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
import argparse
|
| 32 |
+
import json
|
| 33 |
+
import logging
|
| 34 |
+
import os
|
| 35 |
+
import re
|
| 36 |
+
import shlex
|
| 37 |
+
import subprocess
|
| 38 |
+
import sys
|
| 39 |
+
import warnings
|
| 40 |
+
from typing import (
|
| 41 |
+
Any,
|
| 42 |
+
Callable,
|
| 43 |
+
Dict,
|
| 44 |
+
Iterable,
|
| 45 |
+
Optional,
|
| 46 |
+
Sequence,
|
| 47 |
+
TextIO,
|
| 48 |
+
Tuple,
|
| 49 |
+
Type,
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
try:
|
| 53 |
+
from typing import TypedDict
|
| 54 |
+
except ImportError:
|
| 55 |
+
# Python 3.7
|
| 56 |
+
TypedDict = dict
|
| 57 |
+
|
| 58 |
+
__version__ = "1.9.0"
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class VersionDict(TypedDict):
|
| 62 |
+
major: str
|
| 63 |
+
minor: str
|
| 64 |
+
build_number: str
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class InfoDict(TypedDict):
|
| 68 |
+
id: str
|
| 69 |
+
version: str
|
| 70 |
+
version_parts: VersionDict
|
| 71 |
+
like: str
|
| 72 |
+
codename: str
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
_UNIXCONFDIR = os.environ.get("UNIXCONFDIR", "/etc")
|
| 76 |
+
_UNIXUSRLIBDIR = os.environ.get("UNIXUSRLIBDIR", "/usr/lib")
|
| 77 |
+
_OS_RELEASE_BASENAME = "os-release"
|
| 78 |
+
|
| 79 |
+
#: Translation table for normalizing the "ID" attribute defined in os-release
|
| 80 |
+
#: files, for use by the :func:`distro.id` method.
|
| 81 |
+
#:
|
| 82 |
+
#: * Key: Value as defined in the os-release file, translated to lower case,
|
| 83 |
+
#: with blanks translated to underscores.
|
| 84 |
+
#:
|
| 85 |
+
#: * Value: Normalized value.
|
| 86 |
+
NORMALIZED_OS_ID = {
|
| 87 |
+
"ol": "oracle", # Oracle Linux
|
| 88 |
+
"opensuse-leap": "opensuse", # Newer versions of OpenSuSE report as opensuse-leap
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
#: Translation table for normalizing the "Distributor ID" attribute returned by
|
| 92 |
+
#: the lsb_release command, for use by the :func:`distro.id` method.
|
| 93 |
+
#:
|
| 94 |
+
#: * Key: Value as returned by the lsb_release command, translated to lower
|
| 95 |
+
#: case, with blanks translated to underscores.
|
| 96 |
+
#:
|
| 97 |
+
#: * Value: Normalized value.
|
| 98 |
+
NORMALIZED_LSB_ID = {
|
| 99 |
+
"enterpriseenterpriseas": "oracle", # Oracle Enterprise Linux 4
|
| 100 |
+
"enterpriseenterpriseserver": "oracle", # Oracle Linux 5
|
| 101 |
+
"redhatenterpriseworkstation": "rhel", # RHEL 6, 7 Workstation
|
| 102 |
+
"redhatenterpriseserver": "rhel", # RHEL 6, 7 Server
|
| 103 |
+
"redhatenterprisecomputenode": "rhel", # RHEL 6 ComputeNode
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
#: Translation table for normalizing the distro ID derived from the file name
|
| 107 |
+
#: of distro release files, for use by the :func:`distro.id` method.
|
| 108 |
+
#:
|
| 109 |
+
#: * Key: Value as derived from the file name of a distro release file,
|
| 110 |
+
#: translated to lower case, with blanks translated to underscores.
|
| 111 |
+
#:
|
| 112 |
+
#: * Value: Normalized value.
|
| 113 |
+
NORMALIZED_DISTRO_ID = {
|
| 114 |
+
"redhat": "rhel", # RHEL 6.x, 7.x
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
# Pattern for content of distro release file (reversed)
|
| 118 |
+
_DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile(
|
| 119 |
+
r"(?:[^)]*\)(.*)\()? *(?:STL )?([\d.+\-a-z]*\d) *(?:esaeler *)?(.+)"
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
# Pattern for base file name of distro release file
|
| 123 |
+
_DISTRO_RELEASE_BASENAME_PATTERN = re.compile(r"(\w+)[-_](release|version)$")
|
| 124 |
+
|
| 125 |
+
# Base file names to be looked up for if _UNIXCONFDIR is not readable.
|
| 126 |
+
_DISTRO_RELEASE_BASENAMES = [
|
| 127 |
+
"SuSE-release",
|
| 128 |
+
"altlinux-release",
|
| 129 |
+
"arch-release",
|
| 130 |
+
"base-release",
|
| 131 |
+
"centos-release",
|
| 132 |
+
"fedora-release",
|
| 133 |
+
"gentoo-release",
|
| 134 |
+
"mageia-release",
|
| 135 |
+
"mandrake-release",
|
| 136 |
+
"mandriva-release",
|
| 137 |
+
"mandrivalinux-release",
|
| 138 |
+
"manjaro-release",
|
| 139 |
+
"oracle-release",
|
| 140 |
+
"redhat-release",
|
| 141 |
+
"rocky-release",
|
| 142 |
+
"sl-release",
|
| 143 |
+
"slackware-version",
|
| 144 |
+
]
|
| 145 |
+
|
| 146 |
+
# Base file names to be ignored when searching for distro release file
|
| 147 |
+
_DISTRO_RELEASE_IGNORE_BASENAMES = (
|
| 148 |
+
"debian_version",
|
| 149 |
+
"lsb-release",
|
| 150 |
+
"oem-release",
|
| 151 |
+
_OS_RELEASE_BASENAME,
|
| 152 |
+
"system-release",
|
| 153 |
+
"plesk-release",
|
| 154 |
+
"iredmail-release",
|
| 155 |
+
"board-release",
|
| 156 |
+
"ec2_version",
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def linux_distribution(full_distribution_name: bool = True) -> Tuple[str, str, str]:
|
| 161 |
+
"""
|
| 162 |
+
.. deprecated:: 1.6.0
|
| 163 |
+
|
| 164 |
+
:func:`distro.linux_distribution()` is deprecated. It should only be
|
| 165 |
+
used as a compatibility shim with Python's
|
| 166 |
+
:py:func:`platform.linux_distribution()`. Please use :func:`distro.id`,
|
| 167 |
+
:func:`distro.version` and :func:`distro.name` instead.
|
| 168 |
+
|
| 169 |
+
Return information about the current OS distribution as a tuple
|
| 170 |
+
``(id_name, version, codename)`` with items as follows:
|
| 171 |
+
|
| 172 |
+
* ``id_name``: If *full_distribution_name* is false, the result of
|
| 173 |
+
:func:`distro.id`. Otherwise, the result of :func:`distro.name`.
|
| 174 |
+
|
| 175 |
+
* ``version``: The result of :func:`distro.version`.
|
| 176 |
+
|
| 177 |
+
* ``codename``: The extra item (usually in parentheses) after the
|
| 178 |
+
os-release version number, or the result of :func:`distro.codename`.
|
| 179 |
+
|
| 180 |
+
The interface of this function is compatible with the original
|
| 181 |
+
:py:func:`platform.linux_distribution` function, supporting a subset of
|
| 182 |
+
its parameters.
|
| 183 |
+
|
| 184 |
+
The data it returns may not exactly be the same, because it uses more data
|
| 185 |
+
sources than the original function, and that may lead to different data if
|
| 186 |
+
the OS distribution is not consistent across multiple data sources it
|
| 187 |
+
provides (there are indeed such distributions ...).
|
| 188 |
+
|
| 189 |
+
Another reason for differences is the fact that the :func:`distro.id`
|
| 190 |
+
method normalizes the distro ID string to a reliable machine-readable value
|
| 191 |
+
for a number of popular OS distributions.
|
| 192 |
+
"""
|
| 193 |
+
warnings.warn(
|
| 194 |
+
"distro.linux_distribution() is deprecated. It should only be used as a "
|
| 195 |
+
"compatibility shim with Python's platform.linux_distribution(). Please use "
|
| 196 |
+
"distro.id(), distro.version() and distro.name() instead.",
|
| 197 |
+
DeprecationWarning,
|
| 198 |
+
stacklevel=2,
|
| 199 |
+
)
|
| 200 |
+
return _distro.linux_distribution(full_distribution_name)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def id() -> str:
|
| 204 |
+
"""
|
| 205 |
+
Return the distro ID of the current distribution, as a
|
| 206 |
+
machine-readable string.
|
| 207 |
+
|
| 208 |
+
For a number of OS distributions, the returned distro ID value is
|
| 209 |
+
*reliable*, in the sense that it is documented and that it does not change
|
| 210 |
+
across releases of the distribution.
|
| 211 |
+
|
| 212 |
+
This package maintains the following reliable distro ID values:
|
| 213 |
+
|
| 214 |
+
============== =========================================
|
| 215 |
+
Distro ID Distribution
|
| 216 |
+
============== =========================================
|
| 217 |
+
"ubuntu" Ubuntu
|
| 218 |
+
"debian" Debian
|
| 219 |
+
"rhel" RedHat Enterprise Linux
|
| 220 |
+
"centos" CentOS
|
| 221 |
+
"fedora" Fedora
|
| 222 |
+
"sles" SUSE Linux Enterprise Server
|
| 223 |
+
"opensuse" openSUSE
|
| 224 |
+
"amzn" Amazon Linux
|
| 225 |
+
"arch" Arch Linux
|
| 226 |
+
"buildroot" Buildroot
|
| 227 |
+
"cloudlinux" CloudLinux OS
|
| 228 |
+
"exherbo" Exherbo Linux
|
| 229 |
+
"gentoo" GenToo Linux
|
| 230 |
+
"ibm_powerkvm" IBM PowerKVM
|
| 231 |
+
"kvmibm" KVM for IBM z Systems
|
| 232 |
+
"linuxmint" Linux Mint
|
| 233 |
+
"mageia" Mageia
|
| 234 |
+
"mandriva" Mandriva Linux
|
| 235 |
+
"parallels" Parallels
|
| 236 |
+
"pidora" Pidora
|
| 237 |
+
"raspbian" Raspbian
|
| 238 |
+
"oracle" Oracle Linux (and Oracle Enterprise Linux)
|
| 239 |
+
"scientific" Scientific Linux
|
| 240 |
+
"slackware" Slackware
|
| 241 |
+
"xenserver" XenServer
|
| 242 |
+
"openbsd" OpenBSD
|
| 243 |
+
"netbsd" NetBSD
|
| 244 |
+
"freebsd" FreeBSD
|
| 245 |
+
"midnightbsd" MidnightBSD
|
| 246 |
+
"rocky" Rocky Linux
|
| 247 |
+
"aix" AIX
|
| 248 |
+
"guix" Guix System
|
| 249 |
+
"altlinux" ALT Linux
|
| 250 |
+
============== =========================================
|
| 251 |
+
|
| 252 |
+
If you have a need to get distros for reliable IDs added into this set,
|
| 253 |
+
or if you find that the :func:`distro.id` function returns a different
|
| 254 |
+
distro ID for one of the listed distros, please create an issue in the
|
| 255 |
+
`distro issue tracker`_.
|
| 256 |
+
|
| 257 |
+
**Lookup hierarchy and transformations:**
|
| 258 |
+
|
| 259 |
+
First, the ID is obtained from the following sources, in the specified
|
| 260 |
+
order. The first available and non-empty value is used:
|
| 261 |
+
|
| 262 |
+
* the value of the "ID" attribute of the os-release file,
|
| 263 |
+
|
| 264 |
+
* the value of the "Distributor ID" attribute returned by the lsb_release
|
| 265 |
+
command,
|
| 266 |
+
|
| 267 |
+
* the first part of the file name of the distro release file,
|
| 268 |
+
|
| 269 |
+
The so determined ID value then passes the following transformations,
|
| 270 |
+
before it is returned by this method:
|
| 271 |
+
|
| 272 |
+
* it is translated to lower case,
|
| 273 |
+
|
| 274 |
+
* blanks (which should not be there anyway) are translated to underscores,
|
| 275 |
+
|
| 276 |
+
* a normalization of the ID is performed, based upon
|
| 277 |
+
`normalization tables`_. The purpose of this normalization is to ensure
|
| 278 |
+
that the ID is as reliable as possible, even across incompatible changes
|
| 279 |
+
in the OS distributions. A common reason for an incompatible change is
|
| 280 |
+
the addition of an os-release file, or the addition of the lsb_release
|
| 281 |
+
command, with ID values that differ from what was previously determined
|
| 282 |
+
from the distro release file name.
|
| 283 |
+
"""
|
| 284 |
+
return _distro.id()
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def name(pretty: bool = False) -> str:
|
| 288 |
+
"""
|
| 289 |
+
Return the name of the current OS distribution, as a human-readable
|
| 290 |
+
string.
|
| 291 |
+
|
| 292 |
+
If *pretty* is false, the name is returned without version or codename.
|
| 293 |
+
(e.g. "CentOS Linux")
|
| 294 |
+
|
| 295 |
+
If *pretty* is true, the version and codename are appended.
|
| 296 |
+
(e.g. "CentOS Linux 7.1.1503 (Core)")
|
| 297 |
+
|
| 298 |
+
**Lookup hierarchy:**
|
| 299 |
+
|
| 300 |
+
The name is obtained from the following sources, in the specified order.
|
| 301 |
+
The first available and non-empty value is used:
|
| 302 |
+
|
| 303 |
+
* If *pretty* is false:
|
| 304 |
+
|
| 305 |
+
- the value of the "NAME" attribute of the os-release file,
|
| 306 |
+
|
| 307 |
+
- the value of the "Distributor ID" attribute returned by the lsb_release
|
| 308 |
+
command,
|
| 309 |
+
|
| 310 |
+
- the value of the "<name>" field of the distro release file.
|
| 311 |
+
|
| 312 |
+
* If *pretty* is true:
|
| 313 |
+
|
| 314 |
+
- the value of the "PRETTY_NAME" attribute of the os-release file,
|
| 315 |
+
|
| 316 |
+
- the value of the "Description" attribute returned by the lsb_release
|
| 317 |
+
command,
|
| 318 |
+
|
| 319 |
+
- the value of the "<name>" field of the distro release file, appended
|
| 320 |
+
with the value of the pretty version ("<version_id>" and "<codename>"
|
| 321 |
+
fields) of the distro release file, if available.
|
| 322 |
+
"""
|
| 323 |
+
return _distro.name(pretty)
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
def version(pretty: bool = False, best: bool = False) -> str:
|
| 327 |
+
"""
|
| 328 |
+
Return the version of the current OS distribution, as a human-readable
|
| 329 |
+
string.
|
| 330 |
+
|
| 331 |
+
If *pretty* is false, the version is returned without codename (e.g.
|
| 332 |
+
"7.0").
|
| 333 |
+
|
| 334 |
+
If *pretty* is true, the codename in parenthesis is appended, if the
|
| 335 |
+
codename is non-empty (e.g. "7.0 (Maipo)").
|
| 336 |
+
|
| 337 |
+
Some distributions provide version numbers with different precisions in
|
| 338 |
+
the different sources of distribution information. Examining the different
|
| 339 |
+
sources in a fixed priority order does not always yield the most precise
|
| 340 |
+
version (e.g. for Debian 8.2, or CentOS 7.1).
|
| 341 |
+
|
| 342 |
+
Some other distributions may not provide this kind of information. In these
|
| 343 |
+
cases, an empty string would be returned. This behavior can be observed
|
| 344 |
+
with rolling releases distributions (e.g. Arch Linux).
|
| 345 |
+
|
| 346 |
+
The *best* parameter can be used to control the approach for the returned
|
| 347 |
+
version:
|
| 348 |
+
|
| 349 |
+
If *best* is false, the first non-empty version number in priority order of
|
| 350 |
+
the examined sources is returned.
|
| 351 |
+
|
| 352 |
+
If *best* is true, the most precise version number out of all examined
|
| 353 |
+
sources is returned.
|
| 354 |
+
|
| 355 |
+
**Lookup hierarchy:**
|
| 356 |
+
|
| 357 |
+
In all cases, the version number is obtained from the following sources.
|
| 358 |
+
If *best* is false, this order represents the priority order:
|
| 359 |
+
|
| 360 |
+
* the value of the "VERSION_ID" attribute of the os-release file,
|
| 361 |
+
* the value of the "Release" attribute returned by the lsb_release
|
| 362 |
+
command,
|
| 363 |
+
* the version number parsed from the "<version_id>" field of the first line
|
| 364 |
+
of the distro release file,
|
| 365 |
+
* the version number parsed from the "PRETTY_NAME" attribute of the
|
| 366 |
+
os-release file, if it follows the format of the distro release files.
|
| 367 |
+
* the version number parsed from the "Description" attribute returned by
|
| 368 |
+
the lsb_release command, if it follows the format of the distro release
|
| 369 |
+
files.
|
| 370 |
+
"""
|
| 371 |
+
return _distro.version(pretty, best)
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
def version_parts(best: bool = False) -> Tuple[str, str, str]:
|
| 375 |
+
"""
|
| 376 |
+
Return the version of the current OS distribution as a tuple
|
| 377 |
+
``(major, minor, build_number)`` with items as follows:
|
| 378 |
+
|
| 379 |
+
* ``major``: The result of :func:`distro.major_version`.
|
| 380 |
+
|
| 381 |
+
* ``minor``: The result of :func:`distro.minor_version`.
|
| 382 |
+
|
| 383 |
+
* ``build_number``: The result of :func:`distro.build_number`.
|
| 384 |
+
|
| 385 |
+
For a description of the *best* parameter, see the :func:`distro.version`
|
| 386 |
+
method.
|
| 387 |
+
"""
|
| 388 |
+
return _distro.version_parts(best)
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
def major_version(best: bool = False) -> str:
|
| 392 |
+
"""
|
| 393 |
+
Return the major version of the current OS distribution, as a string,
|
| 394 |
+
if provided.
|
| 395 |
+
Otherwise, the empty string is returned. The major version is the first
|
| 396 |
+
part of the dot-separated version string.
|
| 397 |
+
|
| 398 |
+
For a description of the *best* parameter, see the :func:`distro.version`
|
| 399 |
+
method.
|
| 400 |
+
"""
|
| 401 |
+
return _distro.major_version(best)
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
def minor_version(best: bool = False) -> str:
|
| 405 |
+
"""
|
| 406 |
+
Return the minor version of the current OS distribution, as a string,
|
| 407 |
+
if provided.
|
| 408 |
+
Otherwise, the empty string is returned. The minor version is the second
|
| 409 |
+
part of the dot-separated version string.
|
| 410 |
+
|
| 411 |
+
For a description of the *best* parameter, see the :func:`distro.version`
|
| 412 |
+
method.
|
| 413 |
+
"""
|
| 414 |
+
return _distro.minor_version(best)
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def build_number(best: bool = False) -> str:
|
| 418 |
+
"""
|
| 419 |
+
Return the build number of the current OS distribution, as a string,
|
| 420 |
+
if provided.
|
| 421 |
+
Otherwise, the empty string is returned. The build number is the third part
|
| 422 |
+
of the dot-separated version string.
|
| 423 |
+
|
| 424 |
+
For a description of the *best* parameter, see the :func:`distro.version`
|
| 425 |
+
method.
|
| 426 |
+
"""
|
| 427 |
+
return _distro.build_number(best)
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
def like() -> str:
|
| 431 |
+
"""
|
| 432 |
+
Return a space-separated list of distro IDs of distributions that are
|
| 433 |
+
closely related to the current OS distribution in regards to packaging
|
| 434 |
+
and programming interfaces, for example distributions the current
|
| 435 |
+
distribution is a derivative from.
|
| 436 |
+
|
| 437 |
+
**Lookup hierarchy:**
|
| 438 |
+
|
| 439 |
+
This information item is only provided by the os-release file.
|
| 440 |
+
For details, see the description of the "ID_LIKE" attribute in the
|
| 441 |
+
`os-release man page
|
| 442 |
+
<http://www.freedesktop.org/software/systemd/man/os-release.html>`_.
|
| 443 |
+
"""
|
| 444 |
+
return _distro.like()
|
| 445 |
+
|
| 446 |
+
|
| 447 |
+
def codename() -> str:
|
| 448 |
+
"""
|
| 449 |
+
Return the codename for the release of the current OS distribution,
|
| 450 |
+
as a string.
|
| 451 |
+
|
| 452 |
+
If the distribution does not have a codename, an empty string is returned.
|
| 453 |
+
|
| 454 |
+
Note that the returned codename is not always really a codename. For
|
| 455 |
+
example, openSUSE returns "x86_64". This function does not handle such
|
| 456 |
+
cases in any special way and just returns the string it finds, if any.
|
| 457 |
+
|
| 458 |
+
**Lookup hierarchy:**
|
| 459 |
+
|
| 460 |
+
* the codename within the "VERSION" attribute of the os-release file, if
|
| 461 |
+
provided,
|
| 462 |
+
|
| 463 |
+
* the value of the "Codename" attribute returned by the lsb_release
|
| 464 |
+
command,
|
| 465 |
+
|
| 466 |
+
* the value of the "<codename>" field of the distro release file.
|
| 467 |
+
"""
|
| 468 |
+
return _distro.codename()
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
def info(pretty: bool = False, best: bool = False) -> InfoDict:
|
| 472 |
+
"""
|
| 473 |
+
Return certain machine-readable information items about the current OS
|
| 474 |
+
distribution in a dictionary, as shown in the following example:
|
| 475 |
+
|
| 476 |
+
.. sourcecode:: python
|
| 477 |
+
|
| 478 |
+
{
|
| 479 |
+
'id': 'rhel',
|
| 480 |
+
'version': '7.0',
|
| 481 |
+
'version_parts': {
|
| 482 |
+
'major': '7',
|
| 483 |
+
'minor': '0',
|
| 484 |
+
'build_number': ''
|
| 485 |
+
},
|
| 486 |
+
'like': 'fedora',
|
| 487 |
+
'codename': 'Maipo'
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
The dictionary structure and keys are always the same, regardless of which
|
| 491 |
+
information items are available in the underlying data sources. The values
|
| 492 |
+
for the various keys are as follows:
|
| 493 |
+
|
| 494 |
+
* ``id``: The result of :func:`distro.id`.
|
| 495 |
+
|
| 496 |
+
* ``version``: The result of :func:`distro.version`.
|
| 497 |
+
|
| 498 |
+
* ``version_parts -> major``: The result of :func:`distro.major_version`.
|
| 499 |
+
|
| 500 |
+
* ``version_parts -> minor``: The result of :func:`distro.minor_version`.
|
| 501 |
+
|
| 502 |
+
* ``version_parts -> build_number``: The result of
|
| 503 |
+
:func:`distro.build_number`.
|
| 504 |
+
|
| 505 |
+
* ``like``: The result of :func:`distro.like`.
|
| 506 |
+
|
| 507 |
+
* ``codename``: The result of :func:`distro.codename`.
|
| 508 |
+
|
| 509 |
+
For a description of the *pretty* and *best* parameters, see the
|
| 510 |
+
:func:`distro.version` method.
|
| 511 |
+
"""
|
| 512 |
+
return _distro.info(pretty, best)
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
def os_release_info() -> Dict[str, str]:
|
| 516 |
+
"""
|
| 517 |
+
Return a dictionary containing key-value pairs for the information items
|
| 518 |
+
from the os-release file data source of the current OS distribution.
|
| 519 |
+
|
| 520 |
+
See `os-release file`_ for details about these information items.
|
| 521 |
+
"""
|
| 522 |
+
return _distro.os_release_info()
|
| 523 |
+
|
| 524 |
+
|
| 525 |
+
def lsb_release_info() -> Dict[str, str]:
|
| 526 |
+
"""
|
| 527 |
+
Return a dictionary containing key-value pairs for the information items
|
| 528 |
+
from the lsb_release command data source of the current OS distribution.
|
| 529 |
+
|
| 530 |
+
See `lsb_release command output`_ for details about these information
|
| 531 |
+
items.
|
| 532 |
+
"""
|
| 533 |
+
return _distro.lsb_release_info()
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
def distro_release_info() -> Dict[str, str]:
|
| 537 |
+
"""
|
| 538 |
+
Return a dictionary containing key-value pairs for the information items
|
| 539 |
+
from the distro release file data source of the current OS distribution.
|
| 540 |
+
|
| 541 |
+
See `distro release file`_ for details about these information items.
|
| 542 |
+
"""
|
| 543 |
+
return _distro.distro_release_info()
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
def uname_info() -> Dict[str, str]:
|
| 547 |
+
"""
|
| 548 |
+
Return a dictionary containing key-value pairs for the information items
|
| 549 |
+
from the distro release file data source of the current OS distribution.
|
| 550 |
+
"""
|
| 551 |
+
return _distro.uname_info()
|
| 552 |
+
|
| 553 |
+
|
| 554 |
+
def os_release_attr(attribute: str) -> str:
|
| 555 |
+
"""
|
| 556 |
+
Return a single named information item from the os-release file data source
|
| 557 |
+
of the current OS distribution.
|
| 558 |
+
|
| 559 |
+
Parameters:
|
| 560 |
+
|
| 561 |
+
* ``attribute`` (string): Key of the information item.
|
| 562 |
+
|
| 563 |
+
Returns:
|
| 564 |
+
|
| 565 |
+
* (string): Value of the information item, if the item exists.
|
| 566 |
+
The empty string, if the item does not exist.
|
| 567 |
+
|
| 568 |
+
See `os-release file`_ for details about these information items.
|
| 569 |
+
"""
|
| 570 |
+
return _distro.os_release_attr(attribute)
|
| 571 |
+
|
| 572 |
+
|
| 573 |
+
def lsb_release_attr(attribute: str) -> str:
|
| 574 |
+
"""
|
| 575 |
+
Return a single named information item from the lsb_release command output
|
| 576 |
+
data source of the current OS distribution.
|
| 577 |
+
|
| 578 |
+
Parameters:
|
| 579 |
+
|
| 580 |
+
* ``attribute`` (string): Key of the information item.
|
| 581 |
+
|
| 582 |
+
Returns:
|
| 583 |
+
|
| 584 |
+
* (string): Value of the information item, if the item exists.
|
| 585 |
+
The empty string, if the item does not exist.
|
| 586 |
+
|
| 587 |
+
See `lsb_release command output`_ for details about these information
|
| 588 |
+
items.
|
| 589 |
+
"""
|
| 590 |
+
return _distro.lsb_release_attr(attribute)
|
| 591 |
+
|
| 592 |
+
|
| 593 |
+
def distro_release_attr(attribute: str) -> str:
|
| 594 |
+
"""
|
| 595 |
+
Return a single named information item from the distro release file
|
| 596 |
+
data source of the current OS distribution.
|
| 597 |
+
|
| 598 |
+
Parameters:
|
| 599 |
+
|
| 600 |
+
* ``attribute`` (string): Key of the information item.
|
| 601 |
+
|
| 602 |
+
Returns:
|
| 603 |
+
|
| 604 |
+
* (string): Value of the information item, if the item exists.
|
| 605 |
+
The empty string, if the item does not exist.
|
| 606 |
+
|
| 607 |
+
See `distro release file`_ for details about these information items.
|
| 608 |
+
"""
|
| 609 |
+
return _distro.distro_release_attr(attribute)
|
| 610 |
+
|
| 611 |
+
|
| 612 |
+
def uname_attr(attribute: str) -> str:
|
| 613 |
+
"""
|
| 614 |
+
Return a single named information item from the distro release file
|
| 615 |
+
data source of the current OS distribution.
|
| 616 |
+
|
| 617 |
+
Parameters:
|
| 618 |
+
|
| 619 |
+
* ``attribute`` (string): Key of the information item.
|
| 620 |
+
|
| 621 |
+
Returns:
|
| 622 |
+
|
| 623 |
+
* (string): Value of the information item, if the item exists.
|
| 624 |
+
The empty string, if the item does not exist.
|
| 625 |
+
"""
|
| 626 |
+
return _distro.uname_attr(attribute)
|
| 627 |
+
|
| 628 |
+
|
| 629 |
+
try:
|
| 630 |
+
from functools import cached_property
|
| 631 |
+
except ImportError:
|
| 632 |
+
# Python < 3.8
|
| 633 |
+
class cached_property: # type: ignore
|
| 634 |
+
"""A version of @property which caches the value. On access, it calls the
|
| 635 |
+
underlying function and sets the value in `__dict__` so future accesses
|
| 636 |
+
will not re-call the property.
|
| 637 |
+
"""
|
| 638 |
+
|
| 639 |
+
def __init__(self, f: Callable[[Any], Any]) -> None:
|
| 640 |
+
self._fname = f.__name__
|
| 641 |
+
self._f = f
|
| 642 |
+
|
| 643 |
+
def __get__(self, obj: Any, owner: Type[Any]) -> Any:
|
| 644 |
+
assert obj is not None, f"call {self._fname} on an instance"
|
| 645 |
+
ret = obj.__dict__[self._fname] = self._f(obj)
|
| 646 |
+
return ret
|
| 647 |
+
|
| 648 |
+
|
| 649 |
+
class LinuxDistribution:
|
| 650 |
+
"""
|
| 651 |
+
Provides information about a OS distribution.
|
| 652 |
+
|
| 653 |
+
This package creates a private module-global instance of this class with
|
| 654 |
+
default initialization arguments, that is used by the
|
| 655 |
+
`consolidated accessor functions`_ and `single source accessor functions`_.
|
| 656 |
+
By using default initialization arguments, that module-global instance
|
| 657 |
+
returns data about the current OS distribution (i.e. the distro this
|
| 658 |
+
package runs on).
|
| 659 |
+
|
| 660 |
+
Normally, it is not necessary to create additional instances of this class.
|
| 661 |
+
However, in situations where control is needed over the exact data sources
|
| 662 |
+
that are used, instances of this class can be created with a specific
|
| 663 |
+
distro release file, or a specific os-release file, or without invoking the
|
| 664 |
+
lsb_release command.
|
| 665 |
+
"""
|
| 666 |
+
|
| 667 |
+
def __init__(
|
| 668 |
+
self,
|
| 669 |
+
include_lsb: Optional[bool] = None,
|
| 670 |
+
os_release_file: str = "",
|
| 671 |
+
distro_release_file: str = "",
|
| 672 |
+
include_uname: Optional[bool] = None,
|
| 673 |
+
root_dir: Optional[str] = None,
|
| 674 |
+
include_oslevel: Optional[bool] = None,
|
| 675 |
+
) -> None:
|
| 676 |
+
"""
|
| 677 |
+
The initialization method of this class gathers information from the
|
| 678 |
+
available data sources, and stores that in private instance attributes.
|
| 679 |
+
Subsequent access to the information items uses these private instance
|
| 680 |
+
attributes, so that the data sources are read only once.
|
| 681 |
+
|
| 682 |
+
Parameters:
|
| 683 |
+
|
| 684 |
+
* ``include_lsb`` (bool): Controls whether the
|
| 685 |
+
`lsb_release command output`_ is included as a data source.
|
| 686 |
+
|
| 687 |
+
If the lsb_release command is not available in the program execution
|
| 688 |
+
path, the data source for the lsb_release command will be empty.
|
| 689 |
+
|
| 690 |
+
* ``os_release_file`` (string): The path name of the
|
| 691 |
+
`os-release file`_ that is to be used as a data source.
|
| 692 |
+
|
| 693 |
+
An empty string (the default) will cause the default path name to
|
| 694 |
+
be used (see `os-release file`_ for details).
|
| 695 |
+
|
| 696 |
+
If the specified or defaulted os-release file does not exist, the
|
| 697 |
+
data source for the os-release file will be empty.
|
| 698 |
+
|
| 699 |
+
* ``distro_release_file`` (string): The path name of the
|
| 700 |
+
`distro release file`_ that is to be used as a data source.
|
| 701 |
+
|
| 702 |
+
An empty string (the default) will cause a default search algorithm
|
| 703 |
+
to be used (see `distro release file`_ for details).
|
| 704 |
+
|
| 705 |
+
If the specified distro release file does not exist, or if no default
|
| 706 |
+
distro release file can be found, the data source for the distro
|
| 707 |
+
release file will be empty.
|
| 708 |
+
|
| 709 |
+
* ``include_uname`` (bool): Controls whether uname command output is
|
| 710 |
+
included as a data source. If the uname command is not available in
|
| 711 |
+
the program execution path the data source for the uname command will
|
| 712 |
+
be empty.
|
| 713 |
+
|
| 714 |
+
* ``root_dir`` (string): The absolute path to the root directory to use
|
| 715 |
+
to find distro-related information files. Note that ``include_*``
|
| 716 |
+
parameters must not be enabled in combination with ``root_dir``.
|
| 717 |
+
|
| 718 |
+
* ``include_oslevel`` (bool): Controls whether (AIX) oslevel command
|
| 719 |
+
output is included as a data source. If the oslevel command is not
|
| 720 |
+
available in the program execution path the data source will be
|
| 721 |
+
empty.
|
| 722 |
+
|
| 723 |
+
Public instance attributes:
|
| 724 |
+
|
| 725 |
+
* ``os_release_file`` (string): The path name of the
|
| 726 |
+
`os-release file`_ that is actually used as a data source. The
|
| 727 |
+
empty string if no distro release file is used as a data source.
|
| 728 |
+
|
| 729 |
+
* ``distro_release_file`` (string): The path name of the
|
| 730 |
+
`distro release file`_ that is actually used as a data source. The
|
| 731 |
+
empty string if no distro release file is used as a data source.
|
| 732 |
+
|
| 733 |
+
* ``include_lsb`` (bool): The result of the ``include_lsb`` parameter.
|
| 734 |
+
This controls whether the lsb information will be loaded.
|
| 735 |
+
|
| 736 |
+
* ``include_uname`` (bool): The result of the ``include_uname``
|
| 737 |
+
parameter. This controls whether the uname information will
|
| 738 |
+
be loaded.
|
| 739 |
+
|
| 740 |
+
* ``include_oslevel`` (bool): The result of the ``include_oslevel``
|
| 741 |
+
parameter. This controls whether (AIX) oslevel information will be
|
| 742 |
+
loaded.
|
| 743 |
+
|
| 744 |
+
* ``root_dir`` (string): The result of the ``root_dir`` parameter.
|
| 745 |
+
The absolute path to the root directory to use to find distro-related
|
| 746 |
+
information files.
|
| 747 |
+
|
| 748 |
+
Raises:
|
| 749 |
+
|
| 750 |
+
* :py:exc:`ValueError`: Initialization parameters combination is not
|
| 751 |
+
supported.
|
| 752 |
+
|
| 753 |
+
* :py:exc:`OSError`: Some I/O issue with an os-release file or distro
|
| 754 |
+
release file.
|
| 755 |
+
|
| 756 |
+
* :py:exc:`UnicodeError`: A data source has unexpected characters or
|
| 757 |
+
uses an unexpected encoding.
|
| 758 |
+
"""
|
| 759 |
+
self.root_dir = root_dir
|
| 760 |
+
self.etc_dir = os.path.join(root_dir, "etc") if root_dir else _UNIXCONFDIR
|
| 761 |
+
self.usr_lib_dir = (
|
| 762 |
+
os.path.join(root_dir, "usr/lib") if root_dir else _UNIXUSRLIBDIR
|
| 763 |
+
)
|
| 764 |
+
|
| 765 |
+
if os_release_file:
|
| 766 |
+
self.os_release_file = os_release_file
|
| 767 |
+
else:
|
| 768 |
+
etc_dir_os_release_file = os.path.join(self.etc_dir, _OS_RELEASE_BASENAME)
|
| 769 |
+
usr_lib_os_release_file = os.path.join(
|
| 770 |
+
self.usr_lib_dir, _OS_RELEASE_BASENAME
|
| 771 |
+
)
|
| 772 |
+
|
| 773 |
+
# NOTE: The idea is to respect order **and** have it set
|
| 774 |
+
# at all times for API backwards compatibility.
|
| 775 |
+
if os.path.isfile(etc_dir_os_release_file) or not os.path.isfile(
|
| 776 |
+
usr_lib_os_release_file
|
| 777 |
+
):
|
| 778 |
+
self.os_release_file = etc_dir_os_release_file
|
| 779 |
+
else:
|
| 780 |
+
self.os_release_file = usr_lib_os_release_file
|
| 781 |
+
|
| 782 |
+
self.distro_release_file = distro_release_file or "" # updated later
|
| 783 |
+
|
| 784 |
+
is_root_dir_defined = root_dir is not None
|
| 785 |
+
if is_root_dir_defined and (include_lsb or include_uname or include_oslevel):
|
| 786 |
+
raise ValueError(
|
| 787 |
+
"Including subprocess data sources from specific root_dir is disallowed"
|
| 788 |
+
" to prevent false information"
|
| 789 |
+
)
|
| 790 |
+
self.include_lsb = (
|
| 791 |
+
include_lsb if include_lsb is not None else not is_root_dir_defined
|
| 792 |
+
)
|
| 793 |
+
self.include_uname = (
|
| 794 |
+
include_uname if include_uname is not None else not is_root_dir_defined
|
| 795 |
+
)
|
| 796 |
+
self.include_oslevel = (
|
| 797 |
+
include_oslevel if include_oslevel is not None else not is_root_dir_defined
|
| 798 |
+
)
|
| 799 |
+
|
| 800 |
+
def __repr__(self) -> str:
|
| 801 |
+
"""Return repr of all info"""
|
| 802 |
+
return (
|
| 803 |
+
"LinuxDistribution("
|
| 804 |
+
"os_release_file={self.os_release_file!r}, "
|
| 805 |
+
"distro_release_file={self.distro_release_file!r}, "
|
| 806 |
+
"include_lsb={self.include_lsb!r}, "
|
| 807 |
+
"include_uname={self.include_uname!r}, "
|
| 808 |
+
"include_oslevel={self.include_oslevel!r}, "
|
| 809 |
+
"root_dir={self.root_dir!r}, "
|
| 810 |
+
"_os_release_info={self._os_release_info!r}, "
|
| 811 |
+
"_lsb_release_info={self._lsb_release_info!r}, "
|
| 812 |
+
"_distro_release_info={self._distro_release_info!r}, "
|
| 813 |
+
"_uname_info={self._uname_info!r}, "
|
| 814 |
+
"_oslevel_info={self._oslevel_info!r})".format(self=self)
|
| 815 |
+
)
|
| 816 |
+
|
| 817 |
+
def linux_distribution(
|
| 818 |
+
self, full_distribution_name: bool = True
|
| 819 |
+
) -> Tuple[str, str, str]:
|
| 820 |
+
"""
|
| 821 |
+
Return information about the OS distribution that is compatible
|
| 822 |
+
with Python's :func:`platform.linux_distribution`, supporting a subset
|
| 823 |
+
of its parameters.
|
| 824 |
+
|
| 825 |
+
For details, see :func:`distro.linux_distribution`.
|
| 826 |
+
"""
|
| 827 |
+
return (
|
| 828 |
+
self.name() if full_distribution_name else self.id(),
|
| 829 |
+
self.version(),
|
| 830 |
+
self._os_release_info.get("release_codename") or self.codename(),
|
| 831 |
+
)
|
| 832 |
+
|
| 833 |
+
def id(self) -> str:
|
| 834 |
+
"""Return the distro ID of the OS distribution, as a string.
|
| 835 |
+
|
| 836 |
+
For details, see :func:`distro.id`.
|
| 837 |
+
"""
|
| 838 |
+
|
| 839 |
+
def normalize(distro_id: str, table: Dict[str, str]) -> str:
|
| 840 |
+
distro_id = distro_id.lower().replace(" ", "_")
|
| 841 |
+
return table.get(distro_id, distro_id)
|
| 842 |
+
|
| 843 |
+
distro_id = self.os_release_attr("id")
|
| 844 |
+
if distro_id:
|
| 845 |
+
return normalize(distro_id, NORMALIZED_OS_ID)
|
| 846 |
+
|
| 847 |
+
distro_id = self.lsb_release_attr("distributor_id")
|
| 848 |
+
if distro_id:
|
| 849 |
+
return normalize(distro_id, NORMALIZED_LSB_ID)
|
| 850 |
+
|
| 851 |
+
distro_id = self.distro_release_attr("id")
|
| 852 |
+
if distro_id:
|
| 853 |
+
return normalize(distro_id, NORMALIZED_DISTRO_ID)
|
| 854 |
+
|
| 855 |
+
distro_id = self.uname_attr("id")
|
| 856 |
+
if distro_id:
|
| 857 |
+
return normalize(distro_id, NORMALIZED_DISTRO_ID)
|
| 858 |
+
|
| 859 |
+
return ""
|
| 860 |
+
|
| 861 |
+
def name(self, pretty: bool = False) -> str:
|
| 862 |
+
"""
|
| 863 |
+
Return the name of the OS distribution, as a string.
|
| 864 |
+
|
| 865 |
+
For details, see :func:`distro.name`.
|
| 866 |
+
"""
|
| 867 |
+
name = (
|
| 868 |
+
self.os_release_attr("name")
|
| 869 |
+
or self.lsb_release_attr("distributor_id")
|
| 870 |
+
or self.distro_release_attr("name")
|
| 871 |
+
or self.uname_attr("name")
|
| 872 |
+
)
|
| 873 |
+
if pretty:
|
| 874 |
+
name = self.os_release_attr("pretty_name") or self.lsb_release_attr(
|
| 875 |
+
"description"
|
| 876 |
+
)
|
| 877 |
+
if not name:
|
| 878 |
+
name = self.distro_release_attr("name") or self.uname_attr("name")
|
| 879 |
+
version = self.version(pretty=True)
|
| 880 |
+
if version:
|
| 881 |
+
name = f"{name} {version}"
|
| 882 |
+
return name or ""
|
| 883 |
+
|
| 884 |
+
def version(self, pretty: bool = False, best: bool = False) -> str:
|
| 885 |
+
"""
|
| 886 |
+
Return the version of the OS distribution, as a string.
|
| 887 |
+
|
| 888 |
+
For details, see :func:`distro.version`.
|
| 889 |
+
"""
|
| 890 |
+
versions = [
|
| 891 |
+
self.os_release_attr("version_id"),
|
| 892 |
+
self.lsb_release_attr("release"),
|
| 893 |
+
self.distro_release_attr("version_id"),
|
| 894 |
+
self._parse_distro_release_content(self.os_release_attr("pretty_name")).get(
|
| 895 |
+
"version_id", ""
|
| 896 |
+
),
|
| 897 |
+
self._parse_distro_release_content(
|
| 898 |
+
self.lsb_release_attr("description")
|
| 899 |
+
).get("version_id", ""),
|
| 900 |
+
self.uname_attr("release"),
|
| 901 |
+
]
|
| 902 |
+
if self.uname_attr("id").startswith("aix"):
|
| 903 |
+
# On AIX platforms, prefer oslevel command output.
|
| 904 |
+
versions.insert(0, self.oslevel_info())
|
| 905 |
+
elif self.id() == "debian" or "debian" in self.like().split():
|
| 906 |
+
# On Debian-like, add debian_version file content to candidates list.
|
| 907 |
+
versions.append(self._debian_version)
|
| 908 |
+
version = ""
|
| 909 |
+
if best:
|
| 910 |
+
# This algorithm uses the last version in priority order that has
|
| 911 |
+
# the best precision. If the versions are not in conflict, that
|
| 912 |
+
# does not matter; otherwise, using the last one instead of the
|
| 913 |
+
# first one might be considered a surprise.
|
| 914 |
+
for v in versions:
|
| 915 |
+
if v.count(".") > version.count(".") or version == "":
|
| 916 |
+
version = v
|
| 917 |
+
else:
|
| 918 |
+
for v in versions:
|
| 919 |
+
if v != "":
|
| 920 |
+
version = v
|
| 921 |
+
break
|
| 922 |
+
if pretty and version and self.codename():
|
| 923 |
+
version = f"{version} ({self.codename()})"
|
| 924 |
+
return version
|
| 925 |
+
|
| 926 |
+
def version_parts(self, best: bool = False) -> Tuple[str, str, str]:
|
| 927 |
+
"""
|
| 928 |
+
Return the version of the OS distribution, as a tuple of version
|
| 929 |
+
numbers.
|
| 930 |
+
|
| 931 |
+
For details, see :func:`distro.version_parts`.
|
| 932 |
+
"""
|
| 933 |
+
version_str = self.version(best=best)
|
| 934 |
+
if version_str:
|
| 935 |
+
version_regex = re.compile(r"(\d+)\.?(\d+)?\.?(\d+)?")
|
| 936 |
+
matches = version_regex.match(version_str)
|
| 937 |
+
if matches:
|
| 938 |
+
major, minor, build_number = matches.groups()
|
| 939 |
+
return major, minor or "", build_number or ""
|
| 940 |
+
return "", "", ""
|
| 941 |
+
|
| 942 |
+
def major_version(self, best: bool = False) -> str:
|
| 943 |
+
"""
|
| 944 |
+
Return the major version number of the current distribution.
|
| 945 |
+
|
| 946 |
+
For details, see :func:`distro.major_version`.
|
| 947 |
+
"""
|
| 948 |
+
return self.version_parts(best)[0]
|
| 949 |
+
|
| 950 |
+
def minor_version(self, best: bool = False) -> str:
|
| 951 |
+
"""
|
| 952 |
+
Return the minor version number of the current distribution.
|
| 953 |
+
|
| 954 |
+
For details, see :func:`distro.minor_version`.
|
| 955 |
+
"""
|
| 956 |
+
return self.version_parts(best)[1]
|
| 957 |
+
|
| 958 |
+
def build_number(self, best: bool = False) -> str:
|
| 959 |
+
"""
|
| 960 |
+
Return the build number of the current distribution.
|
| 961 |
+
|
| 962 |
+
For details, see :func:`distro.build_number`.
|
| 963 |
+
"""
|
| 964 |
+
return self.version_parts(best)[2]
|
| 965 |
+
|
| 966 |
+
def like(self) -> str:
|
| 967 |
+
"""
|
| 968 |
+
Return the IDs of distributions that are like the OS distribution.
|
| 969 |
+
|
| 970 |
+
For details, see :func:`distro.like`.
|
| 971 |
+
"""
|
| 972 |
+
return self.os_release_attr("id_like") or ""
|
| 973 |
+
|
| 974 |
+
def codename(self) -> str:
|
| 975 |
+
"""
|
| 976 |
+
Return the codename of the OS distribution.
|
| 977 |
+
|
| 978 |
+
For details, see :func:`distro.codename`.
|
| 979 |
+
"""
|
| 980 |
+
try:
|
| 981 |
+
# Handle os_release specially since distros might purposefully set
|
| 982 |
+
# this to empty string to have no codename
|
| 983 |
+
return self._os_release_info["codename"]
|
| 984 |
+
except KeyError:
|
| 985 |
+
return (
|
| 986 |
+
self.lsb_release_attr("codename")
|
| 987 |
+
or self.distro_release_attr("codename")
|
| 988 |
+
or ""
|
| 989 |
+
)
|
| 990 |
+
|
| 991 |
+
def info(self, pretty: bool = False, best: bool = False) -> InfoDict:
|
| 992 |
+
"""
|
| 993 |
+
Return certain machine-readable information about the OS
|
| 994 |
+
distribution.
|
| 995 |
+
|
| 996 |
+
For details, see :func:`distro.info`.
|
| 997 |
+
"""
|
| 998 |
+
return InfoDict(
|
| 999 |
+
id=self.id(),
|
| 1000 |
+
version=self.version(pretty, best),
|
| 1001 |
+
version_parts=VersionDict(
|
| 1002 |
+
major=self.major_version(best),
|
| 1003 |
+
minor=self.minor_version(best),
|
| 1004 |
+
build_number=self.build_number(best),
|
| 1005 |
+
),
|
| 1006 |
+
like=self.like(),
|
| 1007 |
+
codename=self.codename(),
|
| 1008 |
+
)
|
| 1009 |
+
|
| 1010 |
+
def os_release_info(self) -> Dict[str, str]:
|
| 1011 |
+
"""
|
| 1012 |
+
Return a dictionary containing key-value pairs for the information
|
| 1013 |
+
items from the os-release file data source of the OS distribution.
|
| 1014 |
+
|
| 1015 |
+
For details, see :func:`distro.os_release_info`.
|
| 1016 |
+
"""
|
| 1017 |
+
return self._os_release_info
|
| 1018 |
+
|
| 1019 |
+
def lsb_release_info(self) -> Dict[str, str]:
|
| 1020 |
+
"""
|
| 1021 |
+
Return a dictionary containing key-value pairs for the information
|
| 1022 |
+
items from the lsb_release command data source of the OS
|
| 1023 |
+
distribution.
|
| 1024 |
+
|
| 1025 |
+
For details, see :func:`distro.lsb_release_info`.
|
| 1026 |
+
"""
|
| 1027 |
+
return self._lsb_release_info
|
| 1028 |
+
|
| 1029 |
+
def distro_release_info(self) -> Dict[str, str]:
|
| 1030 |
+
"""
|
| 1031 |
+
Return a dictionary containing key-value pairs for the information
|
| 1032 |
+
items from the distro release file data source of the OS
|
| 1033 |
+
distribution.
|
| 1034 |
+
|
| 1035 |
+
For details, see :func:`distro.distro_release_info`.
|
| 1036 |
+
"""
|
| 1037 |
+
return self._distro_release_info
|
| 1038 |
+
|
| 1039 |
+
def uname_info(self) -> Dict[str, str]:
|
| 1040 |
+
"""
|
| 1041 |
+
Return a dictionary containing key-value pairs for the information
|
| 1042 |
+
items from the uname command data source of the OS distribution.
|
| 1043 |
+
|
| 1044 |
+
For details, see :func:`distro.uname_info`.
|
| 1045 |
+
"""
|
| 1046 |
+
return self._uname_info
|
| 1047 |
+
|
| 1048 |
+
def oslevel_info(self) -> str:
|
| 1049 |
+
"""
|
| 1050 |
+
Return AIX' oslevel command output.
|
| 1051 |
+
"""
|
| 1052 |
+
return self._oslevel_info
|
| 1053 |
+
|
| 1054 |
+
def os_release_attr(self, attribute: str) -> str:
|
| 1055 |
+
"""
|
| 1056 |
+
Return a single named information item from the os-release file data
|
| 1057 |
+
source of the OS distribution.
|
| 1058 |
+
|
| 1059 |
+
For details, see :func:`distro.os_release_attr`.
|
| 1060 |
+
"""
|
| 1061 |
+
return self._os_release_info.get(attribute, "")
|
| 1062 |
+
|
| 1063 |
+
def lsb_release_attr(self, attribute: str) -> str:
|
| 1064 |
+
"""
|
| 1065 |
+
Return a single named information item from the lsb_release command
|
| 1066 |
+
output data source of the OS distribution.
|
| 1067 |
+
|
| 1068 |
+
For details, see :func:`distro.lsb_release_attr`.
|
| 1069 |
+
"""
|
| 1070 |
+
return self._lsb_release_info.get(attribute, "")
|
| 1071 |
+
|
| 1072 |
+
def distro_release_attr(self, attribute: str) -> str:
|
| 1073 |
+
"""
|
| 1074 |
+
Return a single named information item from the distro release file
|
| 1075 |
+
data source of the OS distribution.
|
| 1076 |
+
|
| 1077 |
+
For details, see :func:`distro.distro_release_attr`.
|
| 1078 |
+
"""
|
| 1079 |
+
return self._distro_release_info.get(attribute, "")
|
| 1080 |
+
|
| 1081 |
+
def uname_attr(self, attribute: str) -> str:
|
| 1082 |
+
"""
|
| 1083 |
+
Return a single named information item from the uname command
|
| 1084 |
+
output data source of the OS distribution.
|
| 1085 |
+
|
| 1086 |
+
For details, see :func:`distro.uname_attr`.
|
| 1087 |
+
"""
|
| 1088 |
+
return self._uname_info.get(attribute, "")
|
| 1089 |
+
|
| 1090 |
+
@cached_property
|
| 1091 |
+
def _os_release_info(self) -> Dict[str, str]:
|
| 1092 |
+
"""
|
| 1093 |
+
Get the information items from the specified os-release file.
|
| 1094 |
+
|
| 1095 |
+
Returns:
|
| 1096 |
+
A dictionary containing all information items.
|
| 1097 |
+
"""
|
| 1098 |
+
if os.path.isfile(self.os_release_file):
|
| 1099 |
+
with open(self.os_release_file, encoding="utf-8") as release_file:
|
| 1100 |
+
return self._parse_os_release_content(release_file)
|
| 1101 |
+
return {}
|
| 1102 |
+
|
| 1103 |
+
@staticmethod
|
| 1104 |
+
def _parse_os_release_content(lines: TextIO) -> Dict[str, str]:
|
| 1105 |
+
"""
|
| 1106 |
+
Parse the lines of an os-release file.
|
| 1107 |
+
|
| 1108 |
+
Parameters:
|
| 1109 |
+
|
| 1110 |
+
* lines: Iterable through the lines in the os-release file.
|
| 1111 |
+
Each line must be a unicode string or a UTF-8 encoded byte
|
| 1112 |
+
string.
|
| 1113 |
+
|
| 1114 |
+
Returns:
|
| 1115 |
+
A dictionary containing all information items.
|
| 1116 |
+
"""
|
| 1117 |
+
props = {}
|
| 1118 |
+
lexer = shlex.shlex(lines, posix=True)
|
| 1119 |
+
lexer.whitespace_split = True
|
| 1120 |
+
|
| 1121 |
+
tokens = list(lexer)
|
| 1122 |
+
for token in tokens:
|
| 1123 |
+
# At this point, all shell-like parsing has been done (i.e.
|
| 1124 |
+
# comments processed, quotes and backslash escape sequences
|
| 1125 |
+
# processed, multi-line values assembled, trailing newlines
|
| 1126 |
+
# stripped, etc.), so the tokens are now either:
|
| 1127 |
+
# * variable assignments: var=value
|
| 1128 |
+
# * commands or their arguments (not allowed in os-release)
|
| 1129 |
+
# Ignore any tokens that are not variable assignments
|
| 1130 |
+
if "=" in token:
|
| 1131 |
+
k, v = token.split("=", 1)
|
| 1132 |
+
props[k.lower()] = v
|
| 1133 |
+
|
| 1134 |
+
if "version" in props:
|
| 1135 |
+
# extract release codename (if any) from version attribute
|
| 1136 |
+
match = re.search(r"\((\D+)\)|,\s*(\D+)", props["version"])
|
| 1137 |
+
if match:
|
| 1138 |
+
release_codename = match.group(1) or match.group(2)
|
| 1139 |
+
props["codename"] = props["release_codename"] = release_codename
|
| 1140 |
+
|
| 1141 |
+
if "version_codename" in props:
|
| 1142 |
+
# os-release added a version_codename field. Use that in
|
| 1143 |
+
# preference to anything else Note that some distros purposefully
|
| 1144 |
+
# do not have code names. They should be setting
|
| 1145 |
+
# version_codename=""
|
| 1146 |
+
props["codename"] = props["version_codename"]
|
| 1147 |
+
elif "ubuntu_codename" in props:
|
| 1148 |
+
# Same as above but a non-standard field name used on older Ubuntus
|
| 1149 |
+
props["codename"] = props["ubuntu_codename"]
|
| 1150 |
+
|
| 1151 |
+
return props
|
| 1152 |
+
|
| 1153 |
+
@cached_property
|
| 1154 |
+
def _lsb_release_info(self) -> Dict[str, str]:
|
| 1155 |
+
"""
|
| 1156 |
+
Get the information items from the lsb_release command output.
|
| 1157 |
+
|
| 1158 |
+
Returns:
|
| 1159 |
+
A dictionary containing all information items.
|
| 1160 |
+
"""
|
| 1161 |
+
if not self.include_lsb:
|
| 1162 |
+
return {}
|
| 1163 |
+
try:
|
| 1164 |
+
cmd = ("lsb_release", "-a")
|
| 1165 |
+
stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
|
| 1166 |
+
# Command not found or lsb_release returned error
|
| 1167 |
+
except (OSError, subprocess.CalledProcessError):
|
| 1168 |
+
return {}
|
| 1169 |
+
content = self._to_str(stdout).splitlines()
|
| 1170 |
+
return self._parse_lsb_release_content(content)
|
| 1171 |
+
|
| 1172 |
+
@staticmethod
|
| 1173 |
+
def _parse_lsb_release_content(lines: Iterable[str]) -> Dict[str, str]:
|
| 1174 |
+
"""
|
| 1175 |
+
Parse the output of the lsb_release command.
|
| 1176 |
+
|
| 1177 |
+
Parameters:
|
| 1178 |
+
|
| 1179 |
+
* lines: Iterable through the lines of the lsb_release output.
|
| 1180 |
+
Each line must be a unicode string or a UTF-8 encoded byte
|
| 1181 |
+
string.
|
| 1182 |
+
|
| 1183 |
+
Returns:
|
| 1184 |
+
A dictionary containing all information items.
|
| 1185 |
+
"""
|
| 1186 |
+
props = {}
|
| 1187 |
+
for line in lines:
|
| 1188 |
+
kv = line.strip("\n").split(":", 1)
|
| 1189 |
+
if len(kv) != 2:
|
| 1190 |
+
# Ignore lines without colon.
|
| 1191 |
+
continue
|
| 1192 |
+
k, v = kv
|
| 1193 |
+
props.update({k.replace(" ", "_").lower(): v.strip()})
|
| 1194 |
+
return props
|
| 1195 |
+
|
| 1196 |
+
@cached_property
|
| 1197 |
+
def _uname_info(self) -> Dict[str, str]:
|
| 1198 |
+
if not self.include_uname:
|
| 1199 |
+
return {}
|
| 1200 |
+
try:
|
| 1201 |
+
cmd = ("uname", "-rs")
|
| 1202 |
+
stdout = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
|
| 1203 |
+
except OSError:
|
| 1204 |
+
return {}
|
| 1205 |
+
content = self._to_str(stdout).splitlines()
|
| 1206 |
+
return self._parse_uname_content(content)
|
| 1207 |
+
|
| 1208 |
+
@cached_property
|
| 1209 |
+
def _oslevel_info(self) -> str:
|
| 1210 |
+
if not self.include_oslevel:
|
| 1211 |
+
return ""
|
| 1212 |
+
try:
|
| 1213 |
+
stdout = subprocess.check_output("oslevel", stderr=subprocess.DEVNULL)
|
| 1214 |
+
except (OSError, subprocess.CalledProcessError):
|
| 1215 |
+
return ""
|
| 1216 |
+
return self._to_str(stdout).strip()
|
| 1217 |
+
|
| 1218 |
+
@cached_property
|
| 1219 |
+
def _debian_version(self) -> str:
|
| 1220 |
+
try:
|
| 1221 |
+
with open(
|
| 1222 |
+
os.path.join(self.etc_dir, "debian_version"), encoding="ascii"
|
| 1223 |
+
) as fp:
|
| 1224 |
+
return fp.readline().rstrip()
|
| 1225 |
+
except FileNotFoundError:
|
| 1226 |
+
return ""
|
| 1227 |
+
|
| 1228 |
+
@staticmethod
|
| 1229 |
+
def _parse_uname_content(lines: Sequence[str]) -> Dict[str, str]:
|
| 1230 |
+
if not lines:
|
| 1231 |
+
return {}
|
| 1232 |
+
props = {}
|
| 1233 |
+
match = re.search(r"^([^\s]+)\s+([\d\.]+)", lines[0].strip())
|
| 1234 |
+
if match:
|
| 1235 |
+
name, version = match.groups()
|
| 1236 |
+
|
| 1237 |
+
# This is to prevent the Linux kernel version from
|
| 1238 |
+
# appearing as the 'best' version on otherwise
|
| 1239 |
+
# identifiable distributions.
|
| 1240 |
+
if name == "Linux":
|
| 1241 |
+
return {}
|
| 1242 |
+
props["id"] = name.lower()
|
| 1243 |
+
props["name"] = name
|
| 1244 |
+
props["release"] = version
|
| 1245 |
+
return props
|
| 1246 |
+
|
| 1247 |
+
@staticmethod
|
| 1248 |
+
def _to_str(bytestring: bytes) -> str:
|
| 1249 |
+
encoding = sys.getfilesystemencoding()
|
| 1250 |
+
return bytestring.decode(encoding)
|
| 1251 |
+
|
| 1252 |
+
@cached_property
|
| 1253 |
+
def _distro_release_info(self) -> Dict[str, str]:
|
| 1254 |
+
"""
|
| 1255 |
+
Get the information items from the specified distro release file.
|
| 1256 |
+
|
| 1257 |
+
Returns:
|
| 1258 |
+
A dictionary containing all information items.
|
| 1259 |
+
"""
|
| 1260 |
+
if self.distro_release_file:
|
| 1261 |
+
# If it was specified, we use it and parse what we can, even if
|
| 1262 |
+
# its file name or content does not match the expected pattern.
|
| 1263 |
+
distro_info = self._parse_distro_release_file(self.distro_release_file)
|
| 1264 |
+
basename = os.path.basename(self.distro_release_file)
|
| 1265 |
+
# The file name pattern for user-specified distro release files
|
| 1266 |
+
# is somewhat more tolerant (compared to when searching for the
|
| 1267 |
+
# file), because we want to use what was specified as best as
|
| 1268 |
+
# possible.
|
| 1269 |
+
match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
|
| 1270 |
+
else:
|
| 1271 |
+
try:
|
| 1272 |
+
basenames = [
|
| 1273 |
+
basename
|
| 1274 |
+
for basename in os.listdir(self.etc_dir)
|
| 1275 |
+
if basename not in _DISTRO_RELEASE_IGNORE_BASENAMES
|
| 1276 |
+
and os.path.isfile(os.path.join(self.etc_dir, basename))
|
| 1277 |
+
]
|
| 1278 |
+
# We sort for repeatability in cases where there are multiple
|
| 1279 |
+
# distro specific files; e.g. CentOS, Oracle, Enterprise all
|
| 1280 |
+
# containing `redhat-release` on top of their own.
|
| 1281 |
+
basenames.sort()
|
| 1282 |
+
except OSError:
|
| 1283 |
+
# This may occur when /etc is not readable but we can't be
|
| 1284 |
+
# sure about the *-release files. Check common entries of
|
| 1285 |
+
# /etc for information. If they turn out to not be there the
|
| 1286 |
+
# error is handled in `_parse_distro_release_file()`.
|
| 1287 |
+
basenames = _DISTRO_RELEASE_BASENAMES
|
| 1288 |
+
for basename in basenames:
|
| 1289 |
+
match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
|
| 1290 |
+
if match is None:
|
| 1291 |
+
continue
|
| 1292 |
+
filepath = os.path.join(self.etc_dir, basename)
|
| 1293 |
+
distro_info = self._parse_distro_release_file(filepath)
|
| 1294 |
+
# The name is always present if the pattern matches.
|
| 1295 |
+
if "name" not in distro_info:
|
| 1296 |
+
continue
|
| 1297 |
+
self.distro_release_file = filepath
|
| 1298 |
+
break
|
| 1299 |
+
else: # the loop didn't "break": no candidate.
|
| 1300 |
+
return {}
|
| 1301 |
+
|
| 1302 |
+
if match is not None:
|
| 1303 |
+
distro_info["id"] = match.group(1)
|
| 1304 |
+
|
| 1305 |
+
# CloudLinux < 7: manually enrich info with proper id.
|
| 1306 |
+
if "cloudlinux" in distro_info.get("name", "").lower():
|
| 1307 |
+
distro_info["id"] = "cloudlinux"
|
| 1308 |
+
|
| 1309 |
+
return distro_info
|
| 1310 |
+
|
| 1311 |
+
def _parse_distro_release_file(self, filepath: str) -> Dict[str, str]:
|
| 1312 |
+
"""
|
| 1313 |
+
Parse a distro release file.
|
| 1314 |
+
|
| 1315 |
+
Parameters:
|
| 1316 |
+
|
| 1317 |
+
* filepath: Path name of the distro release file.
|
| 1318 |
+
|
| 1319 |
+
Returns:
|
| 1320 |
+
A dictionary containing all information items.
|
| 1321 |
+
"""
|
| 1322 |
+
try:
|
| 1323 |
+
with open(filepath, encoding="utf-8") as fp:
|
| 1324 |
+
# Only parse the first line. For instance, on SLES there
|
| 1325 |
+
# are multiple lines. We don't want them...
|
| 1326 |
+
return self._parse_distro_release_content(fp.readline())
|
| 1327 |
+
except OSError:
|
| 1328 |
+
# Ignore not being able to read a specific, seemingly version
|
| 1329 |
+
# related file.
|
| 1330 |
+
# See https://github.com/python-distro/distro/issues/162
|
| 1331 |
+
return {}
|
| 1332 |
+
|
| 1333 |
+
@staticmethod
|
| 1334 |
+
def _parse_distro_release_content(line: str) -> Dict[str, str]:
|
| 1335 |
+
"""
|
| 1336 |
+
Parse a line from a distro release file.
|
| 1337 |
+
|
| 1338 |
+
Parameters:
|
| 1339 |
+
* line: Line from the distro release file. Must be a unicode string
|
| 1340 |
+
or a UTF-8 encoded byte string.
|
| 1341 |
+
|
| 1342 |
+
Returns:
|
| 1343 |
+
A dictionary containing all information items.
|
| 1344 |
+
"""
|
| 1345 |
+
matches = _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN.match(line.strip()[::-1])
|
| 1346 |
+
distro_info = {}
|
| 1347 |
+
if matches:
|
| 1348 |
+
# regexp ensures non-None
|
| 1349 |
+
distro_info["name"] = matches.group(3)[::-1]
|
| 1350 |
+
if matches.group(2):
|
| 1351 |
+
distro_info["version_id"] = matches.group(2)[::-1]
|
| 1352 |
+
if matches.group(1):
|
| 1353 |
+
distro_info["codename"] = matches.group(1)[::-1]
|
| 1354 |
+
elif line:
|
| 1355 |
+
distro_info["name"] = line.strip()
|
| 1356 |
+
return distro_info
|
| 1357 |
+
|
| 1358 |
+
|
| 1359 |
+
_distro = LinuxDistribution()
|
| 1360 |
+
|
| 1361 |
+
|
| 1362 |
+
def main() -> None:
|
| 1363 |
+
logger = logging.getLogger(__name__)
|
| 1364 |
+
logger.setLevel(logging.DEBUG)
|
| 1365 |
+
logger.addHandler(logging.StreamHandler(sys.stdout))
|
| 1366 |
+
|
| 1367 |
+
parser = argparse.ArgumentParser(description="OS distro info tool")
|
| 1368 |
+
parser.add_argument(
|
| 1369 |
+
"--json", "-j", help="Output in machine readable format", action="store_true"
|
| 1370 |
+
)
|
| 1371 |
+
|
| 1372 |
+
parser.add_argument(
|
| 1373 |
+
"--root-dir",
|
| 1374 |
+
"-r",
|
| 1375 |
+
type=str,
|
| 1376 |
+
dest="root_dir",
|
| 1377 |
+
help="Path to the root filesystem directory (defaults to /)",
|
| 1378 |
+
)
|
| 1379 |
+
|
| 1380 |
+
args = parser.parse_args()
|
| 1381 |
+
|
| 1382 |
+
if args.root_dir:
|
| 1383 |
+
dist = LinuxDistribution(
|
| 1384 |
+
include_lsb=False,
|
| 1385 |
+
include_uname=False,
|
| 1386 |
+
include_oslevel=False,
|
| 1387 |
+
root_dir=args.root_dir,
|
| 1388 |
+
)
|
| 1389 |
+
else:
|
| 1390 |
+
dist = _distro
|
| 1391 |
+
|
| 1392 |
+
if args.json:
|
| 1393 |
+
logger.info(json.dumps(dist.info(), indent=4, sort_keys=True))
|
| 1394 |
+
else:
|
| 1395 |
+
logger.info("Name: %s", dist.name(pretty=True))
|
| 1396 |
+
distribution_version = dist.version(pretty=True)
|
| 1397 |
+
logger.info("Version: %s", distribution_version)
|
| 1398 |
+
distribution_codename = dist.codename()
|
| 1399 |
+
logger.info("Codename: %s", distribution_codename)
|
| 1400 |
+
|
| 1401 |
+
|
| 1402 |
+
if __name__ == "__main__":
|
| 1403 |
+
main()
|
lib/python3.12/site-packages/pip/_vendor/distro/py.typed
ADDED
|
File without changes
|
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (565 Bytes). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc
ADDED
|
Binary file (14 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc
ADDED
|
Binary file (3.25 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/metadata.cpython-312.pyc
ADDED
|
Binary file (27.2 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc
ADDED
|
Binary file (23 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc
ADDED
|
Binary file (6.64 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-312.pyc
ADDED
|
Binary file (20.5 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/pygments/__init__.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Pygments
|
| 3 |
+
~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Pygments is a syntax highlighting package written in Python.
|
| 6 |
+
|
| 7 |
+
It is a generic syntax highlighter for general use in all kinds of software
|
| 8 |
+
such as forum systems, wikis or other applications that need to prettify
|
| 9 |
+
source code. Highlights are:
|
| 10 |
+
|
| 11 |
+
* a wide range of common languages and markup formats is supported
|
| 12 |
+
* special attention is paid to details, increasing quality by a fair amount
|
| 13 |
+
* support for new languages and formats are added easily
|
| 14 |
+
* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image
|
| 15 |
+
formats that PIL supports, and ANSI sequences
|
| 16 |
+
* it is usable as a command-line tool and as a library
|
| 17 |
+
* ... and it highlights even Brainfuck!
|
| 18 |
+
|
| 19 |
+
The `Pygments master branch`_ is installable with ``easy_install Pygments==dev``.
|
| 20 |
+
|
| 21 |
+
.. _Pygments master branch:
|
| 22 |
+
https://github.com/pygments/pygments/archive/master.zip#egg=Pygments-dev
|
| 23 |
+
|
| 24 |
+
:copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
|
| 25 |
+
:license: BSD, see LICENSE for details.
|
| 26 |
+
"""
|
| 27 |
+
from io import StringIO, BytesIO
|
| 28 |
+
|
| 29 |
+
__version__ = '2.18.0'
|
| 30 |
+
__docformat__ = 'restructuredtext'
|
| 31 |
+
|
| 32 |
+
__all__ = ['lex', 'format', 'highlight']
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def lex(code, lexer):
|
| 36 |
+
"""
|
| 37 |
+
Lex `code` with the `lexer` (must be a `Lexer` instance)
|
| 38 |
+
and return an iterable of tokens. Currently, this only calls
|
| 39 |
+
`lexer.get_tokens()`.
|
| 40 |
+
"""
|
| 41 |
+
try:
|
| 42 |
+
return lexer.get_tokens(code)
|
| 43 |
+
except TypeError:
|
| 44 |
+
# Heuristic to catch a common mistake.
|
| 45 |
+
from pip._vendor.pygments.lexer import RegexLexer
|
| 46 |
+
if isinstance(lexer, type) and issubclass(lexer, RegexLexer):
|
| 47 |
+
raise TypeError('lex() argument must be a lexer instance, '
|
| 48 |
+
'not a class')
|
| 49 |
+
raise
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def format(tokens, formatter, outfile=None): # pylint: disable=redefined-builtin
|
| 53 |
+
"""
|
| 54 |
+
Format ``tokens`` (an iterable of tokens) with the formatter ``formatter``
|
| 55 |
+
(a `Formatter` instance).
|
| 56 |
+
|
| 57 |
+
If ``outfile`` is given and a valid file object (an object with a
|
| 58 |
+
``write`` method), the result will be written to it, otherwise it
|
| 59 |
+
is returned as a string.
|
| 60 |
+
"""
|
| 61 |
+
try:
|
| 62 |
+
if not outfile:
|
| 63 |
+
realoutfile = getattr(formatter, 'encoding', None) and BytesIO() or StringIO()
|
| 64 |
+
formatter.format(tokens, realoutfile)
|
| 65 |
+
return realoutfile.getvalue()
|
| 66 |
+
else:
|
| 67 |
+
formatter.format(tokens, outfile)
|
| 68 |
+
except TypeError:
|
| 69 |
+
# Heuristic to catch a common mistake.
|
| 70 |
+
from pip._vendor.pygments.formatter import Formatter
|
| 71 |
+
if isinstance(formatter, type) and issubclass(formatter, Formatter):
|
| 72 |
+
raise TypeError('format() argument must be a formatter instance, '
|
| 73 |
+
'not a class')
|
| 74 |
+
raise
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def highlight(code, lexer, formatter, outfile=None):
|
| 78 |
+
"""
|
| 79 |
+
This is the most high-level highlighting function. It combines `lex` and
|
| 80 |
+
`format` in one function.
|
| 81 |
+
"""
|
| 82 |
+
return format(lex(code, lexer), formatter, outfile)
|
lib/python3.12/site-packages/pip/_vendor/pygments/__main__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
pygments.__main__
|
| 3 |
+
~~~~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Main entry point for ``python -m pygments``.
|
| 6 |
+
|
| 7 |
+
:copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
|
| 8 |
+
:license: BSD, see LICENSE for details.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import sys
|
| 12 |
+
from pip._vendor.pygments.cmdline import main
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
sys.exit(main(sys.argv))
|
| 16 |
+
except KeyboardInterrupt:
|
| 17 |
+
sys.exit(1)
|
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc
ADDED
|
Binary file (38.4 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc
ADDED
|
Binary file (1.57 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-312.pyc
ADDED
|
Binary file (6.7 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc
ADDED
|
Binary file (33 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/pygments/cmdline.py
ADDED
|
@@ -0,0 +1,668 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
pygments.cmdline
|
| 3 |
+
~~~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Command line interface.
|
| 6 |
+
|
| 7 |
+
:copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
|
| 8 |
+
:license: BSD, see LICENSE for details.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import os
|
| 12 |
+
import sys
|
| 13 |
+
import shutil
|
| 14 |
+
import argparse
|
| 15 |
+
from textwrap import dedent
|
| 16 |
+
|
| 17 |
+
from pip._vendor.pygments import __version__, highlight
|
| 18 |
+
from pip._vendor.pygments.util import ClassNotFound, OptionError, docstring_headline, \
|
| 19 |
+
guess_decode, guess_decode_from_terminal, terminal_encoding, \
|
| 20 |
+
UnclosingTextIOWrapper
|
| 21 |
+
from pip._vendor.pygments.lexers import get_all_lexers, get_lexer_by_name, guess_lexer, \
|
| 22 |
+
load_lexer_from_file, get_lexer_for_filename, find_lexer_class_for_filename
|
| 23 |
+
from pip._vendor.pygments.lexers.special import TextLexer
|
| 24 |
+
from pip._vendor.pygments.formatters.latex import LatexEmbeddedLexer, LatexFormatter
|
| 25 |
+
from pip._vendor.pygments.formatters import get_all_formatters, get_formatter_by_name, \
|
| 26 |
+
load_formatter_from_file, get_formatter_for_filename, find_formatter_class
|
| 27 |
+
from pip._vendor.pygments.formatters.terminal import TerminalFormatter
|
| 28 |
+
from pip._vendor.pygments.formatters.terminal256 import Terminal256Formatter, TerminalTrueColorFormatter
|
| 29 |
+
from pip._vendor.pygments.filters import get_all_filters, find_filter_class
|
| 30 |
+
from pip._vendor.pygments.styles import get_all_styles, get_style_by_name
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _parse_options(o_strs):
|
| 34 |
+
opts = {}
|
| 35 |
+
if not o_strs:
|
| 36 |
+
return opts
|
| 37 |
+
for o_str in o_strs:
|
| 38 |
+
if not o_str.strip():
|
| 39 |
+
continue
|
| 40 |
+
o_args = o_str.split(',')
|
| 41 |
+
for o_arg in o_args:
|
| 42 |
+
o_arg = o_arg.strip()
|
| 43 |
+
try:
|
| 44 |
+
o_key, o_val = o_arg.split('=', 1)
|
| 45 |
+
o_key = o_key.strip()
|
| 46 |
+
o_val = o_val.strip()
|
| 47 |
+
except ValueError:
|
| 48 |
+
opts[o_arg] = True
|
| 49 |
+
else:
|
| 50 |
+
opts[o_key] = o_val
|
| 51 |
+
return opts
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _parse_filters(f_strs):
|
| 55 |
+
filters = []
|
| 56 |
+
if not f_strs:
|
| 57 |
+
return filters
|
| 58 |
+
for f_str in f_strs:
|
| 59 |
+
if ':' in f_str:
|
| 60 |
+
fname, fopts = f_str.split(':', 1)
|
| 61 |
+
filters.append((fname, _parse_options([fopts])))
|
| 62 |
+
else:
|
| 63 |
+
filters.append((f_str, {}))
|
| 64 |
+
return filters
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def _print_help(what, name):
|
| 68 |
+
try:
|
| 69 |
+
if what == 'lexer':
|
| 70 |
+
cls = get_lexer_by_name(name)
|
| 71 |
+
print(f"Help on the {cls.name} lexer:")
|
| 72 |
+
print(dedent(cls.__doc__))
|
| 73 |
+
elif what == 'formatter':
|
| 74 |
+
cls = find_formatter_class(name)
|
| 75 |
+
print(f"Help on the {cls.name} formatter:")
|
| 76 |
+
print(dedent(cls.__doc__))
|
| 77 |
+
elif what == 'filter':
|
| 78 |
+
cls = find_filter_class(name)
|
| 79 |
+
print(f"Help on the {name} filter:")
|
| 80 |
+
print(dedent(cls.__doc__))
|
| 81 |
+
return 0
|
| 82 |
+
except (AttributeError, ValueError):
|
| 83 |
+
print(f"{what} not found!", file=sys.stderr)
|
| 84 |
+
return 1
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def _print_list(what):
|
| 88 |
+
if what == 'lexer':
|
| 89 |
+
print()
|
| 90 |
+
print("Lexers:")
|
| 91 |
+
print("~~~~~~~")
|
| 92 |
+
|
| 93 |
+
info = []
|
| 94 |
+
for fullname, names, exts, _ in get_all_lexers():
|
| 95 |
+
tup = (', '.join(names)+':', fullname,
|
| 96 |
+
exts and '(filenames ' + ', '.join(exts) + ')' or '')
|
| 97 |
+
info.append(tup)
|
| 98 |
+
info.sort()
|
| 99 |
+
for i in info:
|
| 100 |
+
print(('* {}\n {} {}').format(*i))
|
| 101 |
+
|
| 102 |
+
elif what == 'formatter':
|
| 103 |
+
print()
|
| 104 |
+
print("Formatters:")
|
| 105 |
+
print("~~~~~~~~~~~")
|
| 106 |
+
|
| 107 |
+
info = []
|
| 108 |
+
for cls in get_all_formatters():
|
| 109 |
+
doc = docstring_headline(cls)
|
| 110 |
+
tup = (', '.join(cls.aliases) + ':', doc, cls.filenames and
|
| 111 |
+
'(filenames ' + ', '.join(cls.filenames) + ')' or '')
|
| 112 |
+
info.append(tup)
|
| 113 |
+
info.sort()
|
| 114 |
+
for i in info:
|
| 115 |
+
print(('* {}\n {} {}').format(*i))
|
| 116 |
+
|
| 117 |
+
elif what == 'filter':
|
| 118 |
+
print()
|
| 119 |
+
print("Filters:")
|
| 120 |
+
print("~~~~~~~~")
|
| 121 |
+
|
| 122 |
+
for name in get_all_filters():
|
| 123 |
+
cls = find_filter_class(name)
|
| 124 |
+
print("* " + name + ':')
|
| 125 |
+
print(f" {docstring_headline(cls)}")
|
| 126 |
+
|
| 127 |
+
elif what == 'style':
|
| 128 |
+
print()
|
| 129 |
+
print("Styles:")
|
| 130 |
+
print("~~~~~~~")
|
| 131 |
+
|
| 132 |
+
for name in get_all_styles():
|
| 133 |
+
cls = get_style_by_name(name)
|
| 134 |
+
print("* " + name + ':')
|
| 135 |
+
print(f" {docstring_headline(cls)}")
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def _print_list_as_json(requested_items):
|
| 139 |
+
import json
|
| 140 |
+
result = {}
|
| 141 |
+
if 'lexer' in requested_items:
|
| 142 |
+
info = {}
|
| 143 |
+
for fullname, names, filenames, mimetypes in get_all_lexers():
|
| 144 |
+
info[fullname] = {
|
| 145 |
+
'aliases': names,
|
| 146 |
+
'filenames': filenames,
|
| 147 |
+
'mimetypes': mimetypes
|
| 148 |
+
}
|
| 149 |
+
result['lexers'] = info
|
| 150 |
+
|
| 151 |
+
if 'formatter' in requested_items:
|
| 152 |
+
info = {}
|
| 153 |
+
for cls in get_all_formatters():
|
| 154 |
+
doc = docstring_headline(cls)
|
| 155 |
+
info[cls.name] = {
|
| 156 |
+
'aliases': cls.aliases,
|
| 157 |
+
'filenames': cls.filenames,
|
| 158 |
+
'doc': doc
|
| 159 |
+
}
|
| 160 |
+
result['formatters'] = info
|
| 161 |
+
|
| 162 |
+
if 'filter' in requested_items:
|
| 163 |
+
info = {}
|
| 164 |
+
for name in get_all_filters():
|
| 165 |
+
cls = find_filter_class(name)
|
| 166 |
+
info[name] = {
|
| 167 |
+
'doc': docstring_headline(cls)
|
| 168 |
+
}
|
| 169 |
+
result['filters'] = info
|
| 170 |
+
|
| 171 |
+
if 'style' in requested_items:
|
| 172 |
+
info = {}
|
| 173 |
+
for name in get_all_styles():
|
| 174 |
+
cls = get_style_by_name(name)
|
| 175 |
+
info[name] = {
|
| 176 |
+
'doc': docstring_headline(cls)
|
| 177 |
+
}
|
| 178 |
+
result['styles'] = info
|
| 179 |
+
|
| 180 |
+
json.dump(result, sys.stdout)
|
| 181 |
+
|
| 182 |
+
def main_inner(parser, argns):
|
| 183 |
+
if argns.help:
|
| 184 |
+
parser.print_help()
|
| 185 |
+
return 0
|
| 186 |
+
|
| 187 |
+
if argns.V:
|
| 188 |
+
print(f'Pygments version {__version__}, (c) 2006-2024 by Georg Brandl, Matthäus '
|
| 189 |
+
'Chajdas and contributors.')
|
| 190 |
+
return 0
|
| 191 |
+
|
| 192 |
+
def is_only_option(opt):
|
| 193 |
+
return not any(v for (k, v) in vars(argns).items() if k != opt)
|
| 194 |
+
|
| 195 |
+
# handle ``pygmentize -L``
|
| 196 |
+
if argns.L is not None:
|
| 197 |
+
arg_set = set()
|
| 198 |
+
for k, v in vars(argns).items():
|
| 199 |
+
if v:
|
| 200 |
+
arg_set.add(k)
|
| 201 |
+
|
| 202 |
+
arg_set.discard('L')
|
| 203 |
+
arg_set.discard('json')
|
| 204 |
+
|
| 205 |
+
if arg_set:
|
| 206 |
+
parser.print_help(sys.stderr)
|
| 207 |
+
return 2
|
| 208 |
+
|
| 209 |
+
# print version
|
| 210 |
+
if not argns.json:
|
| 211 |
+
main(['', '-V'])
|
| 212 |
+
allowed_types = {'lexer', 'formatter', 'filter', 'style'}
|
| 213 |
+
largs = [arg.rstrip('s') for arg in argns.L]
|
| 214 |
+
if any(arg not in allowed_types for arg in largs):
|
| 215 |
+
parser.print_help(sys.stderr)
|
| 216 |
+
return 0
|
| 217 |
+
if not largs:
|
| 218 |
+
largs = allowed_types
|
| 219 |
+
if not argns.json:
|
| 220 |
+
for arg in largs:
|
| 221 |
+
_print_list(arg)
|
| 222 |
+
else:
|
| 223 |
+
_print_list_as_json(largs)
|
| 224 |
+
return 0
|
| 225 |
+
|
| 226 |
+
# handle ``pygmentize -H``
|
| 227 |
+
if argns.H:
|
| 228 |
+
if not is_only_option('H'):
|
| 229 |
+
parser.print_help(sys.stderr)
|
| 230 |
+
return 2
|
| 231 |
+
what, name = argns.H
|
| 232 |
+
if what not in ('lexer', 'formatter', 'filter'):
|
| 233 |
+
parser.print_help(sys.stderr)
|
| 234 |
+
return 2
|
| 235 |
+
return _print_help(what, name)
|
| 236 |
+
|
| 237 |
+
# parse -O options
|
| 238 |
+
parsed_opts = _parse_options(argns.O or [])
|
| 239 |
+
|
| 240 |
+
# parse -P options
|
| 241 |
+
for p_opt in argns.P or []:
|
| 242 |
+
try:
|
| 243 |
+
name, value = p_opt.split('=', 1)
|
| 244 |
+
except ValueError:
|
| 245 |
+
parsed_opts[p_opt] = True
|
| 246 |
+
else:
|
| 247 |
+
parsed_opts[name] = value
|
| 248 |
+
|
| 249 |
+
# encodings
|
| 250 |
+
inencoding = parsed_opts.get('inencoding', parsed_opts.get('encoding'))
|
| 251 |
+
outencoding = parsed_opts.get('outencoding', parsed_opts.get('encoding'))
|
| 252 |
+
|
| 253 |
+
# handle ``pygmentize -N``
|
| 254 |
+
if argns.N:
|
| 255 |
+
lexer = find_lexer_class_for_filename(argns.N)
|
| 256 |
+
if lexer is None:
|
| 257 |
+
lexer = TextLexer
|
| 258 |
+
|
| 259 |
+
print(lexer.aliases[0])
|
| 260 |
+
return 0
|
| 261 |
+
|
| 262 |
+
# handle ``pygmentize -C``
|
| 263 |
+
if argns.C:
|
| 264 |
+
inp = sys.stdin.buffer.read()
|
| 265 |
+
try:
|
| 266 |
+
lexer = guess_lexer(inp, inencoding=inencoding)
|
| 267 |
+
except ClassNotFound:
|
| 268 |
+
lexer = TextLexer
|
| 269 |
+
|
| 270 |
+
print(lexer.aliases[0])
|
| 271 |
+
return 0
|
| 272 |
+
|
| 273 |
+
# handle ``pygmentize -S``
|
| 274 |
+
S_opt = argns.S
|
| 275 |
+
a_opt = argns.a
|
| 276 |
+
if S_opt is not None:
|
| 277 |
+
f_opt = argns.f
|
| 278 |
+
if not f_opt:
|
| 279 |
+
parser.print_help(sys.stderr)
|
| 280 |
+
return 2
|
| 281 |
+
if argns.l or argns.INPUTFILE:
|
| 282 |
+
parser.print_help(sys.stderr)
|
| 283 |
+
return 2
|
| 284 |
+
|
| 285 |
+
try:
|
| 286 |
+
parsed_opts['style'] = S_opt
|
| 287 |
+
fmter = get_formatter_by_name(f_opt, **parsed_opts)
|
| 288 |
+
except ClassNotFound as err:
|
| 289 |
+
print(err, file=sys.stderr)
|
| 290 |
+
return 1
|
| 291 |
+
|
| 292 |
+
print(fmter.get_style_defs(a_opt or ''))
|
| 293 |
+
return 0
|
| 294 |
+
|
| 295 |
+
# if no -S is given, -a is not allowed
|
| 296 |
+
if argns.a is not None:
|
| 297 |
+
parser.print_help(sys.stderr)
|
| 298 |
+
return 2
|
| 299 |
+
|
| 300 |
+
# parse -F options
|
| 301 |
+
F_opts = _parse_filters(argns.F or [])
|
| 302 |
+
|
| 303 |
+
# -x: allow custom (eXternal) lexers and formatters
|
| 304 |
+
allow_custom_lexer_formatter = bool(argns.x)
|
| 305 |
+
|
| 306 |
+
# select lexer
|
| 307 |
+
lexer = None
|
| 308 |
+
|
| 309 |
+
# given by name?
|
| 310 |
+
lexername = argns.l
|
| 311 |
+
if lexername:
|
| 312 |
+
# custom lexer, located relative to user's cwd
|
| 313 |
+
if allow_custom_lexer_formatter and '.py' in lexername:
|
| 314 |
+
try:
|
| 315 |
+
filename = None
|
| 316 |
+
name = None
|
| 317 |
+
if ':' in lexername:
|
| 318 |
+
filename, name = lexername.rsplit(':', 1)
|
| 319 |
+
|
| 320 |
+
if '.py' in name:
|
| 321 |
+
# This can happen on Windows: If the lexername is
|
| 322 |
+
# C:\lexer.py -- return to normal load path in that case
|
| 323 |
+
name = None
|
| 324 |
+
|
| 325 |
+
if filename and name:
|
| 326 |
+
lexer = load_lexer_from_file(filename, name,
|
| 327 |
+
**parsed_opts)
|
| 328 |
+
else:
|
| 329 |
+
lexer = load_lexer_from_file(lexername, **parsed_opts)
|
| 330 |
+
except ClassNotFound as err:
|
| 331 |
+
print('Error:', err, file=sys.stderr)
|
| 332 |
+
return 1
|
| 333 |
+
else:
|
| 334 |
+
try:
|
| 335 |
+
lexer = get_lexer_by_name(lexername, **parsed_opts)
|
| 336 |
+
except (OptionError, ClassNotFound) as err:
|
| 337 |
+
print('Error:', err, file=sys.stderr)
|
| 338 |
+
return 1
|
| 339 |
+
|
| 340 |
+
# read input code
|
| 341 |
+
code = None
|
| 342 |
+
|
| 343 |
+
if argns.INPUTFILE:
|
| 344 |
+
if argns.s:
|
| 345 |
+
print('Error: -s option not usable when input file specified',
|
| 346 |
+
file=sys.stderr)
|
| 347 |
+
return 2
|
| 348 |
+
|
| 349 |
+
infn = argns.INPUTFILE
|
| 350 |
+
try:
|
| 351 |
+
with open(infn, 'rb') as infp:
|
| 352 |
+
code = infp.read()
|
| 353 |
+
except Exception as err:
|
| 354 |
+
print('Error: cannot read infile:', err, file=sys.stderr)
|
| 355 |
+
return 1
|
| 356 |
+
if not inencoding:
|
| 357 |
+
code, inencoding = guess_decode(code)
|
| 358 |
+
|
| 359 |
+
# do we have to guess the lexer?
|
| 360 |
+
if not lexer:
|
| 361 |
+
try:
|
| 362 |
+
lexer = get_lexer_for_filename(infn, code, **parsed_opts)
|
| 363 |
+
except ClassNotFound as err:
|
| 364 |
+
if argns.g:
|
| 365 |
+
try:
|
| 366 |
+
lexer = guess_lexer(code, **parsed_opts)
|
| 367 |
+
except ClassNotFound:
|
| 368 |
+
lexer = TextLexer(**parsed_opts)
|
| 369 |
+
else:
|
| 370 |
+
print('Error:', err, file=sys.stderr)
|
| 371 |
+
return 1
|
| 372 |
+
except OptionError as err:
|
| 373 |
+
print('Error:', err, file=sys.stderr)
|
| 374 |
+
return 1
|
| 375 |
+
|
| 376 |
+
elif not argns.s: # treat stdin as full file (-s support is later)
|
| 377 |
+
# read code from terminal, always in binary mode since we want to
|
| 378 |
+
# decode ourselves and be tolerant with it
|
| 379 |
+
code = sys.stdin.buffer.read() # use .buffer to get a binary stream
|
| 380 |
+
if not inencoding:
|
| 381 |
+
code, inencoding = guess_decode_from_terminal(code, sys.stdin)
|
| 382 |
+
# else the lexer will do the decoding
|
| 383 |
+
if not lexer:
|
| 384 |
+
try:
|
| 385 |
+
lexer = guess_lexer(code, **parsed_opts)
|
| 386 |
+
except ClassNotFound:
|
| 387 |
+
lexer = TextLexer(**parsed_opts)
|
| 388 |
+
|
| 389 |
+
else: # -s option needs a lexer with -l
|
| 390 |
+
if not lexer:
|
| 391 |
+
print('Error: when using -s a lexer has to be selected with -l',
|
| 392 |
+
file=sys.stderr)
|
| 393 |
+
return 2
|
| 394 |
+
|
| 395 |
+
# process filters
|
| 396 |
+
for fname, fopts in F_opts:
|
| 397 |
+
try:
|
| 398 |
+
lexer.add_filter(fname, **fopts)
|
| 399 |
+
except ClassNotFound as err:
|
| 400 |
+
print('Error:', err, file=sys.stderr)
|
| 401 |
+
return 1
|
| 402 |
+
|
| 403 |
+
# select formatter
|
| 404 |
+
outfn = argns.o
|
| 405 |
+
fmter = argns.f
|
| 406 |
+
if fmter:
|
| 407 |
+
# custom formatter, located relative to user's cwd
|
| 408 |
+
if allow_custom_lexer_formatter and '.py' in fmter:
|
| 409 |
+
try:
|
| 410 |
+
filename = None
|
| 411 |
+
name = None
|
| 412 |
+
if ':' in fmter:
|
| 413 |
+
# Same logic as above for custom lexer
|
| 414 |
+
filename, name = fmter.rsplit(':', 1)
|
| 415 |
+
|
| 416 |
+
if '.py' in name:
|
| 417 |
+
name = None
|
| 418 |
+
|
| 419 |
+
if filename and name:
|
| 420 |
+
fmter = load_formatter_from_file(filename, name,
|
| 421 |
+
**parsed_opts)
|
| 422 |
+
else:
|
| 423 |
+
fmter = load_formatter_from_file(fmter, **parsed_opts)
|
| 424 |
+
except ClassNotFound as err:
|
| 425 |
+
print('Error:', err, file=sys.stderr)
|
| 426 |
+
return 1
|
| 427 |
+
else:
|
| 428 |
+
try:
|
| 429 |
+
fmter = get_formatter_by_name(fmter, **parsed_opts)
|
| 430 |
+
except (OptionError, ClassNotFound) as err:
|
| 431 |
+
print('Error:', err, file=sys.stderr)
|
| 432 |
+
return 1
|
| 433 |
+
|
| 434 |
+
if outfn:
|
| 435 |
+
if not fmter:
|
| 436 |
+
try:
|
| 437 |
+
fmter = get_formatter_for_filename(outfn, **parsed_opts)
|
| 438 |
+
except (OptionError, ClassNotFound) as err:
|
| 439 |
+
print('Error:', err, file=sys.stderr)
|
| 440 |
+
return 1
|
| 441 |
+
try:
|
| 442 |
+
outfile = open(outfn, 'wb')
|
| 443 |
+
except Exception as err:
|
| 444 |
+
print('Error: cannot open outfile:', err, file=sys.stderr)
|
| 445 |
+
return 1
|
| 446 |
+
else:
|
| 447 |
+
if not fmter:
|
| 448 |
+
if os.environ.get('COLORTERM','') in ('truecolor', '24bit'):
|
| 449 |
+
fmter = TerminalTrueColorFormatter(**parsed_opts)
|
| 450 |
+
elif '256' in os.environ.get('TERM', ''):
|
| 451 |
+
fmter = Terminal256Formatter(**parsed_opts)
|
| 452 |
+
else:
|
| 453 |
+
fmter = TerminalFormatter(**parsed_opts)
|
| 454 |
+
outfile = sys.stdout.buffer
|
| 455 |
+
|
| 456 |
+
# determine output encoding if not explicitly selected
|
| 457 |
+
if not outencoding:
|
| 458 |
+
if outfn:
|
| 459 |
+
# output file? use lexer encoding for now (can still be None)
|
| 460 |
+
fmter.encoding = inencoding
|
| 461 |
+
else:
|
| 462 |
+
# else use terminal encoding
|
| 463 |
+
fmter.encoding = terminal_encoding(sys.stdout)
|
| 464 |
+
|
| 465 |
+
# provide coloring under Windows, if possible
|
| 466 |
+
if not outfn and sys.platform in ('win32', 'cygwin') and \
|
| 467 |
+
fmter.name in ('Terminal', 'Terminal256'): # pragma: no cover
|
| 468 |
+
# unfortunately colorama doesn't support binary streams on Py3
|
| 469 |
+
outfile = UnclosingTextIOWrapper(outfile, encoding=fmter.encoding)
|
| 470 |
+
fmter.encoding = None
|
| 471 |
+
try:
|
| 472 |
+
import colorama.initialise
|
| 473 |
+
except ImportError:
|
| 474 |
+
pass
|
| 475 |
+
else:
|
| 476 |
+
outfile = colorama.initialise.wrap_stream(
|
| 477 |
+
outfile, convert=None, strip=None, autoreset=False, wrap=True)
|
| 478 |
+
|
| 479 |
+
# When using the LaTeX formatter and the option `escapeinside` is
|
| 480 |
+
# specified, we need a special lexer which collects escaped text
|
| 481 |
+
# before running the chosen language lexer.
|
| 482 |
+
escapeinside = parsed_opts.get('escapeinside', '')
|
| 483 |
+
if len(escapeinside) == 2 and isinstance(fmter, LatexFormatter):
|
| 484 |
+
left = escapeinside[0]
|
| 485 |
+
right = escapeinside[1]
|
| 486 |
+
lexer = LatexEmbeddedLexer(left, right, lexer)
|
| 487 |
+
|
| 488 |
+
# ... and do it!
|
| 489 |
+
if not argns.s:
|
| 490 |
+
# process whole input as per normal...
|
| 491 |
+
try:
|
| 492 |
+
highlight(code, lexer, fmter, outfile)
|
| 493 |
+
finally:
|
| 494 |
+
if outfn:
|
| 495 |
+
outfile.close()
|
| 496 |
+
return 0
|
| 497 |
+
else:
|
| 498 |
+
# line by line processing of stdin (eg: for 'tail -f')...
|
| 499 |
+
try:
|
| 500 |
+
while 1:
|
| 501 |
+
line = sys.stdin.buffer.readline()
|
| 502 |
+
if not line:
|
| 503 |
+
break
|
| 504 |
+
if not inencoding:
|
| 505 |
+
line = guess_decode_from_terminal(line, sys.stdin)[0]
|
| 506 |
+
highlight(line, lexer, fmter, outfile)
|
| 507 |
+
if hasattr(outfile, 'flush'):
|
| 508 |
+
outfile.flush()
|
| 509 |
+
return 0
|
| 510 |
+
except KeyboardInterrupt: # pragma: no cover
|
| 511 |
+
return 0
|
| 512 |
+
finally:
|
| 513 |
+
if outfn:
|
| 514 |
+
outfile.close()
|
| 515 |
+
|
| 516 |
+
|
| 517 |
+
class HelpFormatter(argparse.HelpFormatter):
|
| 518 |
+
def __init__(self, prog, indent_increment=2, max_help_position=16, width=None):
|
| 519 |
+
if width is None:
|
| 520 |
+
try:
|
| 521 |
+
width = shutil.get_terminal_size().columns - 2
|
| 522 |
+
except Exception:
|
| 523 |
+
pass
|
| 524 |
+
argparse.HelpFormatter.__init__(self, prog, indent_increment,
|
| 525 |
+
max_help_position, width)
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
def main(args=sys.argv):
|
| 529 |
+
"""
|
| 530 |
+
Main command line entry point.
|
| 531 |
+
"""
|
| 532 |
+
desc = "Highlight an input file and write the result to an output file."
|
| 533 |
+
parser = argparse.ArgumentParser(description=desc, add_help=False,
|
| 534 |
+
formatter_class=HelpFormatter)
|
| 535 |
+
|
| 536 |
+
operation = parser.add_argument_group('Main operation')
|
| 537 |
+
lexersel = operation.add_mutually_exclusive_group()
|
| 538 |
+
lexersel.add_argument(
|
| 539 |
+
'-l', metavar='LEXER',
|
| 540 |
+
help='Specify the lexer to use. (Query names with -L.) If not '
|
| 541 |
+
'given and -g is not present, the lexer is guessed from the filename.')
|
| 542 |
+
lexersel.add_argument(
|
| 543 |
+
'-g', action='store_true',
|
| 544 |
+
help='Guess the lexer from the file contents, or pass through '
|
| 545 |
+
'as plain text if nothing can be guessed.')
|
| 546 |
+
operation.add_argument(
|
| 547 |
+
'-F', metavar='FILTER[:options]', action='append',
|
| 548 |
+
help='Add a filter to the token stream. (Query names with -L.) '
|
| 549 |
+
'Filter options are given after a colon if necessary.')
|
| 550 |
+
operation.add_argument(
|
| 551 |
+
'-f', metavar='FORMATTER',
|
| 552 |
+
help='Specify the formatter to use. (Query names with -L.) '
|
| 553 |
+
'If not given, the formatter is guessed from the output filename, '
|
| 554 |
+
'and defaults to the terminal formatter if the output is to the '
|
| 555 |
+
'terminal or an unknown file extension.')
|
| 556 |
+
operation.add_argument(
|
| 557 |
+
'-O', metavar='OPTION=value[,OPTION=value,...]', action='append',
|
| 558 |
+
help='Give options to the lexer and formatter as a comma-separated '
|
| 559 |
+
'list of key-value pairs. '
|
| 560 |
+
'Example: `-O bg=light,python=cool`.')
|
| 561 |
+
operation.add_argument(
|
| 562 |
+
'-P', metavar='OPTION=value', action='append',
|
| 563 |
+
help='Give a single option to the lexer and formatter - with this '
|
| 564 |
+
'you can pass options whose value contains commas and equal signs. '
|
| 565 |
+
'Example: `-P "heading=Pygments, the Python highlighter"`.')
|
| 566 |
+
operation.add_argument(
|
| 567 |
+
'-o', metavar='OUTPUTFILE',
|
| 568 |
+
help='Where to write the output. Defaults to standard output.')
|
| 569 |
+
|
| 570 |
+
operation.add_argument(
|
| 571 |
+
'INPUTFILE', nargs='?',
|
| 572 |
+
help='Where to read the input. Defaults to standard input.')
|
| 573 |
+
|
| 574 |
+
flags = parser.add_argument_group('Operation flags')
|
| 575 |
+
flags.add_argument(
|
| 576 |
+
'-v', action='store_true',
|
| 577 |
+
help='Print a detailed traceback on unhandled exceptions, which '
|
| 578 |
+
'is useful for debugging and bug reports.')
|
| 579 |
+
flags.add_argument(
|
| 580 |
+
'-s', action='store_true',
|
| 581 |
+
help='Process lines one at a time until EOF, rather than waiting to '
|
| 582 |
+
'process the entire file. This only works for stdin, only for lexers '
|
| 583 |
+
'with no line-spanning constructs, and is intended for streaming '
|
| 584 |
+
'input such as you get from `tail -f`. '
|
| 585 |
+
'Example usage: `tail -f sql.log | pygmentize -s -l sql`.')
|
| 586 |
+
flags.add_argument(
|
| 587 |
+
'-x', action='store_true',
|
| 588 |
+
help='Allow custom lexers and formatters to be loaded from a .py file '
|
| 589 |
+
'relative to the current working directory. For example, '
|
| 590 |
+
'`-l ./customlexer.py -x`. By default, this option expects a file '
|
| 591 |
+
'with a class named CustomLexer or CustomFormatter; you can also '
|
| 592 |
+
'specify your own class name with a colon (`-l ./lexer.py:MyLexer`). '
|
| 593 |
+
'Users should be very careful not to use this option with untrusted '
|
| 594 |
+
'files, because it will import and run them.')
|
| 595 |
+
flags.add_argument('--json', help='Output as JSON. This can '
|
| 596 |
+
'be only used in conjunction with -L.',
|
| 597 |
+
default=False,
|
| 598 |
+
action='store_true')
|
| 599 |
+
|
| 600 |
+
special_modes_group = parser.add_argument_group(
|
| 601 |
+
'Special modes - do not do any highlighting')
|
| 602 |
+
special_modes = special_modes_group.add_mutually_exclusive_group()
|
| 603 |
+
special_modes.add_argument(
|
| 604 |
+
'-S', metavar='STYLE -f formatter',
|
| 605 |
+
help='Print style definitions for STYLE for a formatter '
|
| 606 |
+
'given with -f. The argument given by -a is formatter '
|
| 607 |
+
'dependent.')
|
| 608 |
+
special_modes.add_argument(
|
| 609 |
+
'-L', nargs='*', metavar='WHAT',
|
| 610 |
+
help='List lexers, formatters, styles or filters -- '
|
| 611 |
+
'give additional arguments for the thing(s) you want to list '
|
| 612 |
+
'(e.g. "styles"), or omit them to list everything.')
|
| 613 |
+
special_modes.add_argument(
|
| 614 |
+
'-N', metavar='FILENAME',
|
| 615 |
+
help='Guess and print out a lexer name based solely on the given '
|
| 616 |
+
'filename. Does not take input or highlight anything. If no specific '
|
| 617 |
+
'lexer can be determined, "text" is printed.')
|
| 618 |
+
special_modes.add_argument(
|
| 619 |
+
'-C', action='store_true',
|
| 620 |
+
help='Like -N, but print out a lexer name based solely on '
|
| 621 |
+
'a given content from standard input.')
|
| 622 |
+
special_modes.add_argument(
|
| 623 |
+
'-H', action='store', nargs=2, metavar=('NAME', 'TYPE'),
|
| 624 |
+
help='Print detailed help for the object <name> of type <type>, '
|
| 625 |
+
'where <type> is one of "lexer", "formatter" or "filter".')
|
| 626 |
+
special_modes.add_argument(
|
| 627 |
+
'-V', action='store_true',
|
| 628 |
+
help='Print the package version.')
|
| 629 |
+
special_modes.add_argument(
|
| 630 |
+
'-h', '--help', action='store_true',
|
| 631 |
+
help='Print this help.')
|
| 632 |
+
special_modes_group.add_argument(
|
| 633 |
+
'-a', metavar='ARG',
|
| 634 |
+
help='Formatter-specific additional argument for the -S (print '
|
| 635 |
+
'style sheet) mode.')
|
| 636 |
+
|
| 637 |
+
argns = parser.parse_args(args[1:])
|
| 638 |
+
|
| 639 |
+
try:
|
| 640 |
+
return main_inner(parser, argns)
|
| 641 |
+
except BrokenPipeError:
|
| 642 |
+
# someone closed our stdout, e.g. by quitting a pager.
|
| 643 |
+
return 0
|
| 644 |
+
except Exception:
|
| 645 |
+
if argns.v:
|
| 646 |
+
print(file=sys.stderr)
|
| 647 |
+
print('*' * 65, file=sys.stderr)
|
| 648 |
+
print('An unhandled exception occurred while highlighting.',
|
| 649 |
+
file=sys.stderr)
|
| 650 |
+
print('Please report the whole traceback to the issue tracker at',
|
| 651 |
+
file=sys.stderr)
|
| 652 |
+
print('<https://github.com/pygments/pygments/issues>.',
|
| 653 |
+
file=sys.stderr)
|
| 654 |
+
print('*' * 65, file=sys.stderr)
|
| 655 |
+
print(file=sys.stderr)
|
| 656 |
+
raise
|
| 657 |
+
import traceback
|
| 658 |
+
info = traceback.format_exception(*sys.exc_info())
|
| 659 |
+
msg = info[-1].strip()
|
| 660 |
+
if len(info) >= 3:
|
| 661 |
+
# extract relevant file and position info
|
| 662 |
+
msg += '\n (f{})'.format(info[-2].split('\n')[0].strip()[1:])
|
| 663 |
+
print(file=sys.stderr)
|
| 664 |
+
print('*** Error while highlighting:', file=sys.stderr)
|
| 665 |
+
print(msg, file=sys.stderr)
|
| 666 |
+
print('*** If this is a bug you want to report, please rerun with -v.',
|
| 667 |
+
file=sys.stderr)
|
| 668 |
+
return 1
|
lib/python3.12/site-packages/pip/_vendor/pygments/console.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
pygments.console
|
| 3 |
+
~~~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Format colored console output.
|
| 6 |
+
|
| 7 |
+
:copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
|
| 8 |
+
:license: BSD, see LICENSE for details.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
esc = "\x1b["
|
| 12 |
+
|
| 13 |
+
codes = {}
|
| 14 |
+
codes[""] = ""
|
| 15 |
+
codes["reset"] = esc + "39;49;00m"
|
| 16 |
+
|
| 17 |
+
codes["bold"] = esc + "01m"
|
| 18 |
+
codes["faint"] = esc + "02m"
|
| 19 |
+
codes["standout"] = esc + "03m"
|
| 20 |
+
codes["underline"] = esc + "04m"
|
| 21 |
+
codes["blink"] = esc + "05m"
|
| 22 |
+
codes["overline"] = esc + "06m"
|
| 23 |
+
|
| 24 |
+
dark_colors = ["black", "red", "green", "yellow", "blue",
|
| 25 |
+
"magenta", "cyan", "gray"]
|
| 26 |
+
light_colors = ["brightblack", "brightred", "brightgreen", "brightyellow", "brightblue",
|
| 27 |
+
"brightmagenta", "brightcyan", "white"]
|
| 28 |
+
|
| 29 |
+
x = 30
|
| 30 |
+
for dark, light in zip(dark_colors, light_colors):
|
| 31 |
+
codes[dark] = esc + "%im" % x
|
| 32 |
+
codes[light] = esc + "%im" % (60 + x)
|
| 33 |
+
x += 1
|
| 34 |
+
|
| 35 |
+
del dark, light, x
|
| 36 |
+
|
| 37 |
+
codes["white"] = codes["bold"]
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def reset_color():
|
| 41 |
+
return codes["reset"]
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def colorize(color_key, text):
|
| 45 |
+
return codes[color_key] + text + codes["reset"]
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def ansiformat(attr, text):
|
| 49 |
+
"""
|
| 50 |
+
Format ``text`` with a color and/or some attributes::
|
| 51 |
+
|
| 52 |
+
color normal color
|
| 53 |
+
*color* bold color
|
| 54 |
+
_color_ underlined color
|
| 55 |
+
+color+ blinking color
|
| 56 |
+
"""
|
| 57 |
+
result = []
|
| 58 |
+
if attr[:1] == attr[-1:] == '+':
|
| 59 |
+
result.append(codes['blink'])
|
| 60 |
+
attr = attr[1:-1]
|
| 61 |
+
if attr[:1] == attr[-1:] == '*':
|
| 62 |
+
result.append(codes['bold'])
|
| 63 |
+
attr = attr[1:-1]
|
| 64 |
+
if attr[:1] == attr[-1:] == '_':
|
| 65 |
+
result.append(codes['underline'])
|
| 66 |
+
attr = attr[1:-1]
|
| 67 |
+
result.append(codes[attr])
|
| 68 |
+
result.append(text)
|
| 69 |
+
result.append(codes['reset'])
|
| 70 |
+
return ''.join(result)
|
lib/python3.12/site-packages/pip/_vendor/pygments/filter.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
pygments.filter
|
| 3 |
+
~~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Module that implements the default filter.
|
| 6 |
+
|
| 7 |
+
:copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
|
| 8 |
+
:license: BSD, see LICENSE for details.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def apply_filters(stream, filters, lexer=None):
|
| 13 |
+
"""
|
| 14 |
+
Use this method to apply an iterable of filters to
|
| 15 |
+
a stream. If lexer is given it's forwarded to the
|
| 16 |
+
filter, otherwise the filter receives `None`.
|
| 17 |
+
"""
|
| 18 |
+
def _apply(filter_, stream):
|
| 19 |
+
yield from filter_.filter(lexer, stream)
|
| 20 |
+
for filter_ in filters:
|
| 21 |
+
stream = _apply(filter_, stream)
|
| 22 |
+
return stream
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def simplefilter(f):
|
| 26 |
+
"""
|
| 27 |
+
Decorator that converts a function into a filter::
|
| 28 |
+
|
| 29 |
+
@simplefilter
|
| 30 |
+
def lowercase(self, lexer, stream, options):
|
| 31 |
+
for ttype, value in stream:
|
| 32 |
+
yield ttype, value.lower()
|
| 33 |
+
"""
|
| 34 |
+
return type(f.__name__, (FunctionFilter,), {
|
| 35 |
+
'__module__': getattr(f, '__module__'),
|
| 36 |
+
'__doc__': f.__doc__,
|
| 37 |
+
'function': f,
|
| 38 |
+
})
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class Filter:
|
| 42 |
+
"""
|
| 43 |
+
Default filter. Subclass this class or use the `simplefilter`
|
| 44 |
+
decorator to create own filters.
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
def __init__(self, **options):
|
| 48 |
+
self.options = options
|
| 49 |
+
|
| 50 |
+
def filter(self, lexer, stream):
|
| 51 |
+
raise NotImplementedError()
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class FunctionFilter(Filter):
|
| 55 |
+
"""
|
| 56 |
+
Abstract class used by `simplefilter` to create simple
|
| 57 |
+
function filters on the fly. The `simplefilter` decorator
|
| 58 |
+
automatically creates subclasses of this class for
|
| 59 |
+
functions passed to it.
|
| 60 |
+
"""
|
| 61 |
+
function = None
|
| 62 |
+
|
| 63 |
+
def __init__(self, **options):
|
| 64 |
+
if not hasattr(self, 'function'):
|
| 65 |
+
raise TypeError(f'{self.__class__.__name__!r} used without bound function')
|
| 66 |
+
Filter.__init__(self, **options)
|
| 67 |
+
|
| 68 |
+
def filter(self, lexer, stream):
|
| 69 |
+
# pylint: disable=not-callable
|
| 70 |
+
yield from self.function(lexer, stream, self.options)
|
lib/python3.12/site-packages/pip/_vendor/pygments/filters/__init__.py
ADDED
|
@@ -0,0 +1,940 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
pygments.filters
|
| 3 |
+
~~~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Module containing filter lookup functions and default
|
| 6 |
+
filters.
|
| 7 |
+
|
| 8 |
+
:copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
|
| 9 |
+
:license: BSD, see LICENSE for details.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import re
|
| 13 |
+
|
| 14 |
+
from pip._vendor.pygments.token import String, Comment, Keyword, Name, Error, Whitespace, \
|
| 15 |
+
string_to_tokentype
|
| 16 |
+
from pip._vendor.pygments.filter import Filter
|
| 17 |
+
from pip._vendor.pygments.util import get_list_opt, get_int_opt, get_bool_opt, \
|
| 18 |
+
get_choice_opt, ClassNotFound, OptionError
|
| 19 |
+
from pip._vendor.pygments.plugin import find_plugin_filters
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def find_filter_class(filtername):
|
| 23 |
+
"""Lookup a filter by name. Return None if not found."""
|
| 24 |
+
if filtername in FILTERS:
|
| 25 |
+
return FILTERS[filtername]
|
| 26 |
+
for name, cls in find_plugin_filters():
|
| 27 |
+
if name == filtername:
|
| 28 |
+
return cls
|
| 29 |
+
return None
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def get_filter_by_name(filtername, **options):
|
| 33 |
+
"""Return an instantiated filter.
|
| 34 |
+
|
| 35 |
+
Options are passed to the filter initializer if wanted.
|
| 36 |
+
Raise a ClassNotFound if not found.
|
| 37 |
+
"""
|
| 38 |
+
cls = find_filter_class(filtername)
|
| 39 |
+
if cls:
|
| 40 |
+
return cls(**options)
|
| 41 |
+
else:
|
| 42 |
+
raise ClassNotFound(f'filter {filtername!r} not found')
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def get_all_filters():
|
| 46 |
+
"""Return a generator of all filter names."""
|
| 47 |
+
yield from FILTERS
|
| 48 |
+
for name, _ in find_plugin_filters():
|
| 49 |
+
yield name
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _replace_special(ttype, value, regex, specialttype,
|
| 53 |
+
replacefunc=lambda x: x):
|
| 54 |
+
last = 0
|
| 55 |
+
for match in regex.finditer(value):
|
| 56 |
+
start, end = match.start(), match.end()
|
| 57 |
+
if start != last:
|
| 58 |
+
yield ttype, value[last:start]
|
| 59 |
+
yield specialttype, replacefunc(value[start:end])
|
| 60 |
+
last = end
|
| 61 |
+
if last != len(value):
|
| 62 |
+
yield ttype, value[last:]
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class CodeTagFilter(Filter):
|
| 66 |
+
"""Highlight special code tags in comments and docstrings.
|
| 67 |
+
|
| 68 |
+
Options accepted:
|
| 69 |
+
|
| 70 |
+
`codetags` : list of strings
|
| 71 |
+
A list of strings that are flagged as code tags. The default is to
|
| 72 |
+
highlight ``XXX``, ``TODO``, ``FIXME``, ``BUG`` and ``NOTE``.
|
| 73 |
+
|
| 74 |
+
.. versionchanged:: 2.13
|
| 75 |
+
Now recognizes ``FIXME`` by default.
|
| 76 |
+
"""
|
| 77 |
+
|
| 78 |
+
def __init__(self, **options):
|
| 79 |
+
Filter.__init__(self, **options)
|
| 80 |
+
tags = get_list_opt(options, 'codetags',
|
| 81 |
+
['XXX', 'TODO', 'FIXME', 'BUG', 'NOTE'])
|
| 82 |
+
self.tag_re = re.compile(r'\b({})\b'.format('|'.join([
|
| 83 |
+
re.escape(tag) for tag in tags if tag
|
| 84 |
+
])))
|
| 85 |
+
|
| 86 |
+
def filter(self, lexer, stream):
|
| 87 |
+
regex = self.tag_re
|
| 88 |
+
for ttype, value in stream:
|
| 89 |
+
if ttype in String.Doc or \
|
| 90 |
+
ttype in Comment and \
|
| 91 |
+
ttype not in Comment.Preproc:
|
| 92 |
+
yield from _replace_special(ttype, value, regex, Comment.Special)
|
| 93 |
+
else:
|
| 94 |
+
yield ttype, value
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
class SymbolFilter(Filter):
|
| 98 |
+
"""Convert mathematical symbols such as \\<longrightarrow> in Isabelle
|
| 99 |
+
or \\longrightarrow in LaTeX into Unicode characters.
|
| 100 |
+
|
| 101 |
+
This is mostly useful for HTML or console output when you want to
|
| 102 |
+
approximate the source rendering you'd see in an IDE.
|
| 103 |
+
|
| 104 |
+
Options accepted:
|
| 105 |
+
|
| 106 |
+
`lang` : string
|
| 107 |
+
The symbol language. Must be one of ``'isabelle'`` or
|
| 108 |
+
``'latex'``. The default is ``'isabelle'``.
|
| 109 |
+
"""
|
| 110 |
+
|
| 111 |
+
latex_symbols = {
|
| 112 |
+
'\\alpha' : '\U000003b1',
|
| 113 |
+
'\\beta' : '\U000003b2',
|
| 114 |
+
'\\gamma' : '\U000003b3',
|
| 115 |
+
'\\delta' : '\U000003b4',
|
| 116 |
+
'\\varepsilon' : '\U000003b5',
|
| 117 |
+
'\\zeta' : '\U000003b6',
|
| 118 |
+
'\\eta' : '\U000003b7',
|
| 119 |
+
'\\vartheta' : '\U000003b8',
|
| 120 |
+
'\\iota' : '\U000003b9',
|
| 121 |
+
'\\kappa' : '\U000003ba',
|
| 122 |
+
'\\lambda' : '\U000003bb',
|
| 123 |
+
'\\mu' : '\U000003bc',
|
| 124 |
+
'\\nu' : '\U000003bd',
|
| 125 |
+
'\\xi' : '\U000003be',
|
| 126 |
+
'\\pi' : '\U000003c0',
|
| 127 |
+
'\\varrho' : '\U000003c1',
|
| 128 |
+
'\\sigma' : '\U000003c3',
|
| 129 |
+
'\\tau' : '\U000003c4',
|
| 130 |
+
'\\upsilon' : '\U000003c5',
|
| 131 |
+
'\\varphi' : '\U000003c6',
|
| 132 |
+
'\\chi' : '\U000003c7',
|
| 133 |
+
'\\psi' : '\U000003c8',
|
| 134 |
+
'\\omega' : '\U000003c9',
|
| 135 |
+
'\\Gamma' : '\U00000393',
|
| 136 |
+
'\\Delta' : '\U00000394',
|
| 137 |
+
'\\Theta' : '\U00000398',
|
| 138 |
+
'\\Lambda' : '\U0000039b',
|
| 139 |
+
'\\Xi' : '\U0000039e',
|
| 140 |
+
'\\Pi' : '\U000003a0',
|
| 141 |
+
'\\Sigma' : '\U000003a3',
|
| 142 |
+
'\\Upsilon' : '\U000003a5',
|
| 143 |
+
'\\Phi' : '\U000003a6',
|
| 144 |
+
'\\Psi' : '\U000003a8',
|
| 145 |
+
'\\Omega' : '\U000003a9',
|
| 146 |
+
'\\leftarrow' : '\U00002190',
|
| 147 |
+
'\\longleftarrow' : '\U000027f5',
|
| 148 |
+
'\\rightarrow' : '\U00002192',
|
| 149 |
+
'\\longrightarrow' : '\U000027f6',
|
| 150 |
+
'\\Leftarrow' : '\U000021d0',
|
| 151 |
+
'\\Longleftarrow' : '\U000027f8',
|
| 152 |
+
'\\Rightarrow' : '\U000021d2',
|
| 153 |
+
'\\Longrightarrow' : '\U000027f9',
|
| 154 |
+
'\\leftrightarrow' : '\U00002194',
|
| 155 |
+
'\\longleftrightarrow' : '\U000027f7',
|
| 156 |
+
'\\Leftrightarrow' : '\U000021d4',
|
| 157 |
+
'\\Longleftrightarrow' : '\U000027fa',
|
| 158 |
+
'\\mapsto' : '\U000021a6',
|
| 159 |
+
'\\longmapsto' : '\U000027fc',
|
| 160 |
+
'\\relbar' : '\U00002500',
|
| 161 |
+
'\\Relbar' : '\U00002550',
|
| 162 |
+
'\\hookleftarrow' : '\U000021a9',
|
| 163 |
+
'\\hookrightarrow' : '\U000021aa',
|
| 164 |
+
'\\leftharpoondown' : '\U000021bd',
|
| 165 |
+
'\\rightharpoondown' : '\U000021c1',
|
| 166 |
+
'\\leftharpoonup' : '\U000021bc',
|
| 167 |
+
'\\rightharpoonup' : '\U000021c0',
|
| 168 |
+
'\\rightleftharpoons' : '\U000021cc',
|
| 169 |
+
'\\leadsto' : '\U0000219d',
|
| 170 |
+
'\\downharpoonleft' : '\U000021c3',
|
| 171 |
+
'\\downharpoonright' : '\U000021c2',
|
| 172 |
+
'\\upharpoonleft' : '\U000021bf',
|
| 173 |
+
'\\upharpoonright' : '\U000021be',
|
| 174 |
+
'\\restriction' : '\U000021be',
|
| 175 |
+
'\\uparrow' : '\U00002191',
|
| 176 |
+
'\\Uparrow' : '\U000021d1',
|
| 177 |
+
'\\downarrow' : '\U00002193',
|
| 178 |
+
'\\Downarrow' : '\U000021d3',
|
| 179 |
+
'\\updownarrow' : '\U00002195',
|
| 180 |
+
'\\Updownarrow' : '\U000021d5',
|
| 181 |
+
'\\langle' : '\U000027e8',
|
| 182 |
+
'\\rangle' : '\U000027e9',
|
| 183 |
+
'\\lceil' : '\U00002308',
|
| 184 |
+
'\\rceil' : '\U00002309',
|
| 185 |
+
'\\lfloor' : '\U0000230a',
|
| 186 |
+
'\\rfloor' : '\U0000230b',
|
| 187 |
+
'\\flqq' : '\U000000ab',
|
| 188 |
+
'\\frqq' : '\U000000bb',
|
| 189 |
+
'\\bot' : '\U000022a5',
|
| 190 |
+
'\\top' : '\U000022a4',
|
| 191 |
+
'\\wedge' : '\U00002227',
|
| 192 |
+
'\\bigwedge' : '\U000022c0',
|
| 193 |
+
'\\vee' : '\U00002228',
|
| 194 |
+
'\\bigvee' : '\U000022c1',
|
| 195 |
+
'\\forall' : '\U00002200',
|
| 196 |
+
'\\exists' : '\U00002203',
|
| 197 |
+
'\\nexists' : '\U00002204',
|
| 198 |
+
'\\neg' : '\U000000ac',
|
| 199 |
+
'\\Box' : '\U000025a1',
|
| 200 |
+
'\\Diamond' : '\U000025c7',
|
| 201 |
+
'\\vdash' : '\U000022a2',
|
| 202 |
+
'\\models' : '\U000022a8',
|
| 203 |
+
'\\dashv' : '\U000022a3',
|
| 204 |
+
'\\surd' : '\U0000221a',
|
| 205 |
+
'\\le' : '\U00002264',
|
| 206 |
+
'\\ge' : '\U00002265',
|
| 207 |
+
'\\ll' : '\U0000226a',
|
| 208 |
+
'\\gg' : '\U0000226b',
|
| 209 |
+
'\\lesssim' : '\U00002272',
|
| 210 |
+
'\\gtrsim' : '\U00002273',
|
| 211 |
+
'\\lessapprox' : '\U00002a85',
|
| 212 |
+
'\\gtrapprox' : '\U00002a86',
|
| 213 |
+
'\\in' : '\U00002208',
|
| 214 |
+
'\\notin' : '\U00002209',
|
| 215 |
+
'\\subset' : '\U00002282',
|
| 216 |
+
'\\supset' : '\U00002283',
|
| 217 |
+
'\\subseteq' : '\U00002286',
|
| 218 |
+
'\\supseteq' : '\U00002287',
|
| 219 |
+
'\\sqsubset' : '\U0000228f',
|
| 220 |
+
'\\sqsupset' : '\U00002290',
|
| 221 |
+
'\\sqsubseteq' : '\U00002291',
|
| 222 |
+
'\\sqsupseteq' : '\U00002292',
|
| 223 |
+
'\\cap' : '\U00002229',
|
| 224 |
+
'\\bigcap' : '\U000022c2',
|
| 225 |
+
'\\cup' : '\U0000222a',
|
| 226 |
+
'\\bigcup' : '\U000022c3',
|
| 227 |
+
'\\sqcup' : '\U00002294',
|
| 228 |
+
'\\bigsqcup' : '\U00002a06',
|
| 229 |
+
'\\sqcap' : '\U00002293',
|
| 230 |
+
'\\Bigsqcap' : '\U00002a05',
|
| 231 |
+
'\\setminus' : '\U00002216',
|
| 232 |
+
'\\propto' : '\U0000221d',
|
| 233 |
+
'\\uplus' : '\U0000228e',
|
| 234 |
+
'\\bigplus' : '\U00002a04',
|
| 235 |
+
'\\sim' : '\U0000223c',
|
| 236 |
+
'\\doteq' : '\U00002250',
|
| 237 |
+
'\\simeq' : '\U00002243',
|
| 238 |
+
'\\approx' : '\U00002248',
|
| 239 |
+
'\\asymp' : '\U0000224d',
|
| 240 |
+
'\\cong' : '\U00002245',
|
| 241 |
+
'\\equiv' : '\U00002261',
|
| 242 |
+
'\\Join' : '\U000022c8',
|
| 243 |
+
'\\bowtie' : '\U00002a1d',
|
| 244 |
+
'\\prec' : '\U0000227a',
|
| 245 |
+
'\\succ' : '\U0000227b',
|
| 246 |
+
'\\preceq' : '\U0000227c',
|
| 247 |
+
'\\succeq' : '\U0000227d',
|
| 248 |
+
'\\parallel' : '\U00002225',
|
| 249 |
+
'\\mid' : '\U000000a6',
|
| 250 |
+
'\\pm' : '\U000000b1',
|
| 251 |
+
'\\mp' : '\U00002213',
|
| 252 |
+
'\\times' : '\U000000d7',
|
| 253 |
+
'\\div' : '\U000000f7',
|
| 254 |
+
'\\cdot' : '\U000022c5',
|
| 255 |
+
'\\star' : '\U000022c6',
|
| 256 |
+
'\\circ' : '\U00002218',
|
| 257 |
+
'\\dagger' : '\U00002020',
|
| 258 |
+
'\\ddagger' : '\U00002021',
|
| 259 |
+
'\\lhd' : '\U000022b2',
|
| 260 |
+
'\\rhd' : '\U000022b3',
|
| 261 |
+
'\\unlhd' : '\U000022b4',
|
| 262 |
+
'\\unrhd' : '\U000022b5',
|
| 263 |
+
'\\triangleleft' : '\U000025c3',
|
| 264 |
+
'\\triangleright' : '\U000025b9',
|
| 265 |
+
'\\triangle' : '\U000025b3',
|
| 266 |
+
'\\triangleq' : '\U0000225c',
|
| 267 |
+
'\\oplus' : '\U00002295',
|
| 268 |
+
'\\bigoplus' : '\U00002a01',
|
| 269 |
+
'\\otimes' : '\U00002297',
|
| 270 |
+
'\\bigotimes' : '\U00002a02',
|
| 271 |
+
'\\odot' : '\U00002299',
|
| 272 |
+
'\\bigodot' : '\U00002a00',
|
| 273 |
+
'\\ominus' : '\U00002296',
|
| 274 |
+
'\\oslash' : '\U00002298',
|
| 275 |
+
'\\dots' : '\U00002026',
|
| 276 |
+
'\\cdots' : '\U000022ef',
|
| 277 |
+
'\\sum' : '\U00002211',
|
| 278 |
+
'\\prod' : '\U0000220f',
|
| 279 |
+
'\\coprod' : '\U00002210',
|
| 280 |
+
'\\infty' : '\U0000221e',
|
| 281 |
+
'\\int' : '\U0000222b',
|
| 282 |
+
'\\oint' : '\U0000222e',
|
| 283 |
+
'\\clubsuit' : '\U00002663',
|
| 284 |
+
'\\diamondsuit' : '\U00002662',
|
| 285 |
+
'\\heartsuit' : '\U00002661',
|
| 286 |
+
'\\spadesuit' : '\U00002660',
|
| 287 |
+
'\\aleph' : '\U00002135',
|
| 288 |
+
'\\emptyset' : '\U00002205',
|
| 289 |
+
'\\nabla' : '\U00002207',
|
| 290 |
+
'\\partial' : '\U00002202',
|
| 291 |
+
'\\flat' : '\U0000266d',
|
| 292 |
+
'\\natural' : '\U0000266e',
|
| 293 |
+
'\\sharp' : '\U0000266f',
|
| 294 |
+
'\\angle' : '\U00002220',
|
| 295 |
+
'\\copyright' : '\U000000a9',
|
| 296 |
+
'\\textregistered' : '\U000000ae',
|
| 297 |
+
'\\textonequarter' : '\U000000bc',
|
| 298 |
+
'\\textonehalf' : '\U000000bd',
|
| 299 |
+
'\\textthreequarters' : '\U000000be',
|
| 300 |
+
'\\textordfeminine' : '\U000000aa',
|
| 301 |
+
'\\textordmasculine' : '\U000000ba',
|
| 302 |
+
'\\euro' : '\U000020ac',
|
| 303 |
+
'\\pounds' : '\U000000a3',
|
| 304 |
+
'\\yen' : '\U000000a5',
|
| 305 |
+
'\\textcent' : '\U000000a2',
|
| 306 |
+
'\\textcurrency' : '\U000000a4',
|
| 307 |
+
'\\textdegree' : '\U000000b0',
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
isabelle_symbols = {
|
| 311 |
+
'\\<zero>' : '\U0001d7ec',
|
| 312 |
+
'\\<one>' : '\U0001d7ed',
|
| 313 |
+
'\\<two>' : '\U0001d7ee',
|
| 314 |
+
'\\<three>' : '\U0001d7ef',
|
| 315 |
+
'\\<four>' : '\U0001d7f0',
|
| 316 |
+
'\\<five>' : '\U0001d7f1',
|
| 317 |
+
'\\<six>' : '\U0001d7f2',
|
| 318 |
+
'\\<seven>' : '\U0001d7f3',
|
| 319 |
+
'\\<eight>' : '\U0001d7f4',
|
| 320 |
+
'\\<nine>' : '\U0001d7f5',
|
| 321 |
+
'\\<A>' : '\U0001d49c',
|
| 322 |
+
'\\<B>' : '\U0000212c',
|
| 323 |
+
'\\<C>' : '\U0001d49e',
|
| 324 |
+
'\\<D>' : '\U0001d49f',
|
| 325 |
+
'\\<E>' : '\U00002130',
|
| 326 |
+
'\\<F>' : '\U00002131',
|
| 327 |
+
'\\<G>' : '\U0001d4a2',
|
| 328 |
+
'\\<H>' : '\U0000210b',
|
| 329 |
+
'\\<I>' : '\U00002110',
|
| 330 |
+
'\\<J>' : '\U0001d4a5',
|
| 331 |
+
'\\<K>' : '\U0001d4a6',
|
| 332 |
+
'\\<L>' : '\U00002112',
|
| 333 |
+
'\\<M>' : '\U00002133',
|
| 334 |
+
'\\<N>' : '\U0001d4a9',
|
| 335 |
+
'\\<O>' : '\U0001d4aa',
|
| 336 |
+
'\\<P>' : '\U0001d4ab',
|
| 337 |
+
'\\<Q>' : '\U0001d4ac',
|
| 338 |
+
'\\<R>' : '\U0000211b',
|
| 339 |
+
'\\<S>' : '\U0001d4ae',
|
| 340 |
+
'\\<T>' : '\U0001d4af',
|
| 341 |
+
'\\<U>' : '\U0001d4b0',
|
| 342 |
+
'\\<V>' : '\U0001d4b1',
|
| 343 |
+
'\\<W>' : '\U0001d4b2',
|
| 344 |
+
'\\<X>' : '\U0001d4b3',
|
| 345 |
+
'\\<Y>' : '\U0001d4b4',
|
| 346 |
+
'\\<Z>' : '\U0001d4b5',
|
| 347 |
+
'\\<a>' : '\U0001d5ba',
|
| 348 |
+
'\\<b>' : '\U0001d5bb',
|
| 349 |
+
'\\<c>' : '\U0001d5bc',
|
| 350 |
+
'\\<d>' : '\U0001d5bd',
|
| 351 |
+
'\\<e>' : '\U0001d5be',
|
| 352 |
+
'\\<f>' : '\U0001d5bf',
|
| 353 |
+
'\\<g>' : '\U0001d5c0',
|
| 354 |
+
'\\<h>' : '\U0001d5c1',
|
| 355 |
+
'\\<i>' : '\U0001d5c2',
|
| 356 |
+
'\\<j>' : '\U0001d5c3',
|
| 357 |
+
'\\<k>' : '\U0001d5c4',
|
| 358 |
+
'\\<l>' : '\U0001d5c5',
|
| 359 |
+
'\\<m>' : '\U0001d5c6',
|
| 360 |
+
'\\<n>' : '\U0001d5c7',
|
| 361 |
+
'\\<o>' : '\U0001d5c8',
|
| 362 |
+
'\\<p>' : '\U0001d5c9',
|
| 363 |
+
'\\<q>' : '\U0001d5ca',
|
| 364 |
+
'\\<r>' : '\U0001d5cb',
|
| 365 |
+
'\\<s>' : '\U0001d5cc',
|
| 366 |
+
'\\<t>' : '\U0001d5cd',
|
| 367 |
+
'\\<u>' : '\U0001d5ce',
|
| 368 |
+
'\\<v>' : '\U0001d5cf',
|
| 369 |
+
'\\<w>' : '\U0001d5d0',
|
| 370 |
+
'\\<x>' : '\U0001d5d1',
|
| 371 |
+
'\\<y>' : '\U0001d5d2',
|
| 372 |
+
'\\<z>' : '\U0001d5d3',
|
| 373 |
+
'\\<AA>' : '\U0001d504',
|
| 374 |
+
'\\<BB>' : '\U0001d505',
|
| 375 |
+
'\\<CC>' : '\U0000212d',
|
| 376 |
+
'\\<DD>' : '\U0001d507',
|
| 377 |
+
'\\<EE>' : '\U0001d508',
|
| 378 |
+
'\\<FF>' : '\U0001d509',
|
| 379 |
+
'\\<GG>' : '\U0001d50a',
|
| 380 |
+
'\\<HH>' : '\U0000210c',
|
| 381 |
+
'\\<II>' : '\U00002111',
|
| 382 |
+
'\\<JJ>' : '\U0001d50d',
|
| 383 |
+
'\\<KK>' : '\U0001d50e',
|
| 384 |
+
'\\<LL>' : '\U0001d50f',
|
| 385 |
+
'\\<MM>' : '\U0001d510',
|
| 386 |
+
'\\<NN>' : '\U0001d511',
|
| 387 |
+
'\\<OO>' : '\U0001d512',
|
| 388 |
+
'\\<PP>' : '\U0001d513',
|
| 389 |
+
'\\<QQ>' : '\U0001d514',
|
| 390 |
+
'\\<RR>' : '\U0000211c',
|
| 391 |
+
'\\<SS>' : '\U0001d516',
|
| 392 |
+
'\\<TT>' : '\U0001d517',
|
| 393 |
+
'\\<UU>' : '\U0001d518',
|
| 394 |
+
'\\<VV>' : '\U0001d519',
|
| 395 |
+
'\\<WW>' : '\U0001d51a',
|
| 396 |
+
'\\<XX>' : '\U0001d51b',
|
| 397 |
+
'\\<YY>' : '\U0001d51c',
|
| 398 |
+
'\\<ZZ>' : '\U00002128',
|
| 399 |
+
'\\<aa>' : '\U0001d51e',
|
| 400 |
+
'\\<bb>' : '\U0001d51f',
|
| 401 |
+
'\\<cc>' : '\U0001d520',
|
| 402 |
+
'\\<dd>' : '\U0001d521',
|
| 403 |
+
'\\<ee>' : '\U0001d522',
|
| 404 |
+
'\\<ff>' : '\U0001d523',
|
| 405 |
+
'\\<gg>' : '\U0001d524',
|
| 406 |
+
'\\<hh>' : '\U0001d525',
|
| 407 |
+
'\\<ii>' : '\U0001d526',
|
| 408 |
+
'\\<jj>' : '\U0001d527',
|
| 409 |
+
'\\<kk>' : '\U0001d528',
|
| 410 |
+
'\\<ll>' : '\U0001d529',
|
| 411 |
+
'\\<mm>' : '\U0001d52a',
|
| 412 |
+
'\\<nn>' : '\U0001d52b',
|
| 413 |
+
'\\<oo>' : '\U0001d52c',
|
| 414 |
+
'\\<pp>' : '\U0001d52d',
|
| 415 |
+
'\\<qq>' : '\U0001d52e',
|
| 416 |
+
'\\<rr>' : '\U0001d52f',
|
| 417 |
+
'\\<ss>' : '\U0001d530',
|
| 418 |
+
'\\<tt>' : '\U0001d531',
|
| 419 |
+
'\\<uu>' : '\U0001d532',
|
| 420 |
+
'\\<vv>' : '\U0001d533',
|
| 421 |
+
'\\<ww>' : '\U0001d534',
|
| 422 |
+
'\\<xx>' : '\U0001d535',
|
| 423 |
+
'\\<yy>' : '\U0001d536',
|
| 424 |
+
'\\<zz>' : '\U0001d537',
|
| 425 |
+
'\\<alpha>' : '\U000003b1',
|
| 426 |
+
'\\<beta>' : '\U000003b2',
|
| 427 |
+
'\\<gamma>' : '\U000003b3',
|
| 428 |
+
'\\<delta>' : '\U000003b4',
|
| 429 |
+
'\\<epsilon>' : '\U000003b5',
|
| 430 |
+
'\\<zeta>' : '\U000003b6',
|
| 431 |
+
'\\<eta>' : '\U000003b7',
|
| 432 |
+
'\\<theta>' : '\U000003b8',
|
| 433 |
+
'\\<iota>' : '\U000003b9',
|
| 434 |
+
'\\<kappa>' : '\U000003ba',
|
| 435 |
+
'\\<lambda>' : '\U000003bb',
|
| 436 |
+
'\\<mu>' : '\U000003bc',
|
| 437 |
+
'\\<nu>' : '\U000003bd',
|
| 438 |
+
'\\<xi>' : '\U000003be',
|
| 439 |
+
'\\<pi>' : '\U000003c0',
|
| 440 |
+
'\\<rho>' : '\U000003c1',
|
| 441 |
+
'\\<sigma>' : '\U000003c3',
|
| 442 |
+
'\\<tau>' : '\U000003c4',
|
| 443 |
+
'\\<upsilon>' : '\U000003c5',
|
| 444 |
+
'\\<phi>' : '\U000003c6',
|
| 445 |
+
'\\<chi>' : '\U000003c7',
|
| 446 |
+
'\\<psi>' : '\U000003c8',
|
| 447 |
+
'\\<omega>' : '\U000003c9',
|
| 448 |
+
'\\<Gamma>' : '\U00000393',
|
| 449 |
+
'\\<Delta>' : '\U00000394',
|
| 450 |
+
'\\<Theta>' : '\U00000398',
|
| 451 |
+
'\\<Lambda>' : '\U0000039b',
|
| 452 |
+
'\\<Xi>' : '\U0000039e',
|
| 453 |
+
'\\<Pi>' : '\U000003a0',
|
| 454 |
+
'\\<Sigma>' : '\U000003a3',
|
| 455 |
+
'\\<Upsilon>' : '\U000003a5',
|
| 456 |
+
'\\<Phi>' : '\U000003a6',
|
| 457 |
+
'\\<Psi>' : '\U000003a8',
|
| 458 |
+
'\\<Omega>' : '\U000003a9',
|
| 459 |
+
'\\<bool>' : '\U0001d539',
|
| 460 |
+
'\\<complex>' : '\U00002102',
|
| 461 |
+
'\\<nat>' : '\U00002115',
|
| 462 |
+
'\\<rat>' : '\U0000211a',
|
| 463 |
+
'\\<real>' : '\U0000211d',
|
| 464 |
+
'\\<int>' : '\U00002124',
|
| 465 |
+
'\\<leftarrow>' : '\U00002190',
|
| 466 |
+
'\\<longleftarrow>' : '\U000027f5',
|
| 467 |
+
'\\<rightarrow>' : '\U00002192',
|
| 468 |
+
'\\<longrightarrow>' : '\U000027f6',
|
| 469 |
+
'\\<Leftarrow>' : '\U000021d0',
|
| 470 |
+
'\\<Longleftarrow>' : '\U000027f8',
|
| 471 |
+
'\\<Rightarrow>' : '\U000021d2',
|
| 472 |
+
'\\<Longrightarrow>' : '\U000027f9',
|
| 473 |
+
'\\<leftrightarrow>' : '\U00002194',
|
| 474 |
+
'\\<longleftrightarrow>' : '\U000027f7',
|
| 475 |
+
'\\<Leftrightarrow>' : '\U000021d4',
|
| 476 |
+
'\\<Longleftrightarrow>' : '\U000027fa',
|
| 477 |
+
'\\<mapsto>' : '\U000021a6',
|
| 478 |
+
'\\<longmapsto>' : '\U000027fc',
|
| 479 |
+
'\\<midarrow>' : '\U00002500',
|
| 480 |
+
'\\<Midarrow>' : '\U00002550',
|
| 481 |
+
'\\<hookleftarrow>' : '\U000021a9',
|
| 482 |
+
'\\<hookrightarrow>' : '\U000021aa',
|
| 483 |
+
'\\<leftharpoondown>' : '\U000021bd',
|
| 484 |
+
'\\<rightharpoondown>' : '\U000021c1',
|
| 485 |
+
'\\<leftharpoonup>' : '\U000021bc',
|
| 486 |
+
'\\<rightharpoonup>' : '\U000021c0',
|
| 487 |
+
'\\<rightleftharpoons>' : '\U000021cc',
|
| 488 |
+
'\\<leadsto>' : '\U0000219d',
|
| 489 |
+
'\\<downharpoonleft>' : '\U000021c3',
|
| 490 |
+
'\\<downharpoonright>' : '\U000021c2',
|
| 491 |
+
'\\<upharpoonleft>' : '\U000021bf',
|
| 492 |
+
'\\<upharpoonright>' : '\U000021be',
|
| 493 |
+
'\\<restriction>' : '\U000021be',
|
| 494 |
+
'\\<Colon>' : '\U00002237',
|
| 495 |
+
'\\<up>' : '\U00002191',
|
| 496 |
+
'\\<Up>' : '\U000021d1',
|
| 497 |
+
'\\<down>' : '\U00002193',
|
| 498 |
+
'\\<Down>' : '\U000021d3',
|
| 499 |
+
'\\<updown>' : '\U00002195',
|
| 500 |
+
'\\<Updown>' : '\U000021d5',
|
| 501 |
+
'\\<langle>' : '\U000027e8',
|
| 502 |
+
'\\<rangle>' : '\U000027e9',
|
| 503 |
+
'\\<lceil>' : '\U00002308',
|
| 504 |
+
'\\<rceil>' : '\U00002309',
|
| 505 |
+
'\\<lfloor>' : '\U0000230a',
|
| 506 |
+
'\\<rfloor>' : '\U0000230b',
|
| 507 |
+
'\\<lparr>' : '\U00002987',
|
| 508 |
+
'\\<rparr>' : '\U00002988',
|
| 509 |
+
'\\<lbrakk>' : '\U000027e6',
|
| 510 |
+
'\\<rbrakk>' : '\U000027e7',
|
| 511 |
+
'\\<lbrace>' : '\U00002983',
|
| 512 |
+
'\\<rbrace>' : '\U00002984',
|
| 513 |
+
'\\<guillemotleft>' : '\U000000ab',
|
| 514 |
+
'\\<guillemotright>' : '\U000000bb',
|
| 515 |
+
'\\<bottom>' : '\U000022a5',
|
| 516 |
+
'\\<top>' : '\U000022a4',
|
| 517 |
+
'\\<and>' : '\U00002227',
|
| 518 |
+
'\\<And>' : '\U000022c0',
|
| 519 |
+
'\\<or>' : '\U00002228',
|
| 520 |
+
'\\<Or>' : '\U000022c1',
|
| 521 |
+
'\\<forall>' : '\U00002200',
|
| 522 |
+
'\\<exists>' : '\U00002203',
|
| 523 |
+
'\\<nexists>' : '\U00002204',
|
| 524 |
+
'\\<not>' : '\U000000ac',
|
| 525 |
+
'\\<box>' : '\U000025a1',
|
| 526 |
+
'\\<diamond>' : '\U000025c7',
|
| 527 |
+
'\\<turnstile>' : '\U000022a2',
|
| 528 |
+
'\\<Turnstile>' : '\U000022a8',
|
| 529 |
+
'\\<tturnstile>' : '\U000022a9',
|
| 530 |
+
'\\<TTurnstile>' : '\U000022ab',
|
| 531 |
+
'\\<stileturn>' : '\U000022a3',
|
| 532 |
+
'\\<surd>' : '\U0000221a',
|
| 533 |
+
'\\<le>' : '\U00002264',
|
| 534 |
+
'\\<ge>' : '\U00002265',
|
| 535 |
+
'\\<lless>' : '\U0000226a',
|
| 536 |
+
'\\<ggreater>' : '\U0000226b',
|
| 537 |
+
'\\<lesssim>' : '\U00002272',
|
| 538 |
+
'\\<greatersim>' : '\U00002273',
|
| 539 |
+
'\\<lessapprox>' : '\U00002a85',
|
| 540 |
+
'\\<greaterapprox>' : '\U00002a86',
|
| 541 |
+
'\\<in>' : '\U00002208',
|
| 542 |
+
'\\<notin>' : '\U00002209',
|
| 543 |
+
'\\<subset>' : '\U00002282',
|
| 544 |
+
'\\<supset>' : '\U00002283',
|
| 545 |
+
'\\<subseteq>' : '\U00002286',
|
| 546 |
+
'\\<supseteq>' : '\U00002287',
|
| 547 |
+
'\\<sqsubset>' : '\U0000228f',
|
| 548 |
+
'\\<sqsupset>' : '\U00002290',
|
| 549 |
+
'\\<sqsubseteq>' : '\U00002291',
|
| 550 |
+
'\\<sqsupseteq>' : '\U00002292',
|
| 551 |
+
'\\<inter>' : '\U00002229',
|
| 552 |
+
'\\<Inter>' : '\U000022c2',
|
| 553 |
+
'\\<union>' : '\U0000222a',
|
| 554 |
+
'\\<Union>' : '\U000022c3',
|
| 555 |
+
'\\<squnion>' : '\U00002294',
|
| 556 |
+
'\\<Squnion>' : '\U00002a06',
|
| 557 |
+
'\\<sqinter>' : '\U00002293',
|
| 558 |
+
'\\<Sqinter>' : '\U00002a05',
|
| 559 |
+
'\\<setminus>' : '\U00002216',
|
| 560 |
+
'\\<propto>' : '\U0000221d',
|
| 561 |
+
'\\<uplus>' : '\U0000228e',
|
| 562 |
+
'\\<Uplus>' : '\U00002a04',
|
| 563 |
+
'\\<noteq>' : '\U00002260',
|
| 564 |
+
'\\<sim>' : '\U0000223c',
|
| 565 |
+
'\\<doteq>' : '\U00002250',
|
| 566 |
+
'\\<simeq>' : '\U00002243',
|
| 567 |
+
'\\<approx>' : '\U00002248',
|
| 568 |
+
'\\<asymp>' : '\U0000224d',
|
| 569 |
+
'\\<cong>' : '\U00002245',
|
| 570 |
+
'\\<smile>' : '\U00002323',
|
| 571 |
+
'\\<equiv>' : '\U00002261',
|
| 572 |
+
'\\<frown>' : '\U00002322',
|
| 573 |
+
'\\<Join>' : '\U000022c8',
|
| 574 |
+
'\\<bowtie>' : '\U00002a1d',
|
| 575 |
+
'\\<prec>' : '\U0000227a',
|
| 576 |
+
'\\<succ>' : '\U0000227b',
|
| 577 |
+
'\\<preceq>' : '\U0000227c',
|
| 578 |
+
'\\<succeq>' : '\U0000227d',
|
| 579 |
+
'\\<parallel>' : '\U00002225',
|
| 580 |
+
'\\<bar>' : '\U000000a6',
|
| 581 |
+
'\\<plusminus>' : '\U000000b1',
|
| 582 |
+
'\\<minusplus>' : '\U00002213',
|
| 583 |
+
'\\<times>' : '\U000000d7',
|
| 584 |
+
'\\<div>' : '\U000000f7',
|
| 585 |
+
'\\<cdot>' : '\U000022c5',
|
| 586 |
+
'\\<star>' : '\U000022c6',
|
| 587 |
+
'\\<bullet>' : '\U00002219',
|
| 588 |
+
'\\<circ>' : '\U00002218',
|
| 589 |
+
'\\<dagger>' : '\U00002020',
|
| 590 |
+
'\\<ddagger>' : '\U00002021',
|
| 591 |
+
'\\<lhd>' : '\U000022b2',
|
| 592 |
+
'\\<rhd>' : '\U000022b3',
|
| 593 |
+
'\\<unlhd>' : '\U000022b4',
|
| 594 |
+
'\\<unrhd>' : '\U000022b5',
|
| 595 |
+
'\\<triangleleft>' : '\U000025c3',
|
| 596 |
+
'\\<triangleright>' : '\U000025b9',
|
| 597 |
+
'\\<triangle>' : '\U000025b3',
|
| 598 |
+
'\\<triangleq>' : '\U0000225c',
|
| 599 |
+
'\\<oplus>' : '\U00002295',
|
| 600 |
+
'\\<Oplus>' : '\U00002a01',
|
| 601 |
+
'\\<otimes>' : '\U00002297',
|
| 602 |
+
'\\<Otimes>' : '\U00002a02',
|
| 603 |
+
'\\<odot>' : '\U00002299',
|
| 604 |
+
'\\<Odot>' : '\U00002a00',
|
| 605 |
+
'\\<ominus>' : '\U00002296',
|
| 606 |
+
'\\<oslash>' : '\U00002298',
|
| 607 |
+
'\\<dots>' : '\U00002026',
|
| 608 |
+
'\\<cdots>' : '\U000022ef',
|
| 609 |
+
'\\<Sum>' : '\U00002211',
|
| 610 |
+
'\\<Prod>' : '\U0000220f',
|
| 611 |
+
'\\<Coprod>' : '\U00002210',
|
| 612 |
+
'\\<infinity>' : '\U0000221e',
|
| 613 |
+
'\\<integral>' : '\U0000222b',
|
| 614 |
+
'\\<ointegral>' : '\U0000222e',
|
| 615 |
+
'\\<clubsuit>' : '\U00002663',
|
| 616 |
+
'\\<diamondsuit>' : '\U00002662',
|
| 617 |
+
'\\<heartsuit>' : '\U00002661',
|
| 618 |
+
'\\<spadesuit>' : '\U00002660',
|
| 619 |
+
'\\<aleph>' : '\U00002135',
|
| 620 |
+
'\\<emptyset>' : '\U00002205',
|
| 621 |
+
'\\<nabla>' : '\U00002207',
|
| 622 |
+
'\\<partial>' : '\U00002202',
|
| 623 |
+
'\\<flat>' : '\U0000266d',
|
| 624 |
+
'\\<natural>' : '\U0000266e',
|
| 625 |
+
'\\<sharp>' : '\U0000266f',
|
| 626 |
+
'\\<angle>' : '\U00002220',
|
| 627 |
+
'\\<copyright>' : '\U000000a9',
|
| 628 |
+
'\\<registered>' : '\U000000ae',
|
| 629 |
+
'\\<hyphen>' : '\U000000ad',
|
| 630 |
+
'\\<inverse>' : '\U000000af',
|
| 631 |
+
'\\<onequarter>' : '\U000000bc',
|
| 632 |
+
'\\<onehalf>' : '\U000000bd',
|
| 633 |
+
'\\<threequarters>' : '\U000000be',
|
| 634 |
+
'\\<ordfeminine>' : '\U000000aa',
|
| 635 |
+
'\\<ordmasculine>' : '\U000000ba',
|
| 636 |
+
'\\<section>' : '\U000000a7',
|
| 637 |
+
'\\<paragraph>' : '\U000000b6',
|
| 638 |
+
'\\<exclamdown>' : '\U000000a1',
|
| 639 |
+
'\\<questiondown>' : '\U000000bf',
|
| 640 |
+
'\\<euro>' : '\U000020ac',
|
| 641 |
+
'\\<pounds>' : '\U000000a3',
|
| 642 |
+
'\\<yen>' : '\U000000a5',
|
| 643 |
+
'\\<cent>' : '\U000000a2',
|
| 644 |
+
'\\<currency>' : '\U000000a4',
|
| 645 |
+
'\\<degree>' : '\U000000b0',
|
| 646 |
+
'\\<amalg>' : '\U00002a3f',
|
| 647 |
+
'\\<mho>' : '\U00002127',
|
| 648 |
+
'\\<lozenge>' : '\U000025ca',
|
| 649 |
+
'\\<wp>' : '\U00002118',
|
| 650 |
+
'\\<wrong>' : '\U00002240',
|
| 651 |
+
'\\<struct>' : '\U000022c4',
|
| 652 |
+
'\\<acute>' : '\U000000b4',
|
| 653 |
+
'\\<index>' : '\U00000131',
|
| 654 |
+
'\\<dieresis>' : '\U000000a8',
|
| 655 |
+
'\\<cedilla>' : '\U000000b8',
|
| 656 |
+
'\\<hungarumlaut>' : '\U000002dd',
|
| 657 |
+
'\\<some>' : '\U000003f5',
|
| 658 |
+
'\\<newline>' : '\U000023ce',
|
| 659 |
+
'\\<open>' : '\U00002039',
|
| 660 |
+
'\\<close>' : '\U0000203a',
|
| 661 |
+
'\\<here>' : '\U00002302',
|
| 662 |
+
'\\<^sub>' : '\U000021e9',
|
| 663 |
+
'\\<^sup>' : '\U000021e7',
|
| 664 |
+
'\\<^bold>' : '\U00002759',
|
| 665 |
+
'\\<^bsub>' : '\U000021d8',
|
| 666 |
+
'\\<^esub>' : '\U000021d9',
|
| 667 |
+
'\\<^bsup>' : '\U000021d7',
|
| 668 |
+
'\\<^esup>' : '\U000021d6',
|
| 669 |
+
}
|
| 670 |
+
|
| 671 |
+
lang_map = {'isabelle' : isabelle_symbols, 'latex' : latex_symbols}
|
| 672 |
+
|
| 673 |
+
def __init__(self, **options):
|
| 674 |
+
Filter.__init__(self, **options)
|
| 675 |
+
lang = get_choice_opt(options, 'lang',
|
| 676 |
+
['isabelle', 'latex'], 'isabelle')
|
| 677 |
+
self.symbols = self.lang_map[lang]
|
| 678 |
+
|
| 679 |
+
def filter(self, lexer, stream):
|
| 680 |
+
for ttype, value in stream:
|
| 681 |
+
if value in self.symbols:
|
| 682 |
+
yield ttype, self.symbols[value]
|
| 683 |
+
else:
|
| 684 |
+
yield ttype, value
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
class KeywordCaseFilter(Filter):
|
| 688 |
+
"""Convert keywords to lowercase or uppercase or capitalize them, which
|
| 689 |
+
means first letter uppercase, rest lowercase.
|
| 690 |
+
|
| 691 |
+
This can be useful e.g. if you highlight Pascal code and want to adapt the
|
| 692 |
+
code to your styleguide.
|
| 693 |
+
|
| 694 |
+
Options accepted:
|
| 695 |
+
|
| 696 |
+
`case` : string
|
| 697 |
+
The casing to convert keywords to. Must be one of ``'lower'``,
|
| 698 |
+
``'upper'`` or ``'capitalize'``. The default is ``'lower'``.
|
| 699 |
+
"""
|
| 700 |
+
|
| 701 |
+
def __init__(self, **options):
|
| 702 |
+
Filter.__init__(self, **options)
|
| 703 |
+
case = get_choice_opt(options, 'case',
|
| 704 |
+
['lower', 'upper', 'capitalize'], 'lower')
|
| 705 |
+
self.convert = getattr(str, case)
|
| 706 |
+
|
| 707 |
+
def filter(self, lexer, stream):
|
| 708 |
+
for ttype, value in stream:
|
| 709 |
+
if ttype in Keyword:
|
| 710 |
+
yield ttype, self.convert(value)
|
| 711 |
+
else:
|
| 712 |
+
yield ttype, value
|
| 713 |
+
|
| 714 |
+
|
| 715 |
+
class NameHighlightFilter(Filter):
|
| 716 |
+
"""Highlight a normal Name (and Name.*) token with a different token type.
|
| 717 |
+
|
| 718 |
+
Example::
|
| 719 |
+
|
| 720 |
+
filter = NameHighlightFilter(
|
| 721 |
+
names=['foo', 'bar', 'baz'],
|
| 722 |
+
tokentype=Name.Function,
|
| 723 |
+
)
|
| 724 |
+
|
| 725 |
+
This would highlight the names "foo", "bar" and "baz"
|
| 726 |
+
as functions. `Name.Function` is the default token type.
|
| 727 |
+
|
| 728 |
+
Options accepted:
|
| 729 |
+
|
| 730 |
+
`names` : list of strings
|
| 731 |
+
A list of names that should be given the different token type.
|
| 732 |
+
There is no default.
|
| 733 |
+
`tokentype` : TokenType or string
|
| 734 |
+
A token type or a string containing a token type name that is
|
| 735 |
+
used for highlighting the strings in `names`. The default is
|
| 736 |
+
`Name.Function`.
|
| 737 |
+
"""
|
| 738 |
+
|
| 739 |
+
def __init__(self, **options):
|
| 740 |
+
Filter.__init__(self, **options)
|
| 741 |
+
self.names = set(get_list_opt(options, 'names', []))
|
| 742 |
+
tokentype = options.get('tokentype')
|
| 743 |
+
if tokentype:
|
| 744 |
+
self.tokentype = string_to_tokentype(tokentype)
|
| 745 |
+
else:
|
| 746 |
+
self.tokentype = Name.Function
|
| 747 |
+
|
| 748 |
+
def filter(self, lexer, stream):
|
| 749 |
+
for ttype, value in stream:
|
| 750 |
+
if ttype in Name and value in self.names:
|
| 751 |
+
yield self.tokentype, value
|
| 752 |
+
else:
|
| 753 |
+
yield ttype, value
|
| 754 |
+
|
| 755 |
+
|
| 756 |
+
class ErrorToken(Exception):
|
| 757 |
+
pass
|
| 758 |
+
|
| 759 |
+
|
| 760 |
+
class RaiseOnErrorTokenFilter(Filter):
|
| 761 |
+
"""Raise an exception when the lexer generates an error token.
|
| 762 |
+
|
| 763 |
+
Options accepted:
|
| 764 |
+
|
| 765 |
+
`excclass` : Exception class
|
| 766 |
+
The exception class to raise.
|
| 767 |
+
The default is `pygments.filters.ErrorToken`.
|
| 768 |
+
|
| 769 |
+
.. versionadded:: 0.8
|
| 770 |
+
"""
|
| 771 |
+
|
| 772 |
+
def __init__(self, **options):
|
| 773 |
+
Filter.__init__(self, **options)
|
| 774 |
+
self.exception = options.get('excclass', ErrorToken)
|
| 775 |
+
try:
|
| 776 |
+
# issubclass() will raise TypeError if first argument is not a class
|
| 777 |
+
if not issubclass(self.exception, Exception):
|
| 778 |
+
raise TypeError
|
| 779 |
+
except TypeError:
|
| 780 |
+
raise OptionError('excclass option is not an exception class')
|
| 781 |
+
|
| 782 |
+
def filter(self, lexer, stream):
|
| 783 |
+
for ttype, value in stream:
|
| 784 |
+
if ttype is Error:
|
| 785 |
+
raise self.exception(value)
|
| 786 |
+
yield ttype, value
|
| 787 |
+
|
| 788 |
+
|
| 789 |
+
class VisibleWhitespaceFilter(Filter):
|
| 790 |
+
"""Convert tabs, newlines and/or spaces to visible characters.
|
| 791 |
+
|
| 792 |
+
Options accepted:
|
| 793 |
+
|
| 794 |
+
`spaces` : string or bool
|
| 795 |
+
If this is a one-character string, spaces will be replaces by this string.
|
| 796 |
+
If it is another true value, spaces will be replaced by ``·`` (unicode
|
| 797 |
+
MIDDLE DOT). If it is a false value, spaces will not be replaced. The
|
| 798 |
+
default is ``False``.
|
| 799 |
+
`tabs` : string or bool
|
| 800 |
+
The same as for `spaces`, but the default replacement character is ``»``
|
| 801 |
+
(unicode RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK). The default value
|
| 802 |
+
is ``False``. Note: this will not work if the `tabsize` option for the
|
| 803 |
+
lexer is nonzero, as tabs will already have been expanded then.
|
| 804 |
+
`tabsize` : int
|
| 805 |
+
If tabs are to be replaced by this filter (see the `tabs` option), this
|
| 806 |
+
is the total number of characters that a tab should be expanded to.
|
| 807 |
+
The default is ``8``.
|
| 808 |
+
`newlines` : string or bool
|
| 809 |
+
The same as for `spaces`, but the default replacement character is ``¶``
|
| 810 |
+
(unicode PILCROW SIGN). The default value is ``False``.
|
| 811 |
+
`wstokentype` : bool
|
| 812 |
+
If true, give whitespace the special `Whitespace` token type. This allows
|
| 813 |
+
styling the visible whitespace differently (e.g. greyed out), but it can
|
| 814 |
+
disrupt background colors. The default is ``True``.
|
| 815 |
+
|
| 816 |
+
.. versionadded:: 0.8
|
| 817 |
+
"""
|
| 818 |
+
|
| 819 |
+
def __init__(self, **options):
|
| 820 |
+
Filter.__init__(self, **options)
|
| 821 |
+
for name, default in [('spaces', '·'),
|
| 822 |
+
('tabs', '»'),
|
| 823 |
+
('newlines', '¶')]:
|
| 824 |
+
opt = options.get(name, False)
|
| 825 |
+
if isinstance(opt, str) and len(opt) == 1:
|
| 826 |
+
setattr(self, name, opt)
|
| 827 |
+
else:
|
| 828 |
+
setattr(self, name, (opt and default or ''))
|
| 829 |
+
tabsize = get_int_opt(options, 'tabsize', 8)
|
| 830 |
+
if self.tabs:
|
| 831 |
+
self.tabs += ' ' * (tabsize - 1)
|
| 832 |
+
if self.newlines:
|
| 833 |
+
self.newlines += '\n'
|
| 834 |
+
self.wstt = get_bool_opt(options, 'wstokentype', True)
|
| 835 |
+
|
| 836 |
+
def filter(self, lexer, stream):
|
| 837 |
+
if self.wstt:
|
| 838 |
+
spaces = self.spaces or ' '
|
| 839 |
+
tabs = self.tabs or '\t'
|
| 840 |
+
newlines = self.newlines or '\n'
|
| 841 |
+
regex = re.compile(r'\s')
|
| 842 |
+
|
| 843 |
+
def replacefunc(wschar):
|
| 844 |
+
if wschar == ' ':
|
| 845 |
+
return spaces
|
| 846 |
+
elif wschar == '\t':
|
| 847 |
+
return tabs
|
| 848 |
+
elif wschar == '\n':
|
| 849 |
+
return newlines
|
| 850 |
+
return wschar
|
| 851 |
+
|
| 852 |
+
for ttype, value in stream:
|
| 853 |
+
yield from _replace_special(ttype, value, regex, Whitespace,
|
| 854 |
+
replacefunc)
|
| 855 |
+
else:
|
| 856 |
+
spaces, tabs, newlines = self.spaces, self.tabs, self.newlines
|
| 857 |
+
# simpler processing
|
| 858 |
+
for ttype, value in stream:
|
| 859 |
+
if spaces:
|
| 860 |
+
value = value.replace(' ', spaces)
|
| 861 |
+
if tabs:
|
| 862 |
+
value = value.replace('\t', tabs)
|
| 863 |
+
if newlines:
|
| 864 |
+
value = value.replace('\n', newlines)
|
| 865 |
+
yield ttype, value
|
| 866 |
+
|
| 867 |
+
|
| 868 |
+
class GobbleFilter(Filter):
|
| 869 |
+
"""Gobbles source code lines (eats initial characters).
|
| 870 |
+
|
| 871 |
+
This filter drops the first ``n`` characters off every line of code. This
|
| 872 |
+
may be useful when the source code fed to the lexer is indented by a fixed
|
| 873 |
+
amount of space that isn't desired in the output.
|
| 874 |
+
|
| 875 |
+
Options accepted:
|
| 876 |
+
|
| 877 |
+
`n` : int
|
| 878 |
+
The number of characters to gobble.
|
| 879 |
+
|
| 880 |
+
.. versionadded:: 1.2
|
| 881 |
+
"""
|
| 882 |
+
def __init__(self, **options):
|
| 883 |
+
Filter.__init__(self, **options)
|
| 884 |
+
self.n = get_int_opt(options, 'n', 0)
|
| 885 |
+
|
| 886 |
+
def gobble(self, value, left):
|
| 887 |
+
if left < len(value):
|
| 888 |
+
return value[left:], 0
|
| 889 |
+
else:
|
| 890 |
+
return '', left - len(value)
|
| 891 |
+
|
| 892 |
+
def filter(self, lexer, stream):
|
| 893 |
+
n = self.n
|
| 894 |
+
left = n # How many characters left to gobble.
|
| 895 |
+
for ttype, value in stream:
|
| 896 |
+
# Remove ``left`` tokens from first line, ``n`` from all others.
|
| 897 |
+
parts = value.split('\n')
|
| 898 |
+
(parts[0], left) = self.gobble(parts[0], left)
|
| 899 |
+
for i in range(1, len(parts)):
|
| 900 |
+
(parts[i], left) = self.gobble(parts[i], n)
|
| 901 |
+
value = '\n'.join(parts)
|
| 902 |
+
|
| 903 |
+
if value != '':
|
| 904 |
+
yield ttype, value
|
| 905 |
+
|
| 906 |
+
|
| 907 |
+
class TokenMergeFilter(Filter):
|
| 908 |
+
"""Merges consecutive tokens with the same token type in the output
|
| 909 |
+
stream of a lexer.
|
| 910 |
+
|
| 911 |
+
.. versionadded:: 1.2
|
| 912 |
+
"""
|
| 913 |
+
def __init__(self, **options):
|
| 914 |
+
Filter.__init__(self, **options)
|
| 915 |
+
|
| 916 |
+
def filter(self, lexer, stream):
|
| 917 |
+
current_type = None
|
| 918 |
+
current_value = None
|
| 919 |
+
for ttype, value in stream:
|
| 920 |
+
if ttype is current_type:
|
| 921 |
+
current_value += value
|
| 922 |
+
else:
|
| 923 |
+
if current_type is not None:
|
| 924 |
+
yield current_type, current_value
|
| 925 |
+
current_type = ttype
|
| 926 |
+
current_value = value
|
| 927 |
+
if current_type is not None:
|
| 928 |
+
yield current_type, current_value
|
| 929 |
+
|
| 930 |
+
|
| 931 |
+
FILTERS = {
|
| 932 |
+
'codetagify': CodeTagFilter,
|
| 933 |
+
'keywordcase': KeywordCaseFilter,
|
| 934 |
+
'highlight': NameHighlightFilter,
|
| 935 |
+
'raiseonerror': RaiseOnErrorTokenFilter,
|
| 936 |
+
'whitespace': VisibleWhitespaceFilter,
|
| 937 |
+
'gobble': GobbleFilter,
|
| 938 |
+
'tokenmerge': TokenMergeFilter,
|
| 939 |
+
'symbols': SymbolFilter,
|
| 940 |
+
}
|
lib/python3.12/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (37.9 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/pygments/formatter.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
pygments.formatter
|
| 3 |
+
~~~~~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Base formatter class.
|
| 6 |
+
|
| 7 |
+
:copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
|
| 8 |
+
:license: BSD, see LICENSE for details.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import codecs
|
| 12 |
+
|
| 13 |
+
from pip._vendor.pygments.util import get_bool_opt
|
| 14 |
+
from pip._vendor.pygments.styles import get_style_by_name
|
| 15 |
+
|
| 16 |
+
__all__ = ['Formatter']
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def _lookup_style(style):
|
| 20 |
+
if isinstance(style, str):
|
| 21 |
+
return get_style_by_name(style)
|
| 22 |
+
return style
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class Formatter:
|
| 26 |
+
"""
|
| 27 |
+
Converts a token stream to text.
|
| 28 |
+
|
| 29 |
+
Formatters should have attributes to help selecting them. These
|
| 30 |
+
are similar to the corresponding :class:`~pygments.lexer.Lexer`
|
| 31 |
+
attributes.
|
| 32 |
+
|
| 33 |
+
.. autoattribute:: name
|
| 34 |
+
:no-value:
|
| 35 |
+
|
| 36 |
+
.. autoattribute:: aliases
|
| 37 |
+
:no-value:
|
| 38 |
+
|
| 39 |
+
.. autoattribute:: filenames
|
| 40 |
+
:no-value:
|
| 41 |
+
|
| 42 |
+
You can pass options as keyword arguments to the constructor.
|
| 43 |
+
All formatters accept these basic options:
|
| 44 |
+
|
| 45 |
+
``style``
|
| 46 |
+
The style to use, can be a string or a Style subclass
|
| 47 |
+
(default: "default"). Not used by e.g. the
|
| 48 |
+
TerminalFormatter.
|
| 49 |
+
``full``
|
| 50 |
+
Tells the formatter to output a "full" document, i.e.
|
| 51 |
+
a complete self-contained document. This doesn't have
|
| 52 |
+
any effect for some formatters (default: false).
|
| 53 |
+
``title``
|
| 54 |
+
If ``full`` is true, the title that should be used to
|
| 55 |
+
caption the document (default: '').
|
| 56 |
+
``encoding``
|
| 57 |
+
If given, must be an encoding name. This will be used to
|
| 58 |
+
convert the Unicode token strings to byte strings in the
|
| 59 |
+
output. If it is "" or None, Unicode strings will be written
|
| 60 |
+
to the output file, which most file-like objects do not
|
| 61 |
+
support (default: None).
|
| 62 |
+
``outencoding``
|
| 63 |
+
Overrides ``encoding`` if given.
|
| 64 |
+
|
| 65 |
+
"""
|
| 66 |
+
|
| 67 |
+
#: Full name for the formatter, in human-readable form.
|
| 68 |
+
name = None
|
| 69 |
+
|
| 70 |
+
#: A list of short, unique identifiers that can be used to lookup
|
| 71 |
+
#: the formatter from a list, e.g. using :func:`.get_formatter_by_name()`.
|
| 72 |
+
aliases = []
|
| 73 |
+
|
| 74 |
+
#: A list of fnmatch patterns that match filenames for which this
|
| 75 |
+
#: formatter can produce output. The patterns in this list should be unique
|
| 76 |
+
#: among all formatters.
|
| 77 |
+
filenames = []
|
| 78 |
+
|
| 79 |
+
#: If True, this formatter outputs Unicode strings when no encoding
|
| 80 |
+
#: option is given.
|
| 81 |
+
unicodeoutput = True
|
| 82 |
+
|
| 83 |
+
def __init__(self, **options):
|
| 84 |
+
"""
|
| 85 |
+
As with lexers, this constructor takes arbitrary optional arguments,
|
| 86 |
+
and if you override it, you should first process your own options, then
|
| 87 |
+
call the base class implementation.
|
| 88 |
+
"""
|
| 89 |
+
self.style = _lookup_style(options.get('style', 'default'))
|
| 90 |
+
self.full = get_bool_opt(options, 'full', False)
|
| 91 |
+
self.title = options.get('title', '')
|
| 92 |
+
self.encoding = options.get('encoding', None) or None
|
| 93 |
+
if self.encoding in ('guess', 'chardet'):
|
| 94 |
+
# can happen for e.g. pygmentize -O encoding=guess
|
| 95 |
+
self.encoding = 'utf-8'
|
| 96 |
+
self.encoding = options.get('outencoding') or self.encoding
|
| 97 |
+
self.options = options
|
| 98 |
+
|
| 99 |
+
def get_style_defs(self, arg=''):
|
| 100 |
+
"""
|
| 101 |
+
This method must return statements or declarations suitable to define
|
| 102 |
+
the current style for subsequent highlighted text (e.g. CSS classes
|
| 103 |
+
in the `HTMLFormatter`).
|
| 104 |
+
|
| 105 |
+
The optional argument `arg` can be used to modify the generation and
|
| 106 |
+
is formatter dependent (it is standardized because it can be given on
|
| 107 |
+
the command line).
|
| 108 |
+
|
| 109 |
+
This method is called by the ``-S`` :doc:`command-line option <cmdline>`,
|
| 110 |
+
the `arg` is then given by the ``-a`` option.
|
| 111 |
+
"""
|
| 112 |
+
return ''
|
| 113 |
+
|
| 114 |
+
def format(self, tokensource, outfile):
|
| 115 |
+
"""
|
| 116 |
+
This method must format the tokens from the `tokensource` iterable and
|
| 117 |
+
write the formatted version to the file object `outfile`.
|
| 118 |
+
|
| 119 |
+
Formatter options can control how exactly the tokens are converted.
|
| 120 |
+
"""
|
| 121 |
+
if self.encoding:
|
| 122 |
+
# wrap the outfile in a StreamWriter
|
| 123 |
+
outfile = codecs.lookup(self.encoding)[3](outfile)
|
| 124 |
+
return self.format_unencoded(tokensource, outfile)
|
| 125 |
+
|
| 126 |
+
# Allow writing Formatter[str] or Formatter[bytes]. That's equivalent to
|
| 127 |
+
# Formatter. This helps when using third-party type stubs from typeshed.
|
| 128 |
+
def __class_getitem__(cls, name):
|
| 129 |
+
return cls
|
lib/python3.12/site-packages/pip/_vendor/pygments/lexer.py
ADDED
|
@@ -0,0 +1,963 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
pygments.lexer
|
| 3 |
+
~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Base lexer classes.
|
| 6 |
+
|
| 7 |
+
:copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
|
| 8 |
+
:license: BSD, see LICENSE for details.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import re
|
| 12 |
+
import sys
|
| 13 |
+
import time
|
| 14 |
+
|
| 15 |
+
from pip._vendor.pygments.filter import apply_filters, Filter
|
| 16 |
+
from pip._vendor.pygments.filters import get_filter_by_name
|
| 17 |
+
from pip._vendor.pygments.token import Error, Text, Other, Whitespace, _TokenType
|
| 18 |
+
from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt, \
|
| 19 |
+
make_analysator, Future, guess_decode
|
| 20 |
+
from pip._vendor.pygments.regexopt import regex_opt
|
| 21 |
+
|
| 22 |
+
__all__ = ['Lexer', 'RegexLexer', 'ExtendedRegexLexer', 'DelegatingLexer',
|
| 23 |
+
'LexerContext', 'include', 'inherit', 'bygroups', 'using', 'this',
|
| 24 |
+
'default', 'words', 'line_re']
|
| 25 |
+
|
| 26 |
+
line_re = re.compile('.*?\n')
|
| 27 |
+
|
| 28 |
+
_encoding_map = [(b'\xef\xbb\xbf', 'utf-8'),
|
| 29 |
+
(b'\xff\xfe\0\0', 'utf-32'),
|
| 30 |
+
(b'\0\0\xfe\xff', 'utf-32be'),
|
| 31 |
+
(b'\xff\xfe', 'utf-16'),
|
| 32 |
+
(b'\xfe\xff', 'utf-16be')]
|
| 33 |
+
|
| 34 |
+
_default_analyse = staticmethod(lambda x: 0.0)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class LexerMeta(type):
|
| 38 |
+
"""
|
| 39 |
+
This metaclass automagically converts ``analyse_text`` methods into
|
| 40 |
+
static methods which always return float values.
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
def __new__(mcs, name, bases, d):
|
| 44 |
+
if 'analyse_text' in d:
|
| 45 |
+
d['analyse_text'] = make_analysator(d['analyse_text'])
|
| 46 |
+
return type.__new__(mcs, name, bases, d)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class Lexer(metaclass=LexerMeta):
|
| 50 |
+
"""
|
| 51 |
+
Lexer for a specific language.
|
| 52 |
+
|
| 53 |
+
See also :doc:`lexerdevelopment`, a high-level guide to writing
|
| 54 |
+
lexers.
|
| 55 |
+
|
| 56 |
+
Lexer classes have attributes used for choosing the most appropriate
|
| 57 |
+
lexer based on various criteria.
|
| 58 |
+
|
| 59 |
+
.. autoattribute:: name
|
| 60 |
+
:no-value:
|
| 61 |
+
.. autoattribute:: aliases
|
| 62 |
+
:no-value:
|
| 63 |
+
.. autoattribute:: filenames
|
| 64 |
+
:no-value:
|
| 65 |
+
.. autoattribute:: alias_filenames
|
| 66 |
+
.. autoattribute:: mimetypes
|
| 67 |
+
:no-value:
|
| 68 |
+
.. autoattribute:: priority
|
| 69 |
+
|
| 70 |
+
Lexers included in Pygments should have two additional attributes:
|
| 71 |
+
|
| 72 |
+
.. autoattribute:: url
|
| 73 |
+
:no-value:
|
| 74 |
+
.. autoattribute:: version_added
|
| 75 |
+
:no-value:
|
| 76 |
+
|
| 77 |
+
Lexers included in Pygments may have additional attributes:
|
| 78 |
+
|
| 79 |
+
.. autoattribute:: _example
|
| 80 |
+
:no-value:
|
| 81 |
+
|
| 82 |
+
You can pass options to the constructor. The basic options recognized
|
| 83 |
+
by all lexers and processed by the base `Lexer` class are:
|
| 84 |
+
|
| 85 |
+
``stripnl``
|
| 86 |
+
Strip leading and trailing newlines from the input (default: True).
|
| 87 |
+
``stripall``
|
| 88 |
+
Strip all leading and trailing whitespace from the input
|
| 89 |
+
(default: False).
|
| 90 |
+
``ensurenl``
|
| 91 |
+
Make sure that the input ends with a newline (default: True). This
|
| 92 |
+
is required for some lexers that consume input linewise.
|
| 93 |
+
|
| 94 |
+
.. versionadded:: 1.3
|
| 95 |
+
|
| 96 |
+
``tabsize``
|
| 97 |
+
If given and greater than 0, expand tabs in the input (default: 0).
|
| 98 |
+
``encoding``
|
| 99 |
+
If given, must be an encoding name. This encoding will be used to
|
| 100 |
+
convert the input string to Unicode, if it is not already a Unicode
|
| 101 |
+
string (default: ``'guess'``, which uses a simple UTF-8 / Locale /
|
| 102 |
+
Latin1 detection. Can also be ``'chardet'`` to use the chardet
|
| 103 |
+
library, if it is installed.
|
| 104 |
+
``inencoding``
|
| 105 |
+
Overrides the ``encoding`` if given.
|
| 106 |
+
"""
|
| 107 |
+
|
| 108 |
+
#: Full name of the lexer, in human-readable form
|
| 109 |
+
name = None
|
| 110 |
+
|
| 111 |
+
#: A list of short, unique identifiers that can be used to look
|
| 112 |
+
#: up the lexer from a list, e.g., using `get_lexer_by_name()`.
|
| 113 |
+
aliases = []
|
| 114 |
+
|
| 115 |
+
#: A list of `fnmatch` patterns that match filenames which contain
|
| 116 |
+
#: content for this lexer. The patterns in this list should be unique among
|
| 117 |
+
#: all lexers.
|
| 118 |
+
filenames = []
|
| 119 |
+
|
| 120 |
+
#: A list of `fnmatch` patterns that match filenames which may or may not
|
| 121 |
+
#: contain content for this lexer. This list is used by the
|
| 122 |
+
#: :func:`.guess_lexer_for_filename()` function, to determine which lexers
|
| 123 |
+
#: are then included in guessing the correct one. That means that
|
| 124 |
+
#: e.g. every lexer for HTML and a template language should include
|
| 125 |
+
#: ``\*.html`` in this list.
|
| 126 |
+
alias_filenames = []
|
| 127 |
+
|
| 128 |
+
#: A list of MIME types for content that can be lexed with this lexer.
|
| 129 |
+
mimetypes = []
|
| 130 |
+
|
| 131 |
+
#: Priority, should multiple lexers match and no content is provided
|
| 132 |
+
priority = 0
|
| 133 |
+
|
| 134 |
+
#: URL of the language specification/definition. Used in the Pygments
|
| 135 |
+
#: documentation. Set to an empty string to disable.
|
| 136 |
+
url = None
|
| 137 |
+
|
| 138 |
+
#: Version of Pygments in which the lexer was added.
|
| 139 |
+
version_added = None
|
| 140 |
+
|
| 141 |
+
#: Example file name. Relative to the ``tests/examplefiles`` directory.
|
| 142 |
+
#: This is used by the documentation generator to show an example.
|
| 143 |
+
_example = None
|
| 144 |
+
|
| 145 |
+
def __init__(self, **options):
|
| 146 |
+
"""
|
| 147 |
+
This constructor takes arbitrary options as keyword arguments.
|
| 148 |
+
Every subclass must first process its own options and then call
|
| 149 |
+
the `Lexer` constructor, since it processes the basic
|
| 150 |
+
options like `stripnl`.
|
| 151 |
+
|
| 152 |
+
An example looks like this:
|
| 153 |
+
|
| 154 |
+
.. sourcecode:: python
|
| 155 |
+
|
| 156 |
+
def __init__(self, **options):
|
| 157 |
+
self.compress = options.get('compress', '')
|
| 158 |
+
Lexer.__init__(self, **options)
|
| 159 |
+
|
| 160 |
+
As these options must all be specifiable as strings (due to the
|
| 161 |
+
command line usage), there are various utility functions
|
| 162 |
+
available to help with that, see `Utilities`_.
|
| 163 |
+
"""
|
| 164 |
+
self.options = options
|
| 165 |
+
self.stripnl = get_bool_opt(options, 'stripnl', True)
|
| 166 |
+
self.stripall = get_bool_opt(options, 'stripall', False)
|
| 167 |
+
self.ensurenl = get_bool_opt(options, 'ensurenl', True)
|
| 168 |
+
self.tabsize = get_int_opt(options, 'tabsize', 0)
|
| 169 |
+
self.encoding = options.get('encoding', 'guess')
|
| 170 |
+
self.encoding = options.get('inencoding') or self.encoding
|
| 171 |
+
self.filters = []
|
| 172 |
+
for filter_ in get_list_opt(options, 'filters', ()):
|
| 173 |
+
self.add_filter(filter_)
|
| 174 |
+
|
| 175 |
+
def __repr__(self):
|
| 176 |
+
if self.options:
|
| 177 |
+
return f'<pygments.lexers.{self.__class__.__name__} with {self.options!r}>'
|
| 178 |
+
else:
|
| 179 |
+
return f'<pygments.lexers.{self.__class__.__name__}>'
|
| 180 |
+
|
| 181 |
+
def add_filter(self, filter_, **options):
|
| 182 |
+
"""
|
| 183 |
+
Add a new stream filter to this lexer.
|
| 184 |
+
"""
|
| 185 |
+
if not isinstance(filter_, Filter):
|
| 186 |
+
filter_ = get_filter_by_name(filter_, **options)
|
| 187 |
+
self.filters.append(filter_)
|
| 188 |
+
|
| 189 |
+
def analyse_text(text):
|
| 190 |
+
"""
|
| 191 |
+
A static method which is called for lexer guessing.
|
| 192 |
+
|
| 193 |
+
It should analyse the text and return a float in the range
|
| 194 |
+
from ``0.0`` to ``1.0``. If it returns ``0.0``, the lexer
|
| 195 |
+
will not be selected as the most probable one, if it returns
|
| 196 |
+
``1.0``, it will be selected immediately. This is used by
|
| 197 |
+
`guess_lexer`.
|
| 198 |
+
|
| 199 |
+
The `LexerMeta` metaclass automatically wraps this function so
|
| 200 |
+
that it works like a static method (no ``self`` or ``cls``
|
| 201 |
+
parameter) and the return value is automatically converted to
|
| 202 |
+
`float`. If the return value is an object that is boolean `False`
|
| 203 |
+
it's the same as if the return values was ``0.0``.
|
| 204 |
+
"""
|
| 205 |
+
|
| 206 |
+
def _preprocess_lexer_input(self, text):
|
| 207 |
+
"""Apply preprocessing such as decoding the input, removing BOM and normalizing newlines."""
|
| 208 |
+
|
| 209 |
+
if not isinstance(text, str):
|
| 210 |
+
if self.encoding == 'guess':
|
| 211 |
+
text, _ = guess_decode(text)
|
| 212 |
+
elif self.encoding == 'chardet':
|
| 213 |
+
try:
|
| 214 |
+
# pip vendoring note: this code is not reachable by pip,
|
| 215 |
+
# removed import of chardet to make it clear.
|
| 216 |
+
raise ImportError('chardet is not vendored by pip')
|
| 217 |
+
except ImportError as e:
|
| 218 |
+
raise ImportError('To enable chardet encoding guessing, '
|
| 219 |
+
'please install the chardet library '
|
| 220 |
+
'from http://chardet.feedparser.org/') from e
|
| 221 |
+
# check for BOM first
|
| 222 |
+
decoded = None
|
| 223 |
+
for bom, encoding in _encoding_map:
|
| 224 |
+
if text.startswith(bom):
|
| 225 |
+
decoded = text[len(bom):].decode(encoding, 'replace')
|
| 226 |
+
break
|
| 227 |
+
# no BOM found, so use chardet
|
| 228 |
+
if decoded is None:
|
| 229 |
+
enc = chardet.detect(text[:1024]) # Guess using first 1KB
|
| 230 |
+
decoded = text.decode(enc.get('encoding') or 'utf-8',
|
| 231 |
+
'replace')
|
| 232 |
+
text = decoded
|
| 233 |
+
else:
|
| 234 |
+
text = text.decode(self.encoding)
|
| 235 |
+
if text.startswith('\ufeff'):
|
| 236 |
+
text = text[len('\ufeff'):]
|
| 237 |
+
else:
|
| 238 |
+
if text.startswith('\ufeff'):
|
| 239 |
+
text = text[len('\ufeff'):]
|
| 240 |
+
|
| 241 |
+
# text now *is* a unicode string
|
| 242 |
+
text = text.replace('\r\n', '\n')
|
| 243 |
+
text = text.replace('\r', '\n')
|
| 244 |
+
if self.stripall:
|
| 245 |
+
text = text.strip()
|
| 246 |
+
elif self.stripnl:
|
| 247 |
+
text = text.strip('\n')
|
| 248 |
+
if self.tabsize > 0:
|
| 249 |
+
text = text.expandtabs(self.tabsize)
|
| 250 |
+
if self.ensurenl and not text.endswith('\n'):
|
| 251 |
+
text += '\n'
|
| 252 |
+
|
| 253 |
+
return text
|
| 254 |
+
|
| 255 |
+
def get_tokens(self, text, unfiltered=False):
|
| 256 |
+
"""
|
| 257 |
+
This method is the basic interface of a lexer. It is called by
|
| 258 |
+
the `highlight()` function. It must process the text and return an
|
| 259 |
+
iterable of ``(tokentype, value)`` pairs from `text`.
|
| 260 |
+
|
| 261 |
+
Normally, you don't need to override this method. The default
|
| 262 |
+
implementation processes the options recognized by all lexers
|
| 263 |
+
(`stripnl`, `stripall` and so on), and then yields all tokens
|
| 264 |
+
from `get_tokens_unprocessed()`, with the ``index`` dropped.
|
| 265 |
+
|
| 266 |
+
If `unfiltered` is set to `True`, the filtering mechanism is
|
| 267 |
+
bypassed even if filters are defined.
|
| 268 |
+
"""
|
| 269 |
+
text = self._preprocess_lexer_input(text)
|
| 270 |
+
|
| 271 |
+
def streamer():
|
| 272 |
+
for _, t, v in self.get_tokens_unprocessed(text):
|
| 273 |
+
yield t, v
|
| 274 |
+
stream = streamer()
|
| 275 |
+
if not unfiltered:
|
| 276 |
+
stream = apply_filters(stream, self.filters, self)
|
| 277 |
+
return stream
|
| 278 |
+
|
| 279 |
+
def get_tokens_unprocessed(self, text):
|
| 280 |
+
"""
|
| 281 |
+
This method should process the text and return an iterable of
|
| 282 |
+
``(index, tokentype, value)`` tuples where ``index`` is the starting
|
| 283 |
+
position of the token within the input text.
|
| 284 |
+
|
| 285 |
+
It must be overridden by subclasses. It is recommended to
|
| 286 |
+
implement it as a generator to maximize effectiveness.
|
| 287 |
+
"""
|
| 288 |
+
raise NotImplementedError
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
class DelegatingLexer(Lexer):
|
| 292 |
+
"""
|
| 293 |
+
This lexer takes two lexer as arguments. A root lexer and
|
| 294 |
+
a language lexer. First everything is scanned using the language
|
| 295 |
+
lexer, afterwards all ``Other`` tokens are lexed using the root
|
| 296 |
+
lexer.
|
| 297 |
+
|
| 298 |
+
The lexers from the ``template`` lexer package use this base lexer.
|
| 299 |
+
"""
|
| 300 |
+
|
| 301 |
+
def __init__(self, _root_lexer, _language_lexer, _needle=Other, **options):
|
| 302 |
+
self.root_lexer = _root_lexer(**options)
|
| 303 |
+
self.language_lexer = _language_lexer(**options)
|
| 304 |
+
self.needle = _needle
|
| 305 |
+
Lexer.__init__(self, **options)
|
| 306 |
+
|
| 307 |
+
def get_tokens_unprocessed(self, text):
|
| 308 |
+
buffered = ''
|
| 309 |
+
insertions = []
|
| 310 |
+
lng_buffer = []
|
| 311 |
+
for i, t, v in self.language_lexer.get_tokens_unprocessed(text):
|
| 312 |
+
if t is self.needle:
|
| 313 |
+
if lng_buffer:
|
| 314 |
+
insertions.append((len(buffered), lng_buffer))
|
| 315 |
+
lng_buffer = []
|
| 316 |
+
buffered += v
|
| 317 |
+
else:
|
| 318 |
+
lng_buffer.append((i, t, v))
|
| 319 |
+
if lng_buffer:
|
| 320 |
+
insertions.append((len(buffered), lng_buffer))
|
| 321 |
+
return do_insertions(insertions,
|
| 322 |
+
self.root_lexer.get_tokens_unprocessed(buffered))
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
# ------------------------------------------------------------------------------
|
| 326 |
+
# RegexLexer and ExtendedRegexLexer
|
| 327 |
+
#
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
class include(str): # pylint: disable=invalid-name
|
| 331 |
+
"""
|
| 332 |
+
Indicates that a state should include rules from another state.
|
| 333 |
+
"""
|
| 334 |
+
pass
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
class _inherit:
|
| 338 |
+
"""
|
| 339 |
+
Indicates the a state should inherit from its superclass.
|
| 340 |
+
"""
|
| 341 |
+
def __repr__(self):
|
| 342 |
+
return 'inherit'
|
| 343 |
+
|
| 344 |
+
inherit = _inherit() # pylint: disable=invalid-name
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
class combined(tuple): # pylint: disable=invalid-name
|
| 348 |
+
"""
|
| 349 |
+
Indicates a state combined from multiple states.
|
| 350 |
+
"""
|
| 351 |
+
|
| 352 |
+
def __new__(cls, *args):
|
| 353 |
+
return tuple.__new__(cls, args)
|
| 354 |
+
|
| 355 |
+
def __init__(self, *args):
|
| 356 |
+
# tuple.__init__ doesn't do anything
|
| 357 |
+
pass
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
class _PseudoMatch:
|
| 361 |
+
"""
|
| 362 |
+
A pseudo match object constructed from a string.
|
| 363 |
+
"""
|
| 364 |
+
|
| 365 |
+
def __init__(self, start, text):
|
| 366 |
+
self._text = text
|
| 367 |
+
self._start = start
|
| 368 |
+
|
| 369 |
+
def start(self, arg=None):
|
| 370 |
+
return self._start
|
| 371 |
+
|
| 372 |
+
def end(self, arg=None):
|
| 373 |
+
return self._start + len(self._text)
|
| 374 |
+
|
| 375 |
+
def group(self, arg=None):
|
| 376 |
+
if arg:
|
| 377 |
+
raise IndexError('No such group')
|
| 378 |
+
return self._text
|
| 379 |
+
|
| 380 |
+
def groups(self):
|
| 381 |
+
return (self._text,)
|
| 382 |
+
|
| 383 |
+
def groupdict(self):
|
| 384 |
+
return {}
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def bygroups(*args):
|
| 388 |
+
"""
|
| 389 |
+
Callback that yields multiple actions for each group in the match.
|
| 390 |
+
"""
|
| 391 |
+
def callback(lexer, match, ctx=None):
|
| 392 |
+
for i, action in enumerate(args):
|
| 393 |
+
if action is None:
|
| 394 |
+
continue
|
| 395 |
+
elif type(action) is _TokenType:
|
| 396 |
+
data = match.group(i + 1)
|
| 397 |
+
if data:
|
| 398 |
+
yield match.start(i + 1), action, data
|
| 399 |
+
else:
|
| 400 |
+
data = match.group(i + 1)
|
| 401 |
+
if data is not None:
|
| 402 |
+
if ctx:
|
| 403 |
+
ctx.pos = match.start(i + 1)
|
| 404 |
+
for item in action(lexer,
|
| 405 |
+
_PseudoMatch(match.start(i + 1), data), ctx):
|
| 406 |
+
if item:
|
| 407 |
+
yield item
|
| 408 |
+
if ctx:
|
| 409 |
+
ctx.pos = match.end()
|
| 410 |
+
return callback
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
class _This:
|
| 414 |
+
"""
|
| 415 |
+
Special singleton used for indicating the caller class.
|
| 416 |
+
Used by ``using``.
|
| 417 |
+
"""
|
| 418 |
+
|
| 419 |
+
this = _This()
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
def using(_other, **kwargs):
|
| 423 |
+
"""
|
| 424 |
+
Callback that processes the match with a different lexer.
|
| 425 |
+
|
| 426 |
+
The keyword arguments are forwarded to the lexer, except `state` which
|
| 427 |
+
is handled separately.
|
| 428 |
+
|
| 429 |
+
`state` specifies the state that the new lexer will start in, and can
|
| 430 |
+
be an enumerable such as ('root', 'inline', 'string') or a simple
|
| 431 |
+
string which is assumed to be on top of the root state.
|
| 432 |
+
|
| 433 |
+
Note: For that to work, `_other` must not be an `ExtendedRegexLexer`.
|
| 434 |
+
"""
|
| 435 |
+
gt_kwargs = {}
|
| 436 |
+
if 'state' in kwargs:
|
| 437 |
+
s = kwargs.pop('state')
|
| 438 |
+
if isinstance(s, (list, tuple)):
|
| 439 |
+
gt_kwargs['stack'] = s
|
| 440 |
+
else:
|
| 441 |
+
gt_kwargs['stack'] = ('root', s)
|
| 442 |
+
|
| 443 |
+
if _other is this:
|
| 444 |
+
def callback(lexer, match, ctx=None):
|
| 445 |
+
# if keyword arguments are given the callback
|
| 446 |
+
# function has to create a new lexer instance
|
| 447 |
+
if kwargs:
|
| 448 |
+
# XXX: cache that somehow
|
| 449 |
+
kwargs.update(lexer.options)
|
| 450 |
+
lx = lexer.__class__(**kwargs)
|
| 451 |
+
else:
|
| 452 |
+
lx = lexer
|
| 453 |
+
s = match.start()
|
| 454 |
+
for i, t, v in lx.get_tokens_unprocessed(match.group(), **gt_kwargs):
|
| 455 |
+
yield i + s, t, v
|
| 456 |
+
if ctx:
|
| 457 |
+
ctx.pos = match.end()
|
| 458 |
+
else:
|
| 459 |
+
def callback(lexer, match, ctx=None):
|
| 460 |
+
# XXX: cache that somehow
|
| 461 |
+
kwargs.update(lexer.options)
|
| 462 |
+
lx = _other(**kwargs)
|
| 463 |
+
|
| 464 |
+
s = match.start()
|
| 465 |
+
for i, t, v in lx.get_tokens_unprocessed(match.group(), **gt_kwargs):
|
| 466 |
+
yield i + s, t, v
|
| 467 |
+
if ctx:
|
| 468 |
+
ctx.pos = match.end()
|
| 469 |
+
return callback
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
class default:
|
| 473 |
+
"""
|
| 474 |
+
Indicates a state or state action (e.g. #pop) to apply.
|
| 475 |
+
For example default('#pop') is equivalent to ('', Token, '#pop')
|
| 476 |
+
Note that state tuples may be used as well.
|
| 477 |
+
|
| 478 |
+
.. versionadded:: 2.0
|
| 479 |
+
"""
|
| 480 |
+
def __init__(self, state):
|
| 481 |
+
self.state = state
|
| 482 |
+
|
| 483 |
+
|
| 484 |
+
class words(Future):
|
| 485 |
+
"""
|
| 486 |
+
Indicates a list of literal words that is transformed into an optimized
|
| 487 |
+
regex that matches any of the words.
|
| 488 |
+
|
| 489 |
+
.. versionadded:: 2.0
|
| 490 |
+
"""
|
| 491 |
+
def __init__(self, words, prefix='', suffix=''):
|
| 492 |
+
self.words = words
|
| 493 |
+
self.prefix = prefix
|
| 494 |
+
self.suffix = suffix
|
| 495 |
+
|
| 496 |
+
def get(self):
|
| 497 |
+
return regex_opt(self.words, prefix=self.prefix, suffix=self.suffix)
|
| 498 |
+
|
| 499 |
+
|
| 500 |
+
class RegexLexerMeta(LexerMeta):
|
| 501 |
+
"""
|
| 502 |
+
Metaclass for RegexLexer, creates the self._tokens attribute from
|
| 503 |
+
self.tokens on the first instantiation.
|
| 504 |
+
"""
|
| 505 |
+
|
| 506 |
+
def _process_regex(cls, regex, rflags, state):
|
| 507 |
+
"""Preprocess the regular expression component of a token definition."""
|
| 508 |
+
if isinstance(regex, Future):
|
| 509 |
+
regex = regex.get()
|
| 510 |
+
return re.compile(regex, rflags).match
|
| 511 |
+
|
| 512 |
+
def _process_token(cls, token):
|
| 513 |
+
"""Preprocess the token component of a token definition."""
|
| 514 |
+
assert type(token) is _TokenType or callable(token), \
|
| 515 |
+
f'token type must be simple type or callable, not {token!r}'
|
| 516 |
+
return token
|
| 517 |
+
|
| 518 |
+
def _process_new_state(cls, new_state, unprocessed, processed):
|
| 519 |
+
"""Preprocess the state transition action of a token definition."""
|
| 520 |
+
if isinstance(new_state, str):
|
| 521 |
+
# an existing state
|
| 522 |
+
if new_state == '#pop':
|
| 523 |
+
return -1
|
| 524 |
+
elif new_state in unprocessed:
|
| 525 |
+
return (new_state,)
|
| 526 |
+
elif new_state == '#push':
|
| 527 |
+
return new_state
|
| 528 |
+
elif new_state[:5] == '#pop:':
|
| 529 |
+
return -int(new_state[5:])
|
| 530 |
+
else:
|
| 531 |
+
assert False, f'unknown new state {new_state!r}'
|
| 532 |
+
elif isinstance(new_state, combined):
|
| 533 |
+
# combine a new state from existing ones
|
| 534 |
+
tmp_state = '_tmp_%d' % cls._tmpname
|
| 535 |
+
cls._tmpname += 1
|
| 536 |
+
itokens = []
|
| 537 |
+
for istate in new_state:
|
| 538 |
+
assert istate != new_state, f'circular state ref {istate!r}'
|
| 539 |
+
itokens.extend(cls._process_state(unprocessed,
|
| 540 |
+
processed, istate))
|
| 541 |
+
processed[tmp_state] = itokens
|
| 542 |
+
return (tmp_state,)
|
| 543 |
+
elif isinstance(new_state, tuple):
|
| 544 |
+
# push more than one state
|
| 545 |
+
for istate in new_state:
|
| 546 |
+
assert (istate in unprocessed or
|
| 547 |
+
istate in ('#pop', '#push')), \
|
| 548 |
+
'unknown new state ' + istate
|
| 549 |
+
return new_state
|
| 550 |
+
else:
|
| 551 |
+
assert False, f'unknown new state def {new_state!r}'
|
| 552 |
+
|
| 553 |
+
def _process_state(cls, unprocessed, processed, state):
|
| 554 |
+
"""Preprocess a single state definition."""
|
| 555 |
+
assert isinstance(state, str), f"wrong state name {state!r}"
|
| 556 |
+
assert state[0] != '#', f"invalid state name {state!r}"
|
| 557 |
+
if state in processed:
|
| 558 |
+
return processed[state]
|
| 559 |
+
tokens = processed[state] = []
|
| 560 |
+
rflags = cls.flags
|
| 561 |
+
for tdef in unprocessed[state]:
|
| 562 |
+
if isinstance(tdef, include):
|
| 563 |
+
# it's a state reference
|
| 564 |
+
assert tdef != state, f"circular state reference {state!r}"
|
| 565 |
+
tokens.extend(cls._process_state(unprocessed, processed,
|
| 566 |
+
str(tdef)))
|
| 567 |
+
continue
|
| 568 |
+
if isinstance(tdef, _inherit):
|
| 569 |
+
# should be processed already, but may not in the case of:
|
| 570 |
+
# 1. the state has no counterpart in any parent
|
| 571 |
+
# 2. the state includes more than one 'inherit'
|
| 572 |
+
continue
|
| 573 |
+
if isinstance(tdef, default):
|
| 574 |
+
new_state = cls._process_new_state(tdef.state, unprocessed, processed)
|
| 575 |
+
tokens.append((re.compile('').match, None, new_state))
|
| 576 |
+
continue
|
| 577 |
+
|
| 578 |
+
assert type(tdef) is tuple, f"wrong rule def {tdef!r}"
|
| 579 |
+
|
| 580 |
+
try:
|
| 581 |
+
rex = cls._process_regex(tdef[0], rflags, state)
|
| 582 |
+
except Exception as err:
|
| 583 |
+
raise ValueError(f"uncompilable regex {tdef[0]!r} in state {state!r} of {cls!r}: {err}") from err
|
| 584 |
+
|
| 585 |
+
token = cls._process_token(tdef[1])
|
| 586 |
+
|
| 587 |
+
if len(tdef) == 2:
|
| 588 |
+
new_state = None
|
| 589 |
+
else:
|
| 590 |
+
new_state = cls._process_new_state(tdef[2],
|
| 591 |
+
unprocessed, processed)
|
| 592 |
+
|
| 593 |
+
tokens.append((rex, token, new_state))
|
| 594 |
+
return tokens
|
| 595 |
+
|
| 596 |
+
def process_tokendef(cls, name, tokendefs=None):
|
| 597 |
+
"""Preprocess a dictionary of token definitions."""
|
| 598 |
+
processed = cls._all_tokens[name] = {}
|
| 599 |
+
tokendefs = tokendefs or cls.tokens[name]
|
| 600 |
+
for state in list(tokendefs):
|
| 601 |
+
cls._process_state(tokendefs, processed, state)
|
| 602 |
+
return processed
|
| 603 |
+
|
| 604 |
+
def get_tokendefs(cls):
|
| 605 |
+
"""
|
| 606 |
+
Merge tokens from superclasses in MRO order, returning a single tokendef
|
| 607 |
+
dictionary.
|
| 608 |
+
|
| 609 |
+
Any state that is not defined by a subclass will be inherited
|
| 610 |
+
automatically. States that *are* defined by subclasses will, by
|
| 611 |
+
default, override that state in the superclass. If a subclass wishes to
|
| 612 |
+
inherit definitions from a superclass, it can use the special value
|
| 613 |
+
"inherit", which will cause the superclass' state definition to be
|
| 614 |
+
included at that point in the state.
|
| 615 |
+
"""
|
| 616 |
+
tokens = {}
|
| 617 |
+
inheritable = {}
|
| 618 |
+
for c in cls.__mro__:
|
| 619 |
+
toks = c.__dict__.get('tokens', {})
|
| 620 |
+
|
| 621 |
+
for state, items in toks.items():
|
| 622 |
+
curitems = tokens.get(state)
|
| 623 |
+
if curitems is None:
|
| 624 |
+
# N.b. because this is assigned by reference, sufficiently
|
| 625 |
+
# deep hierarchies are processed incrementally (e.g. for
|
| 626 |
+
# A(B), B(C), C(RegexLexer), B will be premodified so X(B)
|
| 627 |
+
# will not see any inherits in B).
|
| 628 |
+
tokens[state] = items
|
| 629 |
+
try:
|
| 630 |
+
inherit_ndx = items.index(inherit)
|
| 631 |
+
except ValueError:
|
| 632 |
+
continue
|
| 633 |
+
inheritable[state] = inherit_ndx
|
| 634 |
+
continue
|
| 635 |
+
|
| 636 |
+
inherit_ndx = inheritable.pop(state, None)
|
| 637 |
+
if inherit_ndx is None:
|
| 638 |
+
continue
|
| 639 |
+
|
| 640 |
+
# Replace the "inherit" value with the items
|
| 641 |
+
curitems[inherit_ndx:inherit_ndx+1] = items
|
| 642 |
+
try:
|
| 643 |
+
# N.b. this is the index in items (that is, the superclass
|
| 644 |
+
# copy), so offset required when storing below.
|
| 645 |
+
new_inh_ndx = items.index(inherit)
|
| 646 |
+
except ValueError:
|
| 647 |
+
pass
|
| 648 |
+
else:
|
| 649 |
+
inheritable[state] = inherit_ndx + new_inh_ndx
|
| 650 |
+
|
| 651 |
+
return tokens
|
| 652 |
+
|
| 653 |
+
def __call__(cls, *args, **kwds):
|
| 654 |
+
"""Instantiate cls after preprocessing its token definitions."""
|
| 655 |
+
if '_tokens' not in cls.__dict__:
|
| 656 |
+
cls._all_tokens = {}
|
| 657 |
+
cls._tmpname = 0
|
| 658 |
+
if hasattr(cls, 'token_variants') and cls.token_variants:
|
| 659 |
+
# don't process yet
|
| 660 |
+
pass
|
| 661 |
+
else:
|
| 662 |
+
cls._tokens = cls.process_tokendef('', cls.get_tokendefs())
|
| 663 |
+
|
| 664 |
+
return type.__call__(cls, *args, **kwds)
|
| 665 |
+
|
| 666 |
+
|
| 667 |
+
class RegexLexer(Lexer, metaclass=RegexLexerMeta):
|
| 668 |
+
"""
|
| 669 |
+
Base for simple stateful regular expression-based lexers.
|
| 670 |
+
Simplifies the lexing process so that you need only
|
| 671 |
+
provide a list of states and regular expressions.
|
| 672 |
+
"""
|
| 673 |
+
|
| 674 |
+
#: Flags for compiling the regular expressions.
|
| 675 |
+
#: Defaults to MULTILINE.
|
| 676 |
+
flags = re.MULTILINE
|
| 677 |
+
|
| 678 |
+
#: At all time there is a stack of states. Initially, the stack contains
|
| 679 |
+
#: a single state 'root'. The top of the stack is called "the current state".
|
| 680 |
+
#:
|
| 681 |
+
#: Dict of ``{'state': [(regex, tokentype, new_state), ...], ...}``
|
| 682 |
+
#:
|
| 683 |
+
#: ``new_state`` can be omitted to signify no state transition.
|
| 684 |
+
#: If ``new_state`` is a string, it is pushed on the stack. This ensure
|
| 685 |
+
#: the new current state is ``new_state``.
|
| 686 |
+
#: If ``new_state`` is a tuple of strings, all of those strings are pushed
|
| 687 |
+
#: on the stack and the current state will be the last element of the list.
|
| 688 |
+
#: ``new_state`` can also be ``combined('state1', 'state2', ...)``
|
| 689 |
+
#: to signify a new, anonymous state combined from the rules of two
|
| 690 |
+
#: or more existing ones.
|
| 691 |
+
#: Furthermore, it can be '#pop' to signify going back one step in
|
| 692 |
+
#: the state stack, or '#push' to push the current state on the stack
|
| 693 |
+
#: again. Note that if you push while in a combined state, the combined
|
| 694 |
+
#: state itself is pushed, and not only the state in which the rule is
|
| 695 |
+
#: defined.
|
| 696 |
+
#:
|
| 697 |
+
#: The tuple can also be replaced with ``include('state')``, in which
|
| 698 |
+
#: case the rules from the state named by the string are included in the
|
| 699 |
+
#: current one.
|
| 700 |
+
tokens = {}
|
| 701 |
+
|
| 702 |
+
def get_tokens_unprocessed(self, text, stack=('root',)):
|
| 703 |
+
"""
|
| 704 |
+
Split ``text`` into (tokentype, text) pairs.
|
| 705 |
+
|
| 706 |
+
``stack`` is the initial stack (default: ``['root']``)
|
| 707 |
+
"""
|
| 708 |
+
pos = 0
|
| 709 |
+
tokendefs = self._tokens
|
| 710 |
+
statestack = list(stack)
|
| 711 |
+
statetokens = tokendefs[statestack[-1]]
|
| 712 |
+
while 1:
|
| 713 |
+
for rexmatch, action, new_state in statetokens:
|
| 714 |
+
m = rexmatch(text, pos)
|
| 715 |
+
if m:
|
| 716 |
+
if action is not None:
|
| 717 |
+
if type(action) is _TokenType:
|
| 718 |
+
yield pos, action, m.group()
|
| 719 |
+
else:
|
| 720 |
+
yield from action(self, m)
|
| 721 |
+
pos = m.end()
|
| 722 |
+
if new_state is not None:
|
| 723 |
+
# state transition
|
| 724 |
+
if isinstance(new_state, tuple):
|
| 725 |
+
for state in new_state:
|
| 726 |
+
if state == '#pop':
|
| 727 |
+
if len(statestack) > 1:
|
| 728 |
+
statestack.pop()
|
| 729 |
+
elif state == '#push':
|
| 730 |
+
statestack.append(statestack[-1])
|
| 731 |
+
else:
|
| 732 |
+
statestack.append(state)
|
| 733 |
+
elif isinstance(new_state, int):
|
| 734 |
+
# pop, but keep at least one state on the stack
|
| 735 |
+
# (random code leading to unexpected pops should
|
| 736 |
+
# not allow exceptions)
|
| 737 |
+
if abs(new_state) >= len(statestack):
|
| 738 |
+
del statestack[1:]
|
| 739 |
+
else:
|
| 740 |
+
del statestack[new_state:]
|
| 741 |
+
elif new_state == '#push':
|
| 742 |
+
statestack.append(statestack[-1])
|
| 743 |
+
else:
|
| 744 |
+
assert False, f"wrong state def: {new_state!r}"
|
| 745 |
+
statetokens = tokendefs[statestack[-1]]
|
| 746 |
+
break
|
| 747 |
+
else:
|
| 748 |
+
# We are here only if all state tokens have been considered
|
| 749 |
+
# and there was not a match on any of them.
|
| 750 |
+
try:
|
| 751 |
+
if text[pos] == '\n':
|
| 752 |
+
# at EOL, reset state to "root"
|
| 753 |
+
statestack = ['root']
|
| 754 |
+
statetokens = tokendefs['root']
|
| 755 |
+
yield pos, Whitespace, '\n'
|
| 756 |
+
pos += 1
|
| 757 |
+
continue
|
| 758 |
+
yield pos, Error, text[pos]
|
| 759 |
+
pos += 1
|
| 760 |
+
except IndexError:
|
| 761 |
+
break
|
| 762 |
+
|
| 763 |
+
|
| 764 |
+
class LexerContext:
|
| 765 |
+
"""
|
| 766 |
+
A helper object that holds lexer position data.
|
| 767 |
+
"""
|
| 768 |
+
|
| 769 |
+
def __init__(self, text, pos, stack=None, end=None):
|
| 770 |
+
self.text = text
|
| 771 |
+
self.pos = pos
|
| 772 |
+
self.end = end or len(text) # end=0 not supported ;-)
|
| 773 |
+
self.stack = stack or ['root']
|
| 774 |
+
|
| 775 |
+
def __repr__(self):
|
| 776 |
+
return f'LexerContext({self.text!r}, {self.pos!r}, {self.stack!r})'
|
| 777 |
+
|
| 778 |
+
|
| 779 |
+
class ExtendedRegexLexer(RegexLexer):
|
| 780 |
+
"""
|
| 781 |
+
A RegexLexer that uses a context object to store its state.
|
| 782 |
+
"""
|
| 783 |
+
|
| 784 |
+
def get_tokens_unprocessed(self, text=None, context=None):
|
| 785 |
+
"""
|
| 786 |
+
Split ``text`` into (tokentype, text) pairs.
|
| 787 |
+
If ``context`` is given, use this lexer context instead.
|
| 788 |
+
"""
|
| 789 |
+
tokendefs = self._tokens
|
| 790 |
+
if not context:
|
| 791 |
+
ctx = LexerContext(text, 0)
|
| 792 |
+
statetokens = tokendefs['root']
|
| 793 |
+
else:
|
| 794 |
+
ctx = context
|
| 795 |
+
statetokens = tokendefs[ctx.stack[-1]]
|
| 796 |
+
text = ctx.text
|
| 797 |
+
while 1:
|
| 798 |
+
for rexmatch, action, new_state in statetokens:
|
| 799 |
+
m = rexmatch(text, ctx.pos, ctx.end)
|
| 800 |
+
if m:
|
| 801 |
+
if action is not None:
|
| 802 |
+
if type(action) is _TokenType:
|
| 803 |
+
yield ctx.pos, action, m.group()
|
| 804 |
+
ctx.pos = m.end()
|
| 805 |
+
else:
|
| 806 |
+
yield from action(self, m, ctx)
|
| 807 |
+
if not new_state:
|
| 808 |
+
# altered the state stack?
|
| 809 |
+
statetokens = tokendefs[ctx.stack[-1]]
|
| 810 |
+
# CAUTION: callback must set ctx.pos!
|
| 811 |
+
if new_state is not None:
|
| 812 |
+
# state transition
|
| 813 |
+
if isinstance(new_state, tuple):
|
| 814 |
+
for state in new_state:
|
| 815 |
+
if state == '#pop':
|
| 816 |
+
if len(ctx.stack) > 1:
|
| 817 |
+
ctx.stack.pop()
|
| 818 |
+
elif state == '#push':
|
| 819 |
+
ctx.stack.append(ctx.stack[-1])
|
| 820 |
+
else:
|
| 821 |
+
ctx.stack.append(state)
|
| 822 |
+
elif isinstance(new_state, int):
|
| 823 |
+
# see RegexLexer for why this check is made
|
| 824 |
+
if abs(new_state) >= len(ctx.stack):
|
| 825 |
+
del ctx.stack[1:]
|
| 826 |
+
else:
|
| 827 |
+
del ctx.stack[new_state:]
|
| 828 |
+
elif new_state == '#push':
|
| 829 |
+
ctx.stack.append(ctx.stack[-1])
|
| 830 |
+
else:
|
| 831 |
+
assert False, f"wrong state def: {new_state!r}"
|
| 832 |
+
statetokens = tokendefs[ctx.stack[-1]]
|
| 833 |
+
break
|
| 834 |
+
else:
|
| 835 |
+
try:
|
| 836 |
+
if ctx.pos >= ctx.end:
|
| 837 |
+
break
|
| 838 |
+
if text[ctx.pos] == '\n':
|
| 839 |
+
# at EOL, reset state to "root"
|
| 840 |
+
ctx.stack = ['root']
|
| 841 |
+
statetokens = tokendefs['root']
|
| 842 |
+
yield ctx.pos, Text, '\n'
|
| 843 |
+
ctx.pos += 1
|
| 844 |
+
continue
|
| 845 |
+
yield ctx.pos, Error, text[ctx.pos]
|
| 846 |
+
ctx.pos += 1
|
| 847 |
+
except IndexError:
|
| 848 |
+
break
|
| 849 |
+
|
| 850 |
+
|
| 851 |
+
def do_insertions(insertions, tokens):
|
| 852 |
+
"""
|
| 853 |
+
Helper for lexers which must combine the results of several
|
| 854 |
+
sublexers.
|
| 855 |
+
|
| 856 |
+
``insertions`` is a list of ``(index, itokens)`` pairs.
|
| 857 |
+
Each ``itokens`` iterable should be inserted at position
|
| 858 |
+
``index`` into the token stream given by the ``tokens``
|
| 859 |
+
argument.
|
| 860 |
+
|
| 861 |
+
The result is a combined token stream.
|
| 862 |
+
|
| 863 |
+
TODO: clean up the code here.
|
| 864 |
+
"""
|
| 865 |
+
insertions = iter(insertions)
|
| 866 |
+
try:
|
| 867 |
+
index, itokens = next(insertions)
|
| 868 |
+
except StopIteration:
|
| 869 |
+
# no insertions
|
| 870 |
+
yield from tokens
|
| 871 |
+
return
|
| 872 |
+
|
| 873 |
+
realpos = None
|
| 874 |
+
insleft = True
|
| 875 |
+
|
| 876 |
+
# iterate over the token stream where we want to insert
|
| 877 |
+
# the tokens from the insertion list.
|
| 878 |
+
for i, t, v in tokens:
|
| 879 |
+
# first iteration. store the position of first item
|
| 880 |
+
if realpos is None:
|
| 881 |
+
realpos = i
|
| 882 |
+
oldi = 0
|
| 883 |
+
while insleft and i + len(v) >= index:
|
| 884 |
+
tmpval = v[oldi:index - i]
|
| 885 |
+
if tmpval:
|
| 886 |
+
yield realpos, t, tmpval
|
| 887 |
+
realpos += len(tmpval)
|
| 888 |
+
for it_index, it_token, it_value in itokens:
|
| 889 |
+
yield realpos, it_token, it_value
|
| 890 |
+
realpos += len(it_value)
|
| 891 |
+
oldi = index - i
|
| 892 |
+
try:
|
| 893 |
+
index, itokens = next(insertions)
|
| 894 |
+
except StopIteration:
|
| 895 |
+
insleft = False
|
| 896 |
+
break # not strictly necessary
|
| 897 |
+
if oldi < len(v):
|
| 898 |
+
yield realpos, t, v[oldi:]
|
| 899 |
+
realpos += len(v) - oldi
|
| 900 |
+
|
| 901 |
+
# leftover tokens
|
| 902 |
+
while insleft:
|
| 903 |
+
# no normal tokens, set realpos to zero
|
| 904 |
+
realpos = realpos or 0
|
| 905 |
+
for p, t, v in itokens:
|
| 906 |
+
yield realpos, t, v
|
| 907 |
+
realpos += len(v)
|
| 908 |
+
try:
|
| 909 |
+
index, itokens = next(insertions)
|
| 910 |
+
except StopIteration:
|
| 911 |
+
insleft = False
|
| 912 |
+
break # not strictly necessary
|
| 913 |
+
|
| 914 |
+
|
| 915 |
+
class ProfilingRegexLexerMeta(RegexLexerMeta):
|
| 916 |
+
"""Metaclass for ProfilingRegexLexer, collects regex timing info."""
|
| 917 |
+
|
| 918 |
+
def _process_regex(cls, regex, rflags, state):
|
| 919 |
+
if isinstance(regex, words):
|
| 920 |
+
rex = regex_opt(regex.words, prefix=regex.prefix,
|
| 921 |
+
suffix=regex.suffix)
|
| 922 |
+
else:
|
| 923 |
+
rex = regex
|
| 924 |
+
compiled = re.compile(rex, rflags)
|
| 925 |
+
|
| 926 |
+
def match_func(text, pos, endpos=sys.maxsize):
|
| 927 |
+
info = cls._prof_data[-1].setdefault((state, rex), [0, 0.0])
|
| 928 |
+
t0 = time.time()
|
| 929 |
+
res = compiled.match(text, pos, endpos)
|
| 930 |
+
t1 = time.time()
|
| 931 |
+
info[0] += 1
|
| 932 |
+
info[1] += t1 - t0
|
| 933 |
+
return res
|
| 934 |
+
return match_func
|
| 935 |
+
|
| 936 |
+
|
| 937 |
+
class ProfilingRegexLexer(RegexLexer, metaclass=ProfilingRegexLexerMeta):
|
| 938 |
+
"""Drop-in replacement for RegexLexer that does profiling of its regexes."""
|
| 939 |
+
|
| 940 |
+
_prof_data = []
|
| 941 |
+
_prof_sort_index = 4 # defaults to time per call
|
| 942 |
+
|
| 943 |
+
def get_tokens_unprocessed(self, text, stack=('root',)):
|
| 944 |
+
# this needs to be a stack, since using(this) will produce nested calls
|
| 945 |
+
self.__class__._prof_data.append({})
|
| 946 |
+
yield from RegexLexer.get_tokens_unprocessed(self, text, stack)
|
| 947 |
+
rawdata = self.__class__._prof_data.pop()
|
| 948 |
+
data = sorted(((s, repr(r).strip('u\'').replace('\\\\', '\\')[:65],
|
| 949 |
+
n, 1000 * t, 1000 * t / n)
|
| 950 |
+
for ((s, r), (n, t)) in rawdata.items()),
|
| 951 |
+
key=lambda x: x[self._prof_sort_index],
|
| 952 |
+
reverse=True)
|
| 953 |
+
sum_total = sum(x[3] for x in data)
|
| 954 |
+
|
| 955 |
+
print()
|
| 956 |
+
print('Profiling result for %s lexing %d chars in %.3f ms' %
|
| 957 |
+
(self.__class__.__name__, len(text), sum_total))
|
| 958 |
+
print('=' * 110)
|
| 959 |
+
print('%-20s %-64s ncalls tottime percall' % ('state', 'regex'))
|
| 960 |
+
print('-' * 110)
|
| 961 |
+
for d in data:
|
| 962 |
+
print('%-20s %-65s %5d %8.4f %8.4f' % d)
|
| 963 |
+
print('=' * 110)
|
lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__init__.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
pygments.lexers
|
| 3 |
+
~~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Pygments lexers.
|
| 6 |
+
|
| 7 |
+
:copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
|
| 8 |
+
:license: BSD, see LICENSE for details.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import re
|
| 12 |
+
import sys
|
| 13 |
+
import types
|
| 14 |
+
import fnmatch
|
| 15 |
+
from os.path import basename
|
| 16 |
+
|
| 17 |
+
from pip._vendor.pygments.lexers._mapping import LEXERS
|
| 18 |
+
from pip._vendor.pygments.modeline import get_filetype_from_buffer
|
| 19 |
+
from pip._vendor.pygments.plugin import find_plugin_lexers
|
| 20 |
+
from pip._vendor.pygments.util import ClassNotFound, guess_decode
|
| 21 |
+
|
| 22 |
+
COMPAT = {
|
| 23 |
+
'Python3Lexer': 'PythonLexer',
|
| 24 |
+
'Python3TracebackLexer': 'PythonTracebackLexer',
|
| 25 |
+
'LeanLexer': 'Lean3Lexer',
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
__all__ = ['get_lexer_by_name', 'get_lexer_for_filename', 'find_lexer_class',
|
| 29 |
+
'guess_lexer', 'load_lexer_from_file'] + list(LEXERS) + list(COMPAT)
|
| 30 |
+
|
| 31 |
+
_lexer_cache = {}
|
| 32 |
+
_pattern_cache = {}
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _fn_matches(fn, glob):
|
| 36 |
+
"""Return whether the supplied file name fn matches pattern filename."""
|
| 37 |
+
if glob not in _pattern_cache:
|
| 38 |
+
pattern = _pattern_cache[glob] = re.compile(fnmatch.translate(glob))
|
| 39 |
+
return pattern.match(fn)
|
| 40 |
+
return _pattern_cache[glob].match(fn)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def _load_lexers(module_name):
|
| 44 |
+
"""Load a lexer (and all others in the module too)."""
|
| 45 |
+
mod = __import__(module_name, None, None, ['__all__'])
|
| 46 |
+
for lexer_name in mod.__all__:
|
| 47 |
+
cls = getattr(mod, lexer_name)
|
| 48 |
+
_lexer_cache[cls.name] = cls
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def get_all_lexers(plugins=True):
|
| 52 |
+
"""Return a generator of tuples in the form ``(name, aliases,
|
| 53 |
+
filenames, mimetypes)`` of all know lexers.
|
| 54 |
+
|
| 55 |
+
If *plugins* is true (the default), plugin lexers supplied by entrypoints
|
| 56 |
+
are also returned. Otherwise, only builtin ones are considered.
|
| 57 |
+
"""
|
| 58 |
+
for item in LEXERS.values():
|
| 59 |
+
yield item[1:]
|
| 60 |
+
if plugins:
|
| 61 |
+
for lexer in find_plugin_lexers():
|
| 62 |
+
yield lexer.name, lexer.aliases, lexer.filenames, lexer.mimetypes
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def find_lexer_class(name):
|
| 66 |
+
"""
|
| 67 |
+
Return the `Lexer` subclass that with the *name* attribute as given by
|
| 68 |
+
the *name* argument.
|
| 69 |
+
"""
|
| 70 |
+
if name in _lexer_cache:
|
| 71 |
+
return _lexer_cache[name]
|
| 72 |
+
# lookup builtin lexers
|
| 73 |
+
for module_name, lname, aliases, _, _ in LEXERS.values():
|
| 74 |
+
if name == lname:
|
| 75 |
+
_load_lexers(module_name)
|
| 76 |
+
return _lexer_cache[name]
|
| 77 |
+
# continue with lexers from setuptools entrypoints
|
| 78 |
+
for cls in find_plugin_lexers():
|
| 79 |
+
if cls.name == name:
|
| 80 |
+
return cls
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def find_lexer_class_by_name(_alias):
|
| 84 |
+
"""
|
| 85 |
+
Return the `Lexer` subclass that has `alias` in its aliases list, without
|
| 86 |
+
instantiating it.
|
| 87 |
+
|
| 88 |
+
Like `get_lexer_by_name`, but does not instantiate the class.
|
| 89 |
+
|
| 90 |
+
Will raise :exc:`pygments.util.ClassNotFound` if no lexer with that alias is
|
| 91 |
+
found.
|
| 92 |
+
|
| 93 |
+
.. versionadded:: 2.2
|
| 94 |
+
"""
|
| 95 |
+
if not _alias:
|
| 96 |
+
raise ClassNotFound(f'no lexer for alias {_alias!r} found')
|
| 97 |
+
# lookup builtin lexers
|
| 98 |
+
for module_name, name, aliases, _, _ in LEXERS.values():
|
| 99 |
+
if _alias.lower() in aliases:
|
| 100 |
+
if name not in _lexer_cache:
|
| 101 |
+
_load_lexers(module_name)
|
| 102 |
+
return _lexer_cache[name]
|
| 103 |
+
# continue with lexers from setuptools entrypoints
|
| 104 |
+
for cls in find_plugin_lexers():
|
| 105 |
+
if _alias.lower() in cls.aliases:
|
| 106 |
+
return cls
|
| 107 |
+
raise ClassNotFound(f'no lexer for alias {_alias!r} found')
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def get_lexer_by_name(_alias, **options):
|
| 111 |
+
"""
|
| 112 |
+
Return an instance of a `Lexer` subclass that has `alias` in its
|
| 113 |
+
aliases list. The lexer is given the `options` at its
|
| 114 |
+
instantiation.
|
| 115 |
+
|
| 116 |
+
Will raise :exc:`pygments.util.ClassNotFound` if no lexer with that alias is
|
| 117 |
+
found.
|
| 118 |
+
"""
|
| 119 |
+
if not _alias:
|
| 120 |
+
raise ClassNotFound(f'no lexer for alias {_alias!r} found')
|
| 121 |
+
|
| 122 |
+
# lookup builtin lexers
|
| 123 |
+
for module_name, name, aliases, _, _ in LEXERS.values():
|
| 124 |
+
if _alias.lower() in aliases:
|
| 125 |
+
if name not in _lexer_cache:
|
| 126 |
+
_load_lexers(module_name)
|
| 127 |
+
return _lexer_cache[name](**options)
|
| 128 |
+
# continue with lexers from setuptools entrypoints
|
| 129 |
+
for cls in find_plugin_lexers():
|
| 130 |
+
if _alias.lower() in cls.aliases:
|
| 131 |
+
return cls(**options)
|
| 132 |
+
raise ClassNotFound(f'no lexer for alias {_alias!r} found')
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def load_lexer_from_file(filename, lexername="CustomLexer", **options):
|
| 136 |
+
"""Load a lexer from a file.
|
| 137 |
+
|
| 138 |
+
This method expects a file located relative to the current working
|
| 139 |
+
directory, which contains a Lexer class. By default, it expects the
|
| 140 |
+
Lexer to be name CustomLexer; you can specify your own class name
|
| 141 |
+
as the second argument to this function.
|
| 142 |
+
|
| 143 |
+
Users should be very careful with the input, because this method
|
| 144 |
+
is equivalent to running eval on the input file.
|
| 145 |
+
|
| 146 |
+
Raises ClassNotFound if there are any problems importing the Lexer.
|
| 147 |
+
|
| 148 |
+
.. versionadded:: 2.2
|
| 149 |
+
"""
|
| 150 |
+
try:
|
| 151 |
+
# This empty dict will contain the namespace for the exec'd file
|
| 152 |
+
custom_namespace = {}
|
| 153 |
+
with open(filename, 'rb') as f:
|
| 154 |
+
exec(f.read(), custom_namespace)
|
| 155 |
+
# Retrieve the class `lexername` from that namespace
|
| 156 |
+
if lexername not in custom_namespace:
|
| 157 |
+
raise ClassNotFound(f'no valid {lexername} class found in {filename}')
|
| 158 |
+
lexer_class = custom_namespace[lexername]
|
| 159 |
+
# And finally instantiate it with the options
|
| 160 |
+
return lexer_class(**options)
|
| 161 |
+
except OSError as err:
|
| 162 |
+
raise ClassNotFound(f'cannot read {filename}: {err}')
|
| 163 |
+
except ClassNotFound:
|
| 164 |
+
raise
|
| 165 |
+
except Exception as err:
|
| 166 |
+
raise ClassNotFound(f'error when loading custom lexer: {err}')
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def find_lexer_class_for_filename(_fn, code=None):
|
| 170 |
+
"""Get a lexer for a filename.
|
| 171 |
+
|
| 172 |
+
If multiple lexers match the filename pattern, use ``analyse_text()`` to
|
| 173 |
+
figure out which one is more appropriate.
|
| 174 |
+
|
| 175 |
+
Returns None if not found.
|
| 176 |
+
"""
|
| 177 |
+
matches = []
|
| 178 |
+
fn = basename(_fn)
|
| 179 |
+
for modname, name, _, filenames, _ in LEXERS.values():
|
| 180 |
+
for filename in filenames:
|
| 181 |
+
if _fn_matches(fn, filename):
|
| 182 |
+
if name not in _lexer_cache:
|
| 183 |
+
_load_lexers(modname)
|
| 184 |
+
matches.append((_lexer_cache[name], filename))
|
| 185 |
+
for cls in find_plugin_lexers():
|
| 186 |
+
for filename in cls.filenames:
|
| 187 |
+
if _fn_matches(fn, filename):
|
| 188 |
+
matches.append((cls, filename))
|
| 189 |
+
|
| 190 |
+
if isinstance(code, bytes):
|
| 191 |
+
# decode it, since all analyse_text functions expect unicode
|
| 192 |
+
code = guess_decode(code)
|
| 193 |
+
|
| 194 |
+
def get_rating(info):
|
| 195 |
+
cls, filename = info
|
| 196 |
+
# explicit patterns get a bonus
|
| 197 |
+
bonus = '*' not in filename and 0.5 or 0
|
| 198 |
+
# The class _always_ defines analyse_text because it's included in
|
| 199 |
+
# the Lexer class. The default implementation returns None which
|
| 200 |
+
# gets turned into 0.0. Run scripts/detect_missing_analyse_text.py
|
| 201 |
+
# to find lexers which need it overridden.
|
| 202 |
+
if code:
|
| 203 |
+
return cls.analyse_text(code) + bonus, cls.__name__
|
| 204 |
+
return cls.priority + bonus, cls.__name__
|
| 205 |
+
|
| 206 |
+
if matches:
|
| 207 |
+
matches.sort(key=get_rating)
|
| 208 |
+
# print "Possible lexers, after sort:", matches
|
| 209 |
+
return matches[-1][0]
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def get_lexer_for_filename(_fn, code=None, **options):
|
| 213 |
+
"""Get a lexer for a filename.
|
| 214 |
+
|
| 215 |
+
Return a `Lexer` subclass instance that has a filename pattern
|
| 216 |
+
matching `fn`. The lexer is given the `options` at its
|
| 217 |
+
instantiation.
|
| 218 |
+
|
| 219 |
+
Raise :exc:`pygments.util.ClassNotFound` if no lexer for that filename
|
| 220 |
+
is found.
|
| 221 |
+
|
| 222 |
+
If multiple lexers match the filename pattern, use their ``analyse_text()``
|
| 223 |
+
methods to figure out which one is more appropriate.
|
| 224 |
+
"""
|
| 225 |
+
res = find_lexer_class_for_filename(_fn, code)
|
| 226 |
+
if not res:
|
| 227 |
+
raise ClassNotFound(f'no lexer for filename {_fn!r} found')
|
| 228 |
+
return res(**options)
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def get_lexer_for_mimetype(_mime, **options):
|
| 232 |
+
"""
|
| 233 |
+
Return a `Lexer` subclass instance that has `mime` in its mimetype
|
| 234 |
+
list. The lexer is given the `options` at its instantiation.
|
| 235 |
+
|
| 236 |
+
Will raise :exc:`pygments.util.ClassNotFound` if not lexer for that mimetype
|
| 237 |
+
is found.
|
| 238 |
+
"""
|
| 239 |
+
for modname, name, _, _, mimetypes in LEXERS.values():
|
| 240 |
+
if _mime in mimetypes:
|
| 241 |
+
if name not in _lexer_cache:
|
| 242 |
+
_load_lexers(modname)
|
| 243 |
+
return _lexer_cache[name](**options)
|
| 244 |
+
for cls in find_plugin_lexers():
|
| 245 |
+
if _mime in cls.mimetypes:
|
| 246 |
+
return cls(**options)
|
| 247 |
+
raise ClassNotFound(f'no lexer for mimetype {_mime!r} found')
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def _iter_lexerclasses(plugins=True):
|
| 251 |
+
"""Return an iterator over all lexer classes."""
|
| 252 |
+
for key in sorted(LEXERS):
|
| 253 |
+
module_name, name = LEXERS[key][:2]
|
| 254 |
+
if name not in _lexer_cache:
|
| 255 |
+
_load_lexers(module_name)
|
| 256 |
+
yield _lexer_cache[name]
|
| 257 |
+
if plugins:
|
| 258 |
+
yield from find_plugin_lexers()
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def guess_lexer_for_filename(_fn, _text, **options):
|
| 262 |
+
"""
|
| 263 |
+
As :func:`guess_lexer()`, but only lexers which have a pattern in `filenames`
|
| 264 |
+
or `alias_filenames` that matches `filename` are taken into consideration.
|
| 265 |
+
|
| 266 |
+
:exc:`pygments.util.ClassNotFound` is raised if no lexer thinks it can
|
| 267 |
+
handle the content.
|
| 268 |
+
"""
|
| 269 |
+
fn = basename(_fn)
|
| 270 |
+
primary = {}
|
| 271 |
+
matching_lexers = set()
|
| 272 |
+
for lexer in _iter_lexerclasses():
|
| 273 |
+
for filename in lexer.filenames:
|
| 274 |
+
if _fn_matches(fn, filename):
|
| 275 |
+
matching_lexers.add(lexer)
|
| 276 |
+
primary[lexer] = True
|
| 277 |
+
for filename in lexer.alias_filenames:
|
| 278 |
+
if _fn_matches(fn, filename):
|
| 279 |
+
matching_lexers.add(lexer)
|
| 280 |
+
primary[lexer] = False
|
| 281 |
+
if not matching_lexers:
|
| 282 |
+
raise ClassNotFound(f'no lexer for filename {fn!r} found')
|
| 283 |
+
if len(matching_lexers) == 1:
|
| 284 |
+
return matching_lexers.pop()(**options)
|
| 285 |
+
result = []
|
| 286 |
+
for lexer in matching_lexers:
|
| 287 |
+
rv = lexer.analyse_text(_text)
|
| 288 |
+
if rv == 1.0:
|
| 289 |
+
return lexer(**options)
|
| 290 |
+
result.append((rv, lexer))
|
| 291 |
+
|
| 292 |
+
def type_sort(t):
|
| 293 |
+
# sort by:
|
| 294 |
+
# - analyse score
|
| 295 |
+
# - is primary filename pattern?
|
| 296 |
+
# - priority
|
| 297 |
+
# - last resort: class name
|
| 298 |
+
return (t[0], primary[t[1]], t[1].priority, t[1].__name__)
|
| 299 |
+
result.sort(key=type_sort)
|
| 300 |
+
|
| 301 |
+
return result[-1][1](**options)
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def guess_lexer(_text, **options):
|
| 305 |
+
"""
|
| 306 |
+
Return a `Lexer` subclass instance that's guessed from the text in
|
| 307 |
+
`text`. For that, the :meth:`.analyse_text()` method of every known lexer
|
| 308 |
+
class is called with the text as argument, and the lexer which returned the
|
| 309 |
+
highest value will be instantiated and returned.
|
| 310 |
+
|
| 311 |
+
:exc:`pygments.util.ClassNotFound` is raised if no lexer thinks it can
|
| 312 |
+
handle the content.
|
| 313 |
+
"""
|
| 314 |
+
|
| 315 |
+
if not isinstance(_text, str):
|
| 316 |
+
inencoding = options.get('inencoding', options.get('encoding'))
|
| 317 |
+
if inencoding:
|
| 318 |
+
_text = _text.decode(inencoding or 'utf8')
|
| 319 |
+
else:
|
| 320 |
+
_text, _ = guess_decode(_text)
|
| 321 |
+
|
| 322 |
+
# try to get a vim modeline first
|
| 323 |
+
ft = get_filetype_from_buffer(_text)
|
| 324 |
+
|
| 325 |
+
if ft is not None:
|
| 326 |
+
try:
|
| 327 |
+
return get_lexer_by_name(ft, **options)
|
| 328 |
+
except ClassNotFound:
|
| 329 |
+
pass
|
| 330 |
+
|
| 331 |
+
best_lexer = [0.0, None]
|
| 332 |
+
for lexer in _iter_lexerclasses():
|
| 333 |
+
rv = lexer.analyse_text(_text)
|
| 334 |
+
if rv == 1.0:
|
| 335 |
+
return lexer(**options)
|
| 336 |
+
if rv > best_lexer[0]:
|
| 337 |
+
best_lexer[:] = (rv, lexer)
|
| 338 |
+
if not best_lexer[0] or best_lexer[1] is None:
|
| 339 |
+
raise ClassNotFound('no lexer matching the text found')
|
| 340 |
+
return best_lexer[1](**options)
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
class _automodule(types.ModuleType):
|
| 344 |
+
"""Automatically import lexers."""
|
| 345 |
+
|
| 346 |
+
def __getattr__(self, name):
|
| 347 |
+
info = LEXERS.get(name)
|
| 348 |
+
if info:
|
| 349 |
+
_load_lexers(info[0])
|
| 350 |
+
cls = _lexer_cache[info[1]]
|
| 351 |
+
setattr(self, name, cls)
|
| 352 |
+
return cls
|
| 353 |
+
if name in COMPAT:
|
| 354 |
+
return getattr(self, COMPAT[name])
|
| 355 |
+
raise AttributeError(name)
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
oldmod = sys.modules[__name__]
|
| 359 |
+
newmod = _automodule(__name__)
|
| 360 |
+
newmod.__dict__.update(oldmod.__dict__)
|
| 361 |
+
sys.modules[__name__] = newmod
|
| 362 |
+
del newmod.newmod, newmod.oldmod, newmod.sys, newmod.types
|
lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (14.6 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc
ADDED
|
Binary file (68.3 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc
ADDED
|
Binary file (43 kB). View file
|
|
|
lib/python3.12/site-packages/pip/_vendor/pygments/lexers/_mapping.py
ADDED
|
@@ -0,0 +1,589 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Automatically generated by scripts/gen_mapfiles.py.
|
| 2 |
+
# DO NOT EDIT BY HAND; run `tox -e mapfiles` instead.
|
| 3 |
+
|
| 4 |
+
LEXERS = {
|
| 5 |
+
'ABAPLexer': ('pip._vendor.pygments.lexers.business', 'ABAP', ('abap',), ('*.abap', '*.ABAP'), ('text/x-abap',)),
|
| 6 |
+
'AMDGPULexer': ('pip._vendor.pygments.lexers.amdgpu', 'AMDGPU', ('amdgpu',), ('*.isa',), ()),
|
| 7 |
+
'APLLexer': ('pip._vendor.pygments.lexers.apl', 'APL', ('apl',), ('*.apl', '*.aplf', '*.aplo', '*.apln', '*.aplc', '*.apli', '*.dyalog'), ()),
|
| 8 |
+
'AbnfLexer': ('pip._vendor.pygments.lexers.grammar_notation', 'ABNF', ('abnf',), ('*.abnf',), ('text/x-abnf',)),
|
| 9 |
+
'ActionScript3Lexer': ('pip._vendor.pygments.lexers.actionscript', 'ActionScript 3', ('actionscript3', 'as3'), ('*.as',), ('application/x-actionscript3', 'text/x-actionscript3', 'text/actionscript3')),
|
| 10 |
+
'ActionScriptLexer': ('pip._vendor.pygments.lexers.actionscript', 'ActionScript', ('actionscript', 'as'), ('*.as',), ('application/x-actionscript', 'text/x-actionscript', 'text/actionscript')),
|
| 11 |
+
'AdaLexer': ('pip._vendor.pygments.lexers.ada', 'Ada', ('ada', 'ada95', 'ada2005'), ('*.adb', '*.ads', '*.ada'), ('text/x-ada',)),
|
| 12 |
+
'AdlLexer': ('pip._vendor.pygments.lexers.archetype', 'ADL', ('adl',), ('*.adl', '*.adls', '*.adlf', '*.adlx'), ()),
|
| 13 |
+
'AgdaLexer': ('pip._vendor.pygments.lexers.haskell', 'Agda', ('agda',), ('*.agda',), ('text/x-agda',)),
|
| 14 |
+
'AheuiLexer': ('pip._vendor.pygments.lexers.esoteric', 'Aheui', ('aheui',), ('*.aheui',), ()),
|
| 15 |
+
'AlloyLexer': ('pip._vendor.pygments.lexers.dsls', 'Alloy', ('alloy',), ('*.als',), ('text/x-alloy',)),
|
| 16 |
+
'AmbientTalkLexer': ('pip._vendor.pygments.lexers.ambient', 'AmbientTalk', ('ambienttalk', 'ambienttalk/2', 'at'), ('*.at',), ('text/x-ambienttalk',)),
|
| 17 |
+
'AmplLexer': ('pip._vendor.pygments.lexers.ampl', 'Ampl', ('ampl',), ('*.run',), ()),
|
| 18 |
+
'Angular2HtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML + Angular2', ('html+ng2',), ('*.ng2',), ()),
|
| 19 |
+
'Angular2Lexer': ('pip._vendor.pygments.lexers.templates', 'Angular2', ('ng2',), (), ()),
|
| 20 |
+
'AntlrActionScriptLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With ActionScript Target', ('antlr-actionscript', 'antlr-as'), ('*.G', '*.g'), ()),
|
| 21 |
+
'AntlrCSharpLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With C# Target', ('antlr-csharp', 'antlr-c#'), ('*.G', '*.g'), ()),
|
| 22 |
+
'AntlrCppLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With CPP Target', ('antlr-cpp',), ('*.G', '*.g'), ()),
|
| 23 |
+
'AntlrJavaLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With Java Target', ('antlr-java',), ('*.G', '*.g'), ()),
|
| 24 |
+
'AntlrLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR', ('antlr',), (), ()),
|
| 25 |
+
'AntlrObjectiveCLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With ObjectiveC Target', ('antlr-objc',), ('*.G', '*.g'), ()),
|
| 26 |
+
'AntlrPerlLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With Perl Target', ('antlr-perl',), ('*.G', '*.g'), ()),
|
| 27 |
+
'AntlrPythonLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With Python Target', ('antlr-python',), ('*.G', '*.g'), ()),
|
| 28 |
+
'AntlrRubyLexer': ('pip._vendor.pygments.lexers.parsers', 'ANTLR With Ruby Target', ('antlr-ruby', 'antlr-rb'), ('*.G', '*.g'), ()),
|
| 29 |
+
'ApacheConfLexer': ('pip._vendor.pygments.lexers.configs', 'ApacheConf', ('apacheconf', 'aconf', 'apache'), ('.htaccess', 'apache.conf', 'apache2.conf'), ('text/x-apacheconf',)),
|
| 30 |
+
'AppleScriptLexer': ('pip._vendor.pygments.lexers.scripting', 'AppleScript', ('applescript',), ('*.applescript',), ()),
|
| 31 |
+
'ArduinoLexer': ('pip._vendor.pygments.lexers.c_like', 'Arduino', ('arduino',), ('*.ino',), ('text/x-arduino',)),
|
| 32 |
+
'ArrowLexer': ('pip._vendor.pygments.lexers.arrow', 'Arrow', ('arrow',), ('*.arw',), ()),
|
| 33 |
+
'ArturoLexer': ('pip._vendor.pygments.lexers.arturo', 'Arturo', ('arturo', 'art'), ('*.art',), ()),
|
| 34 |
+
'AscLexer': ('pip._vendor.pygments.lexers.asc', 'ASCII armored', ('asc', 'pem'), ('*.asc', '*.pem', 'id_dsa', 'id_ecdsa', 'id_ecdsa_sk', 'id_ed25519', 'id_ed25519_sk', 'id_rsa'), ('application/pgp-keys', 'application/pgp-encrypted', 'application/pgp-signature', 'application/pem-certificate-chain')),
|
| 35 |
+
'Asn1Lexer': ('pip._vendor.pygments.lexers.asn1', 'ASN.1', ('asn1',), ('*.asn1',), ()),
|
| 36 |
+
'AspectJLexer': ('pip._vendor.pygments.lexers.jvm', 'AspectJ', ('aspectj',), ('*.aj',), ('text/x-aspectj',)),
|
| 37 |
+
'AsymptoteLexer': ('pip._vendor.pygments.lexers.graphics', 'Asymptote', ('asymptote', 'asy'), ('*.asy',), ('text/x-asymptote',)),
|
| 38 |
+
'AugeasLexer': ('pip._vendor.pygments.lexers.configs', 'Augeas', ('augeas',), ('*.aug',), ()),
|
| 39 |
+
'AutoItLexer': ('pip._vendor.pygments.lexers.automation', 'AutoIt', ('autoit',), ('*.au3',), ('text/x-autoit',)),
|
| 40 |
+
'AutohotkeyLexer': ('pip._vendor.pygments.lexers.automation', 'autohotkey', ('autohotkey', 'ahk'), ('*.ahk', '*.ahkl'), ('text/x-autohotkey',)),
|
| 41 |
+
'AwkLexer': ('pip._vendor.pygments.lexers.textedit', 'Awk', ('awk', 'gawk', 'mawk', 'nawk'), ('*.awk',), ('application/x-awk',)),
|
| 42 |
+
'BBCBasicLexer': ('pip._vendor.pygments.lexers.basic', 'BBC Basic', ('bbcbasic',), ('*.bbc',), ()),
|
| 43 |
+
'BBCodeLexer': ('pip._vendor.pygments.lexers.markup', 'BBCode', ('bbcode',), (), ('text/x-bbcode',)),
|
| 44 |
+
'BCLexer': ('pip._vendor.pygments.lexers.algebra', 'BC', ('bc',), ('*.bc',), ()),
|
| 45 |
+
'BQNLexer': ('pip._vendor.pygments.lexers.bqn', 'BQN', ('bqn',), ('*.bqn',), ()),
|
| 46 |
+
'BSTLexer': ('pip._vendor.pygments.lexers.bibtex', 'BST', ('bst', 'bst-pybtex'), ('*.bst',), ()),
|
| 47 |
+
'BareLexer': ('pip._vendor.pygments.lexers.bare', 'BARE', ('bare',), ('*.bare',), ()),
|
| 48 |
+
'BaseMakefileLexer': ('pip._vendor.pygments.lexers.make', 'Base Makefile', ('basemake',), (), ()),
|
| 49 |
+
'BashLexer': ('pip._vendor.pygments.lexers.shell', 'Bash', ('bash', 'sh', 'ksh', 'zsh', 'shell', 'openrc'), ('*.sh', '*.ksh', '*.bash', '*.ebuild', '*.eclass', '*.exheres-0', '*.exlib', '*.zsh', '.bashrc', 'bashrc', '.bash_*', 'bash_*', 'zshrc', '.zshrc', '.kshrc', 'kshrc', 'PKGBUILD'), ('application/x-sh', 'application/x-shellscript', 'text/x-shellscript')),
|
| 50 |
+
'BashSessionLexer': ('pip._vendor.pygments.lexers.shell', 'Bash Session', ('console', 'shell-session'), ('*.sh-session', '*.shell-session'), ('application/x-shell-session', 'application/x-sh-session')),
|
| 51 |
+
'BatchLexer': ('pip._vendor.pygments.lexers.shell', 'Batchfile', ('batch', 'bat', 'dosbatch', 'winbatch'), ('*.bat', '*.cmd'), ('application/x-dos-batch',)),
|
| 52 |
+
'BddLexer': ('pip._vendor.pygments.lexers.bdd', 'Bdd', ('bdd',), ('*.feature',), ('text/x-bdd',)),
|
| 53 |
+
'BefungeLexer': ('pip._vendor.pygments.lexers.esoteric', 'Befunge', ('befunge',), ('*.befunge',), ('application/x-befunge',)),
|
| 54 |
+
'BerryLexer': ('pip._vendor.pygments.lexers.berry', 'Berry', ('berry', 'be'), ('*.be',), ('text/x-berry', 'application/x-berry')),
|
| 55 |
+
'BibTeXLexer': ('pip._vendor.pygments.lexers.bibtex', 'BibTeX', ('bibtex', 'bib'), ('*.bib',), ('text/x-bibtex',)),
|
| 56 |
+
'BlitzBasicLexer': ('pip._vendor.pygments.lexers.basic', 'BlitzBasic', ('blitzbasic', 'b3d', 'bplus'), ('*.bb', '*.decls'), ('text/x-bb',)),
|
| 57 |
+
'BlitzMaxLexer': ('pip._vendor.pygments.lexers.basic', 'BlitzMax', ('blitzmax', 'bmax'), ('*.bmx',), ('text/x-bmx',)),
|
| 58 |
+
'BlueprintLexer': ('pip._vendor.pygments.lexers.blueprint', 'Blueprint', ('blueprint',), ('*.blp',), ('text/x-blueprint',)),
|
| 59 |
+
'BnfLexer': ('pip._vendor.pygments.lexers.grammar_notation', 'BNF', ('bnf',), ('*.bnf',), ('text/x-bnf',)),
|
| 60 |
+
'BoaLexer': ('pip._vendor.pygments.lexers.boa', 'Boa', ('boa',), ('*.boa',), ()),
|
| 61 |
+
'BooLexer': ('pip._vendor.pygments.lexers.dotnet', 'Boo', ('boo',), ('*.boo',), ('text/x-boo',)),
|
| 62 |
+
'BoogieLexer': ('pip._vendor.pygments.lexers.verification', 'Boogie', ('boogie',), ('*.bpl',), ()),
|
| 63 |
+
'BrainfuckLexer': ('pip._vendor.pygments.lexers.esoteric', 'Brainfuck', ('brainfuck', 'bf'), ('*.bf', '*.b'), ('application/x-brainfuck',)),
|
| 64 |
+
'BugsLexer': ('pip._vendor.pygments.lexers.modeling', 'BUGS', ('bugs', 'winbugs', 'openbugs'), ('*.bug',), ()),
|
| 65 |
+
'CAmkESLexer': ('pip._vendor.pygments.lexers.esoteric', 'CAmkES', ('camkes', 'idl4'), ('*.camkes', '*.idl4'), ()),
|
| 66 |
+
'CLexer': ('pip._vendor.pygments.lexers.c_cpp', 'C', ('c',), ('*.c', '*.h', '*.idc', '*.x[bp]m'), ('text/x-chdr', 'text/x-csrc', 'image/x-xbitmap', 'image/x-xpixmap')),
|
| 67 |
+
'CMakeLexer': ('pip._vendor.pygments.lexers.make', 'CMake', ('cmake',), ('*.cmake', 'CMakeLists.txt'), ('text/x-cmake',)),
|
| 68 |
+
'CObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'c-objdump', ('c-objdump',), ('*.c-objdump',), ('text/x-c-objdump',)),
|
| 69 |
+
'CPSALexer': ('pip._vendor.pygments.lexers.lisp', 'CPSA', ('cpsa',), ('*.cpsa',), ()),
|
| 70 |
+
'CSSUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'CSS+UL4', ('css+ul4',), ('*.cssul4',), ()),
|
| 71 |
+
'CSharpAspxLexer': ('pip._vendor.pygments.lexers.dotnet', 'aspx-cs', ('aspx-cs',), ('*.aspx', '*.asax', '*.ascx', '*.ashx', '*.asmx', '*.axd'), ()),
|
| 72 |
+
'CSharpLexer': ('pip._vendor.pygments.lexers.dotnet', 'C#', ('csharp', 'c#', 'cs'), ('*.cs',), ('text/x-csharp',)),
|
| 73 |
+
'Ca65Lexer': ('pip._vendor.pygments.lexers.asm', 'ca65 assembler', ('ca65',), ('*.s',), ()),
|
| 74 |
+
'CadlLexer': ('pip._vendor.pygments.lexers.archetype', 'cADL', ('cadl',), ('*.cadl',), ()),
|
| 75 |
+
'CapDLLexer': ('pip._vendor.pygments.lexers.esoteric', 'CapDL', ('capdl',), ('*.cdl',), ()),
|
| 76 |
+
'CapnProtoLexer': ('pip._vendor.pygments.lexers.capnproto', "Cap'n Proto", ('capnp',), ('*.capnp',), ()),
|
| 77 |
+
'CarbonLexer': ('pip._vendor.pygments.lexers.carbon', 'Carbon', ('carbon',), ('*.carbon',), ('text/x-carbon',)),
|
| 78 |
+
'CbmBasicV2Lexer': ('pip._vendor.pygments.lexers.basic', 'CBM BASIC V2', ('cbmbas',), ('*.bas',), ()),
|
| 79 |
+
'CddlLexer': ('pip._vendor.pygments.lexers.cddl', 'CDDL', ('cddl',), ('*.cddl',), ('text/x-cddl',)),
|
| 80 |
+
'CeylonLexer': ('pip._vendor.pygments.lexers.jvm', 'Ceylon', ('ceylon',), ('*.ceylon',), ('text/x-ceylon',)),
|
| 81 |
+
'Cfengine3Lexer': ('pip._vendor.pygments.lexers.configs', 'CFEngine3', ('cfengine3', 'cf3'), ('*.cf',), ()),
|
| 82 |
+
'ChaiscriptLexer': ('pip._vendor.pygments.lexers.scripting', 'ChaiScript', ('chaiscript', 'chai'), ('*.chai',), ('text/x-chaiscript', 'application/x-chaiscript')),
|
| 83 |
+
'ChapelLexer': ('pip._vendor.pygments.lexers.chapel', 'Chapel', ('chapel', 'chpl'), ('*.chpl',), ()),
|
| 84 |
+
'CharmciLexer': ('pip._vendor.pygments.lexers.c_like', 'Charmci', ('charmci',), ('*.ci',), ()),
|
| 85 |
+
'CheetahHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Cheetah', ('html+cheetah', 'html+spitfire', 'htmlcheetah'), (), ('text/html+cheetah', 'text/html+spitfire')),
|
| 86 |
+
'CheetahJavascriptLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Cheetah', ('javascript+cheetah', 'js+cheetah', 'javascript+spitfire', 'js+spitfire'), (), ('application/x-javascript+cheetah', 'text/x-javascript+cheetah', 'text/javascript+cheetah', 'application/x-javascript+spitfire', 'text/x-javascript+spitfire', 'text/javascript+spitfire')),
|
| 87 |
+
'CheetahLexer': ('pip._vendor.pygments.lexers.templates', 'Cheetah', ('cheetah', 'spitfire'), ('*.tmpl', '*.spt'), ('application/x-cheetah', 'application/x-spitfire')),
|
| 88 |
+
'CheetahXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Cheetah', ('xml+cheetah', 'xml+spitfire'), (), ('application/xml+cheetah', 'application/xml+spitfire')),
|
| 89 |
+
'CirruLexer': ('pip._vendor.pygments.lexers.webmisc', 'Cirru', ('cirru',), ('*.cirru',), ('text/x-cirru',)),
|
| 90 |
+
'ClayLexer': ('pip._vendor.pygments.lexers.c_like', 'Clay', ('clay',), ('*.clay',), ('text/x-clay',)),
|
| 91 |
+
'CleanLexer': ('pip._vendor.pygments.lexers.clean', 'Clean', ('clean',), ('*.icl', '*.dcl'), ()),
|
| 92 |
+
'ClojureLexer': ('pip._vendor.pygments.lexers.jvm', 'Clojure', ('clojure', 'clj'), ('*.clj', '*.cljc'), ('text/x-clojure', 'application/x-clojure')),
|
| 93 |
+
'ClojureScriptLexer': ('pip._vendor.pygments.lexers.jvm', 'ClojureScript', ('clojurescript', 'cljs'), ('*.cljs',), ('text/x-clojurescript', 'application/x-clojurescript')),
|
| 94 |
+
'CobolFreeformatLexer': ('pip._vendor.pygments.lexers.business', 'COBOLFree', ('cobolfree',), ('*.cbl', '*.CBL'), ()),
|
| 95 |
+
'CobolLexer': ('pip._vendor.pygments.lexers.business', 'COBOL', ('cobol',), ('*.cob', '*.COB', '*.cpy', '*.CPY'), ('text/x-cobol',)),
|
| 96 |
+
'CoffeeScriptLexer': ('pip._vendor.pygments.lexers.javascript', 'CoffeeScript', ('coffeescript', 'coffee-script', 'coffee'), ('*.coffee',), ('text/coffeescript',)),
|
| 97 |
+
'ColdfusionCFCLexer': ('pip._vendor.pygments.lexers.templates', 'Coldfusion CFC', ('cfc',), ('*.cfc',), ()),
|
| 98 |
+
'ColdfusionHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'Coldfusion HTML', ('cfm',), ('*.cfm', '*.cfml'), ('application/x-coldfusion',)),
|
| 99 |
+
'ColdfusionLexer': ('pip._vendor.pygments.lexers.templates', 'cfstatement', ('cfs',), (), ()),
|
| 100 |
+
'Comal80Lexer': ('pip._vendor.pygments.lexers.comal', 'COMAL-80', ('comal', 'comal80'), ('*.cml', '*.comal'), ()),
|
| 101 |
+
'CommonLispLexer': ('pip._vendor.pygments.lexers.lisp', 'Common Lisp', ('common-lisp', 'cl', 'lisp'), ('*.cl', '*.lisp'), ('text/x-common-lisp',)),
|
| 102 |
+
'ComponentPascalLexer': ('pip._vendor.pygments.lexers.oberon', 'Component Pascal', ('componentpascal', 'cp'), ('*.cp', '*.cps'), ('text/x-component-pascal',)),
|
| 103 |
+
'CoqLexer': ('pip._vendor.pygments.lexers.theorem', 'Coq', ('coq',), ('*.v',), ('text/x-coq',)),
|
| 104 |
+
'CplintLexer': ('pip._vendor.pygments.lexers.cplint', 'cplint', ('cplint',), ('*.ecl', '*.prolog', '*.pro', '*.pl', '*.P', '*.lpad', '*.cpl'), ('text/x-cplint',)),
|
| 105 |
+
'CppLexer': ('pip._vendor.pygments.lexers.c_cpp', 'C++', ('cpp', 'c++'), ('*.cpp', '*.hpp', '*.c++', '*.h++', '*.cc', '*.hh', '*.cxx', '*.hxx', '*.C', '*.H', '*.cp', '*.CPP', '*.tpp'), ('text/x-c++hdr', 'text/x-c++src')),
|
| 106 |
+
'CppObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'cpp-objdump', ('cpp-objdump', 'c++-objdumb', 'cxx-objdump'), ('*.cpp-objdump', '*.c++-objdump', '*.cxx-objdump'), ('text/x-cpp-objdump',)),
|
| 107 |
+
'CrmshLexer': ('pip._vendor.pygments.lexers.dsls', 'Crmsh', ('crmsh', 'pcmk'), ('*.crmsh', '*.pcmk'), ()),
|
| 108 |
+
'CrocLexer': ('pip._vendor.pygments.lexers.d', 'Croc', ('croc',), ('*.croc',), ('text/x-crocsrc',)),
|
| 109 |
+
'CryptolLexer': ('pip._vendor.pygments.lexers.haskell', 'Cryptol', ('cryptol', 'cry'), ('*.cry',), ('text/x-cryptol',)),
|
| 110 |
+
'CrystalLexer': ('pip._vendor.pygments.lexers.crystal', 'Crystal', ('cr', 'crystal'), ('*.cr',), ('text/x-crystal',)),
|
| 111 |
+
'CsoundDocumentLexer': ('pip._vendor.pygments.lexers.csound', 'Csound Document', ('csound-document', 'csound-csd'), ('*.csd',), ()),
|
| 112 |
+
'CsoundOrchestraLexer': ('pip._vendor.pygments.lexers.csound', 'Csound Orchestra', ('csound', 'csound-orc'), ('*.orc', '*.udo'), ()),
|
| 113 |
+
'CsoundScoreLexer': ('pip._vendor.pygments.lexers.csound', 'Csound Score', ('csound-score', 'csound-sco'), ('*.sco',), ()),
|
| 114 |
+
'CssDjangoLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Django/Jinja', ('css+django', 'css+jinja'), ('*.css.j2', '*.css.jinja2'), ('text/css+django', 'text/css+jinja')),
|
| 115 |
+
'CssErbLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Ruby', ('css+ruby', 'css+erb'), (), ('text/css+ruby',)),
|
| 116 |
+
'CssGenshiLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Genshi Text', ('css+genshitext', 'css+genshi'), (), ('text/css+genshi',)),
|
| 117 |
+
'CssLexer': ('pip._vendor.pygments.lexers.css', 'CSS', ('css',), ('*.css',), ('text/css',)),
|
| 118 |
+
'CssPhpLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+PHP', ('css+php',), (), ('text/css+php',)),
|
| 119 |
+
'CssSmartyLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Smarty', ('css+smarty',), (), ('text/css+smarty',)),
|
| 120 |
+
'CudaLexer': ('pip._vendor.pygments.lexers.c_like', 'CUDA', ('cuda', 'cu'), ('*.cu', '*.cuh'), ('text/x-cuda',)),
|
| 121 |
+
'CypherLexer': ('pip._vendor.pygments.lexers.graph', 'Cypher', ('cypher',), ('*.cyp', '*.cypher'), ()),
|
| 122 |
+
'CythonLexer': ('pip._vendor.pygments.lexers.python', 'Cython', ('cython', 'pyx', 'pyrex'), ('*.pyx', '*.pxd', '*.pxi'), ('text/x-cython', 'application/x-cython')),
|
| 123 |
+
'DLexer': ('pip._vendor.pygments.lexers.d', 'D', ('d',), ('*.d', '*.di'), ('text/x-dsrc',)),
|
| 124 |
+
'DObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'd-objdump', ('d-objdump',), ('*.d-objdump',), ('text/x-d-objdump',)),
|
| 125 |
+
'DarcsPatchLexer': ('pip._vendor.pygments.lexers.diff', 'Darcs Patch', ('dpatch',), ('*.dpatch', '*.darcspatch'), ()),
|
| 126 |
+
'DartLexer': ('pip._vendor.pygments.lexers.javascript', 'Dart', ('dart',), ('*.dart',), ('text/x-dart',)),
|
| 127 |
+
'Dasm16Lexer': ('pip._vendor.pygments.lexers.asm', 'DASM16', ('dasm16',), ('*.dasm16', '*.dasm'), ('text/x-dasm16',)),
|
| 128 |
+
'DaxLexer': ('pip._vendor.pygments.lexers.dax', 'Dax', ('dax',), ('*.dax',), ()),
|
| 129 |
+
'DebianControlLexer': ('pip._vendor.pygments.lexers.installers', 'Debian Control file', ('debcontrol', 'control'), ('control',), ()),
|
| 130 |
+
'DelphiLexer': ('pip._vendor.pygments.lexers.pascal', 'Delphi', ('delphi', 'pas', 'pascal', 'objectpascal'), ('*.pas', '*.dpr'), ('text/x-pascal',)),
|
| 131 |
+
'DesktopLexer': ('pip._vendor.pygments.lexers.configs', 'Desktop file', ('desktop',), ('*.desktop',), ('application/x-desktop',)),
|
| 132 |
+
'DevicetreeLexer': ('pip._vendor.pygments.lexers.devicetree', 'Devicetree', ('devicetree', 'dts'), ('*.dts', '*.dtsi'), ('text/x-c',)),
|
| 133 |
+
'DgLexer': ('pip._vendor.pygments.lexers.python', 'dg', ('dg',), ('*.dg',), ('text/x-dg',)),
|
| 134 |
+
'DiffLexer': ('pip._vendor.pygments.lexers.diff', 'Diff', ('diff', 'udiff'), ('*.diff', '*.patch'), ('text/x-diff', 'text/x-patch')),
|
| 135 |
+
'DjangoLexer': ('pip._vendor.pygments.lexers.templates', 'Django/Jinja', ('django', 'jinja'), (), ('application/x-django-templating', 'application/x-jinja')),
|
| 136 |
+
'DnsZoneLexer': ('pip._vendor.pygments.lexers.dns', 'Zone', ('zone',), ('*.zone',), ('text/dns',)),
|
| 137 |
+
'DockerLexer': ('pip._vendor.pygments.lexers.configs', 'Docker', ('docker', 'dockerfile'), ('Dockerfile', '*.docker'), ('text/x-dockerfile-config',)),
|
| 138 |
+
'DtdLexer': ('pip._vendor.pygments.lexers.html', 'DTD', ('dtd',), ('*.dtd',), ('application/xml-dtd',)),
|
| 139 |
+
'DuelLexer': ('pip._vendor.pygments.lexers.webmisc', 'Duel', ('duel', 'jbst', 'jsonml+bst'), ('*.duel', '*.jbst'), ('text/x-duel', 'text/x-jbst')),
|
| 140 |
+
'DylanConsoleLexer': ('pip._vendor.pygments.lexers.dylan', 'Dylan session', ('dylan-console', 'dylan-repl'), ('*.dylan-console',), ('text/x-dylan-console',)),
|
| 141 |
+
'DylanLexer': ('pip._vendor.pygments.lexers.dylan', 'Dylan', ('dylan',), ('*.dylan', '*.dyl', '*.intr'), ('text/x-dylan',)),
|
| 142 |
+
'DylanLidLexer': ('pip._vendor.pygments.lexers.dylan', 'DylanLID', ('dylan-lid', 'lid'), ('*.lid', '*.hdp'), ('text/x-dylan-lid',)),
|
| 143 |
+
'ECLLexer': ('pip._vendor.pygments.lexers.ecl', 'ECL', ('ecl',), ('*.ecl',), ('application/x-ecl',)),
|
| 144 |
+
'ECLexer': ('pip._vendor.pygments.lexers.c_like', 'eC', ('ec',), ('*.ec', '*.eh'), ('text/x-echdr', 'text/x-ecsrc')),
|
| 145 |
+
'EarlGreyLexer': ('pip._vendor.pygments.lexers.javascript', 'Earl Grey', ('earl-grey', 'earlgrey', 'eg'), ('*.eg',), ('text/x-earl-grey',)),
|
| 146 |
+
'EasytrieveLexer': ('pip._vendor.pygments.lexers.scripting', 'Easytrieve', ('easytrieve',), ('*.ezt', '*.mac'), ('text/x-easytrieve',)),
|
| 147 |
+
'EbnfLexer': ('pip._vendor.pygments.lexers.parsers', 'EBNF', ('ebnf',), ('*.ebnf',), ('text/x-ebnf',)),
|
| 148 |
+
'EiffelLexer': ('pip._vendor.pygments.lexers.eiffel', 'Eiffel', ('eiffel',), ('*.e',), ('text/x-eiffel',)),
|
| 149 |
+
'ElixirConsoleLexer': ('pip._vendor.pygments.lexers.erlang', 'Elixir iex session', ('iex',), (), ('text/x-elixir-shellsession',)),
|
| 150 |
+
'ElixirLexer': ('pip._vendor.pygments.lexers.erlang', 'Elixir', ('elixir', 'ex', 'exs'), ('*.ex', '*.eex', '*.exs', '*.leex'), ('text/x-elixir',)),
|
| 151 |
+
'ElmLexer': ('pip._vendor.pygments.lexers.elm', 'Elm', ('elm',), ('*.elm',), ('text/x-elm',)),
|
| 152 |
+
'ElpiLexer': ('pip._vendor.pygments.lexers.elpi', 'Elpi', ('elpi',), ('*.elpi',), ('text/x-elpi',)),
|
| 153 |
+
'EmacsLispLexer': ('pip._vendor.pygments.lexers.lisp', 'EmacsLisp', ('emacs-lisp', 'elisp', 'emacs'), ('*.el',), ('text/x-elisp', 'application/x-elisp')),
|
| 154 |
+
'EmailLexer': ('pip._vendor.pygments.lexers.email', 'E-mail', ('email', 'eml'), ('*.eml',), ('message/rfc822',)),
|
| 155 |
+
'ErbLexer': ('pip._vendor.pygments.lexers.templates', 'ERB', ('erb',), (), ('application/x-ruby-templating',)),
|
| 156 |
+
'ErlangLexer': ('pip._vendor.pygments.lexers.erlang', 'Erlang', ('erlang',), ('*.erl', '*.hrl', '*.es', '*.escript'), ('text/x-erlang',)),
|
| 157 |
+
'ErlangShellLexer': ('pip._vendor.pygments.lexers.erlang', 'Erlang erl session', ('erl',), ('*.erl-sh',), ('text/x-erl-shellsession',)),
|
| 158 |
+
'EvoqueHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Evoque', ('html+evoque',), ('*.html',), ('text/html+evoque',)),
|
| 159 |
+
'EvoqueLexer': ('pip._vendor.pygments.lexers.templates', 'Evoque', ('evoque',), ('*.evoque',), ('application/x-evoque',)),
|
| 160 |
+
'EvoqueXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Evoque', ('xml+evoque',), ('*.xml',), ('application/xml+evoque',)),
|
| 161 |
+
'ExeclineLexer': ('pip._vendor.pygments.lexers.shell', 'execline', ('execline',), ('*.exec',), ()),
|
| 162 |
+
'EzhilLexer': ('pip._vendor.pygments.lexers.ezhil', 'Ezhil', ('ezhil',), ('*.n',), ('text/x-ezhil',)),
|
| 163 |
+
'FSharpLexer': ('pip._vendor.pygments.lexers.dotnet', 'F#', ('fsharp', 'f#'), ('*.fs', '*.fsi', '*.fsx'), ('text/x-fsharp',)),
|
| 164 |
+
'FStarLexer': ('pip._vendor.pygments.lexers.ml', 'FStar', ('fstar',), ('*.fst', '*.fsti'), ('text/x-fstar',)),
|
| 165 |
+
'FactorLexer': ('pip._vendor.pygments.lexers.factor', 'Factor', ('factor',), ('*.factor',), ('text/x-factor',)),
|
| 166 |
+
'FancyLexer': ('pip._vendor.pygments.lexers.ruby', 'Fancy', ('fancy', 'fy'), ('*.fy', '*.fancypack'), ('text/x-fancysrc',)),
|
| 167 |
+
'FantomLexer': ('pip._vendor.pygments.lexers.fantom', 'Fantom', ('fan',), ('*.fan',), ('application/x-fantom',)),
|
| 168 |
+
'FelixLexer': ('pip._vendor.pygments.lexers.felix', 'Felix', ('felix', 'flx'), ('*.flx', '*.flxh'), ('text/x-felix',)),
|
| 169 |
+
'FennelLexer': ('pip._vendor.pygments.lexers.lisp', 'Fennel', ('fennel', 'fnl'), ('*.fnl',), ()),
|
| 170 |
+
'FiftLexer': ('pip._vendor.pygments.lexers.fift', 'Fift', ('fift', 'fif'), ('*.fif',), ()),
|
| 171 |
+
'FishShellLexer': ('pip._vendor.pygments.lexers.shell', 'Fish', ('fish', 'fishshell'), ('*.fish', '*.load'), ('application/x-fish',)),
|
| 172 |
+
'FlatlineLexer': ('pip._vendor.pygments.lexers.dsls', 'Flatline', ('flatline',), (), ('text/x-flatline',)),
|
| 173 |
+
'FloScriptLexer': ('pip._vendor.pygments.lexers.floscript', 'FloScript', ('floscript', 'flo'), ('*.flo',), ()),
|
| 174 |
+
'ForthLexer': ('pip._vendor.pygments.lexers.forth', 'Forth', ('forth',), ('*.frt', '*.fs'), ('application/x-forth',)),
|
| 175 |
+
'FortranFixedLexer': ('pip._vendor.pygments.lexers.fortran', 'FortranFixed', ('fortranfixed',), ('*.f', '*.F'), ()),
|
| 176 |
+
'FortranLexer': ('pip._vendor.pygments.lexers.fortran', 'Fortran', ('fortran', 'f90'), ('*.f03', '*.f90', '*.F03', '*.F90'), ('text/x-fortran',)),
|
| 177 |
+
'FoxProLexer': ('pip._vendor.pygments.lexers.foxpro', 'FoxPro', ('foxpro', 'vfp', 'clipper', 'xbase'), ('*.PRG', '*.prg'), ()),
|
| 178 |
+
'FreeFemLexer': ('pip._vendor.pygments.lexers.freefem', 'Freefem', ('freefem',), ('*.edp',), ('text/x-freefem',)),
|
| 179 |
+
'FuncLexer': ('pip._vendor.pygments.lexers.func', 'FunC', ('func', 'fc'), ('*.fc', '*.func'), ()),
|
| 180 |
+
'FutharkLexer': ('pip._vendor.pygments.lexers.futhark', 'Futhark', ('futhark',), ('*.fut',), ('text/x-futhark',)),
|
| 181 |
+
'GAPConsoleLexer': ('pip._vendor.pygments.lexers.algebra', 'GAP session', ('gap-console', 'gap-repl'), ('*.tst',), ()),
|
| 182 |
+
'GAPLexer': ('pip._vendor.pygments.lexers.algebra', 'GAP', ('gap',), ('*.g', '*.gd', '*.gi', '*.gap'), ()),
|
| 183 |
+
'GDScriptLexer': ('pip._vendor.pygments.lexers.gdscript', 'GDScript', ('gdscript', 'gd'), ('*.gd',), ('text/x-gdscript', 'application/x-gdscript')),
|
| 184 |
+
'GLShaderLexer': ('pip._vendor.pygments.lexers.graphics', 'GLSL', ('glsl',), ('*.vert', '*.frag', '*.geo'), ('text/x-glslsrc',)),
|
| 185 |
+
'GSQLLexer': ('pip._vendor.pygments.lexers.gsql', 'GSQL', ('gsql',), ('*.gsql',), ()),
|
| 186 |
+
'GasLexer': ('pip._vendor.pygments.lexers.asm', 'GAS', ('gas', 'asm'), ('*.s', '*.S'), ('text/x-gas',)),
|
| 187 |
+
'GcodeLexer': ('pip._vendor.pygments.lexers.gcodelexer', 'g-code', ('gcode',), ('*.gcode',), ()),
|
| 188 |
+
'GenshiLexer': ('pip._vendor.pygments.lexers.templates', 'Genshi', ('genshi', 'kid', 'xml+genshi', 'xml+kid'), ('*.kid',), ('application/x-genshi', 'application/x-kid')),
|
| 189 |
+
'GenshiTextLexer': ('pip._vendor.pygments.lexers.templates', 'Genshi Text', ('genshitext',), (), ('application/x-genshi-text', 'text/x-genshi')),
|
| 190 |
+
'GettextLexer': ('pip._vendor.pygments.lexers.textfmts', 'Gettext Catalog', ('pot', 'po'), ('*.pot', '*.po'), ('application/x-gettext', 'text/x-gettext', 'text/gettext')),
|
| 191 |
+
'GherkinLexer': ('pip._vendor.pygments.lexers.testing', 'Gherkin', ('gherkin', 'cucumber'), ('*.feature',), ('text/x-gherkin',)),
|
| 192 |
+
'GnuplotLexer': ('pip._vendor.pygments.lexers.graphics', 'Gnuplot', ('gnuplot',), ('*.plot', '*.plt'), ('text/x-gnuplot',)),
|
| 193 |
+
'GoLexer': ('pip._vendor.pygments.lexers.go', 'Go', ('go', 'golang'), ('*.go',), ('text/x-gosrc',)),
|
| 194 |
+
'GoloLexer': ('pip._vendor.pygments.lexers.jvm', 'Golo', ('golo',), ('*.golo',), ()),
|
| 195 |
+
'GoodDataCLLexer': ('pip._vendor.pygments.lexers.business', 'GoodData-CL', ('gooddata-cl',), ('*.gdc',), ('text/x-gooddata-cl',)),
|
| 196 |
+
'GosuLexer': ('pip._vendor.pygments.lexers.jvm', 'Gosu', ('gosu',), ('*.gs', '*.gsx', '*.gsp', '*.vark'), ('text/x-gosu',)),
|
| 197 |
+
'GosuTemplateLexer': ('pip._vendor.pygments.lexers.jvm', 'Gosu Template', ('gst',), ('*.gst',), ('text/x-gosu-template',)),
|
| 198 |
+
'GraphQLLexer': ('pip._vendor.pygments.lexers.graphql', 'GraphQL', ('graphql',), ('*.graphql',), ()),
|
| 199 |
+
'GraphvizLexer': ('pip._vendor.pygments.lexers.graphviz', 'Graphviz', ('graphviz', 'dot'), ('*.gv', '*.dot'), ('text/x-graphviz', 'text/vnd.graphviz')),
|
| 200 |
+
'GroffLexer': ('pip._vendor.pygments.lexers.markup', 'Groff', ('groff', 'nroff', 'man'), ('*.[1-9]', '*.man', '*.1p', '*.3pm'), ('application/x-troff', 'text/troff')),
|
| 201 |
+
'GroovyLexer': ('pip._vendor.pygments.lexers.jvm', 'Groovy', ('groovy',), ('*.groovy', '*.gradle'), ('text/x-groovy',)),
|
| 202 |
+
'HLSLShaderLexer': ('pip._vendor.pygments.lexers.graphics', 'HLSL', ('hlsl',), ('*.hlsl', '*.hlsli'), ('text/x-hlsl',)),
|
| 203 |
+
'HTMLUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'HTML+UL4', ('html+ul4',), ('*.htmlul4',), ()),
|
| 204 |
+
'HamlLexer': ('pip._vendor.pygments.lexers.html', 'Haml', ('haml',), ('*.haml',), ('text/x-haml',)),
|
| 205 |
+
'HandlebarsHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Handlebars', ('html+handlebars',), ('*.handlebars', '*.hbs'), ('text/html+handlebars', 'text/x-handlebars-template')),
|
| 206 |
+
'HandlebarsLexer': ('pip._vendor.pygments.lexers.templates', 'Handlebars', ('handlebars',), (), ()),
|
| 207 |
+
'HaskellLexer': ('pip._vendor.pygments.lexers.haskell', 'Haskell', ('haskell', 'hs'), ('*.hs',), ('text/x-haskell',)),
|
| 208 |
+
'HaxeLexer': ('pip._vendor.pygments.lexers.haxe', 'Haxe', ('haxe', 'hxsl', 'hx'), ('*.hx', '*.hxsl'), ('text/haxe', 'text/x-haxe', 'text/x-hx')),
|
| 209 |
+
'HexdumpLexer': ('pip._vendor.pygments.lexers.hexdump', 'Hexdump', ('hexdump',), (), ()),
|
| 210 |
+
'HsailLexer': ('pip._vendor.pygments.lexers.asm', 'HSAIL', ('hsail', 'hsa'), ('*.hsail',), ('text/x-hsail',)),
|
| 211 |
+
'HspecLexer': ('pip._vendor.pygments.lexers.haskell', 'Hspec', ('hspec',), ('*Spec.hs',), ()),
|
| 212 |
+
'HtmlDjangoLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Django/Jinja', ('html+django', 'html+jinja', 'htmldjango'), ('*.html.j2', '*.htm.j2', '*.xhtml.j2', '*.html.jinja2', '*.htm.jinja2', '*.xhtml.jinja2'), ('text/html+django', 'text/html+jinja')),
|
| 213 |
+
'HtmlGenshiLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Genshi', ('html+genshi', 'html+kid'), (), ('text/html+genshi',)),
|
| 214 |
+
'HtmlLexer': ('pip._vendor.pygments.lexers.html', 'HTML', ('html',), ('*.html', '*.htm', '*.xhtml', '*.xslt'), ('text/html', 'application/xhtml+xml')),
|
| 215 |
+
'HtmlPhpLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+PHP', ('html+php',), ('*.phtml',), ('application/x-php', 'application/x-httpd-php', 'application/x-httpd-php3', 'application/x-httpd-php4', 'application/x-httpd-php5')),
|
| 216 |
+
'HtmlSmartyLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Smarty', ('html+smarty',), (), ('text/html+smarty',)),
|
| 217 |
+
'HttpLexer': ('pip._vendor.pygments.lexers.textfmts', 'HTTP', ('http',), (), ()),
|
| 218 |
+
'HxmlLexer': ('pip._vendor.pygments.lexers.haxe', 'Hxml', ('haxeml', 'hxml'), ('*.hxml',), ()),
|
| 219 |
+
'HyLexer': ('pip._vendor.pygments.lexers.lisp', 'Hy', ('hylang', 'hy'), ('*.hy',), ('text/x-hy', 'application/x-hy')),
|
| 220 |
+
'HybrisLexer': ('pip._vendor.pygments.lexers.scripting', 'Hybris', ('hybris',), ('*.hyb',), ('text/x-hybris', 'application/x-hybris')),
|
| 221 |
+
'IDLLexer': ('pip._vendor.pygments.lexers.idl', 'IDL', ('idl',), ('*.pro',), ('text/idl',)),
|
| 222 |
+
'IconLexer': ('pip._vendor.pygments.lexers.unicon', 'Icon', ('icon',), ('*.icon', '*.ICON'), ()),
|
| 223 |
+
'IdrisLexer': ('pip._vendor.pygments.lexers.haskell', 'Idris', ('idris', 'idr'), ('*.idr',), ('text/x-idris',)),
|
| 224 |
+
'IgorLexer': ('pip._vendor.pygments.lexers.igor', 'Igor', ('igor', 'igorpro'), ('*.ipf',), ('text/ipf',)),
|
| 225 |
+
'Inform6Lexer': ('pip._vendor.pygments.lexers.int_fiction', 'Inform 6', ('inform6', 'i6'), ('*.inf',), ()),
|
| 226 |
+
'Inform6TemplateLexer': ('pip._vendor.pygments.lexers.int_fiction', 'Inform 6 template', ('i6t',), ('*.i6t',), ()),
|
| 227 |
+
'Inform7Lexer': ('pip._vendor.pygments.lexers.int_fiction', 'Inform 7', ('inform7', 'i7'), ('*.ni', '*.i7x'), ()),
|
| 228 |
+
'IniLexer': ('pip._vendor.pygments.lexers.configs', 'INI', ('ini', 'cfg', 'dosini'), ('*.ini', '*.cfg', '*.inf', '.editorconfig'), ('text/x-ini', 'text/inf')),
|
| 229 |
+
'IoLexer': ('pip._vendor.pygments.lexers.iolang', 'Io', ('io',), ('*.io',), ('text/x-iosrc',)),
|
| 230 |
+
'IokeLexer': ('pip._vendor.pygments.lexers.jvm', 'Ioke', ('ioke', 'ik'), ('*.ik',), ('text/x-iokesrc',)),
|
| 231 |
+
'IrcLogsLexer': ('pip._vendor.pygments.lexers.textfmts', 'IRC logs', ('irc',), ('*.weechatlog',), ('text/x-irclog',)),
|
| 232 |
+
'IsabelleLexer': ('pip._vendor.pygments.lexers.theorem', 'Isabelle', ('isabelle',), ('*.thy',), ('text/x-isabelle',)),
|
| 233 |
+
'JLexer': ('pip._vendor.pygments.lexers.j', 'J', ('j',), ('*.ijs',), ('text/x-j',)),
|
| 234 |
+
'JMESPathLexer': ('pip._vendor.pygments.lexers.jmespath', 'JMESPath', ('jmespath', 'jp'), ('*.jp',), ()),
|
| 235 |
+
'JSLTLexer': ('pip._vendor.pygments.lexers.jslt', 'JSLT', ('jslt',), ('*.jslt',), ('text/x-jslt',)),
|
| 236 |
+
'JagsLexer': ('pip._vendor.pygments.lexers.modeling', 'JAGS', ('jags',), ('*.jag', '*.bug'), ()),
|
| 237 |
+
'JanetLexer': ('pip._vendor.pygments.lexers.lisp', 'Janet', ('janet',), ('*.janet', '*.jdn'), ('text/x-janet', 'application/x-janet')),
|
| 238 |
+
'JasminLexer': ('pip._vendor.pygments.lexers.jvm', 'Jasmin', ('jasmin', 'jasminxt'), ('*.j',), ()),
|
| 239 |
+
'JavaLexer': ('pip._vendor.pygments.lexers.jvm', 'Java', ('java',), ('*.java',), ('text/x-java',)),
|
| 240 |
+
'JavascriptDjangoLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Django/Jinja', ('javascript+django', 'js+django', 'javascript+jinja', 'js+jinja'), ('*.js.j2', '*.js.jinja2'), ('application/x-javascript+django', 'application/x-javascript+jinja', 'text/x-javascript+django', 'text/x-javascript+jinja', 'text/javascript+django', 'text/javascript+jinja')),
|
| 241 |
+
'JavascriptErbLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Ruby', ('javascript+ruby', 'js+ruby', 'javascript+erb', 'js+erb'), (), ('application/x-javascript+ruby', 'text/x-javascript+ruby', 'text/javascript+ruby')),
|
| 242 |
+
'JavascriptGenshiLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Genshi Text', ('js+genshitext', 'js+genshi', 'javascript+genshitext', 'javascript+genshi'), (), ('application/x-javascript+genshi', 'text/x-javascript+genshi', 'text/javascript+genshi')),
|
| 243 |
+
'JavascriptLexer': ('pip._vendor.pygments.lexers.javascript', 'JavaScript', ('javascript', 'js'), ('*.js', '*.jsm', '*.mjs', '*.cjs'), ('application/javascript', 'application/x-javascript', 'text/x-javascript', 'text/javascript')),
|
| 244 |
+
'JavascriptPhpLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+PHP', ('javascript+php', 'js+php'), (), ('application/x-javascript+php', 'text/x-javascript+php', 'text/javascript+php')),
|
| 245 |
+
'JavascriptSmartyLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Smarty', ('javascript+smarty', 'js+smarty'), (), ('application/x-javascript+smarty', 'text/x-javascript+smarty', 'text/javascript+smarty')),
|
| 246 |
+
'JavascriptUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'Javascript+UL4', ('js+ul4',), ('*.jsul4',), ()),
|
| 247 |
+
'JclLexer': ('pip._vendor.pygments.lexers.scripting', 'JCL', ('jcl',), ('*.jcl',), ('text/x-jcl',)),
|
| 248 |
+
'JsgfLexer': ('pip._vendor.pygments.lexers.grammar_notation', 'JSGF', ('jsgf',), ('*.jsgf',), ('application/jsgf', 'application/x-jsgf', 'text/jsgf')),
|
| 249 |
+
'JsonBareObjectLexer': ('pip._vendor.pygments.lexers.data', 'JSONBareObject', (), (), ()),
|
| 250 |
+
'JsonLdLexer': ('pip._vendor.pygments.lexers.data', 'JSON-LD', ('jsonld', 'json-ld'), ('*.jsonld',), ('application/ld+json',)),
|
| 251 |
+
'JsonLexer': ('pip._vendor.pygments.lexers.data', 'JSON', ('json', 'json-object'), ('*.json', '*.jsonl', '*.ndjson', 'Pipfile.lock'), ('application/json', 'application/json-object', 'application/x-ndjson', 'application/jsonl', 'application/json-seq')),
|
| 252 |
+
'JsonnetLexer': ('pip._vendor.pygments.lexers.jsonnet', 'Jsonnet', ('jsonnet',), ('*.jsonnet', '*.libsonnet'), ()),
|
| 253 |
+
'JspLexer': ('pip._vendor.pygments.lexers.templates', 'Java Server Page', ('jsp',), ('*.jsp',), ('application/x-jsp',)),
|
| 254 |
+
'JsxLexer': ('pip._vendor.pygments.lexers.jsx', 'JSX', ('jsx', 'react'), ('*.jsx', '*.react'), ('text/jsx', 'text/typescript-jsx')),
|
| 255 |
+
'JuliaConsoleLexer': ('pip._vendor.pygments.lexers.julia', 'Julia console', ('jlcon', 'julia-repl'), (), ()),
|
| 256 |
+
'JuliaLexer': ('pip._vendor.pygments.lexers.julia', 'Julia', ('julia', 'jl'), ('*.jl',), ('text/x-julia', 'application/x-julia')),
|
| 257 |
+
'JuttleLexer': ('pip._vendor.pygments.lexers.javascript', 'Juttle', ('juttle',), ('*.juttle',), ('application/juttle', 'application/x-juttle', 'text/x-juttle', 'text/juttle')),
|
| 258 |
+
'KLexer': ('pip._vendor.pygments.lexers.q', 'K', ('k',), ('*.k',), ()),
|
| 259 |
+
'KalLexer': ('pip._vendor.pygments.lexers.javascript', 'Kal', ('kal',), ('*.kal',), ('text/kal', 'application/kal')),
|
| 260 |
+
'KconfigLexer': ('pip._vendor.pygments.lexers.configs', 'Kconfig', ('kconfig', 'menuconfig', 'linux-config', 'kernel-config'), ('Kconfig*', '*Config.in*', 'external.in*', 'standard-modules.in'), ('text/x-kconfig',)),
|
| 261 |
+
'KernelLogLexer': ('pip._vendor.pygments.lexers.textfmts', 'Kernel log', ('kmsg', 'dmesg'), ('*.kmsg', '*.dmesg'), ()),
|
| 262 |
+
'KokaLexer': ('pip._vendor.pygments.lexers.haskell', 'Koka', ('koka',), ('*.kk', '*.kki'), ('text/x-koka',)),
|
| 263 |
+
'KotlinLexer': ('pip._vendor.pygments.lexers.jvm', 'Kotlin', ('kotlin',), ('*.kt', '*.kts'), ('text/x-kotlin',)),
|
| 264 |
+
'KuinLexer': ('pip._vendor.pygments.lexers.kuin', 'Kuin', ('kuin',), ('*.kn',), ()),
|
| 265 |
+
'KustoLexer': ('pip._vendor.pygments.lexers.kusto', 'Kusto', ('kql', 'kusto'), ('*.kql', '*.kusto', '.csl'), ()),
|
| 266 |
+
'LSLLexer': ('pip._vendor.pygments.lexers.scripting', 'LSL', ('lsl',), ('*.lsl',), ('text/x-lsl',)),
|
| 267 |
+
'LassoCssLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Lasso', ('css+lasso',), (), ('text/css+lasso',)),
|
| 268 |
+
'LassoHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Lasso', ('html+lasso',), (), ('text/html+lasso', 'application/x-httpd-lasso', 'application/x-httpd-lasso[89]')),
|
| 269 |
+
'LassoJavascriptLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Lasso', ('javascript+lasso', 'js+lasso'), (), ('application/x-javascript+lasso', 'text/x-javascript+lasso', 'text/javascript+lasso')),
|
| 270 |
+
'LassoLexer': ('pip._vendor.pygments.lexers.javascript', 'Lasso', ('lasso', 'lassoscript'), ('*.lasso', '*.lasso[89]'), ('text/x-lasso',)),
|
| 271 |
+
'LassoXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Lasso', ('xml+lasso',), (), ('application/xml+lasso',)),
|
| 272 |
+
'LdaprcLexer': ('pip._vendor.pygments.lexers.ldap', 'LDAP configuration file', ('ldapconf', 'ldaprc'), ('.ldaprc', 'ldaprc', 'ldap.conf'), ('text/x-ldapconf',)),
|
| 273 |
+
'LdifLexer': ('pip._vendor.pygments.lexers.ldap', 'LDIF', ('ldif',), ('*.ldif',), ('text/x-ldif',)),
|
| 274 |
+
'Lean3Lexer': ('pip._vendor.pygments.lexers.lean', 'Lean', ('lean', 'lean3'), ('*.lean',), ('text/x-lean', 'text/x-lean3')),
|
| 275 |
+
'Lean4Lexer': ('pip._vendor.pygments.lexers.lean', 'Lean4', ('lean4',), ('*.lean',), ('text/x-lean4',)),
|
| 276 |
+
'LessCssLexer': ('pip._vendor.pygments.lexers.css', 'LessCss', ('less',), ('*.less',), ('text/x-less-css',)),
|
| 277 |
+
'LighttpdConfLexer': ('pip._vendor.pygments.lexers.configs', 'Lighttpd configuration file', ('lighttpd', 'lighty'), ('lighttpd.conf',), ('text/x-lighttpd-conf',)),
|
| 278 |
+
'LilyPondLexer': ('pip._vendor.pygments.lexers.lilypond', 'LilyPond', ('lilypond',), ('*.ly',), ()),
|
| 279 |
+
'LimboLexer': ('pip._vendor.pygments.lexers.inferno', 'Limbo', ('limbo',), ('*.b',), ('text/limbo',)),
|
| 280 |
+
'LiquidLexer': ('pip._vendor.pygments.lexers.templates', 'liquid', ('liquid',), ('*.liquid',), ()),
|
| 281 |
+
'LiterateAgdaLexer': ('pip._vendor.pygments.lexers.haskell', 'Literate Agda', ('literate-agda', 'lagda'), ('*.lagda',), ('text/x-literate-agda',)),
|
| 282 |
+
'LiterateCryptolLexer': ('pip._vendor.pygments.lexers.haskell', 'Literate Cryptol', ('literate-cryptol', 'lcryptol', 'lcry'), ('*.lcry',), ('text/x-literate-cryptol',)),
|
| 283 |
+
'LiterateHaskellLexer': ('pip._vendor.pygments.lexers.haskell', 'Literate Haskell', ('literate-haskell', 'lhaskell', 'lhs'), ('*.lhs',), ('text/x-literate-haskell',)),
|
| 284 |
+
'LiterateIdrisLexer': ('pip._vendor.pygments.lexers.haskell', 'Literate Idris', ('literate-idris', 'lidris', 'lidr'), ('*.lidr',), ('text/x-literate-idris',)),
|
| 285 |
+
'LiveScriptLexer': ('pip._vendor.pygments.lexers.javascript', 'LiveScript', ('livescript', 'live-script'), ('*.ls',), ('text/livescript',)),
|
| 286 |
+
'LlvmLexer': ('pip._vendor.pygments.lexers.asm', 'LLVM', ('llvm',), ('*.ll',), ('text/x-llvm',)),
|
| 287 |
+
'LlvmMirBodyLexer': ('pip._vendor.pygments.lexers.asm', 'LLVM-MIR Body', ('llvm-mir-body',), (), ()),
|
| 288 |
+
'LlvmMirLexer': ('pip._vendor.pygments.lexers.asm', 'LLVM-MIR', ('llvm-mir',), ('*.mir',), ()),
|
| 289 |
+
'LogosLexer': ('pip._vendor.pygments.lexers.objective', 'Logos', ('logos',), ('*.x', '*.xi', '*.xm', '*.xmi'), ('text/x-logos',)),
|
| 290 |
+
'LogtalkLexer': ('pip._vendor.pygments.lexers.prolog', 'Logtalk', ('logtalk',), ('*.lgt', '*.logtalk'), ('text/x-logtalk',)),
|
| 291 |
+
'LuaLexer': ('pip._vendor.pygments.lexers.scripting', 'Lua', ('lua',), ('*.lua', '*.wlua'), ('text/x-lua', 'application/x-lua')),
|
| 292 |
+
'LuauLexer': ('pip._vendor.pygments.lexers.scripting', 'Luau', ('luau',), ('*.luau',), ()),
|
| 293 |
+
'MCFunctionLexer': ('pip._vendor.pygments.lexers.minecraft', 'MCFunction', ('mcfunction', 'mcf'), ('*.mcfunction',), ('text/mcfunction',)),
|
| 294 |
+
'MCSchemaLexer': ('pip._vendor.pygments.lexers.minecraft', 'MCSchema', ('mcschema',), ('*.mcschema',), ('text/mcschema',)),
|
| 295 |
+
'MIMELexer': ('pip._vendor.pygments.lexers.mime', 'MIME', ('mime',), (), ('multipart/mixed', 'multipart/related', 'multipart/alternative')),
|
| 296 |
+
'MIPSLexer': ('pip._vendor.pygments.lexers.mips', 'MIPS', ('mips',), ('*.mips', '*.MIPS'), ()),
|
| 297 |
+
'MOOCodeLexer': ('pip._vendor.pygments.lexers.scripting', 'MOOCode', ('moocode', 'moo'), ('*.moo',), ('text/x-moocode',)),
|
| 298 |
+
'MSDOSSessionLexer': ('pip._vendor.pygments.lexers.shell', 'MSDOS Session', ('doscon',), (), ()),
|
| 299 |
+
'Macaulay2Lexer': ('pip._vendor.pygments.lexers.macaulay2', 'Macaulay2', ('macaulay2',), ('*.m2',), ()),
|
| 300 |
+
'MakefileLexer': ('pip._vendor.pygments.lexers.make', 'Makefile', ('make', 'makefile', 'mf', 'bsdmake'), ('*.mak', '*.mk', 'Makefile', 'makefile', 'Makefile.*', 'GNUmakefile'), ('text/x-makefile',)),
|
| 301 |
+
'MakoCssLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Mako', ('css+mako',), (), ('text/css+mako',)),
|
| 302 |
+
'MakoHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Mako', ('html+mako',), (), ('text/html+mako',)),
|
| 303 |
+
'MakoJavascriptLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Mako', ('javascript+mako', 'js+mako'), (), ('application/x-javascript+mako', 'text/x-javascript+mako', 'text/javascript+mako')),
|
| 304 |
+
'MakoLexer': ('pip._vendor.pygments.lexers.templates', 'Mako', ('mako',), ('*.mao',), ('application/x-mako',)),
|
| 305 |
+
'MakoXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Mako', ('xml+mako',), (), ('application/xml+mako',)),
|
| 306 |
+
'MaqlLexer': ('pip._vendor.pygments.lexers.business', 'MAQL', ('maql',), ('*.maql',), ('text/x-gooddata-maql', 'application/x-gooddata-maql')),
|
| 307 |
+
'MarkdownLexer': ('pip._vendor.pygments.lexers.markup', 'Markdown', ('markdown', 'md'), ('*.md', '*.markdown'), ('text/x-markdown',)),
|
| 308 |
+
'MaskLexer': ('pip._vendor.pygments.lexers.javascript', 'Mask', ('mask',), ('*.mask',), ('text/x-mask',)),
|
| 309 |
+
'MasonLexer': ('pip._vendor.pygments.lexers.templates', 'Mason', ('mason',), ('*.m', '*.mhtml', '*.mc', '*.mi', 'autohandler', 'dhandler'), ('application/x-mason',)),
|
| 310 |
+
'MathematicaLexer': ('pip._vendor.pygments.lexers.algebra', 'Mathematica', ('mathematica', 'mma', 'nb'), ('*.nb', '*.cdf', '*.nbp', '*.ma'), ('application/mathematica', 'application/vnd.wolfram.mathematica', 'application/vnd.wolfram.mathematica.package', 'application/vnd.wolfram.cdf')),
|
| 311 |
+
'MatlabLexer': ('pip._vendor.pygments.lexers.matlab', 'Matlab', ('matlab',), ('*.m',), ('text/matlab',)),
|
| 312 |
+
'MatlabSessionLexer': ('pip._vendor.pygments.lexers.matlab', 'Matlab session', ('matlabsession',), (), ()),
|
| 313 |
+
'MaximaLexer': ('pip._vendor.pygments.lexers.maxima', 'Maxima', ('maxima', 'macsyma'), ('*.mac', '*.max'), ()),
|
| 314 |
+
'MesonLexer': ('pip._vendor.pygments.lexers.meson', 'Meson', ('meson', 'meson.build'), ('meson.build', 'meson_options.txt'), ('text/x-meson',)),
|
| 315 |
+
'MiniDLexer': ('pip._vendor.pygments.lexers.d', 'MiniD', ('minid',), (), ('text/x-minidsrc',)),
|
| 316 |
+
'MiniScriptLexer': ('pip._vendor.pygments.lexers.scripting', 'MiniScript', ('miniscript', 'ms'), ('*.ms',), ('text/x-minicript', 'application/x-miniscript')),
|
| 317 |
+
'ModelicaLexer': ('pip._vendor.pygments.lexers.modeling', 'Modelica', ('modelica',), ('*.mo',), ('text/x-modelica',)),
|
| 318 |
+
'Modula2Lexer': ('pip._vendor.pygments.lexers.modula2', 'Modula-2', ('modula2', 'm2'), ('*.def', '*.mod'), ('text/x-modula2',)),
|
| 319 |
+
'MoinWikiLexer': ('pip._vendor.pygments.lexers.markup', 'MoinMoin/Trac Wiki markup', ('trac-wiki', 'moin'), (), ('text/x-trac-wiki',)),
|
| 320 |
+
'MojoLexer': ('pip._vendor.pygments.lexers.mojo', 'Mojo', ('mojo', '🔥'), ('*.mojo', '*.🔥'), ('text/x-mojo', 'application/x-mojo')),
|
| 321 |
+
'MonkeyLexer': ('pip._vendor.pygments.lexers.basic', 'Monkey', ('monkey',), ('*.monkey',), ('text/x-monkey',)),
|
| 322 |
+
'MonteLexer': ('pip._vendor.pygments.lexers.monte', 'Monte', ('monte',), ('*.mt',), ()),
|
| 323 |
+
'MoonScriptLexer': ('pip._vendor.pygments.lexers.scripting', 'MoonScript', ('moonscript', 'moon'), ('*.moon',), ('text/x-moonscript', 'application/x-moonscript')),
|
| 324 |
+
'MoselLexer': ('pip._vendor.pygments.lexers.mosel', 'Mosel', ('mosel',), ('*.mos',), ()),
|
| 325 |
+
'MozPreprocCssLexer': ('pip._vendor.pygments.lexers.markup', 'CSS+mozpreproc', ('css+mozpreproc',), ('*.css.in',), ()),
|
| 326 |
+
'MozPreprocHashLexer': ('pip._vendor.pygments.lexers.markup', 'mozhashpreproc', ('mozhashpreproc',), (), ()),
|
| 327 |
+
'MozPreprocJavascriptLexer': ('pip._vendor.pygments.lexers.markup', 'Javascript+mozpreproc', ('javascript+mozpreproc',), ('*.js.in',), ()),
|
| 328 |
+
'MozPreprocPercentLexer': ('pip._vendor.pygments.lexers.markup', 'mozpercentpreproc', ('mozpercentpreproc',), (), ()),
|
| 329 |
+
'MozPreprocXulLexer': ('pip._vendor.pygments.lexers.markup', 'XUL+mozpreproc', ('xul+mozpreproc',), ('*.xul.in',), ()),
|
| 330 |
+
'MqlLexer': ('pip._vendor.pygments.lexers.c_like', 'MQL', ('mql', 'mq4', 'mq5', 'mql4', 'mql5'), ('*.mq4', '*.mq5', '*.mqh'), ('text/x-mql',)),
|
| 331 |
+
'MscgenLexer': ('pip._vendor.pygments.lexers.dsls', 'Mscgen', ('mscgen', 'msc'), ('*.msc',), ()),
|
| 332 |
+
'MuPADLexer': ('pip._vendor.pygments.lexers.algebra', 'MuPAD', ('mupad',), ('*.mu',), ()),
|
| 333 |
+
'MxmlLexer': ('pip._vendor.pygments.lexers.actionscript', 'MXML', ('mxml',), ('*.mxml',), ()),
|
| 334 |
+
'MySqlLexer': ('pip._vendor.pygments.lexers.sql', 'MySQL', ('mysql',), (), ('text/x-mysql',)),
|
| 335 |
+
'MyghtyCssLexer': ('pip._vendor.pygments.lexers.templates', 'CSS+Myghty', ('css+myghty',), (), ('text/css+myghty',)),
|
| 336 |
+
'MyghtyHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Myghty', ('html+myghty',), (), ('text/html+myghty',)),
|
| 337 |
+
'MyghtyJavascriptLexer': ('pip._vendor.pygments.lexers.templates', 'JavaScript+Myghty', ('javascript+myghty', 'js+myghty'), (), ('application/x-javascript+myghty', 'text/x-javascript+myghty', 'text/javascript+mygthy')),
|
| 338 |
+
'MyghtyLexer': ('pip._vendor.pygments.lexers.templates', 'Myghty', ('myghty',), ('*.myt', 'autodelegate'), ('application/x-myghty',)),
|
| 339 |
+
'MyghtyXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Myghty', ('xml+myghty',), (), ('application/xml+myghty',)),
|
| 340 |
+
'NCLLexer': ('pip._vendor.pygments.lexers.ncl', 'NCL', ('ncl',), ('*.ncl',), ('text/ncl',)),
|
| 341 |
+
'NSISLexer': ('pip._vendor.pygments.lexers.installers', 'NSIS', ('nsis', 'nsi', 'nsh'), ('*.nsi', '*.nsh'), ('text/x-nsis',)),
|
| 342 |
+
'NasmLexer': ('pip._vendor.pygments.lexers.asm', 'NASM', ('nasm',), ('*.asm', '*.ASM', '*.nasm'), ('text/x-nasm',)),
|
| 343 |
+
'NasmObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'objdump-nasm', ('objdump-nasm',), ('*.objdump-intel',), ('text/x-nasm-objdump',)),
|
| 344 |
+
'NemerleLexer': ('pip._vendor.pygments.lexers.dotnet', 'Nemerle', ('nemerle',), ('*.n',), ('text/x-nemerle',)),
|
| 345 |
+
'NesCLexer': ('pip._vendor.pygments.lexers.c_like', 'nesC', ('nesc',), ('*.nc',), ('text/x-nescsrc',)),
|
| 346 |
+
'NestedTextLexer': ('pip._vendor.pygments.lexers.configs', 'NestedText', ('nestedtext', 'nt'), ('*.nt',), ()),
|
| 347 |
+
'NewLispLexer': ('pip._vendor.pygments.lexers.lisp', 'NewLisp', ('newlisp',), ('*.lsp', '*.nl', '*.kif'), ('text/x-newlisp', 'application/x-newlisp')),
|
| 348 |
+
'NewspeakLexer': ('pip._vendor.pygments.lexers.smalltalk', 'Newspeak', ('newspeak',), ('*.ns2',), ('text/x-newspeak',)),
|
| 349 |
+
'NginxConfLexer': ('pip._vendor.pygments.lexers.configs', 'Nginx configuration file', ('nginx',), ('nginx.conf',), ('text/x-nginx-conf',)),
|
| 350 |
+
'NimrodLexer': ('pip._vendor.pygments.lexers.nimrod', 'Nimrod', ('nimrod', 'nim'), ('*.nim', '*.nimrod'), ('text/x-nim',)),
|
| 351 |
+
'NitLexer': ('pip._vendor.pygments.lexers.nit', 'Nit', ('nit',), ('*.nit',), ()),
|
| 352 |
+
'NixLexer': ('pip._vendor.pygments.lexers.nix', 'Nix', ('nixos', 'nix'), ('*.nix',), ('text/x-nix',)),
|
| 353 |
+
'NodeConsoleLexer': ('pip._vendor.pygments.lexers.javascript', 'Node.js REPL console session', ('nodejsrepl',), (), ('text/x-nodejsrepl',)),
|
| 354 |
+
'NotmuchLexer': ('pip._vendor.pygments.lexers.textfmts', 'Notmuch', ('notmuch',), (), ()),
|
| 355 |
+
'NuSMVLexer': ('pip._vendor.pygments.lexers.smv', 'NuSMV', ('nusmv',), ('*.smv',), ()),
|
| 356 |
+
'NumPyLexer': ('pip._vendor.pygments.lexers.python', 'NumPy', ('numpy',), (), ()),
|
| 357 |
+
'ObjdumpLexer': ('pip._vendor.pygments.lexers.asm', 'objdump', ('objdump',), ('*.objdump',), ('text/x-objdump',)),
|
| 358 |
+
'ObjectiveCLexer': ('pip._vendor.pygments.lexers.objective', 'Objective-C', ('objective-c', 'objectivec', 'obj-c', 'objc'), ('*.m', '*.h'), ('text/x-objective-c',)),
|
| 359 |
+
'ObjectiveCppLexer': ('pip._vendor.pygments.lexers.objective', 'Objective-C++', ('objective-c++', 'objectivec++', 'obj-c++', 'objc++'), ('*.mm', '*.hh'), ('text/x-objective-c++',)),
|
| 360 |
+
'ObjectiveJLexer': ('pip._vendor.pygments.lexers.javascript', 'Objective-J', ('objective-j', 'objectivej', 'obj-j', 'objj'), ('*.j',), ('text/x-objective-j',)),
|
| 361 |
+
'OcamlLexer': ('pip._vendor.pygments.lexers.ml', 'OCaml', ('ocaml',), ('*.ml', '*.mli', '*.mll', '*.mly'), ('text/x-ocaml',)),
|
| 362 |
+
'OctaveLexer': ('pip._vendor.pygments.lexers.matlab', 'Octave', ('octave',), ('*.m',), ('text/octave',)),
|
| 363 |
+
'OdinLexer': ('pip._vendor.pygments.lexers.archetype', 'ODIN', ('odin',), ('*.odin',), ('text/odin',)),
|
| 364 |
+
'OmgIdlLexer': ('pip._vendor.pygments.lexers.c_like', 'OMG Interface Definition Language', ('omg-idl',), ('*.idl', '*.pidl'), ()),
|
| 365 |
+
'OocLexer': ('pip._vendor.pygments.lexers.ooc', 'Ooc', ('ooc',), ('*.ooc',), ('text/x-ooc',)),
|
| 366 |
+
'OpaLexer': ('pip._vendor.pygments.lexers.ml', 'Opa', ('opa',), ('*.opa',), ('text/x-opa',)),
|
| 367 |
+
'OpenEdgeLexer': ('pip._vendor.pygments.lexers.business', 'OpenEdge ABL', ('openedge', 'abl', 'progress'), ('*.p', '*.cls'), ('text/x-openedge', 'application/x-openedge')),
|
| 368 |
+
'OpenScadLexer': ('pip._vendor.pygments.lexers.openscad', 'OpenSCAD', ('openscad',), ('*.scad',), ('application/x-openscad',)),
|
| 369 |
+
'OrgLexer': ('pip._vendor.pygments.lexers.markup', 'Org Mode', ('org', 'orgmode', 'org-mode'), ('*.org',), ('text/org',)),
|
| 370 |
+
'OutputLexer': ('pip._vendor.pygments.lexers.special', 'Text output', ('output',), (), ()),
|
| 371 |
+
'PacmanConfLexer': ('pip._vendor.pygments.lexers.configs', 'PacmanConf', ('pacmanconf',), ('pacman.conf',), ()),
|
| 372 |
+
'PanLexer': ('pip._vendor.pygments.lexers.dsls', 'Pan', ('pan',), ('*.pan',), ()),
|
| 373 |
+
'ParaSailLexer': ('pip._vendor.pygments.lexers.parasail', 'ParaSail', ('parasail',), ('*.psi', '*.psl'), ('text/x-parasail',)),
|
| 374 |
+
'PawnLexer': ('pip._vendor.pygments.lexers.pawn', 'Pawn', ('pawn',), ('*.p', '*.pwn', '*.inc'), ('text/x-pawn',)),
|
| 375 |
+
'PegLexer': ('pip._vendor.pygments.lexers.grammar_notation', 'PEG', ('peg',), ('*.peg',), ('text/x-peg',)),
|
| 376 |
+
'Perl6Lexer': ('pip._vendor.pygments.lexers.perl', 'Perl6', ('perl6', 'pl6', 'raku'), ('*.pl', '*.pm', '*.nqp', '*.p6', '*.6pl', '*.p6l', '*.pl6', '*.6pm', '*.p6m', '*.pm6', '*.t', '*.raku', '*.rakumod', '*.rakutest', '*.rakudoc'), ('text/x-perl6', 'application/x-perl6')),
|
| 377 |
+
'PerlLexer': ('pip._vendor.pygments.lexers.perl', 'Perl', ('perl', 'pl'), ('*.pl', '*.pm', '*.t', '*.perl'), ('text/x-perl', 'application/x-perl')),
|
| 378 |
+
'PhixLexer': ('pip._vendor.pygments.lexers.phix', 'Phix', ('phix',), ('*.exw',), ('text/x-phix',)),
|
| 379 |
+
'PhpLexer': ('pip._vendor.pygments.lexers.php', 'PHP', ('php', 'php3', 'php4', 'php5'), ('*.php', '*.php[345]', '*.inc'), ('text/x-php',)),
|
| 380 |
+
'PigLexer': ('pip._vendor.pygments.lexers.jvm', 'Pig', ('pig',), ('*.pig',), ('text/x-pig',)),
|
| 381 |
+
'PikeLexer': ('pip._vendor.pygments.lexers.c_like', 'Pike', ('pike',), ('*.pike', '*.pmod'), ('text/x-pike',)),
|
| 382 |
+
'PkgConfigLexer': ('pip._vendor.pygments.lexers.configs', 'PkgConfig', ('pkgconfig',), ('*.pc',), ()),
|
| 383 |
+
'PlPgsqlLexer': ('pip._vendor.pygments.lexers.sql', 'PL/pgSQL', ('plpgsql',), (), ('text/x-plpgsql',)),
|
| 384 |
+
'PointlessLexer': ('pip._vendor.pygments.lexers.pointless', 'Pointless', ('pointless',), ('*.ptls',), ()),
|
| 385 |
+
'PonyLexer': ('pip._vendor.pygments.lexers.pony', 'Pony', ('pony',), ('*.pony',), ()),
|
| 386 |
+
'PortugolLexer': ('pip._vendor.pygments.lexers.pascal', 'Portugol', ('portugol',), ('*.alg', '*.portugol'), ()),
|
| 387 |
+
'PostScriptLexer': ('pip._vendor.pygments.lexers.graphics', 'PostScript', ('postscript', 'postscr'), ('*.ps', '*.eps'), ('application/postscript',)),
|
| 388 |
+
'PostgresConsoleLexer': ('pip._vendor.pygments.lexers.sql', 'PostgreSQL console (psql)', ('psql', 'postgresql-console', 'postgres-console'), (), ('text/x-postgresql-psql',)),
|
| 389 |
+
'PostgresExplainLexer': ('pip._vendor.pygments.lexers.sql', 'PostgreSQL EXPLAIN dialect', ('postgres-explain',), ('*.explain',), ('text/x-postgresql-explain',)),
|
| 390 |
+
'PostgresLexer': ('pip._vendor.pygments.lexers.sql', 'PostgreSQL SQL dialect', ('postgresql', 'postgres'), (), ('text/x-postgresql',)),
|
| 391 |
+
'PovrayLexer': ('pip._vendor.pygments.lexers.graphics', 'POVRay', ('pov',), ('*.pov', '*.inc'), ('text/x-povray',)),
|
| 392 |
+
'PowerShellLexer': ('pip._vendor.pygments.lexers.shell', 'PowerShell', ('powershell', 'pwsh', 'posh', 'ps1', 'psm1'), ('*.ps1', '*.psm1'), ('text/x-powershell',)),
|
| 393 |
+
'PowerShellSessionLexer': ('pip._vendor.pygments.lexers.shell', 'PowerShell Session', ('pwsh-session', 'ps1con'), (), ()),
|
| 394 |
+
'PraatLexer': ('pip._vendor.pygments.lexers.praat', 'Praat', ('praat',), ('*.praat', '*.proc', '*.psc'), ()),
|
| 395 |
+
'ProcfileLexer': ('pip._vendor.pygments.lexers.procfile', 'Procfile', ('procfile',), ('Procfile',), ()),
|
| 396 |
+
'PrologLexer': ('pip._vendor.pygments.lexers.prolog', 'Prolog', ('prolog',), ('*.ecl', '*.prolog', '*.pro', '*.pl'), ('text/x-prolog',)),
|
| 397 |
+
'PromQLLexer': ('pip._vendor.pygments.lexers.promql', 'PromQL', ('promql',), ('*.promql',), ()),
|
| 398 |
+
'PromelaLexer': ('pip._vendor.pygments.lexers.c_like', 'Promela', ('promela',), ('*.pml', '*.prom', '*.prm', '*.promela', '*.pr', '*.pm'), ('text/x-promela',)),
|
| 399 |
+
'PropertiesLexer': ('pip._vendor.pygments.lexers.configs', 'Properties', ('properties', 'jproperties'), ('*.properties',), ('text/x-java-properties',)),
|
| 400 |
+
'ProtoBufLexer': ('pip._vendor.pygments.lexers.dsls', 'Protocol Buffer', ('protobuf', 'proto'), ('*.proto',), ()),
|
| 401 |
+
'PrqlLexer': ('pip._vendor.pygments.lexers.prql', 'PRQL', ('prql',), ('*.prql',), ('application/prql', 'application/x-prql')),
|
| 402 |
+
'PsyshConsoleLexer': ('pip._vendor.pygments.lexers.php', 'PsySH console session for PHP', ('psysh',), (), ()),
|
| 403 |
+
'PtxLexer': ('pip._vendor.pygments.lexers.ptx', 'PTX', ('ptx',), ('*.ptx',), ('text/x-ptx',)),
|
| 404 |
+
'PugLexer': ('pip._vendor.pygments.lexers.html', 'Pug', ('pug', 'jade'), ('*.pug', '*.jade'), ('text/x-pug', 'text/x-jade')),
|
| 405 |
+
'PuppetLexer': ('pip._vendor.pygments.lexers.dsls', 'Puppet', ('puppet',), ('*.pp',), ()),
|
| 406 |
+
'PyPyLogLexer': ('pip._vendor.pygments.lexers.console', 'PyPy Log', ('pypylog', 'pypy'), ('*.pypylog',), ('application/x-pypylog',)),
|
| 407 |
+
'Python2Lexer': ('pip._vendor.pygments.lexers.python', 'Python 2.x', ('python2', 'py2'), (), ('text/x-python2', 'application/x-python2')),
|
| 408 |
+
'Python2TracebackLexer': ('pip._vendor.pygments.lexers.python', 'Python 2.x Traceback', ('py2tb',), ('*.py2tb',), ('text/x-python2-traceback',)),
|
| 409 |
+
'PythonConsoleLexer': ('pip._vendor.pygments.lexers.python', 'Python console session', ('pycon', 'python-console'), (), ('text/x-python-doctest',)),
|
| 410 |
+
'PythonLexer': ('pip._vendor.pygments.lexers.python', 'Python', ('python', 'py', 'sage', 'python3', 'py3', 'bazel', 'starlark'), ('*.py', '*.pyw', '*.pyi', '*.jy', '*.sage', '*.sc', 'SConstruct', 'SConscript', '*.bzl', 'BUCK', 'BUILD', 'BUILD.bazel', 'WORKSPACE', '*.tac'), ('text/x-python', 'application/x-python', 'text/x-python3', 'application/x-python3')),
|
| 411 |
+
'PythonTracebackLexer': ('pip._vendor.pygments.lexers.python', 'Python Traceback', ('pytb', 'py3tb'), ('*.pytb', '*.py3tb'), ('text/x-python-traceback', 'text/x-python3-traceback')),
|
| 412 |
+
'PythonUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'Python+UL4', ('py+ul4',), ('*.pyul4',), ()),
|
| 413 |
+
'QBasicLexer': ('pip._vendor.pygments.lexers.basic', 'QBasic', ('qbasic', 'basic'), ('*.BAS', '*.bas'), ('text/basic',)),
|
| 414 |
+
'QLexer': ('pip._vendor.pygments.lexers.q', 'Q', ('q',), ('*.q',), ()),
|
| 415 |
+
'QVToLexer': ('pip._vendor.pygments.lexers.qvt', 'QVTO', ('qvto', 'qvt'), ('*.qvto',), ()),
|
| 416 |
+
'QlikLexer': ('pip._vendor.pygments.lexers.qlik', 'Qlik', ('qlik', 'qlikview', 'qliksense', 'qlikscript'), ('*.qvs', '*.qvw'), ()),
|
| 417 |
+
'QmlLexer': ('pip._vendor.pygments.lexers.webmisc', 'QML', ('qml', 'qbs'), ('*.qml', '*.qbs'), ('application/x-qml', 'application/x-qt.qbs+qml')),
|
| 418 |
+
'RConsoleLexer': ('pip._vendor.pygments.lexers.r', 'RConsole', ('rconsole', 'rout'), ('*.Rout',), ()),
|
| 419 |
+
'RNCCompactLexer': ('pip._vendor.pygments.lexers.rnc', 'Relax-NG Compact', ('rng-compact', 'rnc'), ('*.rnc',), ()),
|
| 420 |
+
'RPMSpecLexer': ('pip._vendor.pygments.lexers.installers', 'RPMSpec', ('spec',), ('*.spec',), ('text/x-rpm-spec',)),
|
| 421 |
+
'RacketLexer': ('pip._vendor.pygments.lexers.lisp', 'Racket', ('racket', 'rkt'), ('*.rkt', '*.rktd', '*.rktl'), ('text/x-racket', 'application/x-racket')),
|
| 422 |
+
'RagelCLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in C Host', ('ragel-c',), ('*.rl',), ()),
|
| 423 |
+
'RagelCppLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in CPP Host', ('ragel-cpp',), ('*.rl',), ()),
|
| 424 |
+
'RagelDLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in D Host', ('ragel-d',), ('*.rl',), ()),
|
| 425 |
+
'RagelEmbeddedLexer': ('pip._vendor.pygments.lexers.parsers', 'Embedded Ragel', ('ragel-em',), ('*.rl',), ()),
|
| 426 |
+
'RagelJavaLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in Java Host', ('ragel-java',), ('*.rl',), ()),
|
| 427 |
+
'RagelLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel', ('ragel',), (), ()),
|
| 428 |
+
'RagelObjectiveCLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in Objective C Host', ('ragel-objc',), ('*.rl',), ()),
|
| 429 |
+
'RagelRubyLexer': ('pip._vendor.pygments.lexers.parsers', 'Ragel in Ruby Host', ('ragel-ruby', 'ragel-rb'), ('*.rl',), ()),
|
| 430 |
+
'RawTokenLexer': ('pip._vendor.pygments.lexers.special', 'Raw token data', (), (), ('application/x-pygments-tokens',)),
|
| 431 |
+
'RdLexer': ('pip._vendor.pygments.lexers.r', 'Rd', ('rd',), ('*.Rd',), ('text/x-r-doc',)),
|
| 432 |
+
'ReasonLexer': ('pip._vendor.pygments.lexers.ml', 'ReasonML', ('reasonml', 'reason'), ('*.re', '*.rei'), ('text/x-reasonml',)),
|
| 433 |
+
'RebolLexer': ('pip._vendor.pygments.lexers.rebol', 'REBOL', ('rebol',), ('*.r', '*.r3', '*.reb'), ('text/x-rebol',)),
|
| 434 |
+
'RedLexer': ('pip._vendor.pygments.lexers.rebol', 'Red', ('red', 'red/system'), ('*.red', '*.reds'), ('text/x-red', 'text/x-red-system')),
|
| 435 |
+
'RedcodeLexer': ('pip._vendor.pygments.lexers.esoteric', 'Redcode', ('redcode',), ('*.cw',), ()),
|
| 436 |
+
'RegeditLexer': ('pip._vendor.pygments.lexers.configs', 'reg', ('registry',), ('*.reg',), ('text/x-windows-registry',)),
|
| 437 |
+
'ResourceLexer': ('pip._vendor.pygments.lexers.resource', 'ResourceBundle', ('resourcebundle', 'resource'), (), ()),
|
| 438 |
+
'RexxLexer': ('pip._vendor.pygments.lexers.scripting', 'Rexx', ('rexx', 'arexx'), ('*.rexx', '*.rex', '*.rx', '*.arexx'), ('text/x-rexx',)),
|
| 439 |
+
'RhtmlLexer': ('pip._vendor.pygments.lexers.templates', 'RHTML', ('rhtml', 'html+erb', 'html+ruby'), ('*.rhtml',), ('text/html+ruby',)),
|
| 440 |
+
'RideLexer': ('pip._vendor.pygments.lexers.ride', 'Ride', ('ride',), ('*.ride',), ('text/x-ride',)),
|
| 441 |
+
'RitaLexer': ('pip._vendor.pygments.lexers.rita', 'Rita', ('rita',), ('*.rita',), ('text/rita',)),
|
| 442 |
+
'RoboconfGraphLexer': ('pip._vendor.pygments.lexers.roboconf', 'Roboconf Graph', ('roboconf-graph',), ('*.graph',), ()),
|
| 443 |
+
'RoboconfInstancesLexer': ('pip._vendor.pygments.lexers.roboconf', 'Roboconf Instances', ('roboconf-instances',), ('*.instances',), ()),
|
| 444 |
+
'RobotFrameworkLexer': ('pip._vendor.pygments.lexers.robotframework', 'RobotFramework', ('robotframework',), ('*.robot', '*.resource'), ('text/x-robotframework',)),
|
| 445 |
+
'RqlLexer': ('pip._vendor.pygments.lexers.sql', 'RQL', ('rql',), ('*.rql',), ('text/x-rql',)),
|
| 446 |
+
'RslLexer': ('pip._vendor.pygments.lexers.dsls', 'RSL', ('rsl',), ('*.rsl',), ('text/rsl',)),
|
| 447 |
+
'RstLexer': ('pip._vendor.pygments.lexers.markup', 'reStructuredText', ('restructuredtext', 'rst', 'rest'), ('*.rst', '*.rest'), ('text/x-rst', 'text/prs.fallenstein.rst')),
|
| 448 |
+
'RtsLexer': ('pip._vendor.pygments.lexers.trafficscript', 'TrafficScript', ('trafficscript', 'rts'), ('*.rts',), ()),
|
| 449 |
+
'RubyConsoleLexer': ('pip._vendor.pygments.lexers.ruby', 'Ruby irb session', ('rbcon', 'irb'), (), ('text/x-ruby-shellsession',)),
|
| 450 |
+
'RubyLexer': ('pip._vendor.pygments.lexers.ruby', 'Ruby', ('ruby', 'rb', 'duby'), ('*.rb', '*.rbw', 'Rakefile', '*.rake', '*.gemspec', '*.rbx', '*.duby', 'Gemfile', 'Vagrantfile'), ('text/x-ruby', 'application/x-ruby')),
|
| 451 |
+
'RustLexer': ('pip._vendor.pygments.lexers.rust', 'Rust', ('rust', 'rs'), ('*.rs', '*.rs.in'), ('text/rust', 'text/x-rust')),
|
| 452 |
+
'SASLexer': ('pip._vendor.pygments.lexers.sas', 'SAS', ('sas',), ('*.SAS', '*.sas'), ('text/x-sas', 'text/sas', 'application/x-sas')),
|
| 453 |
+
'SLexer': ('pip._vendor.pygments.lexers.r', 'S', ('splus', 's', 'r'), ('*.S', '*.R', '.Rhistory', '.Rprofile', '.Renviron'), ('text/S-plus', 'text/S', 'text/x-r-source', 'text/x-r', 'text/x-R', 'text/x-r-history', 'text/x-r-profile')),
|
| 454 |
+
'SMLLexer': ('pip._vendor.pygments.lexers.ml', 'Standard ML', ('sml',), ('*.sml', '*.sig', '*.fun'), ('text/x-standardml', 'application/x-standardml')),
|
| 455 |
+
'SNBTLexer': ('pip._vendor.pygments.lexers.minecraft', 'SNBT', ('snbt',), ('*.snbt',), ('text/snbt',)),
|
| 456 |
+
'SarlLexer': ('pip._vendor.pygments.lexers.jvm', 'SARL', ('sarl',), ('*.sarl',), ('text/x-sarl',)),
|
| 457 |
+
'SassLexer': ('pip._vendor.pygments.lexers.css', 'Sass', ('sass',), ('*.sass',), ('text/x-sass',)),
|
| 458 |
+
'SaviLexer': ('pip._vendor.pygments.lexers.savi', 'Savi', ('savi',), ('*.savi',), ()),
|
| 459 |
+
'ScalaLexer': ('pip._vendor.pygments.lexers.jvm', 'Scala', ('scala',), ('*.scala',), ('text/x-scala',)),
|
| 460 |
+
'ScamlLexer': ('pip._vendor.pygments.lexers.html', 'Scaml', ('scaml',), ('*.scaml',), ('text/x-scaml',)),
|
| 461 |
+
'ScdocLexer': ('pip._vendor.pygments.lexers.scdoc', 'scdoc', ('scdoc', 'scd'), ('*.scd', '*.scdoc'), ()),
|
| 462 |
+
'SchemeLexer': ('pip._vendor.pygments.lexers.lisp', 'Scheme', ('scheme', 'scm'), ('*.scm', '*.ss'), ('text/x-scheme', 'application/x-scheme')),
|
| 463 |
+
'ScilabLexer': ('pip._vendor.pygments.lexers.matlab', 'Scilab', ('scilab',), ('*.sci', '*.sce', '*.tst'), ('text/scilab',)),
|
| 464 |
+
'ScssLexer': ('pip._vendor.pygments.lexers.css', 'SCSS', ('scss',), ('*.scss',), ('text/x-scss',)),
|
| 465 |
+
'SedLexer': ('pip._vendor.pygments.lexers.textedit', 'Sed', ('sed', 'gsed', 'ssed'), ('*.sed', '*.[gs]sed'), ('text/x-sed',)),
|
| 466 |
+
'ShExCLexer': ('pip._vendor.pygments.lexers.rdf', 'ShExC', ('shexc', 'shex'), ('*.shex',), ('text/shex',)),
|
| 467 |
+
'ShenLexer': ('pip._vendor.pygments.lexers.lisp', 'Shen', ('shen',), ('*.shen',), ('text/x-shen', 'application/x-shen')),
|
| 468 |
+
'SieveLexer': ('pip._vendor.pygments.lexers.sieve', 'Sieve', ('sieve',), ('*.siv', '*.sieve'), ()),
|
| 469 |
+
'SilverLexer': ('pip._vendor.pygments.lexers.verification', 'Silver', ('silver',), ('*.sil', '*.vpr'), ()),
|
| 470 |
+
'SingularityLexer': ('pip._vendor.pygments.lexers.configs', 'Singularity', ('singularity',), ('*.def', 'Singularity'), ()),
|
| 471 |
+
'SlashLexer': ('pip._vendor.pygments.lexers.slash', 'Slash', ('slash',), ('*.sla',), ()),
|
| 472 |
+
'SlimLexer': ('pip._vendor.pygments.lexers.webmisc', 'Slim', ('slim',), ('*.slim',), ('text/x-slim',)),
|
| 473 |
+
'SlurmBashLexer': ('pip._vendor.pygments.lexers.shell', 'Slurm', ('slurm', 'sbatch'), ('*.sl',), ()),
|
| 474 |
+
'SmaliLexer': ('pip._vendor.pygments.lexers.dalvik', 'Smali', ('smali',), ('*.smali',), ('text/smali',)),
|
| 475 |
+
'SmalltalkLexer': ('pip._vendor.pygments.lexers.smalltalk', 'Smalltalk', ('smalltalk', 'squeak', 'st'), ('*.st',), ('text/x-smalltalk',)),
|
| 476 |
+
'SmartGameFormatLexer': ('pip._vendor.pygments.lexers.sgf', 'SmartGameFormat', ('sgf',), ('*.sgf',), ()),
|
| 477 |
+
'SmartyLexer': ('pip._vendor.pygments.lexers.templates', 'Smarty', ('smarty',), ('*.tpl',), ('application/x-smarty',)),
|
| 478 |
+
'SmithyLexer': ('pip._vendor.pygments.lexers.smithy', 'Smithy', ('smithy',), ('*.smithy',), ()),
|
| 479 |
+
'SnobolLexer': ('pip._vendor.pygments.lexers.snobol', 'Snobol', ('snobol',), ('*.snobol',), ('text/x-snobol',)),
|
| 480 |
+
'SnowballLexer': ('pip._vendor.pygments.lexers.dsls', 'Snowball', ('snowball',), ('*.sbl',), ()),
|
| 481 |
+
'SolidityLexer': ('pip._vendor.pygments.lexers.solidity', 'Solidity', ('solidity',), ('*.sol',), ()),
|
| 482 |
+
'SoongLexer': ('pip._vendor.pygments.lexers.soong', 'Soong', ('androidbp', 'bp', 'soong'), ('Android.bp',), ()),
|
| 483 |
+
'SophiaLexer': ('pip._vendor.pygments.lexers.sophia', 'Sophia', ('sophia',), ('*.aes',), ()),
|
| 484 |
+
'SourcePawnLexer': ('pip._vendor.pygments.lexers.pawn', 'SourcePawn', ('sp',), ('*.sp',), ('text/x-sourcepawn',)),
|
| 485 |
+
'SourcesListLexer': ('pip._vendor.pygments.lexers.installers', 'Debian Sourcelist', ('debsources', 'sourceslist', 'sources.list'), ('sources.list',), ()),
|
| 486 |
+
'SparqlLexer': ('pip._vendor.pygments.lexers.rdf', 'SPARQL', ('sparql',), ('*.rq', '*.sparql'), ('application/sparql-query',)),
|
| 487 |
+
'SpiceLexer': ('pip._vendor.pygments.lexers.spice', 'Spice', ('spice', 'spicelang'), ('*.spice',), ('text/x-spice',)),
|
| 488 |
+
'SqlJinjaLexer': ('pip._vendor.pygments.lexers.templates', 'SQL+Jinja', ('sql+jinja',), ('*.sql', '*.sql.j2', '*.sql.jinja2'), ()),
|
| 489 |
+
'SqlLexer': ('pip._vendor.pygments.lexers.sql', 'SQL', ('sql',), ('*.sql',), ('text/x-sql',)),
|
| 490 |
+
'SqliteConsoleLexer': ('pip._vendor.pygments.lexers.sql', 'sqlite3con', ('sqlite3',), ('*.sqlite3-console',), ('text/x-sqlite3-console',)),
|
| 491 |
+
'SquidConfLexer': ('pip._vendor.pygments.lexers.configs', 'SquidConf', ('squidconf', 'squid.conf', 'squid'), ('squid.conf',), ('text/x-squidconf',)),
|
| 492 |
+
'SrcinfoLexer': ('pip._vendor.pygments.lexers.srcinfo', 'Srcinfo', ('srcinfo',), ('.SRCINFO',), ()),
|
| 493 |
+
'SspLexer': ('pip._vendor.pygments.lexers.templates', 'Scalate Server Page', ('ssp',), ('*.ssp',), ('application/x-ssp',)),
|
| 494 |
+
'StanLexer': ('pip._vendor.pygments.lexers.modeling', 'Stan', ('stan',), ('*.stan',), ()),
|
| 495 |
+
'StataLexer': ('pip._vendor.pygments.lexers.stata', 'Stata', ('stata', 'do'), ('*.do', '*.ado'), ('text/x-stata', 'text/stata', 'application/x-stata')),
|
| 496 |
+
'SuperColliderLexer': ('pip._vendor.pygments.lexers.supercollider', 'SuperCollider', ('supercollider', 'sc'), ('*.sc', '*.scd'), ('application/supercollider', 'text/supercollider')),
|
| 497 |
+
'SwiftLexer': ('pip._vendor.pygments.lexers.objective', 'Swift', ('swift',), ('*.swift',), ('text/x-swift',)),
|
| 498 |
+
'SwigLexer': ('pip._vendor.pygments.lexers.c_like', 'SWIG', ('swig',), ('*.swg', '*.i'), ('text/swig',)),
|
| 499 |
+
'SystemVerilogLexer': ('pip._vendor.pygments.lexers.hdl', 'systemverilog', ('systemverilog', 'sv'), ('*.sv', '*.svh'), ('text/x-systemverilog',)),
|
| 500 |
+
'SystemdLexer': ('pip._vendor.pygments.lexers.configs', 'Systemd', ('systemd',), ('*.service', '*.socket', '*.device', '*.mount', '*.automount', '*.swap', '*.target', '*.path', '*.timer', '*.slice', '*.scope'), ()),
|
| 501 |
+
'TAPLexer': ('pip._vendor.pygments.lexers.testing', 'TAP', ('tap',), ('*.tap',), ()),
|
| 502 |
+
'TNTLexer': ('pip._vendor.pygments.lexers.tnt', 'Typographic Number Theory', ('tnt',), ('*.tnt',), ()),
|
| 503 |
+
'TOMLLexer': ('pip._vendor.pygments.lexers.configs', 'TOML', ('toml',), ('*.toml', 'Pipfile', 'poetry.lock'), ('application/toml',)),
|
| 504 |
+
'TactLexer': ('pip._vendor.pygments.lexers.tact', 'Tact', ('tact',), ('*.tact',), ()),
|
| 505 |
+
'Tads3Lexer': ('pip._vendor.pygments.lexers.int_fiction', 'TADS 3', ('tads3',), ('*.t',), ()),
|
| 506 |
+
'TalLexer': ('pip._vendor.pygments.lexers.tal', 'Tal', ('tal', 'uxntal'), ('*.tal',), ('text/x-uxntal',)),
|
| 507 |
+
'TasmLexer': ('pip._vendor.pygments.lexers.asm', 'TASM', ('tasm',), ('*.asm', '*.ASM', '*.tasm'), ('text/x-tasm',)),
|
| 508 |
+
'TclLexer': ('pip._vendor.pygments.lexers.tcl', 'Tcl', ('tcl',), ('*.tcl', '*.rvt'), ('text/x-tcl', 'text/x-script.tcl', 'application/x-tcl')),
|
| 509 |
+
'TcshLexer': ('pip._vendor.pygments.lexers.shell', 'Tcsh', ('tcsh', 'csh'), ('*.tcsh', '*.csh'), ('application/x-csh',)),
|
| 510 |
+
'TcshSessionLexer': ('pip._vendor.pygments.lexers.shell', 'Tcsh Session', ('tcshcon',), (), ()),
|
| 511 |
+
'TeaTemplateLexer': ('pip._vendor.pygments.lexers.templates', 'Tea', ('tea',), ('*.tea',), ('text/x-tea',)),
|
| 512 |
+
'TealLexer': ('pip._vendor.pygments.lexers.teal', 'teal', ('teal',), ('*.teal',), ()),
|
| 513 |
+
'TeraTermLexer': ('pip._vendor.pygments.lexers.teraterm', 'Tera Term macro', ('teratermmacro', 'teraterm', 'ttl'), ('*.ttl',), ('text/x-teratermmacro',)),
|
| 514 |
+
'TermcapLexer': ('pip._vendor.pygments.lexers.configs', 'Termcap', ('termcap',), ('termcap', 'termcap.src'), ()),
|
| 515 |
+
'TerminfoLexer': ('pip._vendor.pygments.lexers.configs', 'Terminfo', ('terminfo',), ('terminfo', 'terminfo.src'), ()),
|
| 516 |
+
'TerraformLexer': ('pip._vendor.pygments.lexers.configs', 'Terraform', ('terraform', 'tf', 'hcl'), ('*.tf', '*.hcl'), ('application/x-tf', 'application/x-terraform')),
|
| 517 |
+
'TexLexer': ('pip._vendor.pygments.lexers.markup', 'TeX', ('tex', 'latex'), ('*.tex', '*.aux', '*.toc'), ('text/x-tex', 'text/x-latex')),
|
| 518 |
+
'TextLexer': ('pip._vendor.pygments.lexers.special', 'Text only', ('text',), ('*.txt',), ('text/plain',)),
|
| 519 |
+
'ThingsDBLexer': ('pip._vendor.pygments.lexers.thingsdb', 'ThingsDB', ('ti', 'thingsdb'), ('*.ti',), ()),
|
| 520 |
+
'ThriftLexer': ('pip._vendor.pygments.lexers.dsls', 'Thrift', ('thrift',), ('*.thrift',), ('application/x-thrift',)),
|
| 521 |
+
'TiddlyWiki5Lexer': ('pip._vendor.pygments.lexers.markup', 'tiddler', ('tid',), ('*.tid',), ('text/vnd.tiddlywiki',)),
|
| 522 |
+
'TlbLexer': ('pip._vendor.pygments.lexers.tlb', 'Tl-b', ('tlb',), ('*.tlb',), ()),
|
| 523 |
+
'TlsLexer': ('pip._vendor.pygments.lexers.tls', 'TLS Presentation Language', ('tls',), (), ()),
|
| 524 |
+
'TodotxtLexer': ('pip._vendor.pygments.lexers.textfmts', 'Todotxt', ('todotxt',), ('todo.txt', '*.todotxt'), ('text/x-todo',)),
|
| 525 |
+
'TransactSqlLexer': ('pip._vendor.pygments.lexers.sql', 'Transact-SQL', ('tsql', 't-sql'), ('*.sql',), ('text/x-tsql',)),
|
| 526 |
+
'TreetopLexer': ('pip._vendor.pygments.lexers.parsers', 'Treetop', ('treetop',), ('*.treetop', '*.tt'), ()),
|
| 527 |
+
'TurtleLexer': ('pip._vendor.pygments.lexers.rdf', 'Turtle', ('turtle',), ('*.ttl',), ('text/turtle', 'application/x-turtle')),
|
| 528 |
+
'TwigHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Twig', ('html+twig',), ('*.twig',), ('text/html+twig',)),
|
| 529 |
+
'TwigLexer': ('pip._vendor.pygments.lexers.templates', 'Twig', ('twig',), (), ('application/x-twig',)),
|
| 530 |
+
'TypeScriptLexer': ('pip._vendor.pygments.lexers.javascript', 'TypeScript', ('typescript', 'ts'), ('*.ts',), ('application/x-typescript', 'text/x-typescript')),
|
| 531 |
+
'TypoScriptCssDataLexer': ('pip._vendor.pygments.lexers.typoscript', 'TypoScriptCssData', ('typoscriptcssdata',), (), ()),
|
| 532 |
+
'TypoScriptHtmlDataLexer': ('pip._vendor.pygments.lexers.typoscript', 'TypoScriptHtmlData', ('typoscripthtmldata',), (), ()),
|
| 533 |
+
'TypoScriptLexer': ('pip._vendor.pygments.lexers.typoscript', 'TypoScript', ('typoscript',), ('*.typoscript',), ('text/x-typoscript',)),
|
| 534 |
+
'TypstLexer': ('pip._vendor.pygments.lexers.typst', 'Typst', ('typst',), ('*.typ',), ('text/x-typst',)),
|
| 535 |
+
'UL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'UL4', ('ul4',), ('*.ul4',), ()),
|
| 536 |
+
'UcodeLexer': ('pip._vendor.pygments.lexers.unicon', 'ucode', ('ucode',), ('*.u', '*.u1', '*.u2'), ()),
|
| 537 |
+
'UniconLexer': ('pip._vendor.pygments.lexers.unicon', 'Unicon', ('unicon',), ('*.icn',), ('text/unicon',)),
|
| 538 |
+
'UnixConfigLexer': ('pip._vendor.pygments.lexers.configs', 'Unix/Linux config files', ('unixconfig', 'linuxconfig'), (), ()),
|
| 539 |
+
'UrbiscriptLexer': ('pip._vendor.pygments.lexers.urbi', 'UrbiScript', ('urbiscript',), ('*.u',), ('application/x-urbiscript',)),
|
| 540 |
+
'UrlEncodedLexer': ('pip._vendor.pygments.lexers.html', 'urlencoded', ('urlencoded',), (), ('application/x-www-form-urlencoded',)),
|
| 541 |
+
'UsdLexer': ('pip._vendor.pygments.lexers.usd', 'USD', ('usd', 'usda'), ('*.usd', '*.usda'), ()),
|
| 542 |
+
'VBScriptLexer': ('pip._vendor.pygments.lexers.basic', 'VBScript', ('vbscript',), ('*.vbs', '*.VBS'), ()),
|
| 543 |
+
'VCLLexer': ('pip._vendor.pygments.lexers.varnish', 'VCL', ('vcl',), ('*.vcl',), ('text/x-vclsrc',)),
|
| 544 |
+
'VCLSnippetLexer': ('pip._vendor.pygments.lexers.varnish', 'VCLSnippets', ('vclsnippets', 'vclsnippet'), (), ('text/x-vclsnippet',)),
|
| 545 |
+
'VCTreeStatusLexer': ('pip._vendor.pygments.lexers.console', 'VCTreeStatus', ('vctreestatus',), (), ()),
|
| 546 |
+
'VGLLexer': ('pip._vendor.pygments.lexers.dsls', 'VGL', ('vgl',), ('*.rpf',), ()),
|
| 547 |
+
'ValaLexer': ('pip._vendor.pygments.lexers.c_like', 'Vala', ('vala', 'vapi'), ('*.vala', '*.vapi'), ('text/x-vala',)),
|
| 548 |
+
'VbNetAspxLexer': ('pip._vendor.pygments.lexers.dotnet', 'aspx-vb', ('aspx-vb',), ('*.aspx', '*.asax', '*.ascx', '*.ashx', '*.asmx', '*.axd'), ()),
|
| 549 |
+
'VbNetLexer': ('pip._vendor.pygments.lexers.dotnet', 'VB.net', ('vb.net', 'vbnet', 'lobas', 'oobas', 'sobas', 'visual-basic', 'visualbasic'), ('*.vb', '*.bas'), ('text/x-vbnet', 'text/x-vba')),
|
| 550 |
+
'VelocityHtmlLexer': ('pip._vendor.pygments.lexers.templates', 'HTML+Velocity', ('html+velocity',), (), ('text/html+velocity',)),
|
| 551 |
+
'VelocityLexer': ('pip._vendor.pygments.lexers.templates', 'Velocity', ('velocity',), ('*.vm', '*.fhtml'), ()),
|
| 552 |
+
'VelocityXmlLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Velocity', ('xml+velocity',), (), ('application/xml+velocity',)),
|
| 553 |
+
'VerifpalLexer': ('pip._vendor.pygments.lexers.verifpal', 'Verifpal', ('verifpal',), ('*.vp',), ('text/x-verifpal',)),
|
| 554 |
+
'VerilogLexer': ('pip._vendor.pygments.lexers.hdl', 'verilog', ('verilog', 'v'), ('*.v',), ('text/x-verilog',)),
|
| 555 |
+
'VhdlLexer': ('pip._vendor.pygments.lexers.hdl', 'vhdl', ('vhdl',), ('*.vhdl', '*.vhd'), ('text/x-vhdl',)),
|
| 556 |
+
'VimLexer': ('pip._vendor.pygments.lexers.textedit', 'VimL', ('vim',), ('*.vim', '.vimrc', '.exrc', '.gvimrc', '_vimrc', '_exrc', '_gvimrc', 'vimrc', 'gvimrc'), ('text/x-vim',)),
|
| 557 |
+
'VisualPrologGrammarLexer': ('pip._vendor.pygments.lexers.vip', 'Visual Prolog Grammar', ('visualprologgrammar',), ('*.vipgrm',), ()),
|
| 558 |
+
'VisualPrologLexer': ('pip._vendor.pygments.lexers.vip', 'Visual Prolog', ('visualprolog',), ('*.pro', '*.cl', '*.i', '*.pack', '*.ph'), ()),
|
| 559 |
+
'VyperLexer': ('pip._vendor.pygments.lexers.vyper', 'Vyper', ('vyper',), ('*.vy',), ()),
|
| 560 |
+
'WDiffLexer': ('pip._vendor.pygments.lexers.diff', 'WDiff', ('wdiff',), ('*.wdiff',), ()),
|
| 561 |
+
'WatLexer': ('pip._vendor.pygments.lexers.webassembly', 'WebAssembly', ('wast', 'wat'), ('*.wat', '*.wast'), ()),
|
| 562 |
+
'WebIDLLexer': ('pip._vendor.pygments.lexers.webidl', 'Web IDL', ('webidl',), ('*.webidl',), ()),
|
| 563 |
+
'WgslLexer': ('pip._vendor.pygments.lexers.wgsl', 'WebGPU Shading Language', ('wgsl',), ('*.wgsl',), ('text/wgsl',)),
|
| 564 |
+
'WhileyLexer': ('pip._vendor.pygments.lexers.whiley', 'Whiley', ('whiley',), ('*.whiley',), ('text/x-whiley',)),
|
| 565 |
+
'WikitextLexer': ('pip._vendor.pygments.lexers.markup', 'Wikitext', ('wikitext', 'mediawiki'), (), ('text/x-wiki',)),
|
| 566 |
+
'WoWTocLexer': ('pip._vendor.pygments.lexers.wowtoc', 'World of Warcraft TOC', ('wowtoc',), ('*.toc',), ()),
|
| 567 |
+
'WrenLexer': ('pip._vendor.pygments.lexers.wren', 'Wren', ('wren',), ('*.wren',), ()),
|
| 568 |
+
'X10Lexer': ('pip._vendor.pygments.lexers.x10', 'X10', ('x10', 'xten'), ('*.x10',), ('text/x-x10',)),
|
| 569 |
+
'XMLUL4Lexer': ('pip._vendor.pygments.lexers.ul4', 'XML+UL4', ('xml+ul4',), ('*.xmlul4',), ()),
|
| 570 |
+
'XQueryLexer': ('pip._vendor.pygments.lexers.webmisc', 'XQuery', ('xquery', 'xqy', 'xq', 'xql', 'xqm'), ('*.xqy', '*.xquery', '*.xq', '*.xql', '*.xqm'), ('text/xquery', 'application/xquery')),
|
| 571 |
+
'XmlDjangoLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Django/Jinja', ('xml+django', 'xml+jinja'), ('*.xml.j2', '*.xml.jinja2'), ('application/xml+django', 'application/xml+jinja')),
|
| 572 |
+
'XmlErbLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Ruby', ('xml+ruby', 'xml+erb'), (), ('application/xml+ruby',)),
|
| 573 |
+
'XmlLexer': ('pip._vendor.pygments.lexers.html', 'XML', ('xml',), ('*.xml', '*.xsl', '*.rss', '*.xslt', '*.xsd', '*.wsdl', '*.wsf'), ('text/xml', 'application/xml', 'image/svg+xml', 'application/rss+xml', 'application/atom+xml')),
|
| 574 |
+
'XmlPhpLexer': ('pip._vendor.pygments.lexers.templates', 'XML+PHP', ('xml+php',), (), ('application/xml+php',)),
|
| 575 |
+
'XmlSmartyLexer': ('pip._vendor.pygments.lexers.templates', 'XML+Smarty', ('xml+smarty',), (), ('application/xml+smarty',)),
|
| 576 |
+
'XorgLexer': ('pip._vendor.pygments.lexers.xorg', 'Xorg', ('xorg.conf',), ('xorg.conf',), ()),
|
| 577 |
+
'XppLexer': ('pip._vendor.pygments.lexers.dotnet', 'X++', ('xpp', 'x++'), ('*.xpp',), ()),
|
| 578 |
+
'XsltLexer': ('pip._vendor.pygments.lexers.html', 'XSLT', ('xslt',), ('*.xsl', '*.xslt', '*.xpl'), ('application/xsl+xml', 'application/xslt+xml')),
|
| 579 |
+
'XtendLexer': ('pip._vendor.pygments.lexers.jvm', 'Xtend', ('xtend',), ('*.xtend',), ('text/x-xtend',)),
|
| 580 |
+
'XtlangLexer': ('pip._vendor.pygments.lexers.lisp', 'xtlang', ('extempore',), ('*.xtm',), ()),
|
| 581 |
+
'YamlJinjaLexer': ('pip._vendor.pygments.lexers.templates', 'YAML+Jinja', ('yaml+jinja', 'salt', 'sls'), ('*.sls', '*.yaml.j2', '*.yml.j2', '*.yaml.jinja2', '*.yml.jinja2'), ('text/x-yaml+jinja', 'text/x-sls')),
|
| 582 |
+
'YamlLexer': ('pip._vendor.pygments.lexers.data', 'YAML', ('yaml',), ('*.yaml', '*.yml'), ('text/x-yaml',)),
|
| 583 |
+
'YangLexer': ('pip._vendor.pygments.lexers.yang', 'YANG', ('yang',), ('*.yang',), ('application/yang',)),
|
| 584 |
+
'YaraLexer': ('pip._vendor.pygments.lexers.yara', 'YARA', ('yara', 'yar'), ('*.yar',), ('text/x-yara',)),
|
| 585 |
+
'ZeekLexer': ('pip._vendor.pygments.lexers.dsls', 'Zeek', ('zeek', 'bro'), ('*.zeek', '*.bro'), ()),
|
| 586 |
+
'ZephirLexer': ('pip._vendor.pygments.lexers.php', 'Zephir', ('zephir',), ('*.zep',), ()),
|
| 587 |
+
'ZigLexer': ('pip._vendor.pygments.lexers.zig', 'Zig', ('zig',), ('*.zig',), ('text/zig',)),
|
| 588 |
+
'apdlexer': ('pip._vendor.pygments.lexers.apdlexer', 'ANSYS parametric design language', ('ansys', 'apdl'), ('*.ans',), ()),
|
| 589 |
+
}
|
lib/python3.12/site-packages/pip/_vendor/pygments/lexers/python.py
ADDED
|
@@ -0,0 +1,1198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
pygments.lexers.python
|
| 3 |
+
~~~~~~~~~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Lexers for Python and related languages.
|
| 6 |
+
|
| 7 |
+
:copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
|
| 8 |
+
:license: BSD, see LICENSE for details.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import keyword
|
| 12 |
+
|
| 13 |
+
from pip._vendor.pygments.lexer import DelegatingLexer, RegexLexer, include, \
|
| 14 |
+
bygroups, using, default, words, combined, this
|
| 15 |
+
from pip._vendor.pygments.util import get_bool_opt, shebang_matches
|
| 16 |
+
from pip._vendor.pygments.token import Text, Comment, Operator, Keyword, Name, String, \
|
| 17 |
+
Number, Punctuation, Generic, Other, Error, Whitespace
|
| 18 |
+
from pip._vendor.pygments import unistring as uni
|
| 19 |
+
|
| 20 |
+
__all__ = ['PythonLexer', 'PythonConsoleLexer', 'PythonTracebackLexer',
|
| 21 |
+
'Python2Lexer', 'Python2TracebackLexer',
|
| 22 |
+
'CythonLexer', 'DgLexer', 'NumPyLexer']
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class PythonLexer(RegexLexer):
|
| 26 |
+
"""
|
| 27 |
+
For Python source code (version 3.x).
|
| 28 |
+
|
| 29 |
+
.. versionchanged:: 2.5
|
| 30 |
+
This is now the default ``PythonLexer``. It is still available as the
|
| 31 |
+
alias ``Python3Lexer``.
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
name = 'Python'
|
| 35 |
+
url = 'https://www.python.org'
|
| 36 |
+
aliases = ['python', 'py', 'sage', 'python3', 'py3', 'bazel', 'starlark']
|
| 37 |
+
filenames = [
|
| 38 |
+
'*.py',
|
| 39 |
+
'*.pyw',
|
| 40 |
+
# Type stubs
|
| 41 |
+
'*.pyi',
|
| 42 |
+
# Jython
|
| 43 |
+
'*.jy',
|
| 44 |
+
# Sage
|
| 45 |
+
'*.sage',
|
| 46 |
+
# SCons
|
| 47 |
+
'*.sc',
|
| 48 |
+
'SConstruct',
|
| 49 |
+
'SConscript',
|
| 50 |
+
# Skylark/Starlark (used by Bazel, Buck, and Pants)
|
| 51 |
+
'*.bzl',
|
| 52 |
+
'BUCK',
|
| 53 |
+
'BUILD',
|
| 54 |
+
'BUILD.bazel',
|
| 55 |
+
'WORKSPACE',
|
| 56 |
+
# Twisted Application infrastructure
|
| 57 |
+
'*.tac',
|
| 58 |
+
]
|
| 59 |
+
mimetypes = ['text/x-python', 'application/x-python',
|
| 60 |
+
'text/x-python3', 'application/x-python3']
|
| 61 |
+
version_added = '0.10'
|
| 62 |
+
|
| 63 |
+
uni_name = f"[{uni.xid_start}][{uni.xid_continue}]*"
|
| 64 |
+
|
| 65 |
+
def innerstring_rules(ttype):
|
| 66 |
+
return [
|
| 67 |
+
# the old style '%s' % (...) string formatting (still valid in Py3)
|
| 68 |
+
(r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
|
| 69 |
+
'[hlL]?[E-GXc-giorsaux%]', String.Interpol),
|
| 70 |
+
# the new style '{}'.format(...) string formatting
|
| 71 |
+
(r'\{'
|
| 72 |
+
r'((\w+)((\.\w+)|(\[[^\]]+\]))*)?' # field name
|
| 73 |
+
r'(\![sra])?' # conversion
|
| 74 |
+
r'(\:(.?[<>=\^])?[-+ ]?#?0?(\d+)?,?(\.\d+)?[E-GXb-gnosx%]?)?'
|
| 75 |
+
r'\}', String.Interpol),
|
| 76 |
+
|
| 77 |
+
# backslashes, quotes and formatting signs must be parsed one at a time
|
| 78 |
+
(r'[^\\\'"%{\n]+', ttype),
|
| 79 |
+
(r'[\'"\\]', ttype),
|
| 80 |
+
# unhandled string formatting sign
|
| 81 |
+
(r'%|(\{{1,2})', ttype)
|
| 82 |
+
# newlines are an error (use "nl" state)
|
| 83 |
+
]
|
| 84 |
+
|
| 85 |
+
def fstring_rules(ttype):
|
| 86 |
+
return [
|
| 87 |
+
# Assuming that a '}' is the closing brace after format specifier.
|
| 88 |
+
# Sadly, this means that we won't detect syntax error. But it's
|
| 89 |
+
# more important to parse correct syntax correctly, than to
|
| 90 |
+
# highlight invalid syntax.
|
| 91 |
+
(r'\}', String.Interpol),
|
| 92 |
+
(r'\{', String.Interpol, 'expr-inside-fstring'),
|
| 93 |
+
# backslashes, quotes and formatting signs must be parsed one at a time
|
| 94 |
+
(r'[^\\\'"{}\n]+', ttype),
|
| 95 |
+
(r'[\'"\\]', ttype),
|
| 96 |
+
# newlines are an error (use "nl" state)
|
| 97 |
+
]
|
| 98 |
+
|
| 99 |
+
tokens = {
|
| 100 |
+
'root': [
|
| 101 |
+
(r'\n', Whitespace),
|
| 102 |
+
(r'^(\s*)([rRuUbB]{,2})("""(?:.|\n)*?""")',
|
| 103 |
+
bygroups(Whitespace, String.Affix, String.Doc)),
|
| 104 |
+
(r"^(\s*)([rRuUbB]{,2})('''(?:.|\n)*?''')",
|
| 105 |
+
bygroups(Whitespace, String.Affix, String.Doc)),
|
| 106 |
+
(r'\A#!.+$', Comment.Hashbang),
|
| 107 |
+
(r'#.*$', Comment.Single),
|
| 108 |
+
(r'\\\n', Text),
|
| 109 |
+
(r'\\', Text),
|
| 110 |
+
include('keywords'),
|
| 111 |
+
include('soft-keywords'),
|
| 112 |
+
(r'(def)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'funcname'),
|
| 113 |
+
(r'(class)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'classname'),
|
| 114 |
+
(r'(from)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text),
|
| 115 |
+
'fromimport'),
|
| 116 |
+
(r'(import)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text),
|
| 117 |
+
'import'),
|
| 118 |
+
include('expr'),
|
| 119 |
+
],
|
| 120 |
+
'expr': [
|
| 121 |
+
# raw f-strings
|
| 122 |
+
('(?i)(rf|fr)(""")',
|
| 123 |
+
bygroups(String.Affix, String.Double),
|
| 124 |
+
combined('rfstringescape', 'tdqf')),
|
| 125 |
+
("(?i)(rf|fr)(''')",
|
| 126 |
+
bygroups(String.Affix, String.Single),
|
| 127 |
+
combined('rfstringescape', 'tsqf')),
|
| 128 |
+
('(?i)(rf|fr)(")',
|
| 129 |
+
bygroups(String.Affix, String.Double),
|
| 130 |
+
combined('rfstringescape', 'dqf')),
|
| 131 |
+
("(?i)(rf|fr)(')",
|
| 132 |
+
bygroups(String.Affix, String.Single),
|
| 133 |
+
combined('rfstringescape', 'sqf')),
|
| 134 |
+
# non-raw f-strings
|
| 135 |
+
('([fF])(""")', bygroups(String.Affix, String.Double),
|
| 136 |
+
combined('fstringescape', 'tdqf')),
|
| 137 |
+
("([fF])(''')", bygroups(String.Affix, String.Single),
|
| 138 |
+
combined('fstringescape', 'tsqf')),
|
| 139 |
+
('([fF])(")', bygroups(String.Affix, String.Double),
|
| 140 |
+
combined('fstringescape', 'dqf')),
|
| 141 |
+
("([fF])(')", bygroups(String.Affix, String.Single),
|
| 142 |
+
combined('fstringescape', 'sqf')),
|
| 143 |
+
# raw bytes and strings
|
| 144 |
+
('(?i)(rb|br|r)(""")',
|
| 145 |
+
bygroups(String.Affix, String.Double), 'tdqs'),
|
| 146 |
+
("(?i)(rb|br|r)(''')",
|
| 147 |
+
bygroups(String.Affix, String.Single), 'tsqs'),
|
| 148 |
+
('(?i)(rb|br|r)(")',
|
| 149 |
+
bygroups(String.Affix, String.Double), 'dqs'),
|
| 150 |
+
("(?i)(rb|br|r)(')",
|
| 151 |
+
bygroups(String.Affix, String.Single), 'sqs'),
|
| 152 |
+
# non-raw strings
|
| 153 |
+
('([uU]?)(""")', bygroups(String.Affix, String.Double),
|
| 154 |
+
combined('stringescape', 'tdqs')),
|
| 155 |
+
("([uU]?)(''')", bygroups(String.Affix, String.Single),
|
| 156 |
+
combined('stringescape', 'tsqs')),
|
| 157 |
+
('([uU]?)(")', bygroups(String.Affix, String.Double),
|
| 158 |
+
combined('stringescape', 'dqs')),
|
| 159 |
+
("([uU]?)(')", bygroups(String.Affix, String.Single),
|
| 160 |
+
combined('stringescape', 'sqs')),
|
| 161 |
+
# non-raw bytes
|
| 162 |
+
('([bB])(""")', bygroups(String.Affix, String.Double),
|
| 163 |
+
combined('bytesescape', 'tdqs')),
|
| 164 |
+
("([bB])(''')", bygroups(String.Affix, String.Single),
|
| 165 |
+
combined('bytesescape', 'tsqs')),
|
| 166 |
+
('([bB])(")', bygroups(String.Affix, String.Double),
|
| 167 |
+
combined('bytesescape', 'dqs')),
|
| 168 |
+
("([bB])(')", bygroups(String.Affix, String.Single),
|
| 169 |
+
combined('bytesescape', 'sqs')),
|
| 170 |
+
|
| 171 |
+
(r'[^\S\n]+', Text),
|
| 172 |
+
include('numbers'),
|
| 173 |
+
(r'!=|==|<<|>>|:=|[-~+/*%=<>&^|.]', Operator),
|
| 174 |
+
(r'[]{}:(),;[]', Punctuation),
|
| 175 |
+
(r'(in|is|and|or|not)\b', Operator.Word),
|
| 176 |
+
include('expr-keywords'),
|
| 177 |
+
include('builtins'),
|
| 178 |
+
include('magicfuncs'),
|
| 179 |
+
include('magicvars'),
|
| 180 |
+
include('name'),
|
| 181 |
+
],
|
| 182 |
+
'expr-inside-fstring': [
|
| 183 |
+
(r'[{([]', Punctuation, 'expr-inside-fstring-inner'),
|
| 184 |
+
# without format specifier
|
| 185 |
+
(r'(=\s*)?' # debug (https://bugs.python.org/issue36817)
|
| 186 |
+
r'(\![sraf])?' # conversion
|
| 187 |
+
r'\}', String.Interpol, '#pop'),
|
| 188 |
+
# with format specifier
|
| 189 |
+
# we'll catch the remaining '}' in the outer scope
|
| 190 |
+
(r'(=\s*)?' # debug (https://bugs.python.org/issue36817)
|
| 191 |
+
r'(\![sraf])?' # conversion
|
| 192 |
+
r':', String.Interpol, '#pop'),
|
| 193 |
+
(r'\s+', Whitespace), # allow new lines
|
| 194 |
+
include('expr'),
|
| 195 |
+
],
|
| 196 |
+
'expr-inside-fstring-inner': [
|
| 197 |
+
(r'[{([]', Punctuation, 'expr-inside-fstring-inner'),
|
| 198 |
+
(r'[])}]', Punctuation, '#pop'),
|
| 199 |
+
(r'\s+', Whitespace), # allow new lines
|
| 200 |
+
include('expr'),
|
| 201 |
+
],
|
| 202 |
+
'expr-keywords': [
|
| 203 |
+
# Based on https://docs.python.org/3/reference/expressions.html
|
| 204 |
+
(words((
|
| 205 |
+
'async for', 'await', 'else', 'for', 'if', 'lambda',
|
| 206 |
+
'yield', 'yield from'), suffix=r'\b'),
|
| 207 |
+
Keyword),
|
| 208 |
+
(words(('True', 'False', 'None'), suffix=r'\b'), Keyword.Constant),
|
| 209 |
+
],
|
| 210 |
+
'keywords': [
|
| 211 |
+
(words((
|
| 212 |
+
'assert', 'async', 'await', 'break', 'continue', 'del', 'elif',
|
| 213 |
+
'else', 'except', 'finally', 'for', 'global', 'if', 'lambda',
|
| 214 |
+
'pass', 'raise', 'nonlocal', 'return', 'try', 'while', 'yield',
|
| 215 |
+
'yield from', 'as', 'with'), suffix=r'\b'),
|
| 216 |
+
Keyword),
|
| 217 |
+
(words(('True', 'False', 'None'), suffix=r'\b'), Keyword.Constant),
|
| 218 |
+
],
|
| 219 |
+
'soft-keywords': [
|
| 220 |
+
# `match`, `case` and `_` soft keywords
|
| 221 |
+
(r'(^[ \t]*)' # at beginning of line + possible indentation
|
| 222 |
+
r'(match|case)\b' # a possible keyword
|
| 223 |
+
r'(?![ \t]*(?:' # not followed by...
|
| 224 |
+
r'[:,;=^&|@~)\]}]|(?:' + # characters and keywords that mean this isn't
|
| 225 |
+
# pattern matching (but None/True/False is ok)
|
| 226 |
+
r'|'.join(k for k in keyword.kwlist if k[0].islower()) + r')\b))',
|
| 227 |
+
bygroups(Text, Keyword), 'soft-keywords-inner'),
|
| 228 |
+
],
|
| 229 |
+
'soft-keywords-inner': [
|
| 230 |
+
# optional `_` keyword
|
| 231 |
+
(r'(\s+)([^\n_]*)(_\b)', bygroups(Whitespace, using(this), Keyword)),
|
| 232 |
+
default('#pop')
|
| 233 |
+
],
|
| 234 |
+
'builtins': [
|
| 235 |
+
(words((
|
| 236 |
+
'__import__', 'abs', 'aiter', 'all', 'any', 'bin', 'bool', 'bytearray',
|
| 237 |
+
'breakpoint', 'bytes', 'callable', 'chr', 'classmethod', 'compile',
|
| 238 |
+
'complex', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval',
|
| 239 |
+
'filter', 'float', 'format', 'frozenset', 'getattr', 'globals',
|
| 240 |
+
'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'isinstance',
|
| 241 |
+
'issubclass', 'iter', 'len', 'list', 'locals', 'map', 'max',
|
| 242 |
+
'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow',
|
| 243 |
+
'print', 'property', 'range', 'repr', 'reversed', 'round', 'set',
|
| 244 |
+
'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super',
|
| 245 |
+
'tuple', 'type', 'vars', 'zip'), prefix=r'(?<!\.)', suffix=r'\b'),
|
| 246 |
+
Name.Builtin),
|
| 247 |
+
(r'(?<!\.)(self|Ellipsis|NotImplemented|cls)\b', Name.Builtin.Pseudo),
|
| 248 |
+
(words((
|
| 249 |
+
'ArithmeticError', 'AssertionError', 'AttributeError',
|
| 250 |
+
'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning',
|
| 251 |
+
'EOFError', 'EnvironmentError', 'Exception', 'FloatingPointError',
|
| 252 |
+
'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
|
| 253 |
+
'ImportWarning', 'IndentationError', 'IndexError', 'KeyError',
|
| 254 |
+
'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError',
|
| 255 |
+
'NotImplementedError', 'OSError', 'OverflowError',
|
| 256 |
+
'PendingDeprecationWarning', 'ReferenceError', 'ResourceWarning',
|
| 257 |
+
'RuntimeError', 'RuntimeWarning', 'StopIteration',
|
| 258 |
+
'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit',
|
| 259 |
+
'TabError', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
|
| 260 |
+
'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
|
| 261 |
+
'UnicodeWarning', 'UserWarning', 'ValueError', 'VMSError',
|
| 262 |
+
'Warning', 'WindowsError', 'ZeroDivisionError',
|
| 263 |
+
# new builtin exceptions from PEP 3151
|
| 264 |
+
'BlockingIOError', 'ChildProcessError', 'ConnectionError',
|
| 265 |
+
'BrokenPipeError', 'ConnectionAbortedError', 'ConnectionRefusedError',
|
| 266 |
+
'ConnectionResetError', 'FileExistsError', 'FileNotFoundError',
|
| 267 |
+
'InterruptedError', 'IsADirectoryError', 'NotADirectoryError',
|
| 268 |
+
'PermissionError', 'ProcessLookupError', 'TimeoutError',
|
| 269 |
+
# others new in Python 3
|
| 270 |
+
'StopAsyncIteration', 'ModuleNotFoundError', 'RecursionError',
|
| 271 |
+
'EncodingWarning'),
|
| 272 |
+
prefix=r'(?<!\.)', suffix=r'\b'),
|
| 273 |
+
Name.Exception),
|
| 274 |
+
],
|
| 275 |
+
'magicfuncs': [
|
| 276 |
+
(words((
|
| 277 |
+
'__abs__', '__add__', '__aenter__', '__aexit__', '__aiter__',
|
| 278 |
+
'__and__', '__anext__', '__await__', '__bool__', '__bytes__',
|
| 279 |
+
'__call__', '__complex__', '__contains__', '__del__', '__delattr__',
|
| 280 |
+
'__delete__', '__delitem__', '__dir__', '__divmod__', '__enter__',
|
| 281 |
+
'__eq__', '__exit__', '__float__', '__floordiv__', '__format__',
|
| 282 |
+
'__ge__', '__get__', '__getattr__', '__getattribute__',
|
| 283 |
+
'__getitem__', '__gt__', '__hash__', '__iadd__', '__iand__',
|
| 284 |
+
'__ifloordiv__', '__ilshift__', '__imatmul__', '__imod__',
|
| 285 |
+
'__imul__', '__index__', '__init__', '__instancecheck__',
|
| 286 |
+
'__int__', '__invert__', '__ior__', '__ipow__', '__irshift__',
|
| 287 |
+
'__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__',
|
| 288 |
+
'__len__', '__length_hint__', '__lshift__', '__lt__', '__matmul__',
|
| 289 |
+
'__missing__', '__mod__', '__mul__', '__ne__', '__neg__',
|
| 290 |
+
'__new__', '__next__', '__or__', '__pos__', '__pow__',
|
| 291 |
+
'__prepare__', '__radd__', '__rand__', '__rdivmod__', '__repr__',
|
| 292 |
+
'__reversed__', '__rfloordiv__', '__rlshift__', '__rmatmul__',
|
| 293 |
+
'__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__',
|
| 294 |
+
'__rrshift__', '__rshift__', '__rsub__', '__rtruediv__',
|
| 295 |
+
'__rxor__', '__set__', '__setattr__', '__setitem__', '__str__',
|
| 296 |
+
'__sub__', '__subclasscheck__', '__truediv__',
|
| 297 |
+
'__xor__'), suffix=r'\b'),
|
| 298 |
+
Name.Function.Magic),
|
| 299 |
+
],
|
| 300 |
+
'magicvars': [
|
| 301 |
+
(words((
|
| 302 |
+
'__annotations__', '__bases__', '__class__', '__closure__',
|
| 303 |
+
'__code__', '__defaults__', '__dict__', '__doc__', '__file__',
|
| 304 |
+
'__func__', '__globals__', '__kwdefaults__', '__module__',
|
| 305 |
+
'__mro__', '__name__', '__objclass__', '__qualname__',
|
| 306 |
+
'__self__', '__slots__', '__weakref__'), suffix=r'\b'),
|
| 307 |
+
Name.Variable.Magic),
|
| 308 |
+
],
|
| 309 |
+
'numbers': [
|
| 310 |
+
(r'(\d(?:_?\d)*\.(?:\d(?:_?\d)*)?|(?:\d(?:_?\d)*)?\.\d(?:_?\d)*)'
|
| 311 |
+
r'([eE][+-]?\d(?:_?\d)*)?', Number.Float),
|
| 312 |
+
(r'\d(?:_?\d)*[eE][+-]?\d(?:_?\d)*j?', Number.Float),
|
| 313 |
+
(r'0[oO](?:_?[0-7])+', Number.Oct),
|
| 314 |
+
(r'0[bB](?:_?[01])+', Number.Bin),
|
| 315 |
+
(r'0[xX](?:_?[a-fA-F0-9])+', Number.Hex),
|
| 316 |
+
(r'\d(?:_?\d)*', Number.Integer),
|
| 317 |
+
],
|
| 318 |
+
'name': [
|
| 319 |
+
(r'@' + uni_name, Name.Decorator),
|
| 320 |
+
(r'@', Operator), # new matrix multiplication operator
|
| 321 |
+
(uni_name, Name),
|
| 322 |
+
],
|
| 323 |
+
'funcname': [
|
| 324 |
+
include('magicfuncs'),
|
| 325 |
+
(uni_name, Name.Function, '#pop'),
|
| 326 |
+
default('#pop'),
|
| 327 |
+
],
|
| 328 |
+
'classname': [
|
| 329 |
+
(uni_name, Name.Class, '#pop'),
|
| 330 |
+
],
|
| 331 |
+
'import': [
|
| 332 |
+
(r'(\s+)(as)(\s+)', bygroups(Text, Keyword, Text)),
|
| 333 |
+
(r'\.', Name.Namespace),
|
| 334 |
+
(uni_name, Name.Namespace),
|
| 335 |
+
(r'(\s*)(,)(\s*)', bygroups(Text, Operator, Text)),
|
| 336 |
+
default('#pop') # all else: go back
|
| 337 |
+
],
|
| 338 |
+
'fromimport': [
|
| 339 |
+
(r'(\s+)(import)\b', bygroups(Text, Keyword.Namespace), '#pop'),
|
| 340 |
+
(r'\.', Name.Namespace),
|
| 341 |
+
# if None occurs here, it's "raise x from None", since None can
|
| 342 |
+
# never be a module name
|
| 343 |
+
(r'None\b', Keyword.Constant, '#pop'),
|
| 344 |
+
(uni_name, Name.Namespace),
|
| 345 |
+
default('#pop'),
|
| 346 |
+
],
|
| 347 |
+
'rfstringescape': [
|
| 348 |
+
(r'\{\{', String.Escape),
|
| 349 |
+
(r'\}\}', String.Escape),
|
| 350 |
+
],
|
| 351 |
+
'fstringescape': [
|
| 352 |
+
include('rfstringescape'),
|
| 353 |
+
include('stringescape'),
|
| 354 |
+
],
|
| 355 |
+
'bytesescape': [
|
| 356 |
+
(r'\\([\\abfnrtv"\']|\n|x[a-fA-F0-9]{2}|[0-7]{1,3})', String.Escape)
|
| 357 |
+
],
|
| 358 |
+
'stringescape': [
|
| 359 |
+
(r'\\(N\{.*?\}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{8})', String.Escape),
|
| 360 |
+
include('bytesescape')
|
| 361 |
+
],
|
| 362 |
+
'fstrings-single': fstring_rules(String.Single),
|
| 363 |
+
'fstrings-double': fstring_rules(String.Double),
|
| 364 |
+
'strings-single': innerstring_rules(String.Single),
|
| 365 |
+
'strings-double': innerstring_rules(String.Double),
|
| 366 |
+
'dqf': [
|
| 367 |
+
(r'"', String.Double, '#pop'),
|
| 368 |
+
(r'\\\\|\\"|\\\n', String.Escape), # included here for raw strings
|
| 369 |
+
include('fstrings-double')
|
| 370 |
+
],
|
| 371 |
+
'sqf': [
|
| 372 |
+
(r"'", String.Single, '#pop'),
|
| 373 |
+
(r"\\\\|\\'|\\\n", String.Escape), # included here for raw strings
|
| 374 |
+
include('fstrings-single')
|
| 375 |
+
],
|
| 376 |
+
'dqs': [
|
| 377 |
+
(r'"', String.Double, '#pop'),
|
| 378 |
+
(r'\\\\|\\"|\\\n', String.Escape), # included here for raw strings
|
| 379 |
+
include('strings-double')
|
| 380 |
+
],
|
| 381 |
+
'sqs': [
|
| 382 |
+
(r"'", String.Single, '#pop'),
|
| 383 |
+
(r"\\\\|\\'|\\\n", String.Escape), # included here for raw strings
|
| 384 |
+
include('strings-single')
|
| 385 |
+
],
|
| 386 |
+
'tdqf': [
|
| 387 |
+
(r'"""', String.Double, '#pop'),
|
| 388 |
+
include('fstrings-double'),
|
| 389 |
+
(r'\n', String.Double)
|
| 390 |
+
],
|
| 391 |
+
'tsqf': [
|
| 392 |
+
(r"'''", String.Single, '#pop'),
|
| 393 |
+
include('fstrings-single'),
|
| 394 |
+
(r'\n', String.Single)
|
| 395 |
+
],
|
| 396 |
+
'tdqs': [
|
| 397 |
+
(r'"""', String.Double, '#pop'),
|
| 398 |
+
include('strings-double'),
|
| 399 |
+
(r'\n', String.Double)
|
| 400 |
+
],
|
| 401 |
+
'tsqs': [
|
| 402 |
+
(r"'''", String.Single, '#pop'),
|
| 403 |
+
include('strings-single'),
|
| 404 |
+
(r'\n', String.Single)
|
| 405 |
+
],
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
def analyse_text(text):
|
| 409 |
+
return shebang_matches(text, r'pythonw?(3(\.\d)?)?') or \
|
| 410 |
+
'import ' in text[:1000]
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
Python3Lexer = PythonLexer
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
class Python2Lexer(RegexLexer):
|
| 417 |
+
"""
|
| 418 |
+
For Python 2.x source code.
|
| 419 |
+
|
| 420 |
+
.. versionchanged:: 2.5
|
| 421 |
+
This class has been renamed from ``PythonLexer``. ``PythonLexer`` now
|
| 422 |
+
refers to the Python 3 variant. File name patterns like ``*.py`` have
|
| 423 |
+
been moved to Python 3 as well.
|
| 424 |
+
"""
|
| 425 |
+
|
| 426 |
+
name = 'Python 2.x'
|
| 427 |
+
url = 'https://www.python.org'
|
| 428 |
+
aliases = ['python2', 'py2']
|
| 429 |
+
filenames = [] # now taken over by PythonLexer (3.x)
|
| 430 |
+
mimetypes = ['text/x-python2', 'application/x-python2']
|
| 431 |
+
version_added = ''
|
| 432 |
+
|
| 433 |
+
def innerstring_rules(ttype):
|
| 434 |
+
return [
|
| 435 |
+
# the old style '%s' % (...) string formatting
|
| 436 |
+
(r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
|
| 437 |
+
'[hlL]?[E-GXc-giorsux%]', String.Interpol),
|
| 438 |
+
# backslashes, quotes and formatting signs must be parsed one at a time
|
| 439 |
+
(r'[^\\\'"%\n]+', ttype),
|
| 440 |
+
(r'[\'"\\]', ttype),
|
| 441 |
+
# unhandled string formatting sign
|
| 442 |
+
(r'%', ttype),
|
| 443 |
+
# newlines are an error (use "nl" state)
|
| 444 |
+
]
|
| 445 |
+
|
| 446 |
+
tokens = {
|
| 447 |
+
'root': [
|
| 448 |
+
(r'\n', Whitespace),
|
| 449 |
+
(r'^(\s*)([rRuUbB]{,2})("""(?:.|\n)*?""")',
|
| 450 |
+
bygroups(Whitespace, String.Affix, String.Doc)),
|
| 451 |
+
(r"^(\s*)([rRuUbB]{,2})('''(?:.|\n)*?''')",
|
| 452 |
+
bygroups(Whitespace, String.Affix, String.Doc)),
|
| 453 |
+
(r'[^\S\n]+', Text),
|
| 454 |
+
(r'\A#!.+$', Comment.Hashbang),
|
| 455 |
+
(r'#.*$', Comment.Single),
|
| 456 |
+
(r'[]{}:(),;[]', Punctuation),
|
| 457 |
+
(r'\\\n', Text),
|
| 458 |
+
(r'\\', Text),
|
| 459 |
+
(r'(in|is|and|or|not)\b', Operator.Word),
|
| 460 |
+
(r'!=|==|<<|>>|[-~+/*%=<>&^|.]', Operator),
|
| 461 |
+
include('keywords'),
|
| 462 |
+
(r'(def)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'funcname'),
|
| 463 |
+
(r'(class)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'classname'),
|
| 464 |
+
(r'(from)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text),
|
| 465 |
+
'fromimport'),
|
| 466 |
+
(r'(import)((?:\s|\\\s)+)', bygroups(Keyword.Namespace, Text),
|
| 467 |
+
'import'),
|
| 468 |
+
include('builtins'),
|
| 469 |
+
include('magicfuncs'),
|
| 470 |
+
include('magicvars'),
|
| 471 |
+
include('backtick'),
|
| 472 |
+
('([rR]|[uUbB][rR]|[rR][uUbB])(""")',
|
| 473 |
+
bygroups(String.Affix, String.Double), 'tdqs'),
|
| 474 |
+
("([rR]|[uUbB][rR]|[rR][uUbB])(''')",
|
| 475 |
+
bygroups(String.Affix, String.Single), 'tsqs'),
|
| 476 |
+
('([rR]|[uUbB][rR]|[rR][uUbB])(")',
|
| 477 |
+
bygroups(String.Affix, String.Double), 'dqs'),
|
| 478 |
+
("([rR]|[uUbB][rR]|[rR][uUbB])(')",
|
| 479 |
+
bygroups(String.Affix, String.Single), 'sqs'),
|
| 480 |
+
('([uUbB]?)(""")', bygroups(String.Affix, String.Double),
|
| 481 |
+
combined('stringescape', 'tdqs')),
|
| 482 |
+
("([uUbB]?)(''')", bygroups(String.Affix, String.Single),
|
| 483 |
+
combined('stringescape', 'tsqs')),
|
| 484 |
+
('([uUbB]?)(")', bygroups(String.Affix, String.Double),
|
| 485 |
+
combined('stringescape', 'dqs')),
|
| 486 |
+
("([uUbB]?)(')", bygroups(String.Affix, String.Single),
|
| 487 |
+
combined('stringescape', 'sqs')),
|
| 488 |
+
include('name'),
|
| 489 |
+
include('numbers'),
|
| 490 |
+
],
|
| 491 |
+
'keywords': [
|
| 492 |
+
(words((
|
| 493 |
+
'assert', 'break', 'continue', 'del', 'elif', 'else', 'except',
|
| 494 |
+
'exec', 'finally', 'for', 'global', 'if', 'lambda', 'pass',
|
| 495 |
+
'print', 'raise', 'return', 'try', 'while', 'yield',
|
| 496 |
+
'yield from', 'as', 'with'), suffix=r'\b'),
|
| 497 |
+
Keyword),
|
| 498 |
+
],
|
| 499 |
+
'builtins': [
|
| 500 |
+
(words((
|
| 501 |
+
'__import__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin',
|
| 502 |
+
'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod',
|
| 503 |
+
'cmp', 'coerce', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod',
|
| 504 |
+
'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float',
|
| 505 |
+
'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'hex', 'id',
|
| 506 |
+
'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len',
|
| 507 |
+
'list', 'locals', 'long', 'map', 'max', 'min', 'next', 'object',
|
| 508 |
+
'oct', 'open', 'ord', 'pow', 'property', 'range', 'raw_input', 'reduce',
|
| 509 |
+
'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
|
| 510 |
+
'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type',
|
| 511 |
+
'unichr', 'unicode', 'vars', 'xrange', 'zip'),
|
| 512 |
+
prefix=r'(?<!\.)', suffix=r'\b'),
|
| 513 |
+
Name.Builtin),
|
| 514 |
+
(r'(?<!\.)(self|None|Ellipsis|NotImplemented|False|True|cls'
|
| 515 |
+
r')\b', Name.Builtin.Pseudo),
|
| 516 |
+
(words((
|
| 517 |
+
'ArithmeticError', 'AssertionError', 'AttributeError',
|
| 518 |
+
'BaseException', 'DeprecationWarning', 'EOFError', 'EnvironmentError',
|
| 519 |
+
'Exception', 'FloatingPointError', 'FutureWarning', 'GeneratorExit',
|
| 520 |
+
'IOError', 'ImportError', 'ImportWarning', 'IndentationError',
|
| 521 |
+
'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
|
| 522 |
+
'MemoryError', 'NameError',
|
| 523 |
+
'NotImplementedError', 'OSError', 'OverflowError', 'OverflowWarning',
|
| 524 |
+
'PendingDeprecationWarning', 'ReferenceError',
|
| 525 |
+
'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration',
|
| 526 |
+
'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit',
|
| 527 |
+
'TabError', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
|
| 528 |
+
'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
|
| 529 |
+
'UnicodeWarning', 'UserWarning', 'ValueError', 'VMSError', 'Warning',
|
| 530 |
+
'WindowsError', 'ZeroDivisionError'), prefix=r'(?<!\.)', suffix=r'\b'),
|
| 531 |
+
Name.Exception),
|
| 532 |
+
],
|
| 533 |
+
'magicfuncs': [
|
| 534 |
+
(words((
|
| 535 |
+
'__abs__', '__add__', '__and__', '__call__', '__cmp__', '__coerce__',
|
| 536 |
+
'__complex__', '__contains__', '__del__', '__delattr__', '__delete__',
|
| 537 |
+
'__delitem__', '__delslice__', '__div__', '__divmod__', '__enter__',
|
| 538 |
+
'__eq__', '__exit__', '__float__', '__floordiv__', '__ge__', '__get__',
|
| 539 |
+
'__getattr__', '__getattribute__', '__getitem__', '__getslice__', '__gt__',
|
| 540 |
+
'__hash__', '__hex__', '__iadd__', '__iand__', '__idiv__', '__ifloordiv__',
|
| 541 |
+
'__ilshift__', '__imod__', '__imul__', '__index__', '__init__',
|
| 542 |
+
'__instancecheck__', '__int__', '__invert__', '__iop__', '__ior__',
|
| 543 |
+
'__ipow__', '__irshift__', '__isub__', '__iter__', '__itruediv__',
|
| 544 |
+
'__ixor__', '__le__', '__len__', '__long__', '__lshift__', '__lt__',
|
| 545 |
+
'__missing__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__',
|
| 546 |
+
'__nonzero__', '__oct__', '__op__', '__or__', '__pos__', '__pow__',
|
| 547 |
+
'__radd__', '__rand__', '__rcmp__', '__rdiv__', '__rdivmod__', '__repr__',
|
| 548 |
+
'__reversed__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__',
|
| 549 |
+
'__rop__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__',
|
| 550 |
+
'__rtruediv__', '__rxor__', '__set__', '__setattr__', '__setitem__',
|
| 551 |
+
'__setslice__', '__str__', '__sub__', '__subclasscheck__', '__truediv__',
|
| 552 |
+
'__unicode__', '__xor__'), suffix=r'\b'),
|
| 553 |
+
Name.Function.Magic),
|
| 554 |
+
],
|
| 555 |
+
'magicvars': [
|
| 556 |
+
(words((
|
| 557 |
+
'__bases__', '__class__', '__closure__', '__code__', '__defaults__',
|
| 558 |
+
'__dict__', '__doc__', '__file__', '__func__', '__globals__',
|
| 559 |
+
'__metaclass__', '__module__', '__mro__', '__name__', '__self__',
|
| 560 |
+
'__slots__', '__weakref__'),
|
| 561 |
+
suffix=r'\b'),
|
| 562 |
+
Name.Variable.Magic),
|
| 563 |
+
],
|
| 564 |
+
'numbers': [
|
| 565 |
+
(r'(\d+\.\d*|\d*\.\d+)([eE][+-]?[0-9]+)?j?', Number.Float),
|
| 566 |
+
(r'\d+[eE][+-]?[0-9]+j?', Number.Float),
|
| 567 |
+
(r'0[0-7]+j?', Number.Oct),
|
| 568 |
+
(r'0[bB][01]+', Number.Bin),
|
| 569 |
+
(r'0[xX][a-fA-F0-9]+', Number.Hex),
|
| 570 |
+
(r'\d+L', Number.Integer.Long),
|
| 571 |
+
(r'\d+j?', Number.Integer)
|
| 572 |
+
],
|
| 573 |
+
'backtick': [
|
| 574 |
+
('`.*?`', String.Backtick),
|
| 575 |
+
],
|
| 576 |
+
'name': [
|
| 577 |
+
(r'@[\w.]+', Name.Decorator),
|
| 578 |
+
(r'[a-zA-Z_]\w*', Name),
|
| 579 |
+
],
|
| 580 |
+
'funcname': [
|
| 581 |
+
include('magicfuncs'),
|
| 582 |
+
(r'[a-zA-Z_]\w*', Name.Function, '#pop'),
|
| 583 |
+
default('#pop'),
|
| 584 |
+
],
|
| 585 |
+
'classname': [
|
| 586 |
+
(r'[a-zA-Z_]\w*', Name.Class, '#pop')
|
| 587 |
+
],
|
| 588 |
+
'import': [
|
| 589 |
+
(r'(?:[ \t]|\\\n)+', Text),
|
| 590 |
+
(r'as\b', Keyword.Namespace),
|
| 591 |
+
(r',', Operator),
|
| 592 |
+
(r'[a-zA-Z_][\w.]*', Name.Namespace),
|
| 593 |
+
default('#pop') # all else: go back
|
| 594 |
+
],
|
| 595 |
+
'fromimport': [
|
| 596 |
+
(r'(?:[ \t]|\\\n)+', Text),
|
| 597 |
+
(r'import\b', Keyword.Namespace, '#pop'),
|
| 598 |
+
# if None occurs here, it's "raise x from None", since None can
|
| 599 |
+
# never be a module name
|
| 600 |
+
(r'None\b', Name.Builtin.Pseudo, '#pop'),
|
| 601 |
+
# sadly, in "raise x from y" y will be highlighted as namespace too
|
| 602 |
+
(r'[a-zA-Z_.][\w.]*', Name.Namespace),
|
| 603 |
+
# anything else here also means "raise x from y" and is therefore
|
| 604 |
+
# not an error
|
| 605 |
+
default('#pop'),
|
| 606 |
+
],
|
| 607 |
+
'stringescape': [
|
| 608 |
+
(r'\\([\\abfnrtv"\']|\n|N\{.*?\}|u[a-fA-F0-9]{4}|'
|
| 609 |
+
r'U[a-fA-F0-9]{8}|x[a-fA-F0-9]{2}|[0-7]{1,3})', String.Escape)
|
| 610 |
+
],
|
| 611 |
+
'strings-single': innerstring_rules(String.Single),
|
| 612 |
+
'strings-double': innerstring_rules(String.Double),
|
| 613 |
+
'dqs': [
|
| 614 |
+
(r'"', String.Double, '#pop'),
|
| 615 |
+
(r'\\\\|\\"|\\\n', String.Escape), # included here for raw strings
|
| 616 |
+
include('strings-double')
|
| 617 |
+
],
|
| 618 |
+
'sqs': [
|
| 619 |
+
(r"'", String.Single, '#pop'),
|
| 620 |
+
(r"\\\\|\\'|\\\n", String.Escape), # included here for raw strings
|
| 621 |
+
include('strings-single')
|
| 622 |
+
],
|
| 623 |
+
'tdqs': [
|
| 624 |
+
(r'"""', String.Double, '#pop'),
|
| 625 |
+
include('strings-double'),
|
| 626 |
+
(r'\n', String.Double)
|
| 627 |
+
],
|
| 628 |
+
'tsqs': [
|
| 629 |
+
(r"'''", String.Single, '#pop'),
|
| 630 |
+
include('strings-single'),
|
| 631 |
+
(r'\n', String.Single)
|
| 632 |
+
],
|
| 633 |
+
}
|
| 634 |
+
|
| 635 |
+
def analyse_text(text):
|
| 636 |
+
return shebang_matches(text, r'pythonw?2(\.\d)?')
|
| 637 |
+
|
| 638 |
+
class _PythonConsoleLexerBase(RegexLexer):
|
| 639 |
+
name = 'Python console session'
|
| 640 |
+
aliases = ['pycon', 'python-console']
|
| 641 |
+
mimetypes = ['text/x-python-doctest']
|
| 642 |
+
|
| 643 |
+
"""Auxiliary lexer for `PythonConsoleLexer`.
|
| 644 |
+
|
| 645 |
+
Code tokens are output as ``Token.Other.Code``, traceback tokens as
|
| 646 |
+
``Token.Other.Traceback``.
|
| 647 |
+
"""
|
| 648 |
+
tokens = {
|
| 649 |
+
'root': [
|
| 650 |
+
(r'(>>> )(.*\n)', bygroups(Generic.Prompt, Other.Code), 'continuations'),
|
| 651 |
+
# This happens, e.g., when tracebacks are embedded in documentation;
|
| 652 |
+
# trailing whitespaces are often stripped in such contexts.
|
| 653 |
+
(r'(>>>)(\n)', bygroups(Generic.Prompt, Whitespace)),
|
| 654 |
+
(r'(\^C)?Traceback \(most recent call last\):\n', Other.Traceback, 'traceback'),
|
| 655 |
+
# SyntaxError starts with this
|
| 656 |
+
(r' File "[^"]+", line \d+', Other.Traceback, 'traceback'),
|
| 657 |
+
(r'.*\n', Generic.Output),
|
| 658 |
+
],
|
| 659 |
+
'continuations': [
|
| 660 |
+
(r'(\.\.\. )(.*\n)', bygroups(Generic.Prompt, Other.Code)),
|
| 661 |
+
# See above.
|
| 662 |
+
(r'(\.\.\.)(\n)', bygroups(Generic.Prompt, Whitespace)),
|
| 663 |
+
default('#pop'),
|
| 664 |
+
],
|
| 665 |
+
'traceback': [
|
| 666 |
+
# As soon as we see a traceback, consume everything until the next
|
| 667 |
+
# >>> prompt.
|
| 668 |
+
(r'(?=>>>( |$))', Text, '#pop'),
|
| 669 |
+
(r'(KeyboardInterrupt)(\n)', bygroups(Name.Class, Whitespace)),
|
| 670 |
+
(r'.*\n', Other.Traceback),
|
| 671 |
+
],
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
class PythonConsoleLexer(DelegatingLexer):
|
| 675 |
+
"""
|
| 676 |
+
For Python console output or doctests, such as:
|
| 677 |
+
|
| 678 |
+
.. sourcecode:: pycon
|
| 679 |
+
|
| 680 |
+
>>> a = 'foo'
|
| 681 |
+
>>> print(a)
|
| 682 |
+
foo
|
| 683 |
+
>>> 1 / 0
|
| 684 |
+
Traceback (most recent call last):
|
| 685 |
+
File "<stdin>", line 1, in <module>
|
| 686 |
+
ZeroDivisionError: integer division or modulo by zero
|
| 687 |
+
|
| 688 |
+
Additional options:
|
| 689 |
+
|
| 690 |
+
`python3`
|
| 691 |
+
Use Python 3 lexer for code. Default is ``True``.
|
| 692 |
+
|
| 693 |
+
.. versionadded:: 1.0
|
| 694 |
+
.. versionchanged:: 2.5
|
| 695 |
+
Now defaults to ``True``.
|
| 696 |
+
"""
|
| 697 |
+
|
| 698 |
+
name = 'Python console session'
|
| 699 |
+
aliases = ['pycon', 'python-console']
|
| 700 |
+
mimetypes = ['text/x-python-doctest']
|
| 701 |
+
url = 'https://python.org'
|
| 702 |
+
version_added = ''
|
| 703 |
+
|
| 704 |
+
def __init__(self, **options):
|
| 705 |
+
python3 = get_bool_opt(options, 'python3', True)
|
| 706 |
+
if python3:
|
| 707 |
+
pylexer = PythonLexer
|
| 708 |
+
tblexer = PythonTracebackLexer
|
| 709 |
+
else:
|
| 710 |
+
pylexer = Python2Lexer
|
| 711 |
+
tblexer = Python2TracebackLexer
|
| 712 |
+
# We have two auxiliary lexers. Use DelegatingLexer twice with
|
| 713 |
+
# different tokens. TODO: DelegatingLexer should support this
|
| 714 |
+
# directly, by accepting a tuplet of auxiliary lexers and a tuple of
|
| 715 |
+
# distinguishing tokens. Then we wouldn't need this intermediary
|
| 716 |
+
# class.
|
| 717 |
+
class _ReplaceInnerCode(DelegatingLexer):
|
| 718 |
+
def __init__(self, **options):
|
| 719 |
+
super().__init__(pylexer, _PythonConsoleLexerBase, Other.Code, **options)
|
| 720 |
+
super().__init__(tblexer, _ReplaceInnerCode, Other.Traceback, **options)
|
| 721 |
+
|
| 722 |
+
class PythonTracebackLexer(RegexLexer):
|
| 723 |
+
"""
|
| 724 |
+
For Python 3.x tracebacks, with support for chained exceptions.
|
| 725 |
+
|
| 726 |
+
.. versionchanged:: 2.5
|
| 727 |
+
This is now the default ``PythonTracebackLexer``. It is still available
|
| 728 |
+
as the alias ``Python3TracebackLexer``.
|
| 729 |
+
"""
|
| 730 |
+
|
| 731 |
+
name = 'Python Traceback'
|
| 732 |
+
aliases = ['pytb', 'py3tb']
|
| 733 |
+
filenames = ['*.pytb', '*.py3tb']
|
| 734 |
+
mimetypes = ['text/x-python-traceback', 'text/x-python3-traceback']
|
| 735 |
+
url = 'https://python.org'
|
| 736 |
+
version_added = '1.0'
|
| 737 |
+
|
| 738 |
+
tokens = {
|
| 739 |
+
'root': [
|
| 740 |
+
(r'\n', Whitespace),
|
| 741 |
+
(r'^(\^C)?Traceback \(most recent call last\):\n', Generic.Traceback, 'intb'),
|
| 742 |
+
(r'^During handling of the above exception, another '
|
| 743 |
+
r'exception occurred:\n\n', Generic.Traceback),
|
| 744 |
+
(r'^The above exception was the direct cause of the '
|
| 745 |
+
r'following exception:\n\n', Generic.Traceback),
|
| 746 |
+
(r'^(?= File "[^"]+", line \d+)', Generic.Traceback, 'intb'),
|
| 747 |
+
(r'^.*\n', Other),
|
| 748 |
+
],
|
| 749 |
+
'intb': [
|
| 750 |
+
(r'^( File )("[^"]+")(, line )(\d+)(, in )(.+)(\n)',
|
| 751 |
+
bygroups(Text, Name.Builtin, Text, Number, Text, Name, Whitespace)),
|
| 752 |
+
(r'^( File )("[^"]+")(, line )(\d+)(\n)',
|
| 753 |
+
bygroups(Text, Name.Builtin, Text, Number, Whitespace)),
|
| 754 |
+
(r'^( )(.+)(\n)',
|
| 755 |
+
bygroups(Whitespace, using(PythonLexer), Whitespace), 'markers'),
|
| 756 |
+
(r'^([ \t]*)(\.\.\.)(\n)',
|
| 757 |
+
bygroups(Whitespace, Comment, Whitespace)), # for doctests...
|
| 758 |
+
(r'^([^:]+)(: )(.+)(\n)',
|
| 759 |
+
bygroups(Generic.Error, Text, Name, Whitespace), '#pop'),
|
| 760 |
+
(r'^([a-zA-Z_][\w.]*)(:?\n)',
|
| 761 |
+
bygroups(Generic.Error, Whitespace), '#pop'),
|
| 762 |
+
default('#pop'),
|
| 763 |
+
],
|
| 764 |
+
'markers': [
|
| 765 |
+
# Either `PEP 657 <https://www.python.org/dev/peps/pep-0657/>`
|
| 766 |
+
# error locations in Python 3.11+, or single-caret markers
|
| 767 |
+
# for syntax errors before that.
|
| 768 |
+
(r'^( {4,})([~^]+)(\n)',
|
| 769 |
+
bygroups(Whitespace, Punctuation.Marker, Whitespace),
|
| 770 |
+
'#pop'),
|
| 771 |
+
default('#pop'),
|
| 772 |
+
],
|
| 773 |
+
}
|
| 774 |
+
|
| 775 |
+
|
| 776 |
+
Python3TracebackLexer = PythonTracebackLexer
|
| 777 |
+
|
| 778 |
+
|
| 779 |
+
class Python2TracebackLexer(RegexLexer):
|
| 780 |
+
"""
|
| 781 |
+
For Python tracebacks.
|
| 782 |
+
|
| 783 |
+
.. versionchanged:: 2.5
|
| 784 |
+
This class has been renamed from ``PythonTracebackLexer``.
|
| 785 |
+
``PythonTracebackLexer`` now refers to the Python 3 variant.
|
| 786 |
+
"""
|
| 787 |
+
|
| 788 |
+
name = 'Python 2.x Traceback'
|
| 789 |
+
aliases = ['py2tb']
|
| 790 |
+
filenames = ['*.py2tb']
|
| 791 |
+
mimetypes = ['text/x-python2-traceback']
|
| 792 |
+
url = 'https://python.org'
|
| 793 |
+
version_added = '0.7'
|
| 794 |
+
|
| 795 |
+
tokens = {
|
| 796 |
+
'root': [
|
| 797 |
+
# Cover both (most recent call last) and (innermost last)
|
| 798 |
+
# The optional ^C allows us to catch keyboard interrupt signals.
|
| 799 |
+
(r'^(\^C)?(Traceback.*\n)',
|
| 800 |
+
bygroups(Text, Generic.Traceback), 'intb'),
|
| 801 |
+
# SyntaxError starts with this.
|
| 802 |
+
(r'^(?= File "[^"]+", line \d+)', Generic.Traceback, 'intb'),
|
| 803 |
+
(r'^.*\n', Other),
|
| 804 |
+
],
|
| 805 |
+
'intb': [
|
| 806 |
+
(r'^( File )("[^"]+")(, line )(\d+)(, in )(.+)(\n)',
|
| 807 |
+
bygroups(Text, Name.Builtin, Text, Number, Text, Name, Whitespace)),
|
| 808 |
+
(r'^( File )("[^"]+")(, line )(\d+)(\n)',
|
| 809 |
+
bygroups(Text, Name.Builtin, Text, Number, Whitespace)),
|
| 810 |
+
(r'^( )(.+)(\n)',
|
| 811 |
+
bygroups(Text, using(Python2Lexer), Whitespace), 'marker'),
|
| 812 |
+
(r'^([ \t]*)(\.\.\.)(\n)',
|
| 813 |
+
bygroups(Text, Comment, Whitespace)), # for doctests...
|
| 814 |
+
(r'^([^:]+)(: )(.+)(\n)',
|
| 815 |
+
bygroups(Generic.Error, Text, Name, Whitespace), '#pop'),
|
| 816 |
+
(r'^([a-zA-Z_]\w*)(:?\n)',
|
| 817 |
+
bygroups(Generic.Error, Whitespace), '#pop')
|
| 818 |
+
],
|
| 819 |
+
'marker': [
|
| 820 |
+
# For syntax errors.
|
| 821 |
+
(r'( {4,})(\^)', bygroups(Text, Punctuation.Marker), '#pop'),
|
| 822 |
+
default('#pop'),
|
| 823 |
+
],
|
| 824 |
+
}
|
| 825 |
+
|
| 826 |
+
|
| 827 |
+
class CythonLexer(RegexLexer):
|
| 828 |
+
"""
|
| 829 |
+
For Pyrex and Cython source code.
|
| 830 |
+
"""
|
| 831 |
+
|
| 832 |
+
name = 'Cython'
|
| 833 |
+
url = 'https://cython.org'
|
| 834 |
+
aliases = ['cython', 'pyx', 'pyrex']
|
| 835 |
+
filenames = ['*.pyx', '*.pxd', '*.pxi']
|
| 836 |
+
mimetypes = ['text/x-cython', 'application/x-cython']
|
| 837 |
+
version_added = '1.1'
|
| 838 |
+
|
| 839 |
+
tokens = {
|
| 840 |
+
'root': [
|
| 841 |
+
(r'\n', Whitespace),
|
| 842 |
+
(r'^(\s*)("""(?:.|\n)*?""")', bygroups(Whitespace, String.Doc)),
|
| 843 |
+
(r"^(\s*)('''(?:.|\n)*?''')", bygroups(Whitespace, String.Doc)),
|
| 844 |
+
(r'[^\S\n]+', Text),
|
| 845 |
+
(r'#.*$', Comment),
|
| 846 |
+
(r'[]{}:(),;[]', Punctuation),
|
| 847 |
+
(r'\\\n', Whitespace),
|
| 848 |
+
(r'\\', Text),
|
| 849 |
+
(r'(in|is|and|or|not)\b', Operator.Word),
|
| 850 |
+
(r'(<)([a-zA-Z0-9.?]+)(>)',
|
| 851 |
+
bygroups(Punctuation, Keyword.Type, Punctuation)),
|
| 852 |
+
(r'!=|==|<<|>>|[-~+/*%=<>&^|.?]', Operator),
|
| 853 |
+
(r'(from)(\d+)(<=)(\s+)(<)(\d+)(:)',
|
| 854 |
+
bygroups(Keyword, Number.Integer, Operator, Name, Operator,
|
| 855 |
+
Name, Punctuation)),
|
| 856 |
+
include('keywords'),
|
| 857 |
+
(r'(def|property)(\s+)', bygroups(Keyword, Text), 'funcname'),
|
| 858 |
+
(r'(cp?def)(\s+)', bygroups(Keyword, Text), 'cdef'),
|
| 859 |
+
# (should actually start a block with only cdefs)
|
| 860 |
+
(r'(cdef)(:)', bygroups(Keyword, Punctuation)),
|
| 861 |
+
(r'(class|struct)(\s+)', bygroups(Keyword, Text), 'classname'),
|
| 862 |
+
(r'(from)(\s+)', bygroups(Keyword, Text), 'fromimport'),
|
| 863 |
+
(r'(c?import)(\s+)', bygroups(Keyword, Text), 'import'),
|
| 864 |
+
include('builtins'),
|
| 865 |
+
include('backtick'),
|
| 866 |
+
('(?:[rR]|[uU][rR]|[rR][uU])"""', String, 'tdqs'),
|
| 867 |
+
("(?:[rR]|[uU][rR]|[rR][uU])'''", String, 'tsqs'),
|
| 868 |
+
('(?:[rR]|[uU][rR]|[rR][uU])"', String, 'dqs'),
|
| 869 |
+
("(?:[rR]|[uU][rR]|[rR][uU])'", String, 'sqs'),
|
| 870 |
+
('[uU]?"""', String, combined('stringescape', 'tdqs')),
|
| 871 |
+
("[uU]?'''", String, combined('stringescape', 'tsqs')),
|
| 872 |
+
('[uU]?"', String, combined('stringescape', 'dqs')),
|
| 873 |
+
("[uU]?'", String, combined('stringescape', 'sqs')),
|
| 874 |
+
include('name'),
|
| 875 |
+
include('numbers'),
|
| 876 |
+
],
|
| 877 |
+
'keywords': [
|
| 878 |
+
(words((
|
| 879 |
+
'assert', 'async', 'await', 'break', 'by', 'continue', 'ctypedef', 'del', 'elif',
|
| 880 |
+
'else', 'except', 'except?', 'exec', 'finally', 'for', 'fused', 'gil',
|
| 881 |
+
'global', 'if', 'include', 'lambda', 'nogil', 'pass', 'print',
|
| 882 |
+
'raise', 'return', 'try', 'while', 'yield', 'as', 'with'), suffix=r'\b'),
|
| 883 |
+
Keyword),
|
| 884 |
+
(r'(DEF|IF|ELIF|ELSE)\b', Comment.Preproc),
|
| 885 |
+
],
|
| 886 |
+
'builtins': [
|
| 887 |
+
(words((
|
| 888 |
+
'__import__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bint',
|
| 889 |
+
'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr',
|
| 890 |
+
'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'delattr',
|
| 891 |
+
'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit',
|
| 892 |
+
'file', 'filter', 'float', 'frozenset', 'getattr', 'globals',
|
| 893 |
+
'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'intern', 'isinstance',
|
| 894 |
+
'issubclass', 'iter', 'len', 'list', 'locals', 'long', 'map', 'max',
|
| 895 |
+
'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'property', 'Py_ssize_t',
|
| 896 |
+
'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed',
|
| 897 |
+
'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod',
|
| 898 |
+
'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'unsigned',
|
| 899 |
+
'vars', 'xrange', 'zip'), prefix=r'(?<!\.)', suffix=r'\b'),
|
| 900 |
+
Name.Builtin),
|
| 901 |
+
(r'(?<!\.)(self|None|Ellipsis|NotImplemented|False|True|NULL'
|
| 902 |
+
r')\b', Name.Builtin.Pseudo),
|
| 903 |
+
(words((
|
| 904 |
+
'ArithmeticError', 'AssertionError', 'AttributeError',
|
| 905 |
+
'BaseException', 'DeprecationWarning', 'EOFError', 'EnvironmentError',
|
| 906 |
+
'Exception', 'FloatingPointError', 'FutureWarning', 'GeneratorExit',
|
| 907 |
+
'IOError', 'ImportError', 'ImportWarning', 'IndentationError',
|
| 908 |
+
'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
|
| 909 |
+
'MemoryError', 'NameError', 'NotImplemented', 'NotImplementedError',
|
| 910 |
+
'OSError', 'OverflowError', 'OverflowWarning',
|
| 911 |
+
'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError',
|
| 912 |
+
'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError',
|
| 913 |
+
'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError',
|
| 914 |
+
'TypeError', 'UnboundLocalError', 'UnicodeDecodeError',
|
| 915 |
+
'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError',
|
| 916 |
+
'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning',
|
| 917 |
+
'ZeroDivisionError'), prefix=r'(?<!\.)', suffix=r'\b'),
|
| 918 |
+
Name.Exception),
|
| 919 |
+
],
|
| 920 |
+
'numbers': [
|
| 921 |
+
(r'(\d+\.?\d*|\d*\.\d+)([eE][+-]?[0-9]+)?', Number.Float),
|
| 922 |
+
(r'0\d+', Number.Oct),
|
| 923 |
+
(r'0[xX][a-fA-F0-9]+', Number.Hex),
|
| 924 |
+
(r'\d+L', Number.Integer.Long),
|
| 925 |
+
(r'\d+', Number.Integer)
|
| 926 |
+
],
|
| 927 |
+
'backtick': [
|
| 928 |
+
('`.*?`', String.Backtick),
|
| 929 |
+
],
|
| 930 |
+
'name': [
|
| 931 |
+
(r'@\w+', Name.Decorator),
|
| 932 |
+
(r'[a-zA-Z_]\w*', Name),
|
| 933 |
+
],
|
| 934 |
+
'funcname': [
|
| 935 |
+
(r'[a-zA-Z_]\w*', Name.Function, '#pop')
|
| 936 |
+
],
|
| 937 |
+
'cdef': [
|
| 938 |
+
(r'(public|readonly|extern|api|inline)\b', Keyword.Reserved),
|
| 939 |
+
(r'(struct|enum|union|class)\b', Keyword),
|
| 940 |
+
(r'([a-zA-Z_]\w*)(\s*)(?=[(:#=]|$)',
|
| 941 |
+
bygroups(Name.Function, Text), '#pop'),
|
| 942 |
+
(r'([a-zA-Z_]\w*)(\s*)(,)',
|
| 943 |
+
bygroups(Name.Function, Text, Punctuation)),
|
| 944 |
+
(r'from\b', Keyword, '#pop'),
|
| 945 |
+
(r'as\b', Keyword),
|
| 946 |
+
(r':', Punctuation, '#pop'),
|
| 947 |
+
(r'(?=["\'])', Text, '#pop'),
|
| 948 |
+
(r'[a-zA-Z_]\w*', Keyword.Type),
|
| 949 |
+
(r'.', Text),
|
| 950 |
+
],
|
| 951 |
+
'classname': [
|
| 952 |
+
(r'[a-zA-Z_]\w*', Name.Class, '#pop')
|
| 953 |
+
],
|
| 954 |
+
'import': [
|
| 955 |
+
(r'(\s+)(as)(\s+)', bygroups(Text, Keyword, Text)),
|
| 956 |
+
(r'[a-zA-Z_][\w.]*', Name.Namespace),
|
| 957 |
+
(r'(\s*)(,)(\s*)', bygroups(Text, Operator, Text)),
|
| 958 |
+
default('#pop') # all else: go back
|
| 959 |
+
],
|
| 960 |
+
'fromimport': [
|
| 961 |
+
(r'(\s+)(c?import)\b', bygroups(Text, Keyword), '#pop'),
|
| 962 |
+
(r'[a-zA-Z_.][\w.]*', Name.Namespace),
|
| 963 |
+
# ``cdef foo from "header"``, or ``for foo from 0 < i < 10``
|
| 964 |
+
default('#pop'),
|
| 965 |
+
],
|
| 966 |
+
'stringescape': [
|
| 967 |
+
(r'\\([\\abfnrtv"\']|\n|N\{.*?\}|u[a-fA-F0-9]{4}|'
|
| 968 |
+
r'U[a-fA-F0-9]{8}|x[a-fA-F0-9]{2}|[0-7]{1,3})', String.Escape)
|
| 969 |
+
],
|
| 970 |
+
'strings': [
|
| 971 |
+
(r'%(\([a-zA-Z0-9]+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
|
| 972 |
+
'[hlL]?[E-GXc-giorsux%]', String.Interpol),
|
| 973 |
+
(r'[^\\\'"%\n]+', String),
|
| 974 |
+
# quotes, percents and backslashes must be parsed one at a time
|
| 975 |
+
(r'[\'"\\]', String),
|
| 976 |
+
# unhandled string formatting sign
|
| 977 |
+
(r'%', String)
|
| 978 |
+
# newlines are an error (use "nl" state)
|
| 979 |
+
],
|
| 980 |
+
'nl': [
|
| 981 |
+
(r'\n', String)
|
| 982 |
+
],
|
| 983 |
+
'dqs': [
|
| 984 |
+
(r'"', String, '#pop'),
|
| 985 |
+
(r'\\\\|\\"|\\\n', String.Escape), # included here again for raw strings
|
| 986 |
+
include('strings')
|
| 987 |
+
],
|
| 988 |
+
'sqs': [
|
| 989 |
+
(r"'", String, '#pop'),
|
| 990 |
+
(r"\\\\|\\'|\\\n", String.Escape), # included here again for raw strings
|
| 991 |
+
include('strings')
|
| 992 |
+
],
|
| 993 |
+
'tdqs': [
|
| 994 |
+
(r'"""', String, '#pop'),
|
| 995 |
+
include('strings'),
|
| 996 |
+
include('nl')
|
| 997 |
+
],
|
| 998 |
+
'tsqs': [
|
| 999 |
+
(r"'''", String, '#pop'),
|
| 1000 |
+
include('strings'),
|
| 1001 |
+
include('nl')
|
| 1002 |
+
],
|
| 1003 |
+
}
|
| 1004 |
+
|
| 1005 |
+
|
| 1006 |
+
class DgLexer(RegexLexer):
|
| 1007 |
+
"""
|
| 1008 |
+
Lexer for dg,
|
| 1009 |
+
a functional and object-oriented programming language
|
| 1010 |
+
running on the CPython 3 VM.
|
| 1011 |
+
"""
|
| 1012 |
+
name = 'dg'
|
| 1013 |
+
aliases = ['dg']
|
| 1014 |
+
filenames = ['*.dg']
|
| 1015 |
+
mimetypes = ['text/x-dg']
|
| 1016 |
+
url = 'http://pyos.github.io/dg'
|
| 1017 |
+
version_added = '1.6'
|
| 1018 |
+
|
| 1019 |
+
tokens = {
|
| 1020 |
+
'root': [
|
| 1021 |
+
(r'\s+', Text),
|
| 1022 |
+
(r'#.*?$', Comment.Single),
|
| 1023 |
+
|
| 1024 |
+
(r'(?i)0b[01]+', Number.Bin),
|
| 1025 |
+
(r'(?i)0o[0-7]+', Number.Oct),
|
| 1026 |
+
(r'(?i)0x[0-9a-f]+', Number.Hex),
|
| 1027 |
+
(r'(?i)[+-]?[0-9]+\.[0-9]+(e[+-]?[0-9]+)?j?', Number.Float),
|
| 1028 |
+
(r'(?i)[+-]?[0-9]+e[+-]?\d+j?', Number.Float),
|
| 1029 |
+
(r'(?i)[+-]?[0-9]+j?', Number.Integer),
|
| 1030 |
+
|
| 1031 |
+
(r"(?i)(br|r?b?)'''", String, combined('stringescape', 'tsqs', 'string')),
|
| 1032 |
+
(r'(?i)(br|r?b?)"""', String, combined('stringescape', 'tdqs', 'string')),
|
| 1033 |
+
(r"(?i)(br|r?b?)'", String, combined('stringescape', 'sqs', 'string')),
|
| 1034 |
+
(r'(?i)(br|r?b?)"', String, combined('stringescape', 'dqs', 'string')),
|
| 1035 |
+
|
| 1036 |
+
(r"`\w+'*`", Operator),
|
| 1037 |
+
(r'\b(and|in|is|or|where)\b', Operator.Word),
|
| 1038 |
+
(r'[!$%&*+\-./:<-@\\^|~;,]+', Operator),
|
| 1039 |
+
|
| 1040 |
+
(words((
|
| 1041 |
+
'bool', 'bytearray', 'bytes', 'classmethod', 'complex', 'dict', 'dict\'',
|
| 1042 |
+
'float', 'frozenset', 'int', 'list', 'list\'', 'memoryview', 'object',
|
| 1043 |
+
'property', 'range', 'set', 'set\'', 'slice', 'staticmethod', 'str',
|
| 1044 |
+
'super', 'tuple', 'tuple\'', 'type'),
|
| 1045 |
+
prefix=r'(?<!\.)', suffix=r'(?![\'\w])'),
|
| 1046 |
+
Name.Builtin),
|
| 1047 |
+
(words((
|
| 1048 |
+
'__import__', 'abs', 'all', 'any', 'bin', 'bind', 'chr', 'cmp', 'compile',
|
| 1049 |
+
'complex', 'delattr', 'dir', 'divmod', 'drop', 'dropwhile', 'enumerate',
|
| 1050 |
+
'eval', 'exhaust', 'filter', 'flip', 'foldl1?', 'format', 'fst',
|
| 1051 |
+
'getattr', 'globals', 'hasattr', 'hash', 'head', 'hex', 'id', 'init',
|
| 1052 |
+
'input', 'isinstance', 'issubclass', 'iter', 'iterate', 'last', 'len',
|
| 1053 |
+
'locals', 'map', 'max', 'min', 'next', 'oct', 'open', 'ord', 'pow',
|
| 1054 |
+
'print', 'repr', 'reversed', 'round', 'setattr', 'scanl1?', 'snd',
|
| 1055 |
+
'sorted', 'sum', 'tail', 'take', 'takewhile', 'vars', 'zip'),
|
| 1056 |
+
prefix=r'(?<!\.)', suffix=r'(?![\'\w])'),
|
| 1057 |
+
Name.Builtin),
|
| 1058 |
+
(r"(?<!\.)(self|Ellipsis|NotImplemented|None|True|False)(?!['\w])",
|
| 1059 |
+
Name.Builtin.Pseudo),
|
| 1060 |
+
|
| 1061 |
+
(r"(?<!\.)[A-Z]\w*(Error|Exception|Warning)'*(?!['\w])",
|
| 1062 |
+
Name.Exception),
|
| 1063 |
+
(r"(?<!\.)(Exception|GeneratorExit|KeyboardInterrupt|StopIteration|"
|
| 1064 |
+
r"SystemExit)(?!['\w])", Name.Exception),
|
| 1065 |
+
|
| 1066 |
+
(r"(?<![\w.])(except|finally|for|if|import|not|otherwise|raise|"
|
| 1067 |
+
r"subclass|while|with|yield)(?!['\w])", Keyword.Reserved),
|
| 1068 |
+
|
| 1069 |
+
(r"[A-Z_]+'*(?!['\w])", Name),
|
| 1070 |
+
(r"[A-Z]\w+'*(?!['\w])", Keyword.Type),
|
| 1071 |
+
(r"\w+'*", Name),
|
| 1072 |
+
|
| 1073 |
+
(r'[()]', Punctuation),
|
| 1074 |
+
(r'.', Error),
|
| 1075 |
+
],
|
| 1076 |
+
'stringescape': [
|
| 1077 |
+
(r'\\([\\abfnrtv"\']|\n|N\{.*?\}|u[a-fA-F0-9]{4}|'
|
| 1078 |
+
r'U[a-fA-F0-9]{8}|x[a-fA-F0-9]{2}|[0-7]{1,3})', String.Escape)
|
| 1079 |
+
],
|
| 1080 |
+
'string': [
|
| 1081 |
+
(r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
|
| 1082 |
+
'[hlL]?[E-GXc-giorsux%]', String.Interpol),
|
| 1083 |
+
(r'[^\\\'"%\n]+', String),
|
| 1084 |
+
# quotes, percents and backslashes must be parsed one at a time
|
| 1085 |
+
(r'[\'"\\]', String),
|
| 1086 |
+
# unhandled string formatting sign
|
| 1087 |
+
(r'%', String),
|
| 1088 |
+
(r'\n', String)
|
| 1089 |
+
],
|
| 1090 |
+
'dqs': [
|
| 1091 |
+
(r'"', String, '#pop')
|
| 1092 |
+
],
|
| 1093 |
+
'sqs': [
|
| 1094 |
+
(r"'", String, '#pop')
|
| 1095 |
+
],
|
| 1096 |
+
'tdqs': [
|
| 1097 |
+
(r'"""', String, '#pop')
|
| 1098 |
+
],
|
| 1099 |
+
'tsqs': [
|
| 1100 |
+
(r"'''", String, '#pop')
|
| 1101 |
+
],
|
| 1102 |
+
}
|
| 1103 |
+
|
| 1104 |
+
|
| 1105 |
+
class NumPyLexer(PythonLexer):
|
| 1106 |
+
"""
|
| 1107 |
+
A Python lexer recognizing Numerical Python builtins.
|
| 1108 |
+
"""
|
| 1109 |
+
|
| 1110 |
+
name = 'NumPy'
|
| 1111 |
+
url = 'https://numpy.org/'
|
| 1112 |
+
aliases = ['numpy']
|
| 1113 |
+
version_added = '0.10'
|
| 1114 |
+
|
| 1115 |
+
# override the mimetypes to not inherit them from python
|
| 1116 |
+
mimetypes = []
|
| 1117 |
+
filenames = []
|
| 1118 |
+
|
| 1119 |
+
EXTRA_KEYWORDS = {
|
| 1120 |
+
'abs', 'absolute', 'accumulate', 'add', 'alen', 'all', 'allclose',
|
| 1121 |
+
'alltrue', 'alterdot', 'amax', 'amin', 'angle', 'any', 'append',
|
| 1122 |
+
'apply_along_axis', 'apply_over_axes', 'arange', 'arccos', 'arccosh',
|
| 1123 |
+
'arcsin', 'arcsinh', 'arctan', 'arctan2', 'arctanh', 'argmax', 'argmin',
|
| 1124 |
+
'argsort', 'argwhere', 'around', 'array', 'array2string', 'array_equal',
|
| 1125 |
+
'array_equiv', 'array_repr', 'array_split', 'array_str', 'arrayrange',
|
| 1126 |
+
'asanyarray', 'asarray', 'asarray_chkfinite', 'ascontiguousarray',
|
| 1127 |
+
'asfarray', 'asfortranarray', 'asmatrix', 'asscalar', 'astype',
|
| 1128 |
+
'atleast_1d', 'atleast_2d', 'atleast_3d', 'average', 'bartlett',
|
| 1129 |
+
'base_repr', 'beta', 'binary_repr', 'bincount', 'binomial',
|
| 1130 |
+
'bitwise_and', 'bitwise_not', 'bitwise_or', 'bitwise_xor', 'blackman',
|
| 1131 |
+
'bmat', 'broadcast', 'byte_bounds', 'bytes', 'byteswap', 'c_',
|
| 1132 |
+
'can_cast', 'ceil', 'choose', 'clip', 'column_stack', 'common_type',
|
| 1133 |
+
'compare_chararrays', 'compress', 'concatenate', 'conj', 'conjugate',
|
| 1134 |
+
'convolve', 'copy', 'corrcoef', 'correlate', 'cos', 'cosh', 'cov',
|
| 1135 |
+
'cross', 'cumprod', 'cumproduct', 'cumsum', 'delete', 'deprecate',
|
| 1136 |
+
'diag', 'diagflat', 'diagonal', 'diff', 'digitize', 'disp', 'divide',
|
| 1137 |
+
'dot', 'dsplit', 'dstack', 'dtype', 'dump', 'dumps', 'ediff1d', 'empty',
|
| 1138 |
+
'empty_like', 'equal', 'exp', 'expand_dims', 'expm1', 'extract', 'eye',
|
| 1139 |
+
'fabs', 'fastCopyAndTranspose', 'fft', 'fftfreq', 'fftshift', 'fill',
|
| 1140 |
+
'finfo', 'fix', 'flat', 'flatnonzero', 'flatten', 'fliplr', 'flipud',
|
| 1141 |
+
'floor', 'floor_divide', 'fmod', 'frexp', 'fromarrays', 'frombuffer',
|
| 1142 |
+
'fromfile', 'fromfunction', 'fromiter', 'frompyfunc', 'fromstring',
|
| 1143 |
+
'generic', 'get_array_wrap', 'get_include', 'get_numarray_include',
|
| 1144 |
+
'get_numpy_include', 'get_printoptions', 'getbuffer', 'getbufsize',
|
| 1145 |
+
'geterr', 'geterrcall', 'geterrobj', 'getfield', 'gradient', 'greater',
|
| 1146 |
+
'greater_equal', 'gumbel', 'hamming', 'hanning', 'histogram',
|
| 1147 |
+
'histogram2d', 'histogramdd', 'hsplit', 'hstack', 'hypot', 'i0',
|
| 1148 |
+
'identity', 'ifft', 'imag', 'index_exp', 'indices', 'inf', 'info',
|
| 1149 |
+
'inner', 'insert', 'int_asbuffer', 'interp', 'intersect1d',
|
| 1150 |
+
'intersect1d_nu', 'inv', 'invert', 'iscomplex', 'iscomplexobj',
|
| 1151 |
+
'isfinite', 'isfortran', 'isinf', 'isnan', 'isneginf', 'isposinf',
|
| 1152 |
+
'isreal', 'isrealobj', 'isscalar', 'issctype', 'issubclass_',
|
| 1153 |
+
'issubdtype', 'issubsctype', 'item', 'itemset', 'iterable', 'ix_',
|
| 1154 |
+
'kaiser', 'kron', 'ldexp', 'left_shift', 'less', 'less_equal', 'lexsort',
|
| 1155 |
+
'linspace', 'load', 'loads', 'loadtxt', 'log', 'log10', 'log1p', 'log2',
|
| 1156 |
+
'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'logspace',
|
| 1157 |
+
'lstsq', 'mat', 'matrix', 'max', 'maximum', 'maximum_sctype',
|
| 1158 |
+
'may_share_memory', 'mean', 'median', 'meshgrid', 'mgrid', 'min',
|
| 1159 |
+
'minimum', 'mintypecode', 'mod', 'modf', 'msort', 'multiply', 'nan',
|
| 1160 |
+
'nan_to_num', 'nanargmax', 'nanargmin', 'nanmax', 'nanmin', 'nansum',
|
| 1161 |
+
'ndenumerate', 'ndim', 'ndindex', 'negative', 'newaxis', 'newbuffer',
|
| 1162 |
+
'newbyteorder', 'nonzero', 'not_equal', 'obj2sctype', 'ogrid', 'ones',
|
| 1163 |
+
'ones_like', 'outer', 'permutation', 'piecewise', 'pinv', 'pkgload',
|
| 1164 |
+
'place', 'poisson', 'poly', 'poly1d', 'polyadd', 'polyder', 'polydiv',
|
| 1165 |
+
'polyfit', 'polyint', 'polymul', 'polysub', 'polyval', 'power', 'prod',
|
| 1166 |
+
'product', 'ptp', 'put', 'putmask', 'r_', 'randint', 'random_integers',
|
| 1167 |
+
'random_sample', 'ranf', 'rank', 'ravel', 'real', 'real_if_close',
|
| 1168 |
+
'recarray', 'reciprocal', 'reduce', 'remainder', 'repeat', 'require',
|
| 1169 |
+
'reshape', 'resize', 'restoredot', 'right_shift', 'rint', 'roll',
|
| 1170 |
+
'rollaxis', 'roots', 'rot90', 'round', 'round_', 'row_stack', 's_',
|
| 1171 |
+
'sample', 'savetxt', 'sctype2char', 'searchsorted', 'seed', 'select',
|
| 1172 |
+
'set_numeric_ops', 'set_printoptions', 'set_string_function',
|
| 1173 |
+
'setbufsize', 'setdiff1d', 'seterr', 'seterrcall', 'seterrobj',
|
| 1174 |
+
'setfield', 'setflags', 'setmember1d', 'setxor1d', 'shape',
|
| 1175 |
+
'show_config', 'shuffle', 'sign', 'signbit', 'sin', 'sinc', 'sinh',
|
| 1176 |
+
'size', 'slice', 'solve', 'sometrue', 'sort', 'sort_complex', 'source',
|
| 1177 |
+
'split', 'sqrt', 'square', 'squeeze', 'standard_normal', 'std',
|
| 1178 |
+
'subtract', 'sum', 'svd', 'swapaxes', 'take', 'tan', 'tanh', 'tensordot',
|
| 1179 |
+
'test', 'tile', 'tofile', 'tolist', 'tostring', 'trace', 'transpose',
|
| 1180 |
+
'trapz', 'tri', 'tril', 'trim_zeros', 'triu', 'true_divide', 'typeDict',
|
| 1181 |
+
'typename', 'uniform', 'union1d', 'unique', 'unique1d', 'unravel_index',
|
| 1182 |
+
'unwrap', 'vander', 'var', 'vdot', 'vectorize', 'view', 'vonmises',
|
| 1183 |
+
'vsplit', 'vstack', 'weibull', 'where', 'who', 'zeros', 'zeros_like'
|
| 1184 |
+
}
|
| 1185 |
+
|
| 1186 |
+
def get_tokens_unprocessed(self, text):
|
| 1187 |
+
for index, token, value in \
|
| 1188 |
+
PythonLexer.get_tokens_unprocessed(self, text):
|
| 1189 |
+
if token is Name and value in self.EXTRA_KEYWORDS:
|
| 1190 |
+
yield index, Keyword.Pseudo, value
|
| 1191 |
+
else:
|
| 1192 |
+
yield index, token, value
|
| 1193 |
+
|
| 1194 |
+
def analyse_text(text):
|
| 1195 |
+
ltext = text[:1000]
|
| 1196 |
+
return (shebang_matches(text, r'pythonw?(3(\.\d)?)?') or
|
| 1197 |
+
'import ' in ltext) \
|
| 1198 |
+
and ('import numpy' in ltext or 'from numpy import' in ltext)
|
lib/python3.12/site-packages/pip/_vendor/pygments/modeline.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
pygments.modeline
|
| 3 |
+
~~~~~~~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
A simple modeline parser (based on pymodeline).
|
| 6 |
+
|
| 7 |
+
:copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
|
| 8 |
+
:license: BSD, see LICENSE for details.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import re
|
| 12 |
+
|
| 13 |
+
__all__ = ['get_filetype_from_buffer']
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
modeline_re = re.compile(r'''
|
| 17 |
+
(?: vi | vim | ex ) (?: [<=>]? \d* )? :
|
| 18 |
+
.* (?: ft | filetype | syn | syntax ) = ( [^:\s]+ )
|
| 19 |
+
''', re.VERBOSE)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def get_filetype_from_line(l): # noqa: E741
|
| 23 |
+
m = modeline_re.search(l)
|
| 24 |
+
if m:
|
| 25 |
+
return m.group(1)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def get_filetype_from_buffer(buf, max_lines=5):
|
| 29 |
+
"""
|
| 30 |
+
Scan the buffer for modelines and return filetype if one is found.
|
| 31 |
+
"""
|
| 32 |
+
lines = buf.splitlines()
|
| 33 |
+
for line in lines[-1:-max_lines-1:-1]:
|
| 34 |
+
ret = get_filetype_from_line(line)
|
| 35 |
+
if ret:
|
| 36 |
+
return ret
|
| 37 |
+
for i in range(max_lines, -1, -1):
|
| 38 |
+
if i < len(lines):
|
| 39 |
+
ret = get_filetype_from_line(lines[i])
|
| 40 |
+
if ret:
|
| 41 |
+
return ret
|
| 42 |
+
|
| 43 |
+
return None
|